diff --git a/codegen/src/suite/data.ts b/codegen/src/suite/data.ts index e7854cb..4940f55 100644 --- a/codegen/src/suite/data.ts +++ b/codegen/src/suite/data.ts @@ -11,16 +11,24 @@ export const LOCALES = [ ]; export const DATES: number[] = [ + // Out of range negative date + -500000000000000, + // Minimum date -4712-01-01 00:00:00 UTC + -210866803200000, // Mon, February 6, 1956 4:54:57 PM -438678303000, - // Thursday, January 1, 1970 12:00:00 AM GMT + // Thursday, January 1, 1970 12:00:00 AM UTC 0, // Wed, September 23, 1987 5:03:24 PM 559415004000, // Sat, May 11, 1996 3:55:31 AM 831786931000, - // Monday, January 27, 2020 12:34:56 PM GMT + // Monday, January 27, 2020 12:34:56 PM UTC 1580128496000, + // Maximum date 8652-12-31 00:00:00 UTC + 210895056000000, + // Out of range positive date + 500000000000000, ]; export const ZONES: string[] = [ diff --git a/src/main/java/com/squarespace/cldrengine/api/CalendarDate.java b/src/main/java/com/squarespace/cldrengine/api/CalendarDate.java index 2293e11..007f7d4 100644 --- a/src/main/java/com/squarespace/cldrengine/api/CalendarDate.java +++ b/src/main/java/com/squarespace/cldrengine/api/CalendarDate.java @@ -816,7 +816,7 @@ protected String _toString(String type) { return String.format("%s %s%04d-%02d-%02d %02d:%02d:%02d.%03d %s", type, neg ? "-" : "", - year, + Math.abs(year), this.month(), this.dayOfMonth(), this.hourOfDay(), @@ -931,8 +931,8 @@ protected long unixEpochFromJD (long jd, long msDay) { * is relative to these. */ protected void computeBaseFields(long[] f) { - long jd = f[DateField.JULIAN_DAY]; - checkJDRange(jd); + long jd = clamp(f[DateField.JULIAN_DAY], CalendarConstants.JD_MIN, CalendarConstants.JD_MAX); +// checkJDRange(jd); long msDay = f[DateField.MILLIS_IN_DAY]; long ms = msDay + ((jd - CalendarConstants.JD_UNIX_EPOCH) * CalendarConstants.ONE_DAY_MS); @@ -960,17 +960,21 @@ protected void computeBaseFields(long[] f) { f[DateField.DAY_OF_WEEK] = dow; } - protected long checkJDRange(long jd) { - // TODO: emit warning? - -// throw new Error( -// `Julian day ${jd} is outside the supported range of this library: ` + -// `${ConstantsDesc.JD_MIN} to ${ConstantsDesc.JD_MAX}`); - - if (jd < CalendarConstants.JD_MIN) { - return CalendarConstants.JD_MIN; - } - return jd > CalendarConstants.JD_MAX ? CalendarConstants.JD_MAX : jd; + private static long clamp(long n, long min, long max) { + return n < min ? min : (n > max ? max : n); } +// protected long checkJDRange(long jd) { +// // TODO: emit warning? +// +//// throw new Error( +//// `Julian day ${jd} is outside the supported range of this library: ` + +//// `${ConstantsDesc.JD_MIN} to ${ConstantsDesc.JD_MAX}`); +// +// if (jd < CalendarConstants.JD_MIN) { +// return CalendarConstants.JD_MIN; +// } +// return jd > CalendarConstants.JD_MAX ? CalendarConstants.JD_MAX : jd; +// } + } diff --git a/src/main/java/com/squarespace/cldrengine/api/GregorianDate.java b/src/main/java/com/squarespace/cldrengine/api/GregorianDate.java index ee66cd8..d325aca 100644 --- a/src/main/java/com/squarespace/cldrengine/api/GregorianDate.java +++ b/src/main/java/com/squarespace/cldrengine/api/GregorianDate.java @@ -1,5 +1,7 @@ package com.squarespace.cldrengine.api; +import static com.squarespace.cldrengine.internal.MathFix.floorDiv; + import com.squarespace.cldrengine.internal.MathFix; import com.squarespace.cldrengine.utils.MathUtil; @@ -135,7 +137,7 @@ protected void computeGregorianFields(long[] f) { if (doy >= mar1) { corr = isLeap ? 1 : 2; } - int month = (int)Math.floor((12 * (doy + corr) + 6) / 367); + int month = (int)MathFix.floorDiv(12 * (doy + corr) + 6, 367); long dom = doy - MONTH_COUNT[month][isLeap ? 3 : 2] + 1; f[DateField.EXTENDED_YEAR] = year; @@ -152,8 +154,8 @@ protected void computeGregorianFields(long[] f) { */ protected void computeJulianFields(long[] f) { long jed = f[DateField.JULIAN_DAY] - (CalendarConstants.JD_GREGORIAN_EPOCH - 2); - long eyear = (long)Math.floor((4 * jed + 1464) / 1461); - long jan1 = 365 * (eyear - 1) + (long)Math.floor((eyear - 1) / 4); + long eyear = floorDiv(4 * jed + 1464, 1461); + long jan1 = 365 * (eyear - 1) + floorDiv(eyear - 1, 4); long doy = jed - jan1; boolean isLeap = eyear % 4 == 0; long corr = 0; @@ -162,7 +164,7 @@ protected void computeJulianFields(long[] f) { corr = isLeap ? 1 : 2; } - int month = (int)Math.floor((12 * (doy + corr) + 6) / 365); + int month = (int)(12 * (doy + corr) + 6) / 367; long dom = doy - MONTH_COUNT[month][isLeap ? 3 : 2] + 1; f[DateField.EXTENDED_YEAR] = eyear; @@ -171,7 +173,7 @@ protected void computeJulianFields(long[] f) { f[DateField.DAY_OF_YEAR] = doy + 1; f[DateField.IS_LEAP] = isLeap ? 1 : 0; } - + /** * Return true if the given year is a leap year in the Gregorian calendar; false otherwise. * Note that we switch to the Julian calendar at the Gregorian cutover year. @@ -183,7 +185,7 @@ protected boolean leapGregorian(long year) { } return r; } - + private static final int[][] MONTH_COUNT = new int[][] { new int[] { 31, 31, 0, 0 }, // Jan new int[] { 28, 29, 31, 31 }, // Feb diff --git a/src/main/java/com/squarespace/cldrengine/api/PersianDate.java b/src/main/java/com/squarespace/cldrengine/api/PersianDate.java index 297adcd..d9649e9 100644 --- a/src/main/java/com/squarespace/cldrengine/api/PersianDate.java +++ b/src/main/java/com/squarespace/cldrengine/api/PersianDate.java @@ -1,5 +1,6 @@ package com.squarespace.cldrengine.api; +import com.squarespace.cldrengine.internal.MathFix; import com.squarespace.cldrengine.utils.MathUtil; /** @@ -107,10 +108,10 @@ protected long monthStart(long eyear, double month, boolean useMonth) { private void computePersianFields(long[] f) { long jd = f[DateField.JULIAN_DAY]; long days = jd - CalendarConstants.JD_PERSIAN_EPOCH; - long year = 1 + (long)Math.floor((33 * days + 3) / 12053); - long favardin1 = 365 * (year - 1) + (long)Math.floor((8 * year + 21) / 33); + long year = 1 + (long)MathFix.floorDiv((33 * days + 3), 12053); + long favardin1 = 365 * (year - 1) + (long)MathFix.floorDiv((8 * year + 21), 33); long doy = days - favardin1; - int month = (int)Math.floor(doy < 216 ? (doy / 31) : ((doy - 6) / 30)); + int month = (int)(doy < 216 ? MathFix.floorDiv(doy, 31) : MathFix.floorDiv(doy - 6, 30)); long dom = doy - MONTH_COUNT[month][2] + 1; f[DateField.ERA] = 0; diff --git a/src/test/java/com/squarespace/cldrengine/api/GregorianDateTest.java b/src/test/java/com/squarespace/cldrengine/api/GregorianDateTest.java new file mode 100644 index 0000000..f5ec2d7 --- /dev/null +++ b/src/test/java/com/squarespace/cldrengine/api/GregorianDateTest.java @@ -0,0 +1,49 @@ +package com.squarespace.cldrengine.api; + +import static org.testng.Assert.assertEquals; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import com.squarespace.cldrengine.CLDR; +import com.squarespace.cldrengine.api.CalendarDate; +import com.squarespace.cldrengine.api.GregorianDate; +import com.squarespace.cldrengine.calendars.DayOfWeek; + +public class GregorianDateTest { + + private static final CLDR EN = CLDR.get("en"); + private static final String UTC = "Etc/UTC"; + private static final String NEW_YORK = "America/New_York"; + + private static final long unixEpochFromJD(long jd, long msDay) { + long days = jd - CalendarConstants.JD_UNIX_EPOCH; + return days * CalendarConstants.ONE_DAY_MS + Math.round(msDay); + } + + private static final CalendarDate make(long epoch, String zoneId) { + return GregorianDate.fromUnixEpoch(epoch, zoneId, DayOfWeek.SUNDAY, 1); + } + + @Test + public void testMinMaxClamp() { + CalendarDate d; + + long min = unixEpochFromJD(CalendarConstants.JD_MIN, 0); + long max = unixEpochFromJD(CalendarConstants.JD_MAX, 0); + + d = make(min, "UTC"); + assertEquals(d.toString(), "Gregorian -4712-01-01 00:00:00.000 Etc/UTC"); + + // Clamp to minimum date + d = make(min - CalendarConstants.ONE_DAY_MS, "UTC"); + assertEquals(d.toString(), "Gregorian -4712-01-01 00:00:00.000 Etc/UTC"); + + d = make(max, "UTC"); + assertEquals(d.toString(), "Gregorian 8652-12-31 00:00:00.000 Etc/UTC"); + + d = make(max + CalendarConstants.ONE_DAY_MS, "UTC"); + assertEquals(d.toString(), "Gregorian 8652-12-31 00:00:00.000 Etc/UTC"); + } + +}