DateBack.java

package space.sunqian.common.base.date;

import space.sunqian.annotations.Nonnull;
import space.sunqian.common.Fs;

import java.time.DateTimeException;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.TemporalAccessor;
import java.util.Date;
import java.util.Objects;

final class DateBack {

    static @Nonnull DateFormatter ofFormatter(
        @Nonnull DateTimeFormatter formatter, @Nonnull ZoneId zoneId
    ) {
        return new OfFormatter(formatter, zoneId);
    }

    static @Nonnull DateFormatter ofPattern(
        @Nonnull String pattern, @Nonnull ZoneId zoneId
    ) throws DateTimeException {
        try {
            return new OfPattern(pattern, zoneId);
        } catch (Exception e) {
            throw new DateException(e);
        }
    }

    private static class AbsDateFormatter implements DateFormatter {

        private static final @Nonnull String NO_PATTERN = "No pattern in this TimeSpec.";

        protected final @Nonnull DateTimeFormatter formatter;
        private final @Nonnull ZoneId zoneId;

        private AbsDateFormatter(@Nonnull DateTimeFormatter formatter, @Nonnull ZoneId zoneId) {
            this.formatter = formatter;
            this.zoneId = zoneId;
        }

        @Override
        public @Nonnull ZoneId zoneId() {
            return zoneId;
        }

        @Override
        public @Nonnull String pattern() throws DateTimeException {
            throw new DateException(NO_PATTERN);
        }

        @Override
        public boolean hasPattern() {
            return false;
        }

        @Override
        public @Nonnull String format(@Nonnull Date date) throws DateTimeException {
            Instant instant = date.toInstant();
            return format(instant);
        }

        @Override
        public @Nonnull String format(@Nonnull TemporalAccessor time) throws DateTimeException {
            TemporalAccessor withZone = withZoneId(time, zoneId);
            return format0(withZone);
        }

        private @Nonnull String format0(@Nonnull TemporalAccessor time) throws DateTimeException {
            return formatter.format(time);
        }

        @Override
        public <T> @Nonnull T parse(@Nonnull CharSequence date, @Nonnull Class<T> timeType) throws DateTimeException {
            return Fs.as(parse0(date, timeType));
        }

        private @Nonnull Object parse0(
            @Nonnull CharSequence date, @Nonnull Class<?> timeType
        ) throws DateTimeException {
            if (Objects.equals(timeType, Instant.class)) {
                try {
                    return Instant.parse(date);
                } catch (DateTimeParseException e) {
                    LocalDateTime localDateTime = LocalDateTime.parse(date, formatter);
                    return ZonedDateTime.of(localDateTime, zoneId).toInstant();
                }
            }
            if (Objects.equals(timeType, Date.class)) {
                return Date.from(parse(date, Instant.class));
            }
            if (Objects.equals(timeType, LocalDateTime.class)) {
                return LocalDateTime.parse(date, formatter);
            }
            if (Objects.equals(timeType, ZonedDateTime.class)) {
                try {
                    return ZonedDateTime.parse(date, formatter);
                } catch (DateTimeParseException e) {
                    LocalDateTime localDateTime = LocalDateTime.parse(date, formatter);
                    return ZonedDateTime.of(localDateTime, zoneId);
                }
            }
            if (Objects.equals(timeType, OffsetDateTime.class)) {
                try {
                    return OffsetDateTime.parse(date, formatter);
                } catch (DateTimeParseException e) {
                    LocalDateTime localDateTime = LocalDateTime.parse(date, formatter);
                    return OffsetDateTime.of(localDateTime, DateKit.nowOffset());
                }
            }
            if (Objects.equals(timeType, LocalDate.class)) {
                return LocalDate.parse(date, formatter);
            }
            if (Objects.equals(timeType, LocalTime.class)) {
                return LocalTime.parse(date, formatter);
            }
            throw new DateException("Unsupported time type: " + timeType);
        }

        @Override
        public <T> @Nonnull T convert(@Nonnull Date date, @Nonnull Class<T> timeType) throws DateTimeException {
            if (timeType.equals(Date.class)) {
                return Fs.as(date);
            }
            return convert(date.toInstant(), timeType);
        }

        @Override
        public <T> @Nonnull T convert(
            @Nonnull TemporalAccessor time, @Nonnull Class<T> timeType
        ) throws DateTimeException {
            if (timeType.equals(time.getClass())) {
                return Fs.as(time);
            }
            TemporalAccessor withZone = withZoneId(time, zoneId);
            return Fs.as(convert0(withZone, timeType));
        }

        private @Nonnull Object convert0(
            @Nonnull TemporalAccessor time, @Nonnull Class<?> timeType
        ) throws DateTimeException {
            if (Objects.equals(timeType, Instant.class)) {
                return Instant.from(time);
            }
            if (Objects.equals(timeType, Date.class)) {
                return Date.from(Instant.from(time));
            }
            if (Objects.equals(timeType, LocalDateTime.class)) {
                return LocalDateTime.from(time);
            }
            if (Objects.equals(timeType, ZonedDateTime.class)) {
                return ZonedDateTime.from(time);
            }
            if (Objects.equals(timeType, OffsetDateTime.class)) {
                return OffsetDateTime.from(time);
            }
            if (Objects.equals(timeType, LocalDate.class)) {
                return LocalDate.from(time);
            }
            if (Objects.equals(timeType, LocalTime.class)) {
                return LocalTime.from(time);
            }
            throw new DateException("Unsupported conversion from " + time.getClass() + " to " + timeType + ".");
        }

        private @Nonnull TemporalAccessor withZoneId(@Nonnull TemporalAccessor time, @Nonnull ZoneId zoneId) {
            if (time instanceof Instant) {
                return ZonedDateTime.ofInstant((Instant) time, zoneId);
            }
            if (time instanceof LocalDateTime) {
                return ZonedDateTime.of((LocalDateTime) time, zoneId);
            }
            // if (time instanceof LocalDate) {
            //     return ZonedDateTime.of((LocalDate) time, LocalTime.MIN, zoneId);
            // }
            // if (time instanceof LocalTime) {
            //     return ZonedDateTime.of(LocalDate.MIN, (LocalTime) time, zoneId);
            // }
            return time;
        }
    }

    private static final class OfFormatter extends AbsDateFormatter {
        private OfFormatter(@Nonnull DateTimeFormatter formatter, @Nonnull ZoneId zoneId) {
            super(formatter, zoneId);
        }
    }

    private static final class OfPattern extends AbsDateFormatter {

        private final @Nonnull String pattern;

        private OfPattern(@Nonnull String pattern, @Nonnull ZoneId zoneId) throws DateTimeException {
            super(DateTimeFormatter.ofPattern(pattern), zoneId);
            this.pattern = pattern;
        }

        @Override
        public @Nonnull String pattern() {
            return pattern;
        }

        @Override
        public boolean hasPattern() {
            return true;
        }
    }

    private DateBack() {
    }
}