diff --git a/partiql-spi/api/partiql-spi.api b/partiql-spi/api/partiql-spi.api index 2492caeaf..19972abff 100644 --- a/partiql-spi/api/partiql-spi.api +++ b/partiql-spi/api/partiql-spi.api @@ -310,6 +310,124 @@ public final class org/partiql/spi/catalog/Table$DefaultImpls { public static fun getSchema (Lorg/partiql/spi/catalog/Table;)Lorg/partiql/types/PType; } +public class org/partiql/spi/datetime/Date { + public fun equals (Ljava/lang/Object;)Z + public fun getDay ()I + public fun getMonth ()I + public fun getYear ()I + public fun hashCode ()I + public fun minus (Lorg/partiql/spi/datetime/Date;)Lorg/partiql/spi/datetime/Interval; + public fun minus (Lorg/partiql/spi/datetime/Interval;)Lorg/partiql/spi/datetime/Date; + public static fun now ()Lorg/partiql/spi/datetime/Date; + public static fun of (III)Lorg/partiql/spi/datetime/Date; + public static fun of (Ljava/time/LocalDate;)Lorg/partiql/spi/datetime/Date; + public fun plus (Lorg/partiql/spi/datetime/Interval;)Lorg/partiql/spi/datetime/Date; + public fun toLocalDate ()Ljava/time/LocalDate; + public fun toString ()Ljava/lang/String; +} + +public abstract interface class org/partiql/spi/datetime/Interval { + public static fun days (I)Lorg/partiql/spi/datetime/Interval$YM; + public static fun duration (Ljava/time/Duration;)Lorg/partiql/spi/datetime/Interval$DT; + public static fun hours (I)Lorg/partiql/spi/datetime/Interval$DT; + public static fun millis (J)Lorg/partiql/spi/datetime/Interval$DT; + public static fun minutes (I)Lorg/partiql/spi/datetime/Interval$DT; + public static fun months (I)Lorg/partiql/spi/datetime/Interval$YM; + public static fun nanos (J)Lorg/partiql/spi/datetime/Interval$DT; + public static fun period (Ljava/time/Period;)Lorg/partiql/spi/datetime/Interval$YM; + public static fun seconds (J)Lorg/partiql/spi/datetime/Interval$DT; + public static fun weeks (I)Lorg/partiql/spi/datetime/Interval$YM; + public static fun years (I)Lorg/partiql/spi/datetime/Interval$YM; +} + +public class org/partiql/spi/datetime/Interval$DT : org/partiql/spi/datetime/Interval { + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toDuration ()Ljava/time/Duration; + public fun toString ()Ljava/lang/String; +} + +public class org/partiql/spi/datetime/Interval$YM : org/partiql/spi/datetime/Interval { + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toPeriod ()Ljava/time/Period; + public fun toString ()Ljava/lang/String; +} + +public class org/partiql/spi/datetime/Time { + public fun equals (Ljava/lang/Object;)Z + public fun getHour ()I + public fun getMinute ()I + public fun getNano ()I + public fun getSecond ()I + public fun hashCode ()I + public static fun now ()Lorg/partiql/spi/datetime/Time; + public static fun of (II)Lorg/partiql/spi/datetime/Time; + public static fun of (III)Lorg/partiql/spi/datetime/Time; + public static fun of (IIII)Lorg/partiql/spi/datetime/Time; + public static fun of (Ljava/time/LocalTime;)Lorg/partiql/spi/datetime/Time; + public fun toLocalTime ()Ljava/time/LocalTime; + public fun toString ()Ljava/lang/String; +} + +public class org/partiql/spi/datetime/Timestamp { + public fun equals (Ljava/lang/Object;)Z + public fun getDay ()I + public fun getHour ()I + public fun getMinute ()I + public fun getMonth ()I + public fun getNano ()I + public fun getSecond ()I + public fun getYear ()I + public fun hashCode ()I + public fun now ()Lorg/partiql/spi/datetime/Timestamp; + public static fun of (IIIIII)Lorg/partiql/spi/datetime/Timestamp; + public static fun of (IIIIIII)Lorg/partiql/spi/datetime/Timestamp; + public static fun of (Ljava/time/LocalDateTime;)Lorg/partiql/spi/datetime/Timestamp; + public static fun of (Lorg/partiql/spi/datetime/Date;Lorg/partiql/spi/datetime/Time;)Lorg/partiql/spi/datetime/Timestamp; + public fun toDate ()Lorg/partiql/spi/datetime/Date; + public fun toLocalDateTime ()Ljava/time/LocalDateTime; + public fun toString ()Ljava/lang/String; + public fun toTime ()Lorg/partiql/spi/datetime/Time; +} + +public class org/partiql/spi/datetime/Timestampz { + public fun equals (Ljava/lang/Object;)Z + public fun getDay ()I + public fun getHour ()I + public fun getMinute ()I + public fun getMonth ()I + public fun getNano ()I + public fun getOffset ()Ljava/time/ZoneOffset; + public fun getSecond ()I + public fun getYear ()I + public fun hashCode ()I + public fun now ()Lorg/partiql/spi/datetime/Timestampz; + public static fun of (Ljava/time/OffsetDateTime;)Lorg/partiql/spi/datetime/Timestampz; + public static fun of (Lorg/partiql/spi/datetime/Date;Lorg/partiql/spi/datetime/Timez;)Lorg/partiql/spi/datetime/Timestampz; + public fun toDate ()Lorg/partiql/spi/datetime/Date; + public fun toLocalDateTime ()Ljava/time/LocalDateTime; + public fun toOffsetDateTime ()Ljava/time/OffsetDateTime; + public fun toString ()Ljava/lang/String; + public fun toTimez ()Lorg/partiql/spi/datetime/Timez; +} + +public class org/partiql/spi/datetime/Timez { + public fun equals (Ljava/lang/Object;)Z + public fun getHour ()I + public fun getMinute ()I + public fun getNano ()I + public fun getOffset ()Ljava/time/ZoneOffset; + public fun getSecond ()I + public fun hashCode ()I + public static fun now ()Lorg/partiql/spi/datetime/Timez; + public static fun of (Ljava/time/LocalTime;Ljava/time/ZoneOffset;)Lorg/partiql/spi/datetime/Timez; + public static fun of (Ljava/time/OffsetTime;)Lorg/partiql/spi/datetime/Timez; + public fun toLocalTime ()Ljava/time/LocalTime; + public fun toOffsetTime ()Ljava/time/OffsetTime; + public fun toString ()Ljava/lang/String; +} + public final class org/partiql/spi/errors/PError : org/partiql/spi/Enum { public static final field ALWAYS_MISSING I public static final field FEATURE_NOT_SUPPORTED I diff --git a/partiql-spi/src/main/java/org/partiql/spi/datetime/Date.java b/partiql-spi/src/main/java/org/partiql/spi/datetime/Date.java new file mode 100644 index 000000000..3e4cf31ea --- /dev/null +++ b/partiql-spi/src/main/java/org/partiql/spi/datetime/Date.java @@ -0,0 +1,166 @@ +package org.partiql.spi.datetime; + +import org.jetbrains.annotations.NotNull; + +import java.time.Clock; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +/** + * A PartiQL date. + *
+ * This class provides basic APIs for creating a Date. More complex use-cases should create a Date from a LocalDate. + * For example, to create a `now()` Date with an alternative timezone, use: + *
+ *     Clock clock = Clock.system(ZoneId.of("America/New_York"));
+ *     Date date = new Date(LocalDate.now(clock));
+ * 
+ * This API may be extended with additional constructors to reduce verbosity, but it is intentionally lean. + */ +public class Date { + + /** + * Delegate all functionality to the JDK8+ recommend APIs, but keep this internalized. + */ + @NotNull + private final LocalDate date; + + /** + * SQL date formatter. + */ + private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + /** + * NO PUBLIC CONSTRUCTORS. + */ + private Date(@NotNull LocalDate date) { + this.date = date; + } + + /** + * @return date for current date-time for the system clock. + */ + @NotNull + public static Date now() { + return new Date(LocalDate.now(Clock.systemDefaultZone())); + } + + /** + * Create a new Date from a {@link LocalDate}. + * + * @param date the {@link LocalDate} to wrap + */ + @NotNull + public static Date of(@NotNull LocalDate date) { + return new Date(date); + } + + /** + * Create a new Date from a year, month, and day. + * + * @param year the year to represent, from Year.MIN_YEAR to Year.MAX_YEAR + * @param month the month-of-year to represent, from 1 (January) to 12 (December) + * @param day the day of month to represent, from 1 to 31 + */ + @NotNull + public static Date of(int year, int month, int day) { + return new Date(LocalDate.of(year, month, day)); + } + + /** + * @return a {@link LocalDate} representation of this date. + */ + @NotNull + public LocalDate toLocalDate() { + return date; + } + + /** + * @return the year of this date + */ + public int getYear() { + return date.getYear(); + } + + /** + * @return the month (of the year) field value. + */ + public int getMonth() { + return date.getMonthValue(); + } + + /** + * @return the day (of the month) field value. + */ + public int getDay() { + return date.getDayOfMonth(); + } + + /** + * @return temporal difference between this date and the other date. + */ + @NotNull + public Interval minus(Date other) { + // inverse of 'until' + return Interval.period(other.date.until(this.date)); + } + + /** + * @return a new Date representing the result of adding the interval to this Date + */ + @NotNull + public Date plus(Interval interval) { + LocalDate date; + if (interval instanceof Interval.YM) { + date = this.date.plus(((Interval.YM) interval).toPeriod()); + } else { + date = this.date.plus(((Interval.DT) interval).toDuration()); + } + return new Date(date); + } + + /** + * @return a new Date representing the result of subtracting the interval to this Date + */ + @NotNull + public Date minus(Interval interval) { + LocalDate date; + if (interval instanceof Interval.YM) { + date = this.date.minus(((Interval.YM) interval).toPeriod()); + } else { + date = this.date.minus(((Interval.DT) interval).toDuration()); + } + return new Date(date); + } + + @Override + public int hashCode() { + return date.hashCode(); + } + + /** + * In SQL, datetime values are mutually comparable if they share the same fields. + *
+ * + * @param obj the object to compare to + * @return true if the objects are equal, false otherwise + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof Date)) + return false; + return date.equals(((Date) obj).date); + } + + /** + * @return the SQL string for this date. + */ + @Override + public String toString() { + return "DATE " + date.format(formatter); + } +} diff --git a/partiql-spi/src/main/java/org/partiql/spi/datetime/Interval.java b/partiql-spi/src/main/java/org/partiql/spi/datetime/Interval.java new file mode 100644 index 000000000..ed4e8cf18 --- /dev/null +++ b/partiql-spi/src/main/java/org/partiql/spi/datetime/Interval.java @@ -0,0 +1,216 @@ +package org.partiql.spi.datetime; + +import org.jetbrains.annotations.NotNull; + +import java.time.Duration; +import java.time.Period; + +/** + * This class represents a PartiQL Interval value. SQL defines two "classes" of intervals: year-month and day-time. + * In Java these are represented by {@link Period} and {@link Duration} respectively. + */ +public interface Interval { + + /** + * A {@link Period} based interval for the year-month interval class. + */ + public class YM implements Interval { + + /** + * The interval year, month, day (calendar aware). + */ + private final Period period; + + /** + * @param period a year-month INTERVAL part. + */ + private YM(@NotNull Period period) { + this.period = period; + } + + /** + * @return this interval as a Period. + */ + @NotNull + public Period toPeriod() { + return period; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof YM)) + return false; + return period.equals(((YM) obj).period); + } + + @Override + public int hashCode() { + return period.hashCode(); + } + + @Override + public String toString() { + return period.toString(); + } + } + + /** + * A {@link Duration} based interval for the day-time interval class. + */ + public class DT implements Interval { + + /** + * The seconds, nanoseconds of the interval (calendar unaware). + */ + @NotNull + private final Duration duration; + + /** + * @param duration a day-time INTERVAL part. + */ + private DT(@NotNull Duration duration) { + this.duration = duration; + } + + + /** + * @return this interval as a Duration. + */ + @NotNull + public Duration toDuration() { + return duration; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof DT)) + return false; + return duration.equals(((DT) obj).duration); + } + + @Override + public int hashCode() { + return duration.hashCode(); + } + + @Override + public String toString() { + return duration.toString(); + } + } + + @NotNull + public static YM period(Period period) { + return new YM(period); + } + + @NotNull + public static DT duration(Duration duration) { + return new DT(duration); + } + + /** + * Create an interval based on the number of years. + * + * @param years number of years + * @return new Interval + */ + @NotNull + public static YM years(int years) { + return new YM(Period.ofYears(years)); + } + + /** + * Create an interval based on the number of months. + * + * @param months number of months + * @return new Interval + */ + @NotNull + public static YM months(int months) { + return new YM(Period.ofMonths(months)); + } + + /** + * Create an interval based on the number of weeks. + * + * @param weeks number of weeks + * @return new Interval + */ + @NotNull + public static YM weeks(int weeks) { + return new YM(Period.ofWeeks(weeks)); + } + + /** + * Create an interval based on the number of days. + * + * @param days number of days + * @return new Interval + */ + @NotNull + public static YM days(int days) { + return new YM(Period.ofDays(days)); + } + + /** + * Create an interval based on the number of hours. + * + * @param hours number of hours + * @return new Interval + */ + @NotNull + public static DT hours(int hours) { + return new DT(Duration.ofHours(hours)); + } + + /** + * Create an interval based on the number of minutes. + * + * @param minutes number of minutes + * @return new Interval + */ + @NotNull + public static DT minutes(int minutes) { + return new DT(Duration.ofMinutes(minutes)); + } + + /** + * Create an interval based on the number of seconds. + * + * @param seconds number of seconds + * @return new Interval + */ + @NotNull + public static DT seconds(long seconds) { + return new DT(Duration.ofSeconds(seconds)); + } + + /** + * Create an interval based on the number milliseconds. + * @param milliseconds number of milliseconds + * @return new Interval + */ + @NotNull + public static DT millis(long milliseconds) { + return new DT(Duration.ofMillis(milliseconds)); + } + + /** + * Create an interval based on the number nanoseconds. + * @param nanoseconds number of nanoseconds + * @return new Interval + */ + @NotNull + public static DT nanos(long nanoseconds) { + return new DT(Duration.ofNanos(nanoseconds)); + } +} diff --git a/partiql-spi/src/main/java/org/partiql/spi/datetime/Time.java b/partiql-spi/src/main/java/org/partiql/spi/datetime/Time.java new file mode 100644 index 000000000..ce7bd5776 --- /dev/null +++ b/partiql-spi/src/main/java/org/partiql/spi/datetime/Time.java @@ -0,0 +1,150 @@ +package org.partiql.spi.datetime; + +import org.jetbrains.annotations.NotNull; + +import java.time.LocalTime; + +/** + * A PartiQL time without timezone. + *
+ * Usage + *
+ *     Time.of(time); // time instanceof java.time.LocalTime
+ *     Time.of(10, 30);
+ *     Time.of(10, 30, 0);
+ * 
+ */ +public class Time { + + /** + * Delegate all functionality to {@link LocalTime}. + */ + @NotNull + private final LocalTime time; + + /** + * NO PUBLIC CONSTRUCTORS. + */ + private Time(@NotNull LocalTime time) { + this.time = time; + } + + /** + * @return time for current date-time for the system clock. + */ + @NotNull + public static Time now() { + return new Time(LocalTime.now()); + } + + /** + * Create a Time from a {@link LocalTime}. + * + * @param time local time (without timezone). + * @return new Time + */ + @NotNull + public static Time of(@NotNull LocalTime time) { + return new Time(time); + } + + /** + * Create a Time from an hour, minute. + * + * @param hour hour-of-day from 0 to 23 + * @param minute minute-of-hour from 0 to 59 + * @return new Time + */ + + @NotNull + public static Time of(int hour, int minute) { + return new Time(LocalTime.of(hour, minute)); + } + + /** + * Create a Time from an hour, minute, second. + * + * @param hour hour-of-day from 0 to 23 + * @param minute minute-of-hour from 0 to 59 + * @param second second-of-minute from 0 to 59 + * @return new Time + */ + @NotNull + public static Time of(int hour, int minute, int second) { + return new Time(LocalTime.of(hour, minute, second)); + } + + /** + * + * @param hour hour-of-day from 0 to 23 + * @param minute minute-of-hour from 0 to 59 + * @param second second-of-minute from 0 to 59 + * @param nano nano-of-second from 0 to 999,999,999 + * @return new Time + */ + @NotNull + public static Time of(int hour, int minute, int second, int nano) { + return new Time(LocalTime.of(hour, minute, second, nano)); + } + + /** + * @return a {@link LocalTime} for all functionality. + */ + @NotNull + public LocalTime toLocalTime() { + return time; + } + + /** + * @return the hour-of-day, from 0 to 23 + */ + public int getHour() { + return time.getHour(); + } + + /** + * @return the minute-of-hour, from 0 to 59 + */ + public int getMinute() { + return time.getMinute(); + } + + /** + * @return the second-of-minute, from 0 to 59 + */ + public int getSecond() { + return time.getSecond(); + } + + /** + * @return the nano-of-second, from 0 to 999,999,999 + */ + public int getNano() { + return time.getNano(); + } + + @Override + public int hashCode() { + return time.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof Time)) + return false; + return time.equals(((Time) obj).time); + } + + /** + * @return SQL string. + */ + @Override + public String toString() { + // TODO confirm SQL FORMATTER + return "TIME '" + time; + } +} diff --git a/partiql-spi/src/main/java/org/partiql/spi/datetime/Timestamp.java b/partiql-spi/src/main/java/org/partiql/spi/datetime/Timestamp.java new file mode 100644 index 000000000..dfe639164 --- /dev/null +++ b/partiql-spi/src/main/java/org/partiql/spi/datetime/Timestamp.java @@ -0,0 +1,182 @@ +package org.partiql.spi.datetime; + + +import org.jetbrains.annotations.NotNull; + +import java.time.LocalDateTime; + +/** + * A PartiQL timestamp without timezone. + */ +public class Timestamp { + + private final Date date; + private final Time time; + + /** + * NO PUBLIC CONSTRUCTORS. + */ + private Timestamp(@NotNull Date date, @NotNull Time time) { + this.date = date; + this.time = time; + } + + /** + * @return timestamp for current date-time for the system clock. + */ + @NotNull + public Timestamp now() { + return new Timestamp(Date.now(), Time.now()); + } + + /** + * Create a new Timestamp from the date-time pair. + * + * @param date a date. + * @param time a time. + * @return new Timestamp + */ + @NotNull + public static Timestamp of(@NotNull Date date, @NotNull Time time) { + return new Timestamp(date, time); + } + + /** + * Create a new Timestamp from the fields. + * + * @param year the year to represent, from Year.MIN_YEAR to Year.MAX_YEAR + * @param month the month-of-year to represent, from 1 (January) to 12 (December) + * @param day the day of month to represent, from 1 to 31 + * @param hour hour-of-day from 0 to 23 + * @param minute minute-of-hour from 0 to 59 + * @param second second-of-minute from 0 to 59 + * @return new Timestamp + */ + @NotNull + public static Timestamp of(int year, int month, int day, int hour, int minute, int second) { + Date date = Date.of(year, month, day); + Time time = Time.of(hour, minute, second); + return new Timestamp(date, time); + } + + /** + * @param year the year to represent, from Year.MIN_YEAR to Year.MAX_YEAR + * @param month the month-of-year to represent, from 1 (January) to 12 (December) + * @param day the day of month to represent, from 1 to 31 + * @param hour hour-of-day from 0 to 23 + * @param minute minute-of-hour from 0 to 59 + * @param second second-of-minute from 0 to 59 + * @param nano nano-of-second from 0 to 999,999,999 + * @return new Timestamp + */ + @NotNull + public static Timestamp of(int year, int month, int day, int hour, int minute, int second, int nano) { + Date date = Date.of(year, month, day); + Time time = Time.of(hour, minute, second, nano); + return new Timestamp(date, time); + } + + /** + * Create a Timestamp from a {@link LocalDateTime}. + * + * @param timestamp local date time (without timezone). + * @return new Timestamp + */ + @NotNull + public static Timestamp of(@NotNull LocalDateTime timestamp) { + return new Timestamp(Date.of(timestamp.toLocalDate()), Time.of(timestamp.toLocalTime())); + } + + /** + * @return a {@link LocalDateTime} for this timestamp. + */ + @NotNull + public LocalDateTime toLocalDateTime() { + return LocalDateTime.of(date.toLocalDate(), time.toLocalTime()); + } + + /** + * @return the timestamp date. + */ + @NotNull + public Date toDate() { + return date; + } + + /** + * @return the timestamp time. + */ + @NotNull + public Time toTime() { + return time; + } + + /** + * @return the year of this date + */ + public int getYear() { + return date.getYear(); + } + + /** + * @return the month (of the year) field. + */ + public int getMonth() { + return date.getMonth(); + } + + /** + * @return the day-of-month field. + */ + public int getDay() { + return date.getDay(); + } + + /** + * @return the hour-of-day, from 0 to 23 + */ + public int getHour() { + return time.getHour(); + } + + /** + * @return the minute-of-hour, from 0 to 59 + */ + public int getMinute() { + return time.getMinute(); + } + + /** + * @return the second-of-minute, from 0 to 59 + */ + public int getSecond() { + return time.getSecond(); + } + + /** + * @return the nano-of-second, from 0 to 999,999,999 + */ + public int getNano() { + return time.getNano(); + } + + @Override + public int hashCode() { + return toLocalDateTime().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof Timestamp)) return false; + Timestamp o = (Timestamp) obj; + return time.equals(o.time) && date.equals(o.date); + } + + @Override + public String toString() { + // TODO confirm SQL format + return "TIMESTAMP " + date + " " + time; + } +} diff --git a/partiql-spi/src/main/java/org/partiql/spi/datetime/Timestampz.java b/partiql-spi/src/main/java/org/partiql/spi/datetime/Timestampz.java new file mode 100644 index 000000000..e208f3e6c --- /dev/null +++ b/partiql-spi/src/main/java/org/partiql/spi/datetime/Timestampz.java @@ -0,0 +1,163 @@ +package org.partiql.spi.datetime; + + +import org.jetbrains.annotations.NotNull; + +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; + +/** + * A PartiQL timestamp without timezone. + */ +public class Timestampz { + + private final Date date; + private final Timez time; + + /** + * NO PUBLIC CONSTRUCTORS. + */ + private Timestampz(@NotNull Date date, @NotNull Timez time) { + this.date = date; + this.time = time; + } + + /** + * @return timestamp for current date-time for the system clock. + */ + @NotNull + public Timestampz now() { + return new Timestampz(Date.now(), Timez.now()); + } + + /** + * Create a new Timestampz from the date-time pair. + * + * @param date a date. + * @param time a time. + * @return new Timestampz + */ + @NotNull + public static Timestampz of(@NotNull Date date, @NotNull Timez time) { + return new Timestampz(date, time); + } + + /** + * Create a Timestampz from a {@link LocalDateTime}. + * + * @param timestamp local date time (without timezone). + * @return new Timestampz + */ + @NotNull + public static Timestampz of(@NotNull OffsetDateTime timestamp) { + return new Timestampz(Date.of(timestamp.toLocalDate()), Timez.of(timestamp.toOffsetTime())); + } + + /** + * @return a {@link LocalDateTime}. + */ + @NotNull + public LocalDateTime toLocalDateTime() { + return LocalDateTime.of(date.toLocalDate(), time.toLocalTime()); + } + + /** + * @return an {@link OffsetDateTime}. + */ + @NotNull + public OffsetDateTime toOffsetDateTime() { + return OffsetDateTime.of(toLocalDateTime(), time.getOffset()); + } + + /** + * @return the timestamp date. + */ + @NotNull + public Date toDate() { + return date; + } + + /** + * @return the timestamp time. + */ + @NotNull + public Timez toTimez() { + return time; + } + + /** + * @return the year of this date + */ + public int getYear() { + return date.getYear(); + } + + /** + * @return the month (of the year) field. + */ + public int getMonth() { + return date.getMonth(); + } + + /** + * @return the day-of-month field. + */ + public int getDay() { + return date.getDay(); + } + + /** + * @return the hour-of-day, from 0 to 23 + */ + public int getHour() { + return time.getHour(); + } + + /** + * @return the minute-of-hour, from 0 to 59 + */ + public int getMinute() { + return time.getMinute(); + } + + /** + * @return the second-of-minute, from 0 to 59 + */ + public int getSecond() { + return time.getSecond(); + } + + /** + * @return the nano-of-second, from 0 to 999,999,999 + */ + public int getNano() { + return time.getNano(); + } + + + @NotNull + public ZoneOffset getOffset() { + return time.getOffset(); + } + + @Override + public int hashCode() { + return toLocalDateTime().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof Timestampz)) return false; + Timestampz o = (Timestampz) obj; + return time.equals(o.time) && date.equals(o.date); + } + + @Override + public String toString() { + // TODO confirm SQL format + return "TIMESTAMPZ " + date + " " + time; + } +} diff --git a/partiql-spi/src/main/java/org/partiql/spi/datetime/Timez.java b/partiql-spi/src/main/java/org/partiql/spi/datetime/Timez.java new file mode 100644 index 000000000..6bd4a5b6c --- /dev/null +++ b/partiql-spi/src/main/java/org/partiql/spi/datetime/Timez.java @@ -0,0 +1,139 @@ +package org.partiql.spi.datetime; + +import jdk.vm.ci.meta.Local; +import org.jetbrains.annotations.NotNull; + +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.ZoneOffset; + +/** + * A PartiQL time with a UTC-offset timezone. + *
+ * Usage + *
+ *     Timez.of(time); // time instanceof java.time.OffsetTime
+ *     Timez.of(10, 30);
+ *     Timez.of(10, 30, 0);
+ * 
+ */ +public class Timez { + + /** + * Delegate all functionality to {@link OffsetTime}. + */ + @NotNull + private final OffsetTime time; + + /** + * NO PUBLIC CONSTRUCTORS. + */ + private Timez(@NotNull OffsetTime time) { + this.time = time; + } + + /** + * @return timez for current date-time for the system clock. + */ + @NotNull + public static Timez now() { + return new Timez(OffsetTime.now()); + } + + /** + * Create a Timez from a {@link OffsetTime}. + * + * @param time offset time (with timezone). + * @return new Timez + */ + @NotNull + public static Timez of(@NotNull OffsetTime time) { + return new Timez(time); + } + + /** + * Create a Timez from a {@link LocalTime} and {@link ZoneOffset}. + * + * @param time local time (without timezone). + * @param offset timezone offset + * @return new Timez + */ + @NotNull + public static Timez of(@NotNull LocalTime time, @NotNull ZoneOffset offset) { + return new Timez(OffsetTime.of(time, offset)); + } + + /** + * @return a {@link LocalTime} + */ + @NotNull + public LocalTime toLocalTime() { + return time.toLocalTime(); + } + + /** + * @return an {@link OffsetTime} + */ + @NotNull + public OffsetTime toOffsetTime() { + return time; + } + + /** + * @return the hour-of-day, from 0 to 23 + */ + public int getHour() { + return time.getHour(); + } + + /** + * @return the minute-of-hour, from 0 to 59 + */ + public int getMinute() { + return time.getMinute(); + } + + /** + * @return the second-of-minute, from 0 to 59 + */ + public int getSecond() { + return time.getSecond(); + } + + /** + * @return the nano-of-second, from 0 to 999,999,999 + */ + public int getNano() { + return time.getNano(); + } + + @NotNull + public ZoneOffset getOffset() { + return time.getOffset(); + } + + @Override + public int hashCode() { + return time.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof Timez)) + return false; + return time.equals(((Timez) obj).time); + } + + /** + * @return SQL string. + */ + @Override + public String toString() { + // TODO confirm SQL FORMATTER + return "TIMEZ '" + time; + } +} diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/datetime/Date.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/datetime/Date.kt deleted file mode 100644 index f5edc45e8..000000000 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/datetime/Date.kt +++ /dev/null @@ -1,98 +0,0 @@ -package org.partiql.spi.datetime - -import org.partiql.spi.datetime.util.DatetimeComparisons -import java.math.BigDecimal - -/** - * Superclass for all implementations representing date value. - * Date represents a calendar system, (i.e., 2023-06-01). - * It does not include information on time or timezone, instead, it is meant to represent a specific date on calendar. - * For example, 2022-11-25 (black friday in 2022). - * The valid range are from 0001-01-01 to 9999-12-31 - * The [day] must be valid for the year and month, otherwise an exception will be thrown. - */ -public sealed interface Date : Datetime, Comparable { - - public override val year: Int - public override val month: Int - public override val day: Int - - /** - * Hour field for [Date] value is always null. - */ - public override val hour: Int? - get() = null - - /** - * Minute field for [Date] value is always null. - */ - public override val minute: Int? - get() = null - - /** - * Second field for [Date] value is always null. - */ - public override val decimalSecond: BigDecimal? - get() = null - - /** - * Timezone field for [Date] value is always null. - */ - public override val timeZone: Timezone? - get() = null - - // Operation - /** - * Construct a [Timestamp] value by appending [time] to this [Date] value. - */ - public fun atTime(time: Time): Timestamp - - /** - * Returns a [Date] value with the specified number of days added. - * The month and year fields may be changed as necessary to ensure the result remains valid. - * [days] can be negative. - */ - public fun plusDays(days: Long): Date - - /** - * Returns a [Date] value with the specified number of months added. - * The month and year fields may be changed as necessary to ensure the result remains valid. - * [months] can be negative. - */ - public fun plusMonths(months: Long): Date - - /** - * Returns a [Date] value with the specified number of months added. - * [years] can be negative. - */ - public fun plusYears(years: Long): Date - - /** - * Comparison method for [Date] value. - * - * Since [Date] value has no concept of time zone, they are compared as calendar date. - */ - public override fun compareTo(other: Date): Int = - DatetimeComparisons.compareTo(this, other) -} - -/** - * Superclass for all implementation representing date value - */ -public abstract class DateImpl : Date, Comparable { - public final override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other?.javaClass != javaClass) return false - other as DateImpl - if (this.year != other.year) return false - if (this.month != other.month) return false - if (this.day != other.day) return false - return true - } - - public final override fun hashCode(): Int = - year.hashCode() + month.hashCode() + day.hashCode() - - public final override fun toString(): String = - "${this.javaClass.simpleName}(year=$year, month=$month, day=$day)" -} \ No newline at end of file diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/datetime/Datetime.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/datetime/Datetime.kt deleted file mode 100644 index e68e93a26..000000000 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/datetime/Datetime.kt +++ /dev/null @@ -1,292 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file 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.partiql.spi.datetime - -import java.math.BigDecimal - -/** - * Superclass for all classes representing datetime values. - */ -public sealed interface Datetime { - /** - * Year field of date time object - */ - public val year: Int? - - /** - * Month field of date time object - */ - public val month: Int? - - /** - * Day field of date time object - */ - public val day: Int? - - /** - * Hour field of date time object - */ - public val hour: Int? - - /** - * Minute field of date time object - */ - public val minute: Int? - - /** - * Second field of date time object. - * This field includes second fraction. - */ - public val decimalSecond: BigDecimal? - - /** - * Time zone field of date time object. See [Timezone] - */ - public val timeZone: Timezone? - - /** - * Equals method. - * Two [Datetime] values are considered equals if and only if all the fields are equals. - */ - public override fun equals(other: Any?): Boolean - - public override fun hashCode(): Int - - public override fun toString(): String - - public companion object { - - /** - * Create a timestamp value. - * - * If time zone is null, then the value created is timestamp without timezone. - * Otherwise, a timestamp with timezone is created. - * - * @param year Proleptic Year - * @param month Month of Year - * @param day Day Of Month - * @param hour Hour of Day - * @param minute Minute of Hour - * @param second Second, include any fraction second. - * @param timeZone TimeZone offset, see [Timezone] - */ - @JvmStatic - @JvmOverloads - public fun timestamp( - year: Int, - month: Int = 1, - day: Int = 1, - hour: Int = 0, - minute: Int = 0, - second: BigDecimal = BigDecimal.ZERO, - timeZone: Timezone? = null, - ): Timestamp = when (timeZone) { - Timezone.UnknownTimeZone -> { - if (second.scale() <= 9) { - OffsetTimestampLowPrecision.of(year, month, day, hour, minute, second, timeZone) - } else { - OffsetTimestampHighPrecision.of(year, month, day, hour, minute, second, timeZone) - } - } - is Timezone.UtcOffset -> { - if (timeZone.totalOffsetMinutes.absoluteValue > JAVA_MAX_OFFSET) { - OffsetTimestampHighPrecision.of(year, month, day, hour, minute, second, timeZone) - } else if (second.scale() <= 9) { - OffsetTimestampLowPrecision.of(year, month, day, hour, minute, second, timeZone) - } else { - OffsetTimestampHighPrecision.of(year, month, day, hour, minute, second, timeZone) - } - } - - null -> { - if (second.scale() <= 9) LocalTimestampLowPrecision.of(year, month, day, hour, minute, second) - else LocalTimestampHighPrecision.of(year, month, day, hour, minute, second) - } - } - - /** - * Create a timestamp value. - * The timestamp created will have precision 0 (no fractional second). - * - * @param year Proleptic Year - * @param month Month of Year - * @param day Day Of Month - * @param hour Hour of Day - * @param minute Minute of Hour - * @param second whole Second. - * @param timeZone TimeZone offset, see [Timezone] - */ - @JvmStatic - @JvmOverloads - public fun timestamp( - year: Int, - month: Int, - day: Int, - hour: Int, - minute: Int, - second: Int, - timeZone: Timezone? = null, - ): Timestamp = timestamp(year, month, day, hour, minute, second.toBigDecimal(), timeZone) - - /** - * Create a timestamp value. - * If time is an instance of [TimeWithTimeZone], then the timestamp created will be [TimestampWithTimeZone], - * Otherwise it will be a [TimestampWithoutTimeZone]. - * - * @param date: Date. See [Date] - * @param time: Time. See [Time] - */ - @JvmStatic - public fun timestamp(date: Date, time: Time): Timestamp = when (time) { - is TimeWithTimeZone -> timestamp( - date.year, date.month, date.day, time.hour, time.minute, time.decimalSecond, time.timeZone - ) - - is TimeWithoutTimeZone -> timestamp( - date.year, date.month, date.day, time.hour, time.minute, time.decimalSecond - ) - } - - /** - * Create a timestamp value based on [com.amazon.ion.Timestamp] - * The created timestamp will always be an instance of [TimestampWithTimeZone] - */ - @JvmStatic - public fun timestamp(ionTimestamp: com.amazon.ion.Timestamp): TimestampWithTimeZone = - if (ionTimestamp.localOffset != null && ionTimestamp.localOffset.absoluteValue > JAVA_MAX_OFFSET) { - OffsetTimestampHighPrecision.forIonTimestamp(ionTimestamp) - } else if (ionTimestamp.decimalSecond.scale() <= 9) { - OffsetTimestampLowPrecision.forIonTimestamp(ionTimestamp) - } else { - OffsetTimestampHighPrecision.forIonTimestamp(ionTimestamp) - } - - /** - * Create a timestamp value based on displacement of Unix Epoch, at given time zone. - * The created timestamp will always be an instance of [TimestampWithTimeZone] - */ - @JvmStatic - public fun timestamp(epochSeconds: BigDecimal, timeZone: Timezone): TimestampWithTimeZone = when (timeZone) { - Timezone.UnknownTimeZone -> { - if (epochSeconds.scale() <= 9) { - OffsetTimestampLowPrecision.forEpochSeconds(epochSeconds, timeZone) - } else { - OffsetTimestampHighPrecision.forEpochSeconds(epochSeconds, timeZone) - } - } - is Timezone.UtcOffset -> { - if (timeZone.totalOffsetMinutes > JAVA_MAX_OFFSET) { - OffsetTimestampHighPrecision.forEpochSeconds(epochSeconds, timeZone) - } else if (epochSeconds.scale() <= 9) { - OffsetTimestampLowPrecision.forEpochSeconds(epochSeconds, timeZone) - } else { - OffsetTimestampHighPrecision.forEpochSeconds(epochSeconds, timeZone) - } - } - } - - /** - * Create a time value. - * - * If time zone is null, then the value created is time without timezone. - * Otherwise, a time with timezone is created. - * - * @param hour Hour of Day - * @param minute Minute of Hour - * @param second Second, include any fraction second. - * @param timeZone TimeZone offset, see [Timezone] - */ - @JvmStatic - @JvmOverloads - public fun time( - hour: Int, - minute: Int, - second: BigDecimal, - timeZone: Timezone? = null, - ): Time = when (timeZone) { - Timezone.UnknownTimeZone -> { - if (second.scale() <= 9) { - OffsetTimeLowPrecision.of(hour, minute, second, timeZone) - } else { - OffsetTimeHighPrecision.of(hour, minute, second, timeZone) - } - } - is Timezone.UtcOffset -> { - if (timeZone.totalOffsetMinutes.absoluteValue > JAVA_MAX_OFFSET) { - OffsetTimeHighPrecision.of(hour, minute, second, timeZone) - } else if (second.scale() <= 9) { - OffsetTimeLowPrecision.of(hour, minute, second, timeZone) - } else { - OffsetTimeHighPrecision.of(hour, minute, second, timeZone) - } - } - - null -> { - if (second.scale() <= 9) LocalTimeLowPrecision.of(hour, minute, second) - else LocalTimeHighPrecision.of(hour, minute, second) - } - } - - /** - * Create a time value. - * The time created will have precision 0 (no fractional second). - * - * @param hour Hour of Day - * @param minute Minute of Hour - * @param second whole Second. - * @param timeZone TimeZone offset, see [Timezone] - */ - @JvmStatic - @JvmOverloads - public fun time( - hour: Int, - minute: Int, - second: Int, - timeZone: Timezone? = null, - ): Time = time(hour, minute, second.toBigDecimal(), timeZone) - - /** - * Create a time value. - * The time created will have precision 9 (nanosecond precision). - * - * @param hour Hour of Day - * @param minute Minute of Hour - * @param second whole Second. - * @param nano Nano offset. - * @param timeZone TimeZone offset, see [Timezone] - */ - @JvmStatic - @JvmOverloads - public fun time( - hour: Int, - minute: Int, - second: Int, - nano: Int, - timeZone: Timezone? = null, - ): Time { - val decimalSecond = second.toBigDecimal().plus(nano.toBigDecimal().movePointLeft(9)) - return time(hour, minute, decimalSecond, timeZone) - } - - @JvmStatic - public fun date( - year: Int, - month: Int, - day: Int, - ): Date = SqlDate.of(year, month, day) - } -} - diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/datetime/Interval.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/datetime/Interval.kt deleted file mode 100644 index 431703495..000000000 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/datetime/Interval.kt +++ /dev/null @@ -1,53 +0,0 @@ -package org.partiql.spi.datetime - -import java.math.BigDecimal - -/** - * This is experimental and, at present, cannot be instantiated. - * - * An interval is composed of a contiguous subset of the fields. - * The subset of the fields is called the “precision” of the value. - * The leading field, either “YEAR” or “DAY” is only constrained by the “leading-field” precision. - * All fields are integers except for “SECOND” which has “fractional seconds precision”. - */ -public class Interval private constructor( - private val years: Int, - private val months: Int, - private val days: Int, - private val hours: Int, - private val minutes: Int, - private val seconds: BigDecimal -) { - - /** - * Years, range: unconstrained. - */ - public fun getYears(): Int = 0 - - /** - * Months, range: [0,11] - */ - public fun getMonths(): Int = 0 - - /** - * Days, range: unconstrained - */ - public fun getDays(): Int = 0 - - /** - * Hours, range: [0,23] - */ - public fun getHours(): Int = 0 - - /** - * Minutes, range: [0,59] - */ - public fun getMinutes(): Int = 0 - - /** - * Seconds, range: [0,59.999999999...] - * - * NOTE: The BigDecimal value precision reflects the interval seconds precision. - */ - public fun getSeconds(): BigDecimal = BigDecimal.ZERO -} diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/datetime/Time.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/datetime/Time.kt deleted file mode 100644 index c884ae0d0..000000000 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/datetime/Time.kt +++ /dev/null @@ -1,192 +0,0 @@ -package org.partiql.spi.datetime - -import org.partiql.spi.datetime.util.DatetimeComparisons -import org.partiql.spi.datetime.util.DatetimePrecisionUtil -import org.partiql.spi.datetime.util.DatetimeUtil.toBigDecimal -import java.math.BigDecimal - -/** - * Superclass for all implementations representing time value. - * See [TimeWithTimeZone] and [TimeWithoutTimeZone] - */ -public sealed interface Time : Datetime, Comparable