From e8b7493682cd893eddab9a0b1223cac16e908726 Mon Sep 17 00:00:00 2001 From: aokolnychyi Date: Tue, 15 Nov 2022 19:45:03 -0800 Subject: [PATCH] API, Core: Move micros and days conversions to DateTimeUtil --- .../org/apache/iceberg/transforms/Dates.java | 27 ++++----- .../apache/iceberg/transforms/Timestamps.java | 32 ++++------- .../org/apache/iceberg/util/DateTimeUtil.java | 55 +++++++++++++++++++ .../apache/iceberg/util/TestDateTimeUtil.java | 0 4 files changed, 77 insertions(+), 37 deletions(-) rename {core => api}/src/main/java/org/apache/iceberg/util/DateTimeUtil.java (70%) rename {core => api}/src/test/java/org/apache/iceberg/util/TestDateTimeUtil.java (100%) diff --git a/api/src/main/java/org/apache/iceberg/transforms/Dates.java b/api/src/main/java/org/apache/iceberg/transforms/Dates.java index 0543a0c8915a..9128f6c53f35 100644 --- a/api/src/main/java/org/apache/iceberg/transforms/Dates.java +++ b/api/src/main/java/org/apache/iceberg/transforms/Dates.java @@ -18,9 +18,6 @@ */ package org.apache.iceberg.transforms; -import java.time.Instant; -import java.time.LocalDate; -import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; import org.apache.iceberg.expressions.BoundPredicate; import org.apache.iceberg.expressions.BoundTransform; @@ -30,6 +27,7 @@ import org.apache.iceberg.relocated.com.google.common.base.Preconditions; import org.apache.iceberg.types.Type; import org.apache.iceberg.types.Types; +import org.apache.iceberg.util.DateTimeUtil; import org.apache.iceberg.util.SerializableFunction; enum Dates implements Transform { @@ -50,24 +48,19 @@ public Integer apply(Integer days) { return null; } - if (granularity == ChronoUnit.DAYS) { - return days; - } - - if (days >= 0) { - LocalDate date = EPOCH.plusDays(days); - return (int) granularity.between(EPOCH, date); - } else { - // add 1 day to the value to account for the case where there is exactly 1 unit between the - // date and epoch because the result will always be decremented. - LocalDate date = EPOCH.plusDays(days + 1); - return (int) granularity.between(EPOCH, date) - 1; + switch (granularity) { + case YEARS: + return DateTimeUtil.daysToYears(days); + case MONTHS: + return DateTimeUtil.daysToMonths(days); + case DAYS: + return days; + default: + throw new UnsupportedOperationException("Unsupported time unit: " + granularity); } } } - private static final LocalDate EPOCH = - Instant.ofEpochSecond(0).atOffset(ZoneOffset.UTC).toLocalDate(); private final ChronoUnit granularity; private final String name; private final Apply apply; diff --git a/api/src/main/java/org/apache/iceberg/transforms/Timestamps.java b/api/src/main/java/org/apache/iceberg/transforms/Timestamps.java index e63ee39ac19e..b148c23a5ab1 100644 --- a/api/src/main/java/org/apache/iceberg/transforms/Timestamps.java +++ b/api/src/main/java/org/apache/iceberg/transforms/Timestamps.java @@ -18,9 +18,6 @@ */ package org.apache.iceberg.transforms; -import java.time.Instant; -import java.time.OffsetDateTime; -import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; import org.apache.iceberg.expressions.BoundPredicate; import org.apache.iceberg.expressions.BoundTransform; @@ -30,6 +27,7 @@ import org.apache.iceberg.relocated.com.google.common.base.Preconditions; import org.apache.iceberg.types.Type; import org.apache.iceberg.types.Types; +import org.apache.iceberg.util.DateTimeUtil; import org.apache.iceberg.util.SerializableFunction; enum Timestamps implements Transform { @@ -51,27 +49,21 @@ public Integer apply(Long timestampMicros) { return null; } - if (timestampMicros >= 0) { - OffsetDateTime timestamp = - Instant.ofEpochSecond( - Math.floorDiv(timestampMicros, 1_000_000), - Math.floorMod(timestampMicros, 1_000_000) * 1000) - .atOffset(ZoneOffset.UTC); - return (int) granularity.between(EPOCH, timestamp); - } else { - // add 1 micro to the value to account for the case where there is exactly 1 unit between - // the timestamp and epoch because the result will always be decremented. - OffsetDateTime timestamp = - Instant.ofEpochSecond( - Math.floorDiv(timestampMicros, 1_000_000), - Math.floorMod(timestampMicros + 1, 1_000_000) * 1000) - .atOffset(ZoneOffset.UTC); - return (int) granularity.between(EPOCH, timestamp) - 1; + switch (granularity) { + case YEARS: + return DateTimeUtil.microsToYears(timestampMicros); + case MONTHS: + return DateTimeUtil.microsToMonths(timestampMicros); + case DAYS: + return DateTimeUtil.microsToDays(timestampMicros); + case HOURS: + return DateTimeUtil.microsToHours(timestampMicros); + default: + throw new UnsupportedOperationException("Unsupported time unit: " + granularity); } } } - private static final OffsetDateTime EPOCH = Instant.ofEpochSecond(0).atOffset(ZoneOffset.UTC); private final ChronoUnit granularity; private final String name; private final SerializableFunction apply; diff --git a/core/src/main/java/org/apache/iceberg/util/DateTimeUtil.java b/api/src/main/java/org/apache/iceberg/util/DateTimeUtil.java similarity index 70% rename from core/src/main/java/org/apache/iceberg/util/DateTimeUtil.java rename to api/src/main/java/org/apache/iceberg/util/DateTimeUtil.java index 29214ff8831d..a2f5301f44a9 100644 --- a/core/src/main/java/org/apache/iceberg/util/DateTimeUtil.java +++ b/api/src/main/java/org/apache/iceberg/util/DateTimeUtil.java @@ -34,6 +34,7 @@ private DateTimeUtil() {} public static final OffsetDateTime EPOCH = Instant.ofEpochSecond(0).atOffset(ZoneOffset.UTC); public static final LocalDate EPOCH_DAY = EPOCH.toLocalDate(); public static final long MICROS_PER_MILLIS = 1000L; + public static final long MICROS_PER_SECOND = 1_000_000L; public static LocalDate dateFromDays(int daysFromEpoch) { return ChronoUnit.DAYS.addTo(EPOCH_DAY, daysFromEpoch); @@ -133,4 +134,58 @@ public static long isoTimestampToMicros(String timestampString) { return microsFromTimestamp( LocalDateTime.parse(timestampString, DateTimeFormatter.ISO_LOCAL_DATE_TIME)); } + + public static int daysToYears(int days) { + return convertDays(days, ChronoUnit.YEARS); + } + + public static int daysToMonths(int days) { + return convertDays(days, ChronoUnit.MONTHS); + } + + private static int convertDays(int days, ChronoUnit granularity) { + if (days >= 0) { + LocalDate date = EPOCH_DAY.plusDays(days); + return (int) granularity.between(EPOCH_DAY, date); + } else { + // add 1 day to the value to account for the case where there is exactly 1 unit between the + // date and epoch because the result will always be decremented. + LocalDate date = EPOCH_DAY.plusDays(days + 1); + return (int) granularity.between(EPOCH_DAY, date) - 1; + } + } + + public static int microsToYears(long micros) { + return convertMicros(micros, ChronoUnit.YEARS); + } + + public static int microsToMonths(long micros) { + return convertMicros(micros, ChronoUnit.MONTHS); + } + + public static int microsToDays(long micros) { + return convertMicros(micros, ChronoUnit.DAYS); + } + + public static int microsToHours(long micros) { + return convertMicros(micros, ChronoUnit.HOURS); + } + + private static int convertMicros(long micros, ChronoUnit granularity) { + if (micros >= 0) { + long epochSecond = Math.floorDiv(micros, MICROS_PER_SECOND); + long nanoAdjustment = Math.floorMod(micros, MICROS_PER_SECOND) * 1000; + return (int) granularity.between(EPOCH, toOffsetDateTime(epochSecond, nanoAdjustment)); + } else { + // add 1 micro to the value to account for the case where there is exactly 1 unit between + // the timestamp and epoch because the result will always be decremented. + long epochSecond = Math.floorDiv(micros, MICROS_PER_SECOND); + long nanoAdjustment = Math.floorMod(micros + 1, MICROS_PER_SECOND) * 1000; + return (int) granularity.between(EPOCH, toOffsetDateTime(epochSecond, nanoAdjustment)) - 1; + } + } + + private static OffsetDateTime toOffsetDateTime(long epochSecond, long nanoAdjustment) { + return Instant.ofEpochSecond(epochSecond, nanoAdjustment).atOffset(ZoneOffset.UTC); + } } diff --git a/core/src/test/java/org/apache/iceberg/util/TestDateTimeUtil.java b/api/src/test/java/org/apache/iceberg/util/TestDateTimeUtil.java similarity index 100% rename from core/src/test/java/org/apache/iceberg/util/TestDateTimeUtil.java rename to api/src/test/java/org/apache/iceberg/util/TestDateTimeUtil.java