Repository: calcite-avatica
Updated Branches:
  refs/heads/branch-avatica-1.12 bd9c968e8 -> 0dd3d6ea1 (forced update)


[CALCITE-2303] In EXTRACT function, support MICROSECONDS, MILLISECONDS, EPOCH, 
ISODOW, ISOYEAR and DECADE time units (Sergey Nuyanzin)

Also, fixed issue related to week extraction (wrong ISO-8601 week
calculation in some cases, additional tests provided).

Close apache/calcite-avatica#50


Project: http://git-wip-us.apache.org/repos/asf/calcite-avatica/repo
Commit: http://git-wip-us.apache.org/repos/asf/calcite-avatica/commit/b8639882
Tree: http://git-wip-us.apache.org/repos/asf/calcite-avatica/tree/b8639882
Diff: http://git-wip-us.apache.org/repos/asf/calcite-avatica/diff/b8639882

Branch: refs/heads/branch-avatica-1.12
Commit: b863988291db6cd64b1517d238b49e98aaaf24d2
Parents: 4beeef4
Author: snuyanzin <snuyan...@gmail.com>
Authored: Sun May 27 13:49:17 2018 +0300
Committer: Julian Hyde <jh...@apache.org>
Committed: Fri Jun 1 10:52:03 2018 -0700

----------------------------------------------------------------------
 .../calcite/avatica/util/DateTimeUtils.java     |  55 ++++-
 .../apache/calcite/avatica/util/TimeUnit.java   |  11 +-
 .../calcite/avatica/util/TimeUnitRange.java     |   2 +
 .../calcite/avatica/util/DateTimeUtilsTest.java | 208 ++++++++++++++++++-
 4 files changed, 263 insertions(+), 13 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/b8639882/core/src/main/java/org/apache/calcite/avatica/util/DateTimeUtils.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/avatica/util/DateTimeUtils.java 
