class AgeFunction extends Scalar<Period, Object> {

    private static final FunctionName NAME = new FunctionName(PgCatalogSchemaInfo.NAME, "age");

    public static void register(Functions.Builder builder) {
        builder.add(
            scalar(
                NAME,
                DataTypes.TIMESTAMP.getTypeSignature(),
                DataTypes.INTERVAL.getTypeSignature()
            ).withFeatures(EnumSet.of(Feature.NULLABLE)),
            AgeFunction::new
        );

        builder.add(
            scalar(
                NAME,
                DataTypes.TIMESTAMP.getTypeSignature(),
                DataTypes.TIMESTAMP.getTypeSignature(),
                DataTypes.INTERVAL.getTypeSignature()
            ).withFeatures(EnumSet.of(Feature.NULLABLE)),
            AgeFunction::new
        );
    }


    public AgeFunction(Signature signature, BoundSignature boundSignature) {
        super(signature, boundSignature);
    }

    @Override
    public Period evaluate(TransactionContext txnCtx, NodeContext nodeContext, Input<Object>... args) {
        /*
         We cannot create Period from millis difference
         since sometimes we need to treat different millis range as same interval.
         Postgres treats age between first days of the sequential months as
         '1 month' despite on possibly different 28, 30, 31 days/millis range.
         */
        assert args.length > 0 && args.length < 3 : "Invalid number of arguments";

        var arg1 = args[0].value();
        if (arg1 == null) {
            return null;
        }
        if (args.length == 1) {
            long curDateMillis = txnCtx.currentInstant().toEpochMilli();
            curDateMillis = curDateMillis - curDateMillis % 86400000; // current_date at midnight, similar to CurrentDateFunction implementation.
            return getPeriod(curDateMillis, (long) arg1);
        } else {
            var arg2 = args[1].value();
            if (arg2 == null) {
                return null;
            }
            return getPeriod((long) arg1, (long) arg2);
        }
    }

    /**
     * returns Period in yearMonthDayTime which corresponds to Postgres default Interval output format.
     * See https://www.postgresql.org/docs/14/datatype-datetime.html#DATATYPE-INTERVAL-OUTPUT
     */
    public static Period getPeriod(long timestamp1, long timestamp2) {<FILL_FUNCTION_BODY>}
}

        /*
         PeriodType is important as it affects the internal representation of the Period object.
         PeriodType.yearMonthDayTime() is needed to return 8 days but not 1 week 1 day.
         Streamer of the IntervalType will simply put 0 in 'out.writeVInt(p.getWeeks())' as getWeeks() returns zero for unused fields.
         */

        if (timestamp1 < timestamp2) {
            /*
            In Postgres second argument is subtracted from the first.
            Interval's first argument must be smaller than second and thus we swap params and negate.

            We need to pass UTC timezone to be sure that Interval doesn't end up using system default time zone.
            Currently, timestamps are in UTC (see https://github.com/crate/crate/issues/10037 and
            https://github.com/crate/crate/issues/12064) but if https://github.com/crate/crate/issues/7196 ever gets
            implemented, we need to pass here not UTC but time zone set by SET TIMEZONE.
            */
            return new Interval(timestamp1, timestamp2, DateTimeZone.UTC).toPeriod(PeriodType.yearMonthDayTime()).negated();
        } else {
            return new Interval(timestamp2, timestamp1, DateTimeZone.UTC).toPeriod(PeriodType.yearMonthDayTime());
        }
    
<methods>public BoundSignature boundSignature() ,public Scalar<Period,java.lang.Object> compile(List<io.crate.expression.symbol.Symbol>, java.lang.String, io.crate.role.Roles) ,public transient abstract Period evaluate(io.crate.metadata.TransactionContext, io.crate.metadata.NodeContext, Input<java.lang.Object>[]) ,public io.crate.expression.symbol.Symbol normalizeSymbol(io.crate.expression.symbol.Function, io.crate.metadata.TransactionContext, io.crate.metadata.NodeContext) ,public io.crate.metadata.functions.Signature signature() <variables>public static final Set<io.crate.metadata.Scalar.Feature> DETERMINISTIC_AND_COMPARISON_REPLACEMENT,public static final Set<io.crate.metadata.Scalar.Feature> DETERMINISTIC_ONLY,public static final Set<io.crate.metadata.Scalar.Feature> NO_FEATURES,protected final non-sealed BoundSignature boundSignature,protected final non-sealed io.crate.metadata.functions.Signature signature