TimeStampNsLiteral.java

// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

package org.apache.doris.nereids.trees.expressions.literal;

import org.apache.doris.analysis.LiteralExpr;
import org.apache.doris.nereids.exceptions.AnalysisException;
import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
import org.apache.doris.nereids.types.TimeStampNsType;
import org.apache.doris.nereids.util.DateUtils;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalQueries;
import java.util.Objects;

/** Literal for the fixed nanosecond-precision TIMESTAMP_NS type. */
public final class TimeStampNsLiteral extends DateLiteral {
    private static final long NANOS_PER_SECOND = 1_000_000_000L;
    private static final long MAX_NANOSECOND = NANOS_PER_SECOND - 1;
    private static final LocalDateTime MIN_VALUE
            = LocalDateTime.of(1677, 9, 21, 0, 12, 43, 145224192);
    private static final LocalDateTime MAX_VALUE
            = LocalDateTime.of(2262, 4, 11, 23, 47, 16, 854775807);

    private final long hour;
    private final long minute;
    private final long second;
    private final long nanosecond;

    public TimeStampNsLiteral(String value) {
        this(parse(value));
    }

    /** Construct a TIMESTAMP_NS literal from civil datetime fields. */
    public TimeStampNsLiteral(long year, long month, long day, long hour, long minute, long second,
            long nanosecond) {
        super(TimeStampNsType.INSTANCE, year, month, day);
        this.hour = hour;
        this.minute = minute;
        this.second = second;
        this.nanosecond = nanosecond;
        if (checkRange()) {
            throw new AnalysisException("timestamp_ns literal [" + toString()
                    + "] is outside Int64 epoch nanosecond range");
        }
    }

    private TimeStampNsLiteral(LocalDateTime value) {
        this(value.getYear(), value.getMonthValue(), value.getDayOfMonth(),
                value.getHour(), value.getMinute(), value.getSecond(), value.getNano());
    }

    private static LocalDateTime parse(String value) {
        TemporalAccessor temporal = parseDateTime(value).get();
        long year = DateUtils.getOrDefault(temporal, ChronoField.YEAR);
        long month = DateUtils.getOrDefault(temporal, ChronoField.MONTH_OF_YEAR);
        long day = DateUtils.getOrDefault(temporal, ChronoField.DAY_OF_MONTH);
        long hour = DateUtils.getOrDefault(temporal, ChronoField.HOUR_OF_DAY);
        long minute = DateUtils.getOrDefault(temporal, ChronoField.MINUTE_OF_HOUR);
        long second = DateUtils.getOrDefault(temporal, ChronoField.SECOND_OF_MINUTE);

        ZoneId zoneId = temporal.query(TemporalQueries.zone());
        if (zoneId != null) {
            LocalDateTime converted = DateUtils.convertTimeZone(
                    year, month, day, hour, minute, second, zoneId, DateUtils.getTimeZone());
            year = converted.getYear();
            month = converted.getMonthValue();
            day = converted.getDayOfMonth();
            hour = converted.getHour();
            minute = converted.getMinute();
            second = converted.getSecond();
        }

        LocalDateTime result = LocalDateTime.of((int) year, (int) month, (int) day,
                (int) hour, (int) minute, (int) second,
                DateUtils.getOrDefault(temporal, ChronoField.NANO_OF_SECOND));
        if (DateUtils.getNanosecondGuardDigit(value) >= 5) {
            result = result.plusNanos(1);
        }
        return result;
    }

    public static TimeStampNsLiteral getMinValue() {
        return new TimeStampNsLiteral(MIN_VALUE);
    }

    public static TimeStampNsLiteral getMaxValue() {
        return new TimeStampNsLiteral(MAX_VALUE);
    }

    public static TimeStampNsLiteral createEndOfDay(long year, long month, long day) {
        return new TimeStampNsLiteral(year, month, day, 23, 59, 59, MAX_NANOSECOND);
    }

    public static TimeStampNsLiteral fromJavaDateType(LocalDateTime dateTime) {
        return new TimeStampNsLiteral(dateTime);
    }

    /** Return whether the civil fields are invalid or outside the signed epoch-nanosecond range. */
    public boolean checkRange() {
        if (checkRange(year, month, day) || month < 1 || day < 1 || checkDate(year, month, day)
                || hour < 0 || hour > 23 || minute < 0 || minute > 59
                || second < 0 || second > 59 || nanosecond < 0 || nanosecond > MAX_NANOSECOND) {
            return true;
        }
        LocalDateTime value = toJavaDateType();
        return value.isBefore(MIN_VALUE) || value.isAfter(MAX_VALUE);
    }

    @Override
    public boolean isMidnight() {
        return hour == 0 && minute == 0 && second == 0 && nanosecond == 0;
    }

    @Override
    public TimeStampNsType getDataType() {
        return TimeStampNsType.INSTANCE;
    }

    @Override
    public Long getValue() {
        return (year * 10000 + month * 100 + day) * 1000000L + hour * 10000 + minute * 100 + second;
    }

    @Override
    public long getTimePartInNanoseconds() {
        return ((hour * 60L + minute) * 60L + second) * NANOS_PER_SECOND + nanosecond;
    }

    @Override
    public long getFractionalSecondInNanoseconds() {
        return nanosecond;
    }

    @Override
    public double getDouble() {
        return getValue() + nanosecond / (double) NANOS_PER_SECOND;
    }

    @Override
    public String getStringValue() {
        return DateUtils.formatDateTime(year, month, day, hour, minute, second,
                nanosecond, TimeStampNsType.SCALE);
    }

    @Override
    public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
        return visitor.visitTimeStampNsLiteral(this, context);
    }

    @Override
    public LiteralExpr toLegacyLiteral() {
        return new org.apache.doris.analysis.TimeStampNsLiteral(
                year, month, day, hour, minute, second, nanosecond);
    }

    public DateTimeV2Literal roundFloorToDateTimeV2(int scale) {
        long factor = (long) Math.pow(10, DateUtils.NANOSECOND_SCALE - scale);
        LocalDateTime rounded = toJavaDateType().withNano((int) (nanosecond / factor * factor));
        return DateTimeV2Literal.fromJavaDateType(rounded, scale);
    }

    public DateTimeV2Literal roundCeilingToDateTimeV2(int scale) {
        long factor = (long) Math.pow(10, DateUtils.NANOSECOND_SCALE - scale);
        long remainder = nanosecond % factor;
        LocalDateTime rounded = remainder == 0 ? toJavaDateType()
                : toJavaDateType().plusNanos(factor - remainder);
        return DateTimeV2Literal.fromJavaDateType(rounded, scale);
    }

    @Override
    public LocalDateTime toJavaDateType() {
        return LocalDateTime.of((int) year, (int) month, (int) day,
                (int) hour, (int) minute, (int) second, (int) nanosecond);
    }

    public long getHour() {
        return hour;
    }

    public long getMinute() {
        return minute;
    }

    public long getSecond() {
        return second;
    }

    public long getMicroSecond() {
        return nanosecond / 1000;
    }

    public long getNanoSecond() {
        return nanosecond;
    }

    public int getScale() {
        return TimeStampNsType.SCALE;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof TimeStampNsLiteral)) {
            return false;
        }
        TimeStampNsLiteral literal = (TimeStampNsLiteral) o;
        return Objects.equals(getValue(), literal.getValue()) && nanosecond == literal.nanosecond;
    }

    @Override
    protected int computeHashCode() {
        return Objects.hash(getValue(), nanosecond);
    }
}