b/core/src/main/java/org/apache/calcite/avatica/util/DateTimeUtils.java
index e1f6999..b4148dc 100644
--- a/core/src/main/java/org/apache/calcite/avatica/util/DateTimeUtils.java
+++ b/core/src/main/java/org/apache/calcite/avatica/util/DateTimeUtils.java
@@ -87,6 +87,11 @@ public class DateTimeUtils {
   public static final long MILLIS_PER_DAY = 86400000; // = 24 * 60 * 60 * 1000
 
   /**
+   * The number of seconds in a day.
+   */
+  public static final long SECONDS_PER_DAY = 86_400; // = 24 * 60 * 60
+
+  /**
    * Calendar set to the epoch (1970-01-01 00:00:00 UTC). Useful for
    * initializing other values. Calendars are not immutable, so be careful not
    * to screw up this object for everyone else.
@@ -729,7 +734,13 @@ public class DateTimeUtils {
   }
 
   public static long unixDateExtract(TimeUnitRange range, long date) {
-    return julianExtract(range, (int) date + EPOCH_JULIAN);
+    switch (range) {
+    case EPOCH:
+      // no need to extract year/month/day, just multiply
+      return date * SECONDS_PER_DAY;
+    default:
+      return julianExtract(range, (int) date + EPOCH_JULIAN);
+    }
   }
 
   private static int julianExtract(TimeUnitRange range, int julian) {
@@ -758,6 +769,14 @@ public class DateTimeUtils {
     switch (range) {
     case YEAR:
       return year;
+    case ISOYEAR:
+      int weekNumber = getIso8601WeekNumber(julian, year, month, day);
+      if (weekNumber == 1 && month == 12) {
+        return year + 1;
+      } else if (month == 1 && weekNumber > 50) {
+        return year - 1;
+      }
+      return year;
     case QUARTER:
       return (month + 2) / 3;
     case MONTH:
@@ -766,15 +785,15 @@ public class DateTimeUtils {
       return day;
     case DOW:
       return (int) floorMod(julian + 1, 7) + 1; // sun=1, sat=7
+    case ISODOW:
+      return (int) floorMod(julian, 7) + 1; // mon=1, sun=7
     case WEEK:
-      long fmofw = firstMondayOfFirstWeek(year);
-      if (julian < fmofw) {
-        fmofw = firstMondayOfFirstWeek(year - 1);
-      }
-      return (int) (julian - fmofw) / 7 + 1;
+      return getIso8601WeekNumber(julian, year, month, day);
     case DOY:
       final long janFirst = ymdToJulian(year, 1, 1);
       return (int) (julian - janFirst) + 1;
+    case DECADE:
+      return year / 10;
     case CENTURY:
       return year > 0
           ? (year + 99) / 100
@@ -798,6 +817,30 @@ public class DateTimeUtils {
     return janFirst + (11 - janFirstDow) % 7 - 3;
   }
 
+  /** Returns the ISO-8601 week number based on year, month, day.
+   * Per ISO-8601 it is the Monday of the week that contains Jan 4,
+   * or equivalently, it is a Monday between Dec 29 and Jan 4.
+   * Sometimes it is in the year before the given year, sometimes after. */
+  private static int getIso8601WeekNumber(int julian, int year, int month, int 
day) {
+    long fmofw = firstMondayOfFirstWeek(year);
+    if (month == 12 && day > 28) {
+      if (31 - day + 4 > 7 - ((int) floorMod(julian, 7) + 1)
+          && 31 - day + (int) (floorMod(julian, 7) + 1) >= 4) {
+        return (int) (julian - fmofw) / 7 + 1;
+      } else {
+        return 1;
+      }
+    } else if (month == 1 && day < 5) {
+      if (4 - day <= 7 - ((int) floorMod(julian, 7) + 1)
+          && day - ((int) (floorMod(julian, 7) + 1)) >= -3) {
+        return 1;
+      } else {
+        return (int) (julian - firstMondayOfFirstWeek(year - 1)) / 7 + 1;
+      }
+    }
+    return (int) (julian - fmofw) / 7 + 1;
+  }
+
   /** Extracts a time unit from a UNIX date (milliseconds since epoch). */
   public static int unixTimestampExtract(TimeUnitRange range,
       long timestamp) {

http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/b8639882/core/src/main/java/org/apache/calcite/avatica/util/TimeUnit.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/avatica/util/TimeUnit.java 
b/core/src/main/java/org/apache/calcite/avatica/util/TimeUnit.java
index 251c4cf..4516410 100644
--- a/core/src/main/java/org/apache/calcite/avatica/util/TimeUnit.java
+++ b/core/src/main/java/org/apache/calcite/avatica/util/TimeUnit.java
@@ -21,15 +21,16 @@ import java.math.BigDecimal;
 /**
  * Enumeration of time units used to construct an interval.
  *
- * <p>Only {@link #YEAR}, {@link #YEAR}, {@link #MONTH}, {@link #DAY},
+ * <p>Only {@link #YEAR}, {@link #MONTH}, {@link #DAY},
  * {@link #HOUR}, {@link #MINUTE}, {@link #SECOND} can be the unit of a SQL
  * interval.
  *
  * <p>The others ({@link #QUARTER}, {@link #WEEK}, {@link #MILLISECOND},
  * {@link #DOW}, {@link #DOY}, {@link #EPOCH}, {@link #DECADE}, {@link 
#CENTURY},
- * {@link #MILLENNIUM} and {@link #MICROSECOND}) are convenient to use 
internally,
- * when converting to and from UNIX timestamps. And also may be arguments to 
the
- * {@code EXTRACT}, {@code TIMESTAMPADD} and {@code TIMESTAMPDIFF} functions.
+ * {@link #MILLENNIUM}, {@link #MICROSECOND}, {@link #ISODOW} and {@link 
#ISOYEAR})
+ * are convenient to use internally, when converting to and from UNIX 
timestamps.
+ * And also may be arguments to the {@code EXTRACT}, {@code TIMESTAMPADD} and
+ * {@code TIMESTAMPDIFF} functions.
  */
 public enum TimeUnit {
   YEAR(true, ' ', BigDecimal.valueOf(12) /* months */, null),
@@ -43,12 +44,14 @@ public enum TimeUnit {
       BigDecimal.valueOf(60)),
 
   QUARTER(true, '*', BigDecimal.valueOf(3) /* months */, 
BigDecimal.valueOf(4)),
+  ISOYEAR(true, ' ', BigDecimal.valueOf(12) /* months */, null),
   WEEK(false, '*', BigDecimal.valueOf(DateTimeUtils.MILLIS_PER_DAY * 7),
       BigDecimal.valueOf(53)),
   MILLISECOND(false, '.', BigDecimal.ONE, BigDecimal.valueOf(1000)),
   MICROSECOND(false, '.', BigDecimal.ONE.scaleByPowerOfTen(-3),
       BigDecimal.valueOf(1000000)),
   DOW(false, '-', null, null),
+  ISODOW(false, '-', null, null),
   DOY(false, '-', null, null),
   EPOCH(false, '*', null, null),
   DECADE(true, '*', BigDecimal.valueOf(120) /* months */, null),

http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/b8639882/core/src/main/java/org/apache/calcite/avatica/util/TimeUnitRange.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/avatica/util/TimeUnitRange.java 
b/core/src/main/java/org/apache/calcite/avatica/util/TimeUnitRange.java
index 42d44dc..2e65ed3 100644
--- a/core/src/main/java/org/apache/calcite/avatica/util/TimeUnitRange.java
+++ b/core/src/main/java/org/apache/calcite/avatica/util/TimeUnitRange.java
@@ -39,11 +39,13 @@ public enum TimeUnitRange {
   SECOND(TimeUnit.SECOND, null),
 
   // non-standard time units cannot participate in ranges
+  ISOYEAR(TimeUnit.ISOYEAR, null),
   QUARTER(TimeUnit.QUARTER, null),
   WEEK(TimeUnit.WEEK, null),
   MILLISECOND(TimeUnit.MILLISECOND, null),
   MICROSECOND(TimeUnit.MICROSECOND, null),
   DOW(TimeUnit.DOW, null),
+  ISODOW(TimeUnit.ISODOW, null),
   DOY(TimeUnit.DOY, null),
   EPOCH(TimeUnit.EPOCH, null),
   DECADE(TimeUnit.DECADE, null),

http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/b8639882/core/src/test/java/org/apache/calcite/avatica/util/DateTimeUtilsTest.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/calcite/avatica/util/DateTimeUtilsTest.java 
b/core/src/test/java/org/apache/calcite/avatica/util/DateTimeUtilsTest.java
index ea25349..9c9bc34 100644
--- a/core/src/test/java/org/apache/calcite/avatica/util/DateTimeUtilsTest.java
+++ b/core/src/test/java/org/apache/calcite/avatica/util/DateTimeUtilsTest.java
@@ -313,6 +313,15 @@ public class DateTimeUtilsTest {
     assertThat(unixDateExtract(TimeUnitRange.DOW, 365), is(6L));
     assertThat(unixDateExtract(TimeUnitRange.DOW, 366), is(7L));
 
+    // 1969/12/31 was a Wed (4)
+    assertThat(unixDateExtract(TimeUnitRange.ISODOW, -1), is(3L)); // wed
+    assertThat(unixDateExtract(TimeUnitRange.ISODOW, 0), is(4L)); // thu
+    assertThat(unixDateExtract(TimeUnitRange.ISODOW, 1), is(5L)); // fri
+    assertThat(unixDateExtract(TimeUnitRange.ISODOW, 2), is(6L)); // sat
+    assertThat(unixDateExtract(TimeUnitRange.ISODOW, 3), is(7L)); // sun
+    assertThat(unixDateExtract(TimeUnitRange.ISODOW, 365), is(5L));
+    assertThat(unixDateExtract(TimeUnitRange.ISODOW, 366), is(6L));
+
     assertThat(unixDateExtract(TimeUnitRange.DOY, -1), is(365L));
     assertThat(unixDateExtract(TimeUnitRange.DOY, 0), is(1L));
     assertThat(unixDateExtract(TimeUnitRange.DOY, 1), is(2L));
@@ -342,14 +351,64 @@ public class DateTimeUtilsTest {
         is(1L)); // thu
     assertThat(unixDateExtract(TimeUnitRange.WEEK, ymdToUnixDate(2005, 1, 1)),
         is(53L)); // sat
+    assertThat(unixDateExtract(TimeUnitRange.WEEK, ymdToUnixDate(2005, 1, 2)),
+        is(53L)); // sun
+    assertThat(unixDateExtract(TimeUnitRange.WEEK, ymdToUnixDate(2005, 12, 
31)),
+        is(52L)); // sat
     assertThat(unixDateExtract(TimeUnitRange.WEEK, ymdToUnixDate(2006, 1, 1)),
         is(52L)); // sun
+    assertThat(unixDateExtract(TimeUnitRange.WEEK, ymdToUnixDate(2006, 1, 2)),
+        is(1L)); // mon
+    assertThat(unixDateExtract(TimeUnitRange.WEEK, ymdToUnixDate(2006, 12, 
31)),
+        is(52L)); // sun
+    assertThat(unixDateExtract(TimeUnitRange.WEEK, ymdToUnixDate(2007, 1, 1)),
+        is(1L)); // mon
+    assertThat(unixDateExtract(TimeUnitRange.WEEK, ymdToUnixDate(2007, 12, 
30)),
+        is(52L)); // sun
+    assertThat(unixDateExtract(TimeUnitRange.WEEK, ymdToUnixDate(2007, 12, 
31)),
+        is(1L)); // mon
+    assertThat(unixDateExtract(TimeUnitRange.WEEK, ymdToUnixDate(2008, 12, 
28)),
+        is(52L)); // sun
+    assertThat(unixDateExtract(TimeUnitRange.WEEK, ymdToUnixDate(2008, 12, 
29)),
+        is(1L)); // mon
+    assertThat(unixDateExtract(TimeUnitRange.WEEK, ymdToUnixDate(2008, 12, 
30)),
+        is(1L)); // tue
+    assertThat(unixDateExtract(TimeUnitRange.WEEK, ymdToUnixDate(2008, 12, 
31)),
+        is(1L)); // wen
+    assertThat(unixDateExtract(TimeUnitRange.WEEK, ymdToUnixDate(2009, 1, 1)),
+        is(1L)); // thu
+    assertThat(unixDateExtract(TimeUnitRange.WEEK, ymdToUnixDate(2009, 12, 
31)),
+        is(53L)); // thu
+    assertThat(unixDateExtract(TimeUnitRange.WEEK, ymdToUnixDate(2010, 1, 1)),
+        is(53L)); // fri
+    assertThat(unixDateExtract(TimeUnitRange.WEEK, ymdToUnixDate(2010, 1, 2)),
+        is(53L)); // sat
+    assertThat(unixDateExtract(TimeUnitRange.WEEK, ymdToUnixDate(2010, 1, 3)),
+        is(53L)); // sun
+    assertThat(unixDateExtract(TimeUnitRange.WEEK, ymdToUnixDate(2010, 1, 4)),
+        is(1L)); // mon
+    assertThat(unixDateExtract(TimeUnitRange.WEEK, ymdToUnixDate(2012, 12, 
30)),
+        is(52L)); // sun
+    assertThat(unixDateExtract(TimeUnitRange.WEEK, ymdToUnixDate(2012, 12, 
31)),
+        is(1L)); // mon
+    assertThat(unixDateExtract(TimeUnitRange.WEEK, ymdToUnixDate(2014, 12, 
30)),
+        is(1L)); // tue
+    assertThat(unixDateExtract(TimeUnitRange.WEEK, ymdToUnixDate(2014, 12, 
31)),
+        is(1L)); // wen
     assertThat(unixDateExtract(TimeUnitRange.WEEK, ymdToUnixDate(1970, 1, 1)),
         is(1L)); // thu
 
-    assertThat(unixDateExtract(TimeUnitRange.WEEK, -1), is(53L)); // wed
+    // Based on the rule: The number of the ISO 8601 week-numbering week of 
the year.
+    // By definition, ISO weeks start on Mondays and the first week of a year 
contains
+    // January 4 of that year. In other words, the first Thursday of a year is 
in
+    // week 1 of that year.
+    // For that reason 1969-12-31, 1969-12-30 and 1969-12-29 are in the 1-st 
ISO week of 1970
+    assertThat(unixDateExtract(TimeUnitRange.WEEK, -4), is(52L)); // sun
+    assertThat(unixDateExtract(TimeUnitRange.WEEK, -3), is(1L)); // mon
+    assertThat(unixDateExtract(TimeUnitRange.WEEK, -2), is(1L)); // tue
+    assertThat(unixDateExtract(TimeUnitRange.WEEK, -1), is(1L)); // wed
     assertThat(unixDateExtract(TimeUnitRange.WEEK, 0), is(1L)); // thu
-    assertThat(unixDateExtract(TimeUnitRange.WEEK, 1), is(1L)); // fru
+    assertThat(unixDateExtract(TimeUnitRange.WEEK, 1), is(1L)); // fri
     assertThat(unixDateExtract(TimeUnitRange.WEEK, 2), is(1L)); // sat
     assertThat(unixDateExtract(TimeUnitRange.WEEK, 3), is(1L)); // sun
     assertThat(unixDateExtract(TimeUnitRange.WEEK, 4), is(2L)); // mon
@@ -428,6 +487,32 @@ public class DateTimeUtilsTest {
         unixDateExtract(TimeUnitRange.CENTURY, ymdToUnixDate(-2, 1, 1)),
         is(-1L));
 
+    //The 201st decade started on 2010/01/01. A little bit different but based 
on
+    
//https://www.postgresql.org/docs/9.1/static/functions-datetime.html#FUNCTIONS-DATETIME-EXTRACT
+    assertThat(
+        unixDateExtract(TimeUnitRange.DECADE, ymdToUnixDate(2010, 1, 1)),
+        is(201L));
+    assertThat(
+        unixDateExtract(TimeUnitRange.DECADE, ymdToUnixDate(2000, 12, 31)),
+        is(200L));
+    assertThat(
+        unixDateExtract(TimeUnitRange.DECADE, ymdToUnixDate(1852, 6, 7)),
+        is(185L));
+    assertThat(
+        unixDateExtract(TimeUnitRange.DECADE, ymdToUnixDate(1, 2, 1)),
+        is(0L));
+    // TODO: For a small time range around year 1, due to the Gregorian shift,
+    // we end up in the wrong decade. Should be 1.
+    assertThat(
+        unixDateExtract(TimeUnitRange.DECADE, ymdToUnixDate(1, 1, 1)),
+        is(0L));
+    assertThat(
+        unixDateExtract(TimeUnitRange.DECADE, ymdToUnixDate(-2, 1, 1)),
+        is(0L));
+    assertThat(
+        unixDateExtract(TimeUnitRange.DECADE, ymdToUnixDate(-20, 1, 1)),
+        is(-2L));
+
     // The 3rd millennium started on 2001/01/01
     assertThat(
         unixDateExtract(TimeUnitRange.MILLENNIUM, ymdToUnixDate(2001, 1, 1)),
@@ -447,6 +532,117 @@ public class DateTimeUtilsTest {
     assertThat(
         unixDateExtract(TimeUnitRange.MILLENNIUM, ymdToUnixDate(-2, 1, 1)),
         is(-1L));
+
+    // The ISO 8601 week-numbering year that the date falls in (not applicable
+    // to intervals). Each ISO 8601 week-numbering year begins with the Monday
+    // of the week containing the 4th of January, so in early January or late
+    // December the ISO year may be different from the Gregorian year. See the
+    // week field for more information.
+    assertThat(
+        unixDateExtract(TimeUnitRange.ISOYEAR, ymdToUnixDate(2003, 1, 1)),
+        is(2003L)); // wed
+    assertThat(
+        unixDateExtract(TimeUnitRange.ISOYEAR, ymdToUnixDate(2004, 1, 1)),
+        is(2004L)); // thu
+    assertThat(
+        unixDateExtract(TimeUnitRange.ISOYEAR, ymdToUnixDate(2005, 1, 1)),
+        is(2004L)); // sat
+    assertThat(
+        unixDateExtract(TimeUnitRange.ISOYEAR, ymdToUnixDate(2005, 1, 2)),
+        is(2004L)); // sun
+    assertThat(
+        unixDateExtract(TimeUnitRange.ISOYEAR, ymdToUnixDate(2005, 1, 3)),
+        is(2005L)); // mon
+    assertThat(
+        unixDateExtract(TimeUnitRange.ISOYEAR, ymdToUnixDate(2005, 12, 31)),
+        is(2005L)); // sat
+    assertThat(
+        unixDateExtract(TimeUnitRange.ISOYEAR, ymdToUnixDate(2006, 1, 1)),
+        is(2005L)); // sun
+    assertThat(
+        unixDateExtract(TimeUnitRange.ISOYEAR, ymdToUnixDate(2006, 1, 2)),
+        is(2006L)); // mon
+    assertThat(
+        unixDateExtract(TimeUnitRange.ISOYEAR, ymdToUnixDate(2006, 12, 31)),
+        is(2006L)); // sun
+    assertThat(
+        unixDateExtract(TimeUnitRange.ISOYEAR, ymdToUnixDate(2007, 1, 1)),
+        is(2007L)); // mon
+    assertThat(
+        unixDateExtract(TimeUnitRange.ISOYEAR, ymdToUnixDate(2007, 12, 30)),
+        is(2007L)); // sun
+    assertThat(
+        unixDateExtract(TimeUnitRange.ISOYEAR, ymdToUnixDate(2007, 12, 31)),
+        is(2008L)); // mon
+    assertThat(
+        unixDateExtract(TimeUnitRange.ISOYEAR, ymdToUnixDate(2008, 12, 28)),
+        is(2008L)); // sun
+    assertThat(
+        unixDateExtract(TimeUnitRange.ISOYEAR, ymdToUnixDate(2008, 12, 29)),
+        is(2009L)); // mon
+    assertThat(
+        unixDateExtract(TimeUnitRange.ISOYEAR, ymdToUnixDate(2008, 12, 30)),
+        is(2009L)); // tue
+    assertThat(
+        unixDateExtract(TimeUnitRange.ISOYEAR, ymdToUnixDate(2008, 12, 31)),
+        is(2009L)); // wen
+    assertThat(
+        unixDateExtract(TimeUnitRange.ISOYEAR, ymdToUnixDate(2009, 1, 1)),
+        is(2009L)); // thu
+    assertThat(
+        unixDateExtract(TimeUnitRange.ISOYEAR, ymdToUnixDate(2009, 12, 31)),
+        is(2009L)); // thu
+    assertThat(
+        unixDateExtract(TimeUnitRange.ISOYEAR, ymdToUnixDate(2010, 1, 1)),
+        is(2009L)); // fri
+    assertThat(
+        unixDateExtract(TimeUnitRange.ISOYEAR, ymdToUnixDate(2010, 1, 2)),
+        is(2009L)); // sat
+    assertThat(
+        unixDateExtract(TimeUnitRange.ISOYEAR, ymdToUnixDate(2010, 1, 3)),
+        is(2009L)); // sun
+    assertThat(
+        unixDateExtract(TimeUnitRange.ISOYEAR, ymdToUnixDate(2010, 1, 4)),
+        is(2010L)); // mon
+    assertThat(
+        unixDateExtract(TimeUnitRange.ISOYEAR, ymdToUnixDate(2012, 12, 29)),
+        is(2012L)); // sat
+    assertThat(
+        unixDateExtract(TimeUnitRange.ISOYEAR, ymdToUnixDate(2012, 12, 30)),
+        is(2012L)); // sun
+    assertThat(
+        unixDateExtract(TimeUnitRange.ISOYEAR, ymdToUnixDate(2012, 12, 31)),
+        is(2013L)); // mon
+    assertThat(
+        unixDateExtract(TimeUnitRange.ISOYEAR, ymdToUnixDate(2014, 12, 30)),
+        is(2015L)); // tue
+    assertThat(
+        unixDateExtract(TimeUnitRange.ISOYEAR, ymdToUnixDate(2014, 12, 31)),
+        is(2015L)); // wen
+    assertThat(
+        unixDateExtract(TimeUnitRange.ISOYEAR, ymdToUnixDate(1970, 1, 1)),
+        is(1970L)); // thu
+
+    // For date and timestamp values, the number of seconds since 1970-01-01 
00:00:00 UTC
+    // (can be negative); for interval values, the total number of seconds in 
the interval
+    assertThat(
+        unixDateExtract(TimeUnitRange.EPOCH, ymdToUnixDate(2001, 1, 1)),
+        is(978_307_200L));
+    assertThat(
+        unixDateExtract(TimeUnitRange.EPOCH, ymdToUnixDate(1969, 12, 31)),
+        is(-86_400L));
+    assertThat(
+        unixDateExtract(TimeUnitRange.EPOCH, ymdToUnixDate(1970, 1, 1)),
+        is(0L));
+    assertThat(
+        unixDateExtract(TimeUnitRange.EPOCH, ymdToUnixDate(1, 1, 1)),
+        is(-62_135_596_800L));
+    assertThat(
+        unixDateExtract(TimeUnitRange.EPOCH, ymdToUnixDate(1, 2, 1)),
+        is(-62_132_918_400L));
+    assertThat(
+        unixDateExtract(TimeUnitRange.EPOCH, ymdToUnixDate(-2, 1, 1)),
+        is(-62_230_291_200L));
   }
 
   @Test public void testUnixDate() {
@@ -522,14 +718,20 @@ public class DateTimeUtilsTest {
         is((long) month));
     assertThat(unixDateExtract(TimeUnitRange.DAY, unixDate),
         is((long) day));
+    final long isoYear = unixDateExtract(TimeUnitRange.ISOYEAR, unixDate);
+    assertTrue(isoYear >= year - 1 && isoYear <= year + 1);
     final long w = unixDateExtract(TimeUnitRange.WEEK, unixDate);
     assertTrue(w >= 1 && w <= 53);
     final long dow = unixDateExtract(TimeUnitRange.DOW, unixDate);
     assertTrue(dow >= 1 && dow <= 7);
+    final long iso_dow = unixDateExtract(TimeUnitRange.ISODOW, unixDate);
+    assertTrue(iso_dow >= 1 && iso_dow <= 7);
     final long doy = unixDateExtract(TimeUnitRange.DOY, unixDate);
-    assertTrue(doy >= 1 && dow <= 366);
+    assertTrue(doy >= 1 && doy <= 366);
     final long q = unixDateExtract(TimeUnitRange.QUARTER, unixDate);
     assertTrue(q >= 1 && q <= 4);
+    final long d = unixDateExtract(TimeUnitRange.DECADE, unixDate);
+    assertTrue(d == year / 10);
     final long c = unixDateExtract(TimeUnitRange.CENTURY, unixDate);
     assertTrue(c == (year > 0 ? (year + 99) / 100 : (year - 99) / 100));
     final long m = unixDateExtract(TimeUnitRange.MILLENNIUM, unixDate);

Reply via email to