From c2753c252129d54e9e8fb237a12601ca2c027a17 Mon Sep 17 00:00:00 2001 From: Guille Date: Thu, 30 Jan 2020 09:54:00 +0100 Subject: [PATCH 01/31] Change test offsets to something that will fail in all systems a +2/-2 hours offset will only fail around midnight, whereas a +12/-13 hours offset ensures that any system will fail using one of them because the current date will fall into a different day. --- .../javarosa/core/model/utils/test/DateUtilsTests.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java index 46a17809d..8a7c578cd 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java @@ -146,7 +146,7 @@ public void testTimeParses() { testTime("10:00+02:30", startOfDayDate + 1000 * 60 * (60 * 10 - 150)); testTime("10:00-02:30", startOfDayDate + 1000 * 60 * (60 * 10 + 150)); - TimeZone offsetTwoHours = TimeZone.getTimeZone("GMT+02"); + TimeZone offsetTwoHours = TimeZone.getTimeZone("GMT+12"); TimeZone.setDefault(offsetTwoHours); @@ -159,7 +159,7 @@ public void testTimeParses() { testTime("10:00+02:30", startOfDayDate + 1000 * 60 * (60 * 10 - 150)); testTime("10:00-02:30", startOfDayDate + 1000 * 60 * (60 * 10 + 150)); - TimeZone offsetMinusTwoHours = TimeZone.getTimeZone("GMT-02"); + TimeZone offsetMinusTwoHours = TimeZone.getTimeZone("GMT-13"); TimeZone.setDefault(offsetMinusTwoHours); @@ -294,8 +294,8 @@ private LangJanSun(String language, String january, String sunday) { } LangJanSun langJanSuns[] = new LangJanSun[]{ - new LangJanSun("en", "Jan", "Sun"), - new LangJanSun("es", "ene", "dom"), + new LangJanSun("en", "Jan", "Sun"), + new LangJanSun("es", "ene", "dom"), new LangJanSun("fr", "janv.", "dim.") }; From cbe926854065ff32a595e74e449caf55882e2574 Mon Sep 17 00:00:00 2001 From: Guille Date: Thu, 30 Jan 2020 09:55:28 +0100 Subject: [PATCH 02/31] Fix the test by refreshing the base value we use to define expectations Every time there's a time offset change, we need to ensure we use an updated "today at start of day" epoch --- .../core/model/utils/test/DateUtilsTests.java | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java index 8a7c578cd..59df0edab 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java @@ -19,6 +19,8 @@ import static org.junit.Assert.assertEquals; import java.text.DateFormat; +import java.time.LocalDate; +import java.time.ZoneOffset; import java.util.Calendar; import java.util.Date; import java.util.Locale; @@ -129,13 +131,7 @@ public void testTimeParses() { TimeZone.setDefault(TimeZone.getTimeZone("UTC")); - Calendar startOfDay = Calendar.getInstance(); - startOfDay.set(Calendar.HOUR_OF_DAY, 0); - startOfDay.set(Calendar.MINUTE, 0); - startOfDay.set(Calendar.SECOND, 0); - startOfDay.set(Calendar.MILLISECOND, 0); - - long startOfDayDate = startOfDay.getTime().getTime(); + long startOfDayDate = getTodayStartOfDayUTCEpoch(); testTime("10:00", startOfDayDate + 1000 * 60 * 60 * 10 - getOffset()); testTime("10:00Z", startOfDayDate + 1000 * 60 * 60 * 10); @@ -150,6 +146,8 @@ public void testTimeParses() { TimeZone.setDefault(offsetTwoHours); + startOfDayDate = getTodayStartOfDayUTCEpoch(); + testTime("10:00", startOfDayDate + 1000 * 60 * 60 * 10 - getOffset()); testTime("10:00Z", startOfDayDate + 1000 * 60 * 60 * 10); @@ -163,6 +161,8 @@ public void testTimeParses() { TimeZone.setDefault(offsetMinusTwoHours); + startOfDayDate = getTodayStartOfDayUTCEpoch(); + testTime("14:00", startOfDayDate + 1000 * 60 * 60 * 14 - getOffset()); testTime("14:00Z", startOfDayDate + 1000 * 60 * 60 * 14); @@ -177,6 +177,8 @@ public void testTimeParses() { TimeZone.setDefault(offsetPlusHalf); + startOfDayDate = getTodayStartOfDayUTCEpoch(); + testTime("14:00", startOfDayDate + 1000 * 60 * 60 * 14 - getOffset()); testTime("14:00Z", startOfDayDate + 1000 * 60 * 60 * 14); @@ -189,6 +191,10 @@ public void testTimeParses() { testTime("14:00+04:00", startOfDayDate + 1000 * 60 * 60 * 10); } + public long getTodayStartOfDayUTCEpoch() { + return LocalDate.now().atStartOfDay().atOffset(ZoneOffset.UTC).toInstant().toEpochMilli(); + } + private void testTime(String in, long test) { Date d = DateUtils.parseTime(in); From fe64bb2925399aeb91b3c882e17b1dad722bbb8a Mon Sep 17 00:00:00 2001 From: Guille Date: Thu, 30 Jan 2020 09:56:22 +0100 Subject: [PATCH 03/31] Use the same assertions for consistency --- .../core/model/utils/test/DateUtilsTests.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java index 59df0edab..df22ce6a3 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java @@ -148,14 +148,14 @@ public void testTimeParses() { startOfDayDate = getTodayStartOfDayUTCEpoch(); - testTime("10:00", startOfDayDate + 1000 * 60 * 60 * 10 - getOffset()); - testTime("10:00Z", startOfDayDate + 1000 * 60 * 60 * 10); + testTime("14:00", startOfDayDate + 1000 * 60 * 60 * 14 - getOffset()); + testTime("14:00Z", startOfDayDate + 1000 * 60 * 60 * 14); - testTime("10:00+02", startOfDayDate + 1000 * 60 * 60 * 8); - testTime("10:00-02", startOfDayDate + 1000 * 60 * 60 * 12); + testTime("14:00+02", startOfDayDate + 1000 * 60 * 60 * 12); + testTime("14:00-02", startOfDayDate + 1000 * 60 * 60 * 16); - testTime("10:00+02:30", startOfDayDate + 1000 * 60 * (60 * 10 - 150)); - testTime("10:00-02:30", startOfDayDate + 1000 * 60 * (60 * 10 + 150)); + testTime("14:00+02:30", startOfDayDate + 1000 * 60 * (60 * 14 - 150)); + testTime("14:00-02:30", startOfDayDate + 1000 * 60 * (60 * 14 + 150)); TimeZone offsetMinusTwoHours = TimeZone.getTimeZone("GMT-13"); From 65920377a88ec185bc08ded98fc0f6ba5850c3b4 Mon Sep 17 00:00:00 2001 From: Guille Date: Thu, 30 Jan 2020 09:57:48 +0100 Subject: [PATCH 04/31] Migrate test to java.time --- .../javarosa/core/model/utils/test/DateUtilsTests.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java index df22ce6a3..78639171f 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java @@ -20,6 +20,8 @@ import java.text.DateFormat; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.util.Calendar; import java.util.Date; @@ -28,7 +30,6 @@ import java.util.TimeZone; import org.javarosa.core.model.utils.DateUtils; import org.javarosa.core.model.utils.DateUtils.DateFields; -import org.joda.time.LocalDateTime; import org.junit.After; import org.junit.Before; import org.junit.Ignore; @@ -45,8 +46,8 @@ public class DateUtilsTests { public void setUp() { backupLocale = Locale.getDefault(); backupZone = TimeZone.getDefault(); - testLocalDateTime = new LocalDateTime(2018, 1, 1, 10, 20, 30, 400); - testDate = testLocalDateTime.toDate(); + testLocalDateTime = LocalDateTime.of(2018, 1, 1, 10, 20, 30, 400); + testDate = Date.from(testLocalDateTime.toInstant(OffsetDateTime.now().getOffset())); } @After @@ -66,7 +67,7 @@ public void testGetXMLStringValueFormat() { assertEquals("The date string was not of the proper length", currentDate.length(), "YYYY-MM-DD".length()); assertEquals("The date string does not have proper year formatting", currentDate.indexOf("-"), "YYYY-".indexOf("-")); assertEquals(testLocalDateTime.getYear(), Integer.parseInt(currentDate.substring(0, 4))); - assertEquals(testLocalDateTime.getMonthOfYear(), Integer.parseInt(currentDate.substring(5, 7))); + assertEquals(testLocalDateTime.getMonth().getValue(), Integer.parseInt(currentDate.substring(5, 7))); assertEquals(testLocalDateTime.getDayOfMonth(), Integer.parseInt(currentDate.substring(8, 10))); } @@ -148,6 +149,7 @@ public void testTimeParses() { startOfDayDate = getTodayStartOfDayUTCEpoch(); + testTime("14:00", startOfDayDate + 1000 * 60 * 60 * 14 - getOffset()); testTime("14:00Z", startOfDayDate + 1000 * 60 * 60 * 14); From 0a48fa49f5df6728294f38c797ca2d5c06f17241 Mon Sep 17 00:00:00 2001 From: Guille Date: Thu, 30 Jan 2020 10:15:39 +0100 Subject: [PATCH 05/31] Migrate assertions to something that's easier to reason about Using epoch values was working but it's harder to understand by humans than actual time strings. Also, by using OffsetDateTime values and parsing strings with java.time we are ensuring as well that our test values are correctly parsed from string to date times because java.time is a battle tested Java API. Now the only thing that could be wrong in the tests are the input and output time strings (both in human readable format, easy to check) and the offset definitions. --- .../core/model/utils/test/DateUtilsTests.java | 127 ++++++++++-------- 1 file changed, 69 insertions(+), 58 deletions(-) diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java index 78639171f..0de6e93eb 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java @@ -16,16 +16,22 @@ package org.javarosa.core.model.utils.test; +import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; import java.text.DateFormat; -import java.time.LocalDate; +import java.time.Instant; import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.OffsetDateTime; -import java.time.ZoneOffset; +import java.time.OffsetTime; +import java.time.ZoneId; +import java.time.temporal.Temporal; import java.util.Calendar; import java.util.Date; import java.util.Locale; +import java.util.Objects; import java.util.SimpleTimeZone; import java.util.TimeZone; import org.javarosa.core.model.utils.DateUtils; @@ -126,75 +132,87 @@ private void testDateTime(String in, long test) { @Test public void testTimeParses() { - //This is all kind of tricky. We need to assume J2ME level compliance, so - //dates won't every be assumed to have an intrinsic timezone, they'll be - //assumed to be in the phone's default timezone + // The tricky part of DateUtils.parseTime is that we allow for input time + // values to include time offset declarations, which has issues at different + // levels: + // - Conceptually, time values with offsets don't make sense until they're + // paired with a date so, how can we reason about what "10:00+02:00" means, + // and what should be a valid expected output for that input value? + // - Next, DateUtils.parseTime() produces Date values, which are a date and a + // time in the system's default time zone. Then, which date would we have to + // expect? + // + // To solve these issues, DateUtils.parseTime() will use the system's current + // date as a base for its output value whenever that is and whichever time zone + // the system's at. TimeZone.setDefault(TimeZone.getTimeZone("UTC")); - long startOfDayDate = getTodayStartOfDayUTCEpoch(); - - testTime("10:00", startOfDayDate + 1000 * 60 * 60 * 10 - getOffset()); - testTime("10:00Z", startOfDayDate + 1000 * 60 * 60 * 10); - - testTime("10:00+02", startOfDayDate + 1000 * 60 * 60 * 8); - testTime("10:00-02", startOfDayDate + 1000 * 60 * 60 * 12); - - testTime("10:00+02:30", startOfDayDate + 1000 * 60 * (60 * 10 - 150)); - testTime("10:00-02:30", startOfDayDate + 1000 * 60 * (60 * 10 + 150)); + assertThat(parseTime("14:00"), is(LocalTime.parse("14:00"))); + assertThat(parseTime("14:00Z"), is(OffsetTime.parse("14:00Z"))); + assertThat(parseTime("14:00+02"), is(OffsetTime.parse("14:00+02:00"))); + assertThat(parseTime("14:00-02"), is(OffsetTime.parse("14:00-02:00"))); + assertThat(parseTime("14:00+02:30"), is(OffsetTime.parse("14:00+02:30"))); + assertThat(parseTime("14:00-02:30"), is(OffsetTime.parse("14:00-02:30"))); TimeZone offsetTwoHours = TimeZone.getTimeZone("GMT+12"); TimeZone.setDefault(offsetTwoHours); - startOfDayDate = getTodayStartOfDayUTCEpoch(); - - - testTime("14:00", startOfDayDate + 1000 * 60 * 60 * 14 - getOffset()); - testTime("14:00Z", startOfDayDate + 1000 * 60 * 60 * 14); - - testTime("14:00+02", startOfDayDate + 1000 * 60 * 60 * 12); - testTime("14:00-02", startOfDayDate + 1000 * 60 * 60 * 16); - - testTime("14:00+02:30", startOfDayDate + 1000 * 60 * (60 * 14 - 150)); - testTime("14:00-02:30", startOfDayDate + 1000 * 60 * (60 * 14 + 150)); + assertThat(parseTime("14:00"), is(LocalTime.parse("14:00"))); + assertThat(parseTime("14:00Z"), is(OffsetTime.parse("14:00Z"))); + assertThat(parseTime("14:00+02"), is(OffsetTime.parse("14:00+02:00"))); + assertThat(parseTime("14:00-02"), is(OffsetTime.parse("14:00-02:00"))); + assertThat(parseTime("14:00+02:30"), is(OffsetTime.parse("14:00+02:30"))); + assertThat(parseTime("14:00-02:30"), is(OffsetTime.parse("14:00-02:30"))); TimeZone offsetMinusTwoHours = TimeZone.getTimeZone("GMT-13"); TimeZone.setDefault(offsetMinusTwoHours); - startOfDayDate = getTodayStartOfDayUTCEpoch(); - - testTime("14:00", startOfDayDate + 1000 * 60 * 60 * 14 - getOffset()); - testTime("14:00Z", startOfDayDate + 1000 * 60 * 60 * 14); - - testTime("14:00+02", startOfDayDate + 1000 * 60 * 60 * 12); - testTime("14:00-02", startOfDayDate + 1000 * 60 * 60 * 16); - - testTime("14:00+02:30", startOfDayDate + 1000 * 60 * (60 * 14 - 150)); - testTime("14:00-02:30", startOfDayDate + 1000 * 60 * (60 * 14 + 150)); - + assertThat(parseTime("14:00"), is(LocalTime.parse("14:00"))); + assertThat(parseTime("14:00Z"), is(OffsetTime.parse("14:00Z"))); + assertThat(parseTime("14:00+02"), is(OffsetTime.parse("14:00+02:00"))); + assertThat(parseTime("14:00-02"), is(OffsetTime.parse("14:00-02:00"))); + assertThat(parseTime("14:00+02:30"), is(OffsetTime.parse("14:00+02:30"))); + assertThat(parseTime("14:00-02:30"), is(OffsetTime.parse("14:00-02:30"))); TimeZone offsetPlusHalf = TimeZone.getTimeZone("GMT+0230"); TimeZone.setDefault(offsetPlusHalf); - startOfDayDate = getTodayStartOfDayUTCEpoch(); - - testTime("14:00", startOfDayDate + 1000 * 60 * 60 * 14 - getOffset()); - testTime("14:00Z", startOfDayDate + 1000 * 60 * 60 * 14); - - testTime("14:00+02", startOfDayDate + 1000 * 60 * 60 * 12); - testTime("14:00-02", startOfDayDate + 1000 * 60 * 60 * 16); - - testTime("14:00+02:30", startOfDayDate + 1000 * 60 * (60 * 14 - 150)); - testTime("14:00-02:30", startOfDayDate + 1000 * 60 * (60 * 14 + 150)); - - testTime("14:00+04:00", startOfDayDate + 1000 * 60 * 60 * 10); + assertThat(parseTime("14:00"), is(LocalTime.parse("14:00"))); + assertThat(parseTime("14:00Z"), is(OffsetTime.parse("14:00Z"))); + assertThat(parseTime("14:00+02"), is(OffsetTime.parse("14:00+02:00"))); + assertThat(parseTime("14:00-02"), is(OffsetTime.parse("14:00-02:00"))); + assertThat(parseTime("14:00+02:30"), is(OffsetTime.parse("14:00+02:30"))); + assertThat(parseTime("14:00-02:30"), is(OffsetTime.parse("14:00-02:30"))); + assertThat(parseTime("14:00+04:00"), is(OffsetTime.parse("14:00+04:00"))); } - public long getTodayStartOfDayUTCEpoch() { - return LocalDate.now().atStartOfDay().atOffset(ZoneOffset.UTC).toInstant().toEpochMilli(); + /** + * Returns a LocalTime or a OffsetTime obtained from the result of + * calling DateUtils.parseTime() with the provided input. + *

+ * The interim OffsetDateTime value ensures that it represents the + * same instant as the Date from the call to DateUtils.parseTime(). + */ + public Temporal parseTime(String input) { + Instant inputInstant = Objects.requireNonNull(DateUtils.parseTime(input)).toInstant(); + + // No time offset declared. Return a LocalTime + if (input.length() == 5) + return OffsetDateTime.ofInstant(inputInstant, ZoneId.systemDefault()).toLocalTime(); + + // The input time is at UTC + if (input.charAt(5) == 'Z') + return OffsetDateTime.ofInstant(inputInstant, ZoneId.of("Z")).toOffsetTime(); + + // The input declares some positive or negative time offset + String offsetPart = input.substring(5); + // Fix for unparseable short offset notations such as +02 + String offset = offsetPart.length() == 3 ? offsetPart + ":00" : offsetPart; + return OffsetDateTime.ofInstant(inputInstant, ZoneId.of(offset)).toOffsetTime(); } private void testTime(String in, long test) { @@ -205,13 +223,6 @@ private void testTime(String in, long test) { assertEquals("Fail: " + in + "(" + TimeZone.getDefault().getDisplayName() + ")", test, value); } - private long getOffset() { - DateFields df = new DateFields(); - Date d = DateUtils.getDate(df); - - return -d.getTime(); - } - @Test public void testParity() { From 0d3538e307d25b38c366d7c3e417457896a251e3 Mon Sep 17 00:00:00 2001 From: Guille Date: Thu, 30 Jan 2020 10:47:14 +0100 Subject: [PATCH 06/31] Move the parseTime test to its own test class This way, we can parameterize the test and add more cases and context --- .../utils/test/DateUtilsParseTimeTests.java | 133 ++++++++++++++++++ .../core/model/utils/test/DateUtilsTests.java | 93 ------------ 2 files changed, 133 insertions(+), 93 deletions(-) create mode 100644 src/test/java/org/javarosa/core/model/utils/test/DateUtilsParseTimeTests.java diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsParseTimeTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsParseTimeTests.java new file mode 100644 index 000000000..8cdc16e72 --- /dev/null +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsParseTimeTests.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2009 JavaRosa + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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.javarosa.core.model.utils.test; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import java.time.Instant; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.OffsetTime; +import java.time.ZoneId; +import java.time.temporal.Temporal; +import java.util.Arrays; +import java.util.Collection; +import java.util.Locale; +import java.util.Objects; +import java.util.TimeZone; +import java.util.stream.Stream; +import org.javarosa.core.model.utils.DateUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class DateUtilsParseTimeTests { + private Locale backupLocale; + private TimeZone backupZone; + + @Parameterized.Parameter(value = 0) + public String input; + + @Parameterized.Parameter(value = 1) + public Temporal expectedTime; + + @Parameterized.Parameters(name = "Input: {0}") + public static Collection data() { + return Arrays.asList(new Object[][]{ + {"14:00", LocalTime.parse("14:00")}, + {"14:00Z", OffsetTime.parse("14:00Z")}, + {"14:00+02", OffsetTime.parse("14:00+02:00")}, + {"14:00-02", OffsetTime.parse("14:00-02:00")}, + {"14:00+02:30", OffsetTime.parse("14:00+02:30")}, + {"14:00-02:30", OffsetTime.parse("14:00-02:30")}, + }); + } + + @Before + public void setUp() { + backupLocale = Locale.getDefault(); + backupZone = TimeZone.getDefault(); + } + + @After + public void tearDown() { + TimeZone.setDefault(backupZone); + Locale.setDefault(backupLocale); + } + + @Test + public void testTimeParses() { + // The tricky part of DateUtils.parseTime is that we allow for input time + // values to include time offset declarations, which has issues at different + // levels: + // - Conceptually, time values with offsets don't make sense until they're + // paired with a date so, how can we reason about what "10:00+02:00" means, + // and what should be a valid expected output for that input value? + // - Next, DateUtils.parseTime() produces Date values, which are a date and a + // time in the system's default time zone. Then, which date would we have to + // expect? + // + // To solve these issues, DateUtils.parseTime() will use the system's current + // date as a base for its output value whenever that is and whichever time zone + // the system's at. + // + // By testing parseTime under different system default time zones we're trying + // to have the confidence that our resulting Date objects will always translate + // to the same time declaration from the input string (ignoring their date part, + // of course). + Stream.of( + TimeZone.getTimeZone("UTC"), + TimeZone.getTimeZone("GMT+12"), + TimeZone.getTimeZone("GMT-13"), + TimeZone.getTimeZone("GMT+0230") + ).forEach(timeZone -> { + TimeZone backupZone = TimeZone.getDefault(); + TimeZone.setDefault(timeZone); + assertThat(parseTime(input), is(expectedTime)); + TimeZone.setDefault(backupZone); + }); + } + + /** + * Returns a LocalTime or a OffsetTime obtained from the result of + * calling DateUtils.parseTime() with the provided input. + *

+ * The interim OffsetDateTime value ensures that it represents the + * same instant as the Date from the call to DateUtils.parseTime(). + */ + public Temporal parseTime(String input) { + Instant inputInstant = Objects.requireNonNull(DateUtils.parseTime(input)).toInstant(); + + // No time offset declared. Return a LocalTime + if (input.length() == 5) + return OffsetDateTime.ofInstant(inputInstant, ZoneId.systemDefault()).toLocalTime(); + + // The input time is at UTC + if (input.charAt(5) == 'Z') + return OffsetDateTime.ofInstant(inputInstant, ZoneId.of("Z")).toOffsetTime(); + + // The input declares some positive or negative time offset + String offsetPart = input.substring(5); + // Fix for unparseable short offset notations such as +02 + String offset = offsetPart.length() == 3 ? offsetPart + ":00" : offsetPart; + return OffsetDateTime.ofInstant(inputInstant, ZoneId.of(offset)).toOffsetTime(); + } +} diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java index 0de6e93eb..773ed092b 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java @@ -16,22 +16,14 @@ package org.javarosa.core.model.utils.test; -import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; import java.text.DateFormat; -import java.time.Instant; import java.time.LocalDateTime; -import java.time.LocalTime; import java.time.OffsetDateTime; -import java.time.OffsetTime; -import java.time.ZoneId; -import java.time.temporal.Temporal; import java.util.Calendar; import java.util.Date; import java.util.Locale; -import java.util.Objects; import java.util.SimpleTimeZone; import java.util.TimeZone; import org.javarosa.core.model.utils.DateUtils; @@ -130,91 +122,6 @@ private void testDateTime(String in, long test) { assertEquals("Fail: " + in + "(" + TimeZone.getDefault().getDisplayName() + ")", test, value); } - @Test - public void testTimeParses() { - // The tricky part of DateUtils.parseTime is that we allow for input time - // values to include time offset declarations, which has issues at different - // levels: - // - Conceptually, time values with offsets don't make sense until they're - // paired with a date so, how can we reason about what "10:00+02:00" means, - // and what should be a valid expected output for that input value? - // - Next, DateUtils.parseTime() produces Date values, which are a date and a - // time in the system's default time zone. Then, which date would we have to - // expect? - // - // To solve these issues, DateUtils.parseTime() will use the system's current - // date as a base for its output value whenever that is and whichever time zone - // the system's at. - - TimeZone.setDefault(TimeZone.getTimeZone("UTC")); - - assertThat(parseTime("14:00"), is(LocalTime.parse("14:00"))); - assertThat(parseTime("14:00Z"), is(OffsetTime.parse("14:00Z"))); - assertThat(parseTime("14:00+02"), is(OffsetTime.parse("14:00+02:00"))); - assertThat(parseTime("14:00-02"), is(OffsetTime.parse("14:00-02:00"))); - assertThat(parseTime("14:00+02:30"), is(OffsetTime.parse("14:00+02:30"))); - assertThat(parseTime("14:00-02:30"), is(OffsetTime.parse("14:00-02:30"))); - - TimeZone offsetTwoHours = TimeZone.getTimeZone("GMT+12"); - - TimeZone.setDefault(offsetTwoHours); - - assertThat(parseTime("14:00"), is(LocalTime.parse("14:00"))); - assertThat(parseTime("14:00Z"), is(OffsetTime.parse("14:00Z"))); - assertThat(parseTime("14:00+02"), is(OffsetTime.parse("14:00+02:00"))); - assertThat(parseTime("14:00-02"), is(OffsetTime.parse("14:00-02:00"))); - assertThat(parseTime("14:00+02:30"), is(OffsetTime.parse("14:00+02:30"))); - assertThat(parseTime("14:00-02:30"), is(OffsetTime.parse("14:00-02:30"))); - - TimeZone offsetMinusTwoHours = TimeZone.getTimeZone("GMT-13"); - - TimeZone.setDefault(offsetMinusTwoHours); - - assertThat(parseTime("14:00"), is(LocalTime.parse("14:00"))); - assertThat(parseTime("14:00Z"), is(OffsetTime.parse("14:00Z"))); - assertThat(parseTime("14:00+02"), is(OffsetTime.parse("14:00+02:00"))); - assertThat(parseTime("14:00-02"), is(OffsetTime.parse("14:00-02:00"))); - assertThat(parseTime("14:00+02:30"), is(OffsetTime.parse("14:00+02:30"))); - assertThat(parseTime("14:00-02:30"), is(OffsetTime.parse("14:00-02:30"))); - - TimeZone offsetPlusHalf = TimeZone.getTimeZone("GMT+0230"); - - TimeZone.setDefault(offsetPlusHalf); - - assertThat(parseTime("14:00"), is(LocalTime.parse("14:00"))); - assertThat(parseTime("14:00Z"), is(OffsetTime.parse("14:00Z"))); - assertThat(parseTime("14:00+02"), is(OffsetTime.parse("14:00+02:00"))); - assertThat(parseTime("14:00-02"), is(OffsetTime.parse("14:00-02:00"))); - assertThat(parseTime("14:00+02:30"), is(OffsetTime.parse("14:00+02:30"))); - assertThat(parseTime("14:00-02:30"), is(OffsetTime.parse("14:00-02:30"))); - assertThat(parseTime("14:00+04:00"), is(OffsetTime.parse("14:00+04:00"))); - } - - /** - * Returns a LocalTime or a OffsetTime obtained from the result of - * calling DateUtils.parseTime() with the provided input. - *

- * The interim OffsetDateTime value ensures that it represents the - * same instant as the Date from the call to DateUtils.parseTime(). - */ - public Temporal parseTime(String input) { - Instant inputInstant = Objects.requireNonNull(DateUtils.parseTime(input)).toInstant(); - - // No time offset declared. Return a LocalTime - if (input.length() == 5) - return OffsetDateTime.ofInstant(inputInstant, ZoneId.systemDefault()).toLocalTime(); - - // The input time is at UTC - if (input.charAt(5) == 'Z') - return OffsetDateTime.ofInstant(inputInstant, ZoneId.of("Z")).toOffsetTime(); - - // The input declares some positive or negative time offset - String offsetPart = input.substring(5); - // Fix for unparseable short offset notations such as +02 - String offset = offsetPart.length() == 3 ? offsetPart + ":00" : offsetPart; - return OffsetDateTime.ofInstant(inputInstant, ZoneId.of(offset)).toOffsetTime(); - } - private void testTime(String in, long test) { Date d = DateUtils.parseTime(in); From 2252a5774fb0721048f0bb633349c7a580e7715e Mon Sep 17 00:00:00 2001 From: Guille Date: Thu, 30 Jan 2020 10:50:01 +0100 Subject: [PATCH 07/31] Add default zone to the list and remove unnecessary before/after methods --- .../utils/test/DateUtilsParseTimeTests.java | 42 +++++++------------ 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsParseTimeTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsParseTimeTests.java index 8cdc16e72..023b8ef7c 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsParseTimeTests.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsParseTimeTests.java @@ -16,6 +16,7 @@ package org.javarosa.core.model.utils.test; +import static java.util.TimeZone.getTimeZone; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; @@ -27,22 +28,16 @@ import java.time.temporal.Temporal; import java.util.Arrays; import java.util.Collection; -import java.util.Locale; import java.util.Objects; import java.util.TimeZone; import java.util.stream.Stream; import org.javarosa.core.model.utils.DateUtils; -import org.junit.After; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @RunWith(Parameterized.class) public class DateUtilsParseTimeTests { - private Locale backupLocale; - private TimeZone backupZone; - @Parameterized.Parameter(value = 0) public String input; @@ -61,18 +56,6 @@ public static Collection data() { }); } - @Before - public void setUp() { - backupLocale = Locale.getDefault(); - backupZone = TimeZone.getDefault(); - } - - @After - public void tearDown() { - TimeZone.setDefault(backupZone); - Locale.setDefault(backupLocale); - } - @Test public void testTimeParses() { // The tricky part of DateUtils.parseTime is that we allow for input time @@ -94,16 +77,19 @@ public void testTimeParses() { // to the same time declaration from the input string (ignoring their date part, // of course). Stream.of( - TimeZone.getTimeZone("UTC"), - TimeZone.getTimeZone("GMT+12"), - TimeZone.getTimeZone("GMT-13"), - TimeZone.getTimeZone("GMT+0230") - ).forEach(timeZone -> { - TimeZone backupZone = TimeZone.getDefault(); - TimeZone.setDefault(timeZone); - assertThat(parseTime(input), is(expectedTime)); - TimeZone.setDefault(backupZone); - }); + TimeZone.getDefault(), + getTimeZone("UTC"), + getTimeZone("GMT+12"), + getTimeZone("GMT-13"), + getTimeZone("GMT+0230") + ).forEach(tz -> withTimeZone(tz, () -> assertThat(parseTime(input), is(expectedTime)))); + } + + private void withTimeZone(TimeZone timeZone, Runnable block) { + TimeZone backupZone = TimeZone.getDefault(); + TimeZone.setDefault(timeZone); + block.run(); + TimeZone.setDefault(backupZone); } /** From 72b6797985aaae91f57dcf867f1201cdfe9f694b Mon Sep 17 00:00:00 2001 From: Guille Date: Thu, 30 Jan 2020 11:07:20 +0100 Subject: [PATCH 08/31] Migrate DateUtils.parseDateTime tests to use OffsetDateTime.parse Same reasons as a couple of commits before, where we argued that using OffsetDateTime.parse is easier to understand and safer. --- .../utils/test/DateUtilsParseTimeTests.java | 23 ++--- .../core/model/utils/test/DateUtilsTests.java | 84 +++++++++++-------- 2 files changed, 60 insertions(+), 47 deletions(-) diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsParseTimeTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsParseTimeTests.java index 023b8ef7c..3004b338b 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsParseTimeTests.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsParseTimeTests.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertThat; import java.time.Instant; +import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetDateTime; import java.time.OffsetTime; @@ -102,18 +103,20 @@ private void withTimeZone(TimeZone timeZone, Runnable block) { public Temporal parseTime(String input) { Instant inputInstant = Objects.requireNonNull(DateUtils.parseTime(input)).toInstant(); - // No time offset declared. Return a LocalTime - if (input.length() == 5) - return OffsetDateTime.ofInstant(inputInstant, ZoneId.systemDefault()).toLocalTime(); + if (input.contains("+") || input.contains("-")) { + // The input declares some positive or negative time offset + int beginOfOffsetPart = input.contains("+") ? input.indexOf("+") : input.indexOf("-"); + String offsetPart = input.substring(beginOfOffsetPart); + // The input declares some positive or negative time offset + String offset = offsetPart.length() == 3 ? offsetPart + ":00" : offsetPart; + return OffsetDateTime.ofInstant(inputInstant, ZoneId.of(offset)).toOffsetTime(); + } - // The input time is at UTC - if (input.charAt(5) == 'Z') + if (input.endsWith("Z")) + // The input time is at UTC return OffsetDateTime.ofInstant(inputInstant, ZoneId.of("Z")).toOffsetTime(); - // The input declares some positive or negative time offset - String offsetPart = input.substring(5); - // Fix for unparseable short offset notations such as +02 - String offset = offsetPart.length() == 3 ? offsetPart + ":00" : offsetPart; - return OffsetDateTime.ofInstant(inputInstant, ZoneId.of(offset)).toOffsetTime(); + // No time offset declared. Return a LocalTime + return LocalDateTime.ofInstant(inputInstant, ZoneId.systemDefault()).toLocalTime(); } } diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java index 773ed092b..43b25bff2 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java @@ -16,14 +16,20 @@ package org.javarosa.core.model.utils.test; +import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; import java.text.DateFormat; +import java.time.Instant; import java.time.LocalDateTime; import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.temporal.Temporal; import java.util.Calendar; import java.util.Date; import java.util.Locale; +import java.util.Objects; import java.util.SimpleTimeZone; import java.util.TimeZone; import org.javarosa.core.model.utils.DateUtils; @@ -73,53 +79,57 @@ public void testGetXMLStringValueFormat() { public void testDateTimeParses() { TimeZone.setDefault(TimeZone.getTimeZone("UTC")); - testDateTime("2016-04-13T16:26:00.000-07", 1460589960000L); - testDateTime("2015-12-16T16:09:00.000-08", 1450310940000L); // wraps day!!! - testDateTime("2015-12-16T07:09:00.000+08", 1450220940000L); // wraps day!!! - - testDateTime("2015-11-30T16:09:00.000-08", 1448928540000L); // wraps month!!! - testDateTime("2015-11-01T07:09:00.000+08", 1446332940000L); // wraps month!!! - - testDateTime("2015-12-31T16:09:00.000-08", 1451606940000L); // wraps year!!! - testDateTime("2015-01-01T07:09:00.000+08", 1420067340000L); // wraps year!!! - - testDateTime("2016-01-26T10:39:00.000-08", 1453833540000L); + assertThat(parseDateTime("2016-04-13T16:26:00.000-07"), is(OffsetDateTime.parse("2016-04-13T16:26:00.000-07:00"))); + assertThat(parseDateTime("2015-12-16T16:09:00.000-08"), is(OffsetDateTime.parse("2015-12-16T16:09:00.000-08:00"))); // wraps day!!! + assertThat(parseDateTime("2015-12-16T07:09:00.000+08"), is(OffsetDateTime.parse("2015-12-16T07:09:00.000+08:00"))); // wraps day!!! + assertThat(parseDateTime("2015-11-30T16:09:00.000-08"), is(OffsetDateTime.parse("2015-11-30T16:09:00.000-08:00"))); // wraps month!!! + assertThat(parseDateTime("2015-11-01T07:09:00.000+08"), is(OffsetDateTime.parse("2015-11-01T07:09:00.000+08:00"))); // wraps month!!! + assertThat(parseDateTime("2015-12-31T16:09:00.000-08"), is(OffsetDateTime.parse("2015-12-31T16:09:00.000-08:00"))); // wraps year!!! + assertThat(parseDateTime("2015-01-01T07:09:00.000+08"), is(OffsetDateTime.parse("2015-01-01T07:09:00.000+08:00"))); // wraps year!!! + assertThat(parseDateTime("2016-01-26T10:39:00.000-08"), is(OffsetDateTime.parse("2016-01-26T10:39:00.000-08:00"))); TimeZone.setDefault(TimeZone.getTimeZone("PST")); - testDateTime("2016-04-13T16:26:00.000-07", 1460589960000L); - testDateTime("2015-12-16T16:09:00.000-08", 1450310940000L); // wraps day!!! - testDateTime("2015-12-16T07:09:00.000+08", 1450220940000L); // wraps day!!! - - testDateTime("2015-11-30T16:09:00.000-08", 1448928540000L); // wraps month!!! - testDateTime("2015-11-01T07:09:00.000+08", 1446332940000L); // wraps month!!! - - testDateTime("2015-12-31T16:09:00.000-08", 1451606940000L); // wraps year!!! - testDateTime("2015-01-01T07:09:00.000+08", 1420067340000L); // wraps year!!! - - testDateTime("2016-01-26T10:39:00.000-08", 1453833540000L); + assertThat(parseDateTime("2016-04-13T16:26:00.000-07"), is(OffsetDateTime.parse("2016-04-13T16:26:00.000-07:00"))); + assertThat(parseDateTime("2015-12-16T16:09:00.000-08"), is(OffsetDateTime.parse("2015-12-16T16:09:00.000-08:00"))); // wraps day!!! + assertThat(parseDateTime("2015-12-16T07:09:00.000+08"), is(OffsetDateTime.parse("2015-12-16T07:09:00.000+08:00"))); // wraps day!!! + assertThat(parseDateTime("2015-11-30T16:09:00.000-08"), is(OffsetDateTime.parse("2015-11-30T16:09:00.000-08:00"))); // wraps month!!! + assertThat(parseDateTime("2015-11-01T07:09:00.000+08"), is(OffsetDateTime.parse("2015-11-01T07:09:00.000+08:00"))); // wraps month!!! + assertThat(parseDateTime("2015-12-31T16:09:00.000-08"), is(OffsetDateTime.parse("2015-12-31T16:09:00.000-08:00"))); // wraps year!!! + assertThat(parseDateTime("2015-01-01T07:09:00.000+08"), is(OffsetDateTime.parse("2015-01-01T07:09:00.000+08:00"))); // wraps year!!! + assertThat(parseDateTime("2016-01-26T10:39:00.000-08"), is(OffsetDateTime.parse("2016-01-26T10:39:00.000-08:00"))); TimeZone.setDefault(TimeZone.getTimeZone("PDT")); - testDateTime("2016-04-13T16:26:00.000-07", 1460589960000L); - testDateTime("2015-12-16T16:09:00.000-08", 1450310940000L); // wraps day!!! - testDateTime("2015-12-16T07:09:00.000+08", 1450220940000L); // wraps day!!! - - testDateTime("2015-11-30T16:09:00.000-08", 1448928540000L); // wraps month!!! - testDateTime("2015-11-01T07:09:00.000+08", 1446332940000L); // wraps month!!! - - testDateTime("2015-12-31T16:09:00.000-08", 1451606940000L); // wraps year!!! - testDateTime("2015-01-01T07:09:00.000+08", 1420067340000L); // wraps year!!! - - testDateTime("2016-01-26T10:39:00.000-08", 1453833540000L); + assertThat(parseDateTime("2016-04-13T16:26:00.000-07"), is(OffsetDateTime.parse("2016-04-13T16:26:00.000-07:00"))); + assertThat(parseDateTime("2015-12-16T16:09:00.000-08"), is(OffsetDateTime.parse("2015-12-16T16:09:00.000-08:00"))); // wraps day!!! + assertThat(parseDateTime("2015-12-16T07:09:00.000+08"), is(OffsetDateTime.parse("2015-12-16T07:09:00.000+08:00"))); // wraps day!!! + assertThat(parseDateTime("2015-11-30T16:09:00.000-08"), is(OffsetDateTime.parse("2015-11-30T16:09:00.000-08:00"))); // wraps month!!! + assertThat(parseDateTime("2015-11-01T07:09:00.000+08"), is(OffsetDateTime.parse("2015-11-01T07:09:00.000+08:00"))); // wraps month!!! + assertThat(parseDateTime("2015-12-31T16:09:00.000-08"), is(OffsetDateTime.parse("2015-12-31T16:09:00.000-08:00"))); // wraps year!!! + assertThat(parseDateTime("2015-01-01T07:09:00.000+08"), is(OffsetDateTime.parse("2015-01-01T07:09:00.000+08:00"))); // wraps year!!! + assertThat(parseDateTime("2016-01-26T10:39:00.000-08"), is(OffsetDateTime.parse("2016-01-26T10:39:00.000-08:00"))); } - private void testDateTime(String in, long test) { - Date d = DateUtils.parseDateTime(in); + private Temporal parseDateTime(String input) { + Instant inputInstant = Objects.requireNonNull(DateUtils.parseDateTime(input)).toInstant(); + + String timePart = input.substring(11); + if (timePart.contains("+") || timePart.contains("-")) { + // The input declares some positive or negative time offset + int beginOfOffsetPart = timePart.contains("+") ? timePart.indexOf("+") : timePart.indexOf("-"); + String offsetPart = timePart.substring(beginOfOffsetPart); + // The input declares some positive or negative time offset + String offset = offsetPart.length() == 3 ? offsetPart + ":00" : offsetPart; + return OffsetDateTime.ofInstant(inputInstant, ZoneId.of(offset)); + } - long value = d.getTime(); + if (input.endsWith("Z")) + // The input time is at UTC + return OffsetDateTime.ofInstant(inputInstant, ZoneId.of("Z")); - assertEquals("Fail: " + in + "(" + TimeZone.getDefault().getDisplayName() + ")", test, value); + // No time offset declared. Return a LocalTime + return LocalDateTime.ofInstant(inputInstant, ZoneId.systemDefault()); } private void testTime(String in, long test) { From 8e22fa085e35749221f3ea7055887a0fc42b0d28 Mon Sep 17 00:00:00 2001 From: Guille Date: Thu, 30 Jan 2020 11:11:13 +0100 Subject: [PATCH 09/31] Extract the DateUtils.parseDateTime to their own test class Same as we did with DateUtils.parseTime --- .../test/DateUtilsParseDateTimeTests.java | 101 ++++++++++++++++++ .../core/model/utils/test/DateUtilsTests.java | 63 ----------- 2 files changed, 101 insertions(+), 63 deletions(-) create mode 100644 src/test/java/org/javarosa/core/model/utils/test/DateUtilsParseDateTimeTests.java diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsParseDateTimeTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsParseDateTimeTests.java new file mode 100644 index 000000000..d8c378d6f --- /dev/null +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsParseDateTimeTests.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2009 JavaRosa + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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.javarosa.core.model.utils.test; + +import static java.util.TimeZone.getTimeZone; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.temporal.Temporal; +import java.util.Arrays; +import java.util.Collection; +import java.util.Objects; +import java.util.TimeZone; +import java.util.stream.Stream; +import org.javarosa.core.model.utils.DateUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class DateUtilsParseDateTimeTests { + @Parameterized.Parameter(value = 0) + public String input; + + @Parameterized.Parameter(value = 1) + public Temporal expectedDateTime; + + @Parameterized.Parameters(name = "Input: {0}") + public static Collection data() { + return Arrays.asList(new Object[][]{ + {"2016-04-13T16:26:00.000-07", OffsetDateTime.parse("2016-04-13T16:26:00.000-07:00")}, + {"2015-12-16T16:09:00.000-08", OffsetDateTime.parse("2015-12-16T16:09:00.000-08:00")}, + {"2015-12-16T07:09:00.000+08", OffsetDateTime.parse("2015-12-16T07:09:00.000+08:00")}, + {"2015-11-30T16:09:00.000-08", OffsetDateTime.parse("2015-11-30T16:09:00.000-08:00")}, + {"2015-11-01T07:09:00.000+08", OffsetDateTime.parse("2015-11-01T07:09:00.000+08:00")}, + {"2015-12-31T16:09:00.000-08", OffsetDateTime.parse("2015-12-31T16:09:00.000-08:00")}, + }); + } + + @Test + public void testTimeParses() { + Stream.of( + TimeZone.getDefault(), + getTimeZone("UTC"), + getTimeZone("GMT+12"), + getTimeZone("GMT-13"), + getTimeZone("GMT+0230") + ).forEach(tz -> withTimeZone(tz, () -> assertThat(parseDateTime(input), is(expectedDateTime)))); + } + + private void withTimeZone(TimeZone timeZone, Runnable block) { + TimeZone backupZone = TimeZone.getDefault(); + TimeZone.setDefault(timeZone); + block.run(); + TimeZone.setDefault(backupZone); + } + + /** + * Returns a LocalDateTime or an OffsetTimeTime obtained from the result of + * calling DateUtils.parseDateTime() with the provided input, ensuring that + * both represent the same instant. + */ + private Temporal parseDateTime(String input) { + Instant inputInstant = Objects.requireNonNull(DateUtils.parseDateTime(input)).toInstant(); + + String timePart = input.substring(11); + if (timePart.contains("+") || timePart.contains("-")) { + // The input declares some positive or negative time offset + int beginOfOffsetPart = timePart.contains("+") ? timePart.indexOf("+") : timePart.indexOf("-"); + String offsetPart = timePart.substring(beginOfOffsetPart); + // The input declares some positive or negative time offset + String offset = offsetPart.length() == 3 ? offsetPart + ":00" : offsetPart; + return OffsetDateTime.ofInstant(inputInstant, ZoneId.of(offset)); + } + + if (input.endsWith("Z")) + // The input time is at UTC + return OffsetDateTime.ofInstant(inputInstant, ZoneId.of("Z")); + + // No time offset declared. Return a LocalTime + return LocalDateTime.ofInstant(inputInstant, ZoneId.systemDefault()); + } +} diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java index 43b25bff2..a378a5cb5 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java @@ -16,20 +16,14 @@ package org.javarosa.core.model.utils.test; -import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; import java.text.DateFormat; -import java.time.Instant; import java.time.LocalDateTime; import java.time.OffsetDateTime; -import java.time.ZoneId; -import java.time.temporal.Temporal; import java.util.Calendar; import java.util.Date; import java.util.Locale; -import java.util.Objects; import java.util.SimpleTimeZone; import java.util.TimeZone; import org.javarosa.core.model.utils.DateUtils; @@ -75,63 +69,6 @@ public void testGetXMLStringValueFormat() { assertEquals(testLocalDateTime.getDayOfMonth(), Integer.parseInt(currentDate.substring(8, 10))); } - @Test - public void testDateTimeParses() { - TimeZone.setDefault(TimeZone.getTimeZone("UTC")); - - assertThat(parseDateTime("2016-04-13T16:26:00.000-07"), is(OffsetDateTime.parse("2016-04-13T16:26:00.000-07:00"))); - assertThat(parseDateTime("2015-12-16T16:09:00.000-08"), is(OffsetDateTime.parse("2015-12-16T16:09:00.000-08:00"))); // wraps day!!! - assertThat(parseDateTime("2015-12-16T07:09:00.000+08"), is(OffsetDateTime.parse("2015-12-16T07:09:00.000+08:00"))); // wraps day!!! - assertThat(parseDateTime("2015-11-30T16:09:00.000-08"), is(OffsetDateTime.parse("2015-11-30T16:09:00.000-08:00"))); // wraps month!!! - assertThat(parseDateTime("2015-11-01T07:09:00.000+08"), is(OffsetDateTime.parse("2015-11-01T07:09:00.000+08:00"))); // wraps month!!! - assertThat(parseDateTime("2015-12-31T16:09:00.000-08"), is(OffsetDateTime.parse("2015-12-31T16:09:00.000-08:00"))); // wraps year!!! - assertThat(parseDateTime("2015-01-01T07:09:00.000+08"), is(OffsetDateTime.parse("2015-01-01T07:09:00.000+08:00"))); // wraps year!!! - assertThat(parseDateTime("2016-01-26T10:39:00.000-08"), is(OffsetDateTime.parse("2016-01-26T10:39:00.000-08:00"))); - - TimeZone.setDefault(TimeZone.getTimeZone("PST")); - - assertThat(parseDateTime("2016-04-13T16:26:00.000-07"), is(OffsetDateTime.parse("2016-04-13T16:26:00.000-07:00"))); - assertThat(parseDateTime("2015-12-16T16:09:00.000-08"), is(OffsetDateTime.parse("2015-12-16T16:09:00.000-08:00"))); // wraps day!!! - assertThat(parseDateTime("2015-12-16T07:09:00.000+08"), is(OffsetDateTime.parse("2015-12-16T07:09:00.000+08:00"))); // wraps day!!! - assertThat(parseDateTime("2015-11-30T16:09:00.000-08"), is(OffsetDateTime.parse("2015-11-30T16:09:00.000-08:00"))); // wraps month!!! - assertThat(parseDateTime("2015-11-01T07:09:00.000+08"), is(OffsetDateTime.parse("2015-11-01T07:09:00.000+08:00"))); // wraps month!!! - assertThat(parseDateTime("2015-12-31T16:09:00.000-08"), is(OffsetDateTime.parse("2015-12-31T16:09:00.000-08:00"))); // wraps year!!! - assertThat(parseDateTime("2015-01-01T07:09:00.000+08"), is(OffsetDateTime.parse("2015-01-01T07:09:00.000+08:00"))); // wraps year!!! - assertThat(parseDateTime("2016-01-26T10:39:00.000-08"), is(OffsetDateTime.parse("2016-01-26T10:39:00.000-08:00"))); - - TimeZone.setDefault(TimeZone.getTimeZone("PDT")); - - assertThat(parseDateTime("2016-04-13T16:26:00.000-07"), is(OffsetDateTime.parse("2016-04-13T16:26:00.000-07:00"))); - assertThat(parseDateTime("2015-12-16T16:09:00.000-08"), is(OffsetDateTime.parse("2015-12-16T16:09:00.000-08:00"))); // wraps day!!! - assertThat(parseDateTime("2015-12-16T07:09:00.000+08"), is(OffsetDateTime.parse("2015-12-16T07:09:00.000+08:00"))); // wraps day!!! - assertThat(parseDateTime("2015-11-30T16:09:00.000-08"), is(OffsetDateTime.parse("2015-11-30T16:09:00.000-08:00"))); // wraps month!!! - assertThat(parseDateTime("2015-11-01T07:09:00.000+08"), is(OffsetDateTime.parse("2015-11-01T07:09:00.000+08:00"))); // wraps month!!! - assertThat(parseDateTime("2015-12-31T16:09:00.000-08"), is(OffsetDateTime.parse("2015-12-31T16:09:00.000-08:00"))); // wraps year!!! - assertThat(parseDateTime("2015-01-01T07:09:00.000+08"), is(OffsetDateTime.parse("2015-01-01T07:09:00.000+08:00"))); // wraps year!!! - assertThat(parseDateTime("2016-01-26T10:39:00.000-08"), is(OffsetDateTime.parse("2016-01-26T10:39:00.000-08:00"))); - } - - private Temporal parseDateTime(String input) { - Instant inputInstant = Objects.requireNonNull(DateUtils.parseDateTime(input)).toInstant(); - - String timePart = input.substring(11); - if (timePart.contains("+") || timePart.contains("-")) { - // The input declares some positive or negative time offset - int beginOfOffsetPart = timePart.contains("+") ? timePart.indexOf("+") : timePart.indexOf("-"); - String offsetPart = timePart.substring(beginOfOffsetPart); - // The input declares some positive or negative time offset - String offset = offsetPart.length() == 3 ? offsetPart + ":00" : offsetPart; - return OffsetDateTime.ofInstant(inputInstant, ZoneId.of(offset)); - } - - if (input.endsWith("Z")) - // The input time is at UTC - return OffsetDateTime.ofInstant(inputInstant, ZoneId.of("Z")); - - // No time offset declared. Return a LocalTime - return LocalDateTime.ofInstant(inputInstant, ZoneId.systemDefault()); - } - private void testTime(String in, long test) { Date d = DateUtils.parseTime(in); From 3d934b73885f6af1a9e3130033d160f0b9ebbe33 Mon Sep 17 00:00:00 2001 From: Guille Date: Thu, 30 Jan 2020 11:11:52 +0100 Subject: [PATCH 10/31] Add a case without input time zone for completeness --- .../core/model/utils/test/DateUtilsParseDateTimeTests.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsParseDateTimeTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsParseDateTimeTests.java index d8c378d6f..a3bb12eac 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsParseDateTimeTests.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsParseDateTimeTests.java @@ -46,6 +46,7 @@ public class DateUtilsParseDateTimeTests { @Parameterized.Parameters(name = "Input: {0}") public static Collection data() { return Arrays.asList(new Object[][]{ + {"2016-04-13T16:26:00.000", LocalDateTime.parse("2016-04-13T16:26:00.000")}, {"2016-04-13T16:26:00.000-07", OffsetDateTime.parse("2016-04-13T16:26:00.000-07:00")}, {"2015-12-16T16:09:00.000-08", OffsetDateTime.parse("2015-12-16T16:09:00.000-08:00")}, {"2015-12-16T07:09:00.000+08", OffsetDateTime.parse("2015-12-16T07:09:00.000+08:00")}, From c3bc3b2b85759e75ca098ff3ca449b6d4addda6d Mon Sep 17 00:00:00 2001 From: Guille Date: Thu, 30 Jan 2020 11:14:48 +0100 Subject: [PATCH 11/31] Remove ignored test It doesn't feel important now that we're improving the test suite and adding more context to DateUtils --- .../core/model/utils/test/DateUtilsTests.java | 41 ------------------- 1 file changed, 41 deletions(-) diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java index a378a5cb5..03fe1c208 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java @@ -69,14 +69,6 @@ public void testGetXMLStringValueFormat() { assertEquals(testLocalDateTime.getDayOfMonth(), Integer.parseInt(currentDate.substring(8, 10))); } - private void testTime(String in, long test) { - Date d = DateUtils.parseTime(in); - - long value = d.getTime(); - - assertEquals("Fail: " + in + "(" + TimeZone.getDefault().getDisplayName() + ")", test, value); - } - @Test public void testParity() { @@ -113,39 +105,6 @@ public void testParity() { } - @Test - @Ignore - // This test doesn't make sense: - // - A time has no offset nor zone. It can only have one - // when bound to a date, which is not the case - // - We're effectively binding all times to the EPOCH date - // (1970-01-01, UTC), which has no DST - public void testParseTime_with_DST() {// testFormatting - Locale.setDefault(Locale.US); - - // this is a timezone that operates DST every day of the year! - SimpleTimeZone dstTimezone = new SimpleTimeZone( - 2 * 60 * 60 * 1000, - "Europe/Athens", - Calendar.JANUARY, 1, 0, - 0, SimpleTimeZone.UTC_TIME, - Calendar.DECEMBER, 31, 0, - 24 * 60 * 60 * 1000, SimpleTimeZone.UTC_TIME, - 60 * 60 * 1000); - TimeZone.setDefault(dstTimezone); - - String time = "12:03:05.000Z"; - testTime(time, 43385000L); - - Date date = DateUtils.parseTime(time); - - DateFormat formatter = DateFormat.getTimeInstance(); - String formatted = formatter.format(date); - - // It should shift 3 hours, 2 for the zone and 1 for DST. - assertEquals("3:03:05 PM", formatted); - } - private void testCycle(Date in) { String formatted = DateUtils.formatDateTime(in, DateUtils.FORMAT_ISO8601); Date out = DateUtils.parseDateTime(formatted); From 218d603cdd2d657b54cf90163874b767bf261bde Mon Sep 17 00:00:00 2001 From: Guille Date: Thu, 30 Jan 2020 11:18:27 +0100 Subject: [PATCH 12/31] These members are only used in a test. Move to local --- .../core/model/utils/test/DateUtilsTests.java | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java index 03fe1c208..894ca3d3f 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java @@ -18,34 +18,26 @@ import static org.junit.Assert.assertEquals; -import java.text.DateFormat; import java.time.LocalDateTime; import java.time.OffsetDateTime; -import java.util.Calendar; import java.util.Date; import java.util.Locale; -import java.util.SimpleTimeZone; import java.util.TimeZone; import org.javarosa.core.model.utils.DateUtils; import org.javarosa.core.model.utils.DateUtils.DateFields; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; public class DateUtilsTests { - private Locale backupLocale; private TimeZone backupZone; - private Date testDate; - private LocalDateTime testLocalDateTime; @Before public void setUp() { backupLocale = Locale.getDefault(); backupZone = TimeZone.getDefault(); - testLocalDateTime = LocalDateTime.of(2018, 1, 1, 10, 20, 30, 400); - testDate = Date.from(testLocalDateTime.toInstant(OffsetDateTime.now().getOffset())); + } @After @@ -61,6 +53,8 @@ public void tearDown() { */ @Test public void testGetXMLStringValueFormat() { + LocalDateTime testLocalDateTime = LocalDateTime.of(2018, 1, 1, 10, 20, 30, 400); + Date testDate = Date.from(testLocalDateTime.toInstant(OffsetDateTime.now().getOffset())); String currentDate = DateUtils.getXMLStringValue(testDate); assertEquals("The date string was not of the proper length", currentDate.length(), "YYYY-MM-DD".length()); assertEquals("The date string does not have proper year formatting", currentDate.indexOf("-"), "YYYY-".indexOf("-")); From 9ae963681224acc208fe6e7b0ed77e809560b895 Mon Sep 17 00:00:00 2001 From: Guille Date: Thu, 30 Jan 2020 11:20:20 +0100 Subject: [PATCH 13/31] Simplify by reusing the same test date time --- .../org/javarosa/core/model/utils/test/DateUtilsTests.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java index 894ca3d3f..9b5b977e8 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java @@ -54,8 +54,7 @@ public void tearDown() { @Test public void testGetXMLStringValueFormat() { LocalDateTime testLocalDateTime = LocalDateTime.of(2018, 1, 1, 10, 20, 30, 400); - Date testDate = Date.from(testLocalDateTime.toInstant(OffsetDateTime.now().getOffset())); - String currentDate = DateUtils.getXMLStringValue(testDate); + String currentDate = DateUtils.getXMLStringValue(Date.from(testLocalDateTime.toInstant(OffsetDateTime.now().getOffset()))); assertEquals("The date string was not of the proper length", currentDate.length(), "YYYY-MM-DD".length()); assertEquals("The date string does not have proper year formatting", currentDate.indexOf("-"), "YYYY-".indexOf("-")); assertEquals(testLocalDateTime.getYear(), Integer.parseInt(currentDate.substring(0, 4))); From f06f5891f3612ad24b6afa36117ff1e0077e0730 Mon Sep 17 00:00:00 2001 From: Guille Date: Thu, 30 Jan 2020 11:22:27 +0100 Subject: [PATCH 14/31] Simplify. XML date strings can be parsed by LocalDate Let's use LocalDate.parse to verify we produce valid XML date strings --- .../core/model/utils/test/DateUtilsTests.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java index 9b5b977e8..b912e394a 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java @@ -16,8 +16,11 @@ package org.javarosa.core.model.utils.test; +import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import java.time.LocalDate; import java.time.LocalDateTime; import java.time.OffsetDateTime; import java.util.Date; @@ -49,17 +52,15 @@ public void tearDown() { /** * This test ensures that the Strings returned * by the getXMLStringValue function are in - * the proper XML compliant format. + * the proper XML compliant format, which can be + * parsed by LocalDate.parse() */ @Test public void testGetXMLStringValueFormat() { - LocalDateTime testLocalDateTime = LocalDateTime.of(2018, 1, 1, 10, 20, 30, 400); - String currentDate = DateUtils.getXMLStringValue(Date.from(testLocalDateTime.toInstant(OffsetDateTime.now().getOffset()))); - assertEquals("The date string was not of the proper length", currentDate.length(), "YYYY-MM-DD".length()); - assertEquals("The date string does not have proper year formatting", currentDate.indexOf("-"), "YYYY-".indexOf("-")); - assertEquals(testLocalDateTime.getYear(), Integer.parseInt(currentDate.substring(0, 4))); - assertEquals(testLocalDateTime.getMonth().getValue(), Integer.parseInt(currentDate.substring(5, 7))); - assertEquals(testLocalDateTime.getDayOfMonth(), Integer.parseInt(currentDate.substring(8, 10))); + LocalDateTime nowDateTime = LocalDateTime.now(); + Date nowDate = Date.from(nowDateTime.toInstant(OffsetDateTime.now().getOffset())); + String nowXmlFormatterDate = DateUtils.getXMLStringValue(nowDate); + assertThat(LocalDate.parse(nowXmlFormatterDate), is(nowDateTime.toLocalDate())); } @Test From 7f494653cc5d5a61a8f1b15ba277bde72285c9fb Mon Sep 17 00:00:00 2001 From: Guille Date: Thu, 30 Jan 2020 11:38:08 +0100 Subject: [PATCH 15/31] Remove indirection for easier understanding of the test --- .../core/model/utils/test/DateUtilsTests.java | 42 +++++++++---------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java index b912e394a..3326f180b 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java @@ -17,6 +17,11 @@ package org.javarosa.core.model.utils.test; import static org.hamcrest.Matchers.is; +import static org.javarosa.core.model.utils.DateUtils.FORMAT_ISO8601; +import static org.javarosa.core.model.utils.DateUtils.format; +import static org.javarosa.core.model.utils.DateUtils.formatDateTime; +import static org.javarosa.core.model.utils.DateUtils.getXMLStringValue; +import static org.javarosa.core.model.utils.DateUtils.parseDateTime; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; @@ -26,7 +31,7 @@ import java.util.Date; import java.util.Locale; import java.util.TimeZone; -import org.javarosa.core.model.utils.DateUtils; +import java.util.stream.Stream; import org.javarosa.core.model.utils.DateUtils.DateFields; import org.junit.After; import org.junit.Before; @@ -59,50 +64,43 @@ public void tearDown() { public void testGetXMLStringValueFormat() { LocalDateTime nowDateTime = LocalDateTime.now(); Date nowDate = Date.from(nowDateTime.toInstant(OffsetDateTime.now().getOffset())); - String nowXmlFormatterDate = DateUtils.getXMLStringValue(nowDate); + String nowXmlFormatterDate = getXMLStringValue(nowDate); assertThat(LocalDate.parse(nowXmlFormatterDate), is(nowDateTime.toLocalDate())); } @Test - public void testParity() { + public void sanity_check_iso_format_and_parse_back() { - testCycle(new Date(1300139579000L)); - testCycle(new Date(0)); + Stream.of(new Date(1300139579000L), new Date(0)).forEach(input -> + assertThat(parseDateTime(formatDateTime(input, FORMAT_ISO8601)), is(input))); TimeZone.setDefault(TimeZone.getTimeZone("UTC")); - testCycle(new Date(1300139579000L)); - testCycle(new Date(0)); + Stream.of(new Date(1300139579000L), new Date(0)).forEach(input -> + assertThat(parseDateTime(formatDateTime(input, FORMAT_ISO8601)), is(input))); TimeZone offsetTwoHours = TimeZone.getTimeZone("GMT+02"); TimeZone.setDefault(offsetTwoHours); - testCycle(new Date(1300139579000L)); - testCycle(new Date(0)); - + Stream.of(new Date(1300139579000L), new Date(0)).forEach(input -> + assertThat(parseDateTime(formatDateTime(input, FORMAT_ISO8601)), is(input))); TimeZone offTwoHalf = TimeZone.getTimeZone("GMT+0230"); TimeZone.setDefault(offTwoHalf); - testCycle(new Date(1300139579000L)); - testCycle(new Date(0)); + Stream.of(new Date(1300139579000L), new Date(0)).forEach(input -> + assertThat(parseDateTime(formatDateTime(input, FORMAT_ISO8601)), is(input))); TimeZone offMinTwoHalf = TimeZone.getTimeZone("GMT-0230"); TimeZone.setDefault(offMinTwoHalf); - testCycle(new Date(1300139579000L)); - testCycle(new Date(0)); - + Stream.of(new Date(1300139579000L), new Date(0)).forEach(input -> + assertThat(parseDateTime(formatDateTime(input, FORMAT_ISO8601)), is(input))); - } - private void testCycle(Date in) { - String formatted = DateUtils.formatDateTime(in, DateUtils.FORMAT_ISO8601); - Date out = DateUtils.parseDateTime(formatted); - assertEquals("Fail:", in.getTime(), out.getTime()); } @Test @@ -128,11 +126,11 @@ private LangJanSun(String language, String january, String sunday) { for (LangJanSun ljs : langJanSuns) { Locale.setDefault(Locale.forLanguageTag(ljs.language)); - String month = DateUtils.format(DateFields.of(2018, 1, 1, 10, 20, 30, 400), "%b"); + String month = format(DateFields.of(2018, 1, 1, 10, 20, 30, 400), "%b"); assertEquals(ljs.january, month); // 2018-04-01 was sunday - String day = DateUtils.format(DateFields.of(2018, 4, 1, 10, 20, 30, 400), "%a"); + String day = format(DateFields.of(2018, 4, 1, 10, 20, 30, 400), "%a"); assertEquals(ljs.sunday, day); } } From 9558475eb6cace78c7d21db3323547639cf7a3c4 Mon Sep 17 00:00:00 2001 From: Guille Date: Thu, 30 Jan 2020 11:40:44 +0100 Subject: [PATCH 16/31] Simplify by removing repetition --- .../core/model/utils/test/DateUtilsTests.java | 49 +++++++------------ 1 file changed, 19 insertions(+), 30 deletions(-) diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java index 3326f180b..582ebedc2 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java @@ -16,6 +16,7 @@ package org.javarosa.core.model.utils.test; +import static java.util.TimeZone.getTimeZone; import static org.hamcrest.Matchers.is; import static org.javarosa.core.model.utils.DateUtils.FORMAT_ISO8601; import static org.javarosa.core.model.utils.DateUtils.format; @@ -70,37 +71,25 @@ public void testGetXMLStringValueFormat() { @Test public void sanity_check_iso_format_and_parse_back() { + Stream.of( + TimeZone.getDefault(), + getTimeZone("UTC"), + getTimeZone("GMT+12"), + getTimeZone("GMT-13"), + getTimeZone("GMT+0230") + ).forEach(timeZone -> withTimeZone(timeZone, () -> + Stream.of( + new Date(1300139579000L), + new Date(0) + ).forEach(input -> + assertThat(parseDateTime(formatDateTime(input, FORMAT_ISO8601)), is(input))))); + } - Stream.of(new Date(1300139579000L), new Date(0)).forEach(input -> - assertThat(parseDateTime(formatDateTime(input, FORMAT_ISO8601)), is(input))); - - TimeZone.setDefault(TimeZone.getTimeZone("UTC")); - - Stream.of(new Date(1300139579000L), new Date(0)).forEach(input -> - assertThat(parseDateTime(formatDateTime(input, FORMAT_ISO8601)), is(input))); - - TimeZone offsetTwoHours = TimeZone.getTimeZone("GMT+02"); - - TimeZone.setDefault(offsetTwoHours); - - Stream.of(new Date(1300139579000L), new Date(0)).forEach(input -> - assertThat(parseDateTime(formatDateTime(input, FORMAT_ISO8601)), is(input))); - - TimeZone offTwoHalf = TimeZone.getTimeZone("GMT+0230"); - - TimeZone.setDefault(offTwoHalf); - - Stream.of(new Date(1300139579000L), new Date(0)).forEach(input -> - assertThat(parseDateTime(formatDateTime(input, FORMAT_ISO8601)), is(input))); - - TimeZone offMinTwoHalf = TimeZone.getTimeZone("GMT-0230"); - - TimeZone.setDefault(offMinTwoHalf); - - Stream.of(new Date(1300139579000L), new Date(0)).forEach(input -> - assertThat(parseDateTime(formatDateTime(input, FORMAT_ISO8601)), is(input))); - - + private void withTimeZone(TimeZone timeZone, Runnable block) { + TimeZone backupZone = TimeZone.getDefault(); + TimeZone.setDefault(timeZone); + block.run(); + TimeZone.setDefault(backupZone); } @Test From 294f5bfb41919d489bdab4aade6c62c5b3975ba0 Mon Sep 17 00:00:00 2001 From: Guille Date: Thu, 30 Jan 2020 11:45:02 +0100 Subject: [PATCH 17/31] Extract sanity check to its own test class --- .../test/DateUtilsFormatSanityCheckTests.java | 67 +++++++++++++++++++ .../core/model/utils/test/DateUtilsTests.java | 28 -------- 2 files changed, 67 insertions(+), 28 deletions(-) create mode 100644 src/test/java/org/javarosa/core/model/utils/test/DateUtilsFormatSanityCheckTests.java diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsFormatSanityCheckTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsFormatSanityCheckTests.java new file mode 100644 index 000000000..7daab6666 --- /dev/null +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsFormatSanityCheckTests.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2009 JavaRosa + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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.javarosa.core.model.utils.test; + +import static java.util.TimeZone.getTimeZone; +import static org.hamcrest.Matchers.is; +import static org.javarosa.core.model.utils.DateUtils.FORMAT_ISO8601; +import static org.javarosa.core.model.utils.DateUtils.formatDateTime; +import static org.javarosa.core.model.utils.DateUtils.parseDateTime; +import static org.junit.Assert.assertThat; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.TimeZone; +import java.util.stream.Stream; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class DateUtilsFormatSanityCheckTests { + @Parameterized.Parameter(value = 0) + public long inputTimestamp; + + @Parameterized.Parameters(name = "Input timestamp: {0}") + public static Collection data() { + return Arrays.asList(new Object[][]{ + {1300139579000L}, + {0} + }); + } + + @Test + public void sanity_check_iso_format_and_parse_back() { + Date input = new Date(inputTimestamp); + Stream.of( + TimeZone.getDefault(), + getTimeZone("UTC"), + getTimeZone("GMT+12"), + getTimeZone("GMT-13"), + getTimeZone("GMT+0230") + ).forEach(timeZone -> withTimeZone(timeZone, () -> + assertThat(parseDateTime(formatDateTime(input, FORMAT_ISO8601)), is(input)))); + } + + private void withTimeZone(TimeZone timeZone, Runnable block) { + TimeZone backupZone = TimeZone.getDefault(); + TimeZone.setDefault(timeZone); + block.run(); + TimeZone.setDefault(backupZone); + } +} diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java index 582ebedc2..ad1f8aba1 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java @@ -16,13 +16,9 @@ package org.javarosa.core.model.utils.test; -import static java.util.TimeZone.getTimeZone; import static org.hamcrest.Matchers.is; -import static org.javarosa.core.model.utils.DateUtils.FORMAT_ISO8601; import static org.javarosa.core.model.utils.DateUtils.format; -import static org.javarosa.core.model.utils.DateUtils.formatDateTime; import static org.javarosa.core.model.utils.DateUtils.getXMLStringValue; -import static org.javarosa.core.model.utils.DateUtils.parseDateTime; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; @@ -32,7 +28,6 @@ import java.util.Date; import java.util.Locale; import java.util.TimeZone; -import java.util.stream.Stream; import org.javarosa.core.model.utils.DateUtils.DateFields; import org.junit.After; import org.junit.Before; @@ -69,29 +64,6 @@ public void testGetXMLStringValueFormat() { assertThat(LocalDate.parse(nowXmlFormatterDate), is(nowDateTime.toLocalDate())); } - @Test - public void sanity_check_iso_format_and_parse_back() { - Stream.of( - TimeZone.getDefault(), - getTimeZone("UTC"), - getTimeZone("GMT+12"), - getTimeZone("GMT-13"), - getTimeZone("GMT+0230") - ).forEach(timeZone -> withTimeZone(timeZone, () -> - Stream.of( - new Date(1300139579000L), - new Date(0) - ).forEach(input -> - assertThat(parseDateTime(formatDateTime(input, FORMAT_ISO8601)), is(input))))); - } - - private void withTimeZone(TimeZone timeZone, Runnable block) { - TimeZone backupZone = TimeZone.getDefault(); - TimeZone.setDefault(timeZone); - block.run(); - TimeZone.setDefault(backupZone); - } - @Test public void testFormatting() { class LangJanSun { From cfbe47795cbe244914f81ff2695af2ad1a17754a Mon Sep 17 00:00:00 2001 From: Guille Date: Thu, 30 Jan 2020 11:49:24 +0100 Subject: [PATCH 18/31] Now the test method's name is more precise and there is no indirection --- .../javarosa/core/model/utils/test/DateUtilsTests.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java index ad1f8aba1..03bce0664 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java @@ -17,7 +17,6 @@ package org.javarosa.core.model.utils.test; import static org.hamcrest.Matchers.is; -import static org.javarosa.core.model.utils.DateUtils.format; import static org.javarosa.core.model.utils.DateUtils.getXMLStringValue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; @@ -28,6 +27,7 @@ import java.util.Date; import java.util.Locale; import java.util.TimeZone; +import org.javarosa.core.model.utils.DateUtils; import org.javarosa.core.model.utils.DateUtils.DateFields; import org.junit.After; import org.junit.Before; @@ -65,7 +65,7 @@ public void testGetXMLStringValueFormat() { } @Test - public void testFormatting() { + public void format_is_localized() { class LangJanSun { private LangJanSun(String language, String january, String sunday) { this.language = language; @@ -87,11 +87,11 @@ private LangJanSun(String language, String january, String sunday) { for (LangJanSun ljs : langJanSuns) { Locale.setDefault(Locale.forLanguageTag(ljs.language)); - String month = format(DateFields.of(2018, 1, 1, 10, 20, 30, 400), "%b"); + String month = DateUtils.format(DateFields.of(2018, 1, 1, 10, 20, 30, 400), "%b"); assertEquals(ljs.january, month); // 2018-04-01 was sunday - String day = format(DateFields.of(2018, 4, 1, 10, 20, 30, 400), "%a"); + String day = DateUtils.format(DateFields.of(2018, 4, 1, 10, 20, 30, 400), "%a"); assertEquals(ljs.sunday, day); } } From 5a4ead28dc46d595d1ccd0ba5d4a6efb41ed7d05 Mon Sep 17 00:00:00 2001 From: Guille Date: Thu, 30 Jan 2020 11:53:57 +0100 Subject: [PATCH 19/31] Improve by using human readable dates and java.time parse. --- .../javarosa/core/model/utils/test/DateUtilsTests.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java index 03bce0664..c0523ddcc 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java @@ -28,7 +28,6 @@ import java.util.Locale; import java.util.TimeZone; import org.javarosa.core.model.utils.DateUtils; -import org.javarosa.core.model.utils.DateUtils.DateFields; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -87,11 +86,14 @@ private LangJanSun(String language, String january, String sunday) { for (LangJanSun ljs : langJanSuns) { Locale.setDefault(Locale.forLanguageTag(ljs.language)); - String month = DateUtils.format(DateFields.of(2018, 1, 1, 10, 20, 30, 400), "%b"); + // Use a Sunday in January for our test + LocalDateTime localDateTime = LocalDateTime.parse("2018-01-07T10:20:30.400"); + Date date = Date.from(localDateTime.toInstant(OffsetDateTime.now().getOffset())); + + String month = DateUtils.format(date, "%b"); assertEquals(ljs.january, month); - // 2018-04-01 was sunday - String day = DateUtils.format(DateFields.of(2018, 4, 1, 10, 20, 30, 400), "%a"); + String day = DateUtils.format(date, "%a"); assertEquals(ljs.sunday, day); } } From 3df902f68541fe8d06c4f0d00988093ee4cfd2e6 Mon Sep 17 00:00:00 2001 From: Guille Date: Thu, 30 Jan 2020 12:00:24 +0100 Subject: [PATCH 20/31] Migrate formatting test to use Locales and values that depend on the JVM Previously, different JVMs could produce different textual formats for date parts (even the same JVM vendor and version in different environments) The test now verifies what we really want to verify: that DateUtils.format produces localized outputs --- .../core/model/utils/test/DateUtilsTests.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java index c0523ddcc..0c1a21f3e 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java @@ -21,9 +21,12 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; +import java.time.DayOfWeek; import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.Month; import java.time.OffsetDateTime; +import java.time.format.TextStyle; import java.util.Date; import java.util.Locale; import java.util.TimeZone; @@ -66,25 +69,25 @@ public void testGetXMLStringValueFormat() { @Test public void format_is_localized() { class LangJanSun { - private LangJanSun(String language, String january, String sunday) { - this.language = language; + private LangJanSun(Locale locale, String january, String sunday) { + this.locale = locale; this.january = january; this.sunday = sunday; } - private String language; + private Locale locale; private String january; private String sunday; } LangJanSun langJanSuns[] = new LangJanSun[]{ - new LangJanSun("en", "Jan", "Sun"), - new LangJanSun("es", "ene", "dom"), - new LangJanSun("fr", "janv.", "dim.") + new LangJanSun(Locale.ENGLISH, Month.JANUARY.getDisplayName(TextStyle.SHORT, Locale.ENGLISH), DayOfWeek.SUNDAY.getDisplayName(TextStyle.SHORT, Locale.ENGLISH)), + new LangJanSun(Locale.forLanguageTag("es-ES"), Month.JANUARY.getDisplayName(TextStyle.SHORT, Locale.forLanguageTag("es-ES")), DayOfWeek.SUNDAY.getDisplayName(TextStyle.SHORT, Locale.forLanguageTag("es-ES"))), + new LangJanSun(Locale.FRENCH, Month.JANUARY.getDisplayName(TextStyle.SHORT, Locale.FRENCH), DayOfWeek.SUNDAY.getDisplayName(TextStyle.SHORT, Locale.FRENCH)) }; for (LangJanSun ljs : langJanSuns) { - Locale.setDefault(Locale.forLanguageTag(ljs.language)); + Locale.setDefault(ljs.locale); // Use a Sunday in January for our test LocalDateTime localDateTime = LocalDateTime.parse("2018-01-07T10:20:30.400"); From 16f998ff0db47702c3739dc678a0ae5da6b63a00 Mon Sep 17 00:00:00 2001 From: Guille Date: Thu, 30 Jan 2020 12:02:36 +0100 Subject: [PATCH 21/31] Simplify by leaving only the moving parts The creation of the values for January and Sunday only depends on the locale --- .../core/model/utils/test/DateUtilsTests.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java index 0c1a21f3e..c39b81364 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java @@ -16,15 +16,15 @@ package org.javarosa.core.model.utils.test; +import static java.time.DayOfWeek.SUNDAY; +import static java.time.Month.JANUARY; import static org.hamcrest.Matchers.is; import static org.javarosa.core.model.utils.DateUtils.getXMLStringValue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; -import java.time.DayOfWeek; import java.time.LocalDate; import java.time.LocalDateTime; -import java.time.Month; import java.time.OffsetDateTime; import java.time.format.TextStyle; import java.util.Date; @@ -69,10 +69,10 @@ public void testGetXMLStringValueFormat() { @Test public void format_is_localized() { class LangJanSun { - private LangJanSun(Locale locale, String january, String sunday) { + private LangJanSun(Locale locale) { this.locale = locale; - this.january = january; - this.sunday = sunday; + this.january = JANUARY.getDisplayName(TextStyle.SHORT, locale); + this.sunday = SUNDAY.getDisplayName(TextStyle.SHORT, locale); } private Locale locale; @@ -81,9 +81,9 @@ private LangJanSun(Locale locale, String january, String sunday) { } LangJanSun langJanSuns[] = new LangJanSun[]{ - new LangJanSun(Locale.ENGLISH, Month.JANUARY.getDisplayName(TextStyle.SHORT, Locale.ENGLISH), DayOfWeek.SUNDAY.getDisplayName(TextStyle.SHORT, Locale.ENGLISH)), - new LangJanSun(Locale.forLanguageTag("es-ES"), Month.JANUARY.getDisplayName(TextStyle.SHORT, Locale.forLanguageTag("es-ES")), DayOfWeek.SUNDAY.getDisplayName(TextStyle.SHORT, Locale.forLanguageTag("es-ES"))), - new LangJanSun(Locale.FRENCH, Month.JANUARY.getDisplayName(TextStyle.SHORT, Locale.FRENCH), DayOfWeek.SUNDAY.getDisplayName(TextStyle.SHORT, Locale.FRENCH)) + new LangJanSun(Locale.ENGLISH), + new LangJanSun(Locale.forLanguageTag("es-ES")), + new LangJanSun(Locale.FRENCH) }; for (LangJanSun ljs : langJanSuns) { From e8e9dc108968fba5220d99663c3af03fccc069d0 Mon Sep 17 00:00:00 2001 From: Guille Date: Thu, 30 Jan 2020 12:04:51 +0100 Subject: [PATCH 22/31] Wrap block execution to ensure it runs in a certain locale safely The default locale is reset after the block is run --- .../core/model/utils/test/DateUtilsTests.java | 44 +++++++------------ 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java index c39b81364..103625def 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java @@ -29,29 +29,10 @@ import java.time.format.TextStyle; import java.util.Date; import java.util.Locale; -import java.util.TimeZone; import org.javarosa.core.model.utils.DateUtils; -import org.junit.After; -import org.junit.Before; import org.junit.Test; public class DateUtilsTests { - private Locale backupLocale; - private TimeZone backupZone; - - @Before - public void setUp() { - backupLocale = Locale.getDefault(); - backupZone = TimeZone.getDefault(); - - } - - @After - public void tearDown() { - TimeZone.setDefault(backupZone); - Locale.setDefault(backupLocale); - } - /** * This test ensures that the Strings returned * by the getXMLStringValue function are in @@ -87,17 +68,24 @@ private LangJanSun(Locale locale) { }; for (LangJanSun ljs : langJanSuns) { - Locale.setDefault(ljs.locale); - - // Use a Sunday in January for our test - LocalDateTime localDateTime = LocalDateTime.parse("2018-01-07T10:20:30.400"); - Date date = Date.from(localDateTime.toInstant(OffsetDateTime.now().getOffset())); + withLocale(ljs.locale, () -> { + // Use a Sunday in January for our test + LocalDateTime localDateTime = LocalDateTime.parse("2018-01-07T10:20:30.400"); + Date date = Date.from(localDateTime.toInstant(OffsetDateTime.now().getOffset())); - String month = DateUtils.format(date, "%b"); - assertEquals(ljs.january, month); + String month = DateUtils.format(date, "%b"); + assertEquals(ljs.january, month); - String day = DateUtils.format(date, "%a"); - assertEquals(ljs.sunday, day); + String day = DateUtils.format(date, "%a"); + assertEquals(ljs.sunday, day); + }); } } + + public void withLocale(Locale locale, Runnable block) { + Locale backupLocale = Locale.getDefault(); + Locale.setDefault(locale); + block.run(); + Locale.setDefault(backupLocale); + } } From 86a2c08677ee7070fead25c4faa1fd1ba2af0b4b Mon Sep 17 00:00:00 2001 From: Guille Date: Thu, 30 Jan 2020 12:06:56 +0100 Subject: [PATCH 23/31] Simplify by requiring less state and making the assertion more explicit Now the expected values are created where they're used. Less indirection --- .../core/model/utils/test/DateUtilsTests.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java index 103625def..4161273b1 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java @@ -29,6 +29,7 @@ import java.time.format.TextStyle; import java.util.Date; import java.util.Locale; +import java.util.function.Consumer; import org.javarosa.core.model.utils.DateUtils; import org.junit.Test; @@ -52,13 +53,9 @@ public void format_is_localized() { class LangJanSun { private LangJanSun(Locale locale) { this.locale = locale; - this.january = JANUARY.getDisplayName(TextStyle.SHORT, locale); - this.sunday = SUNDAY.getDisplayName(TextStyle.SHORT, locale); } private Locale locale; - private String january; - private String sunday; } LangJanSun langJanSuns[] = new LangJanSun[]{ @@ -68,24 +65,27 @@ private LangJanSun(Locale locale) { }; for (LangJanSun ljs : langJanSuns) { - withLocale(ljs.locale, () -> { + withLocale(ljs.locale, locale -> { + String expectedJanuary = JANUARY.getDisplayName(TextStyle.SHORT, locale); + String expectedSunday = SUNDAY.getDisplayName(TextStyle.SHORT, locale); + // Use a Sunday in January for our test LocalDateTime localDateTime = LocalDateTime.parse("2018-01-07T10:20:30.400"); Date date = Date.from(localDateTime.toInstant(OffsetDateTime.now().getOffset())); String month = DateUtils.format(date, "%b"); - assertEquals(ljs.january, month); + assertEquals(expectedJanuary, month); String day = DateUtils.format(date, "%a"); - assertEquals(ljs.sunday, day); + assertEquals(expectedSunday, day); }); } } - public void withLocale(Locale locale, Runnable block) { + public void withLocale(Locale locale, Consumer block) { Locale backupLocale = Locale.getDefault(); Locale.setDefault(locale); - block.run(); + block.accept(locale); Locale.setDefault(backupLocale); } } From 863da8df733ad9e4ad861e5b3989089d8923cda1 Mon Sep 17 00:00:00 2001 From: Guille Date: Thu, 30 Jan 2020 12:07:32 +0100 Subject: [PATCH 24/31] Refactor: move creation of variable to where it's used --- .../org/javarosa/core/model/utils/test/DateUtilsTests.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java index 4161273b1..669ef8d16 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java @@ -66,17 +66,17 @@ private LangJanSun(Locale locale) { for (LangJanSun ljs : langJanSuns) { withLocale(ljs.locale, locale -> { - String expectedJanuary = JANUARY.getDisplayName(TextStyle.SHORT, locale); - String expectedSunday = SUNDAY.getDisplayName(TextStyle.SHORT, locale); // Use a Sunday in January for our test LocalDateTime localDateTime = LocalDateTime.parse("2018-01-07T10:20:30.400"); Date date = Date.from(localDateTime.toInstant(OffsetDateTime.now().getOffset())); String month = DateUtils.format(date, "%b"); + String expectedJanuary = JANUARY.getDisplayName(TextStyle.SHORT, locale); assertEquals(expectedJanuary, month); String day = DateUtils.format(date, "%a"); + String expectedSunday = SUNDAY.getDisplayName(TextStyle.SHORT, locale); assertEquals(expectedSunday, day); }); } From f5e0a0fffe519e74877c60d695c3be4a58e78559 Mon Sep 17 00:00:00 2001 From: Guille Date: Thu, 30 Jan 2020 12:08:50 +0100 Subject: [PATCH 25/31] Migrate assertions to assertThat for conformity and natural reading --- .../core/model/utils/test/DateUtilsTests.java | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java index 669ef8d16..eaed0d2c8 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java @@ -20,7 +20,6 @@ import static java.time.Month.JANUARY; import static org.hamcrest.Matchers.is; import static org.javarosa.core.model.utils.DateUtils.getXMLStringValue; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import java.time.LocalDate; @@ -66,18 +65,12 @@ private LangJanSun(Locale locale) { for (LangJanSun ljs : langJanSuns) { withLocale(ljs.locale, locale -> { - // Use a Sunday in January for our test LocalDateTime localDateTime = LocalDateTime.parse("2018-01-07T10:20:30.400"); Date date = Date.from(localDateTime.toInstant(OffsetDateTime.now().getOffset())); - String month = DateUtils.format(date, "%b"); - String expectedJanuary = JANUARY.getDisplayName(TextStyle.SHORT, locale); - assertEquals(expectedJanuary, month); - - String day = DateUtils.format(date, "%a"); - String expectedSunday = SUNDAY.getDisplayName(TextStyle.SHORT, locale); - assertEquals(expectedSunday, day); + assertThat(DateUtils.format(date, "%b"), is(JANUARY.getDisplayName(TextStyle.SHORT, locale))); + assertThat(DateUtils.format(date, "%a"), is(SUNDAY.getDisplayName(TextStyle.SHORT, locale))); }); } } From 79c201494b8520044c6c0f733dc6c27af93dc364 Mon Sep 17 00:00:00 2001 From: Guille Date: Thu, 30 Jan 2020 12:09:13 +0100 Subject: [PATCH 26/31] Extract static part from the assertions block --- .../javarosa/core/model/utils/test/DateUtilsTests.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java index eaed0d2c8..b2d26b659 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java @@ -63,12 +63,12 @@ private LangJanSun(Locale locale) { new LangJanSun(Locale.FRENCH) }; + // Use a Sunday in January for our test + LocalDateTime localDateTime = LocalDateTime.parse("2018-01-07T10:20:30.400"); + Date date = Date.from(localDateTime.toInstant(OffsetDateTime.now().getOffset())); + for (LangJanSun ljs : langJanSuns) { withLocale(ljs.locale, locale -> { - // Use a Sunday in January for our test - LocalDateTime localDateTime = LocalDateTime.parse("2018-01-07T10:20:30.400"); - Date date = Date.from(localDateTime.toInstant(OffsetDateTime.now().getOffset())); - assertThat(DateUtils.format(date, "%b"), is(JANUARY.getDisplayName(TextStyle.SHORT, locale))); assertThat(DateUtils.format(date, "%a"), is(SUNDAY.getDisplayName(TextStyle.SHORT, locale))); }); From 5f2c10d429e98303960ab2633e430cb50a405e37 Mon Sep 17 00:00:00 2001 From: Guille Date: Thu, 30 Jan 2020 12:09:49 +0100 Subject: [PATCH 27/31] Extract test about DateUtils.format localization to its own class --- .../DateUtilsFormatLocalizationTests.java | 70 +++++++++++++++++++ .../core/model/utils/test/DateUtilsTests.java | 40 ----------- 2 files changed, 70 insertions(+), 40 deletions(-) create mode 100644 src/test/java/org/javarosa/core/model/utils/test/DateUtilsFormatLocalizationTests.java diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsFormatLocalizationTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsFormatLocalizationTests.java new file mode 100644 index 000000000..cd6e2e869 --- /dev/null +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsFormatLocalizationTests.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2009 JavaRosa + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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.javarosa.core.model.utils.test; + +import static java.time.DayOfWeek.SUNDAY; +import static java.time.Month.JANUARY; +import static org.hamcrest.Matchers.is; +import static org.javarosa.core.model.utils.DateUtils.getXMLStringValue; +import static org.junit.Assert.assertThat; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.format.TextStyle; +import java.util.Date; +import java.util.Locale; +import java.util.function.Consumer; +import org.javarosa.core.model.utils.DateUtils; +import org.junit.Test; + +public class DateUtilsFormatLocalizationTests { + @Test + public void format_is_localized() { + class LangJanSun { + private LangJanSun(Locale locale) { + this.locale = locale; + } + + private Locale locale; + } + + LangJanSun langJanSuns[] = new LangJanSun[]{ + new LangJanSun(Locale.ENGLISH), + new LangJanSun(Locale.forLanguageTag("es-ES")), + new LangJanSun(Locale.FRENCH) + }; + + // Use a Sunday in January for our test + LocalDateTime localDateTime = LocalDateTime.parse("2018-01-07T10:20:30.400"); + Date date = Date.from(localDateTime.toInstant(OffsetDateTime.now().getOffset())); + + for (LangJanSun ljs : langJanSuns) { + withLocale(ljs.locale, locale -> { + assertThat(DateUtils.format(date, "%b"), is(JANUARY.getDisplayName(TextStyle.SHORT, locale))); + assertThat(DateUtils.format(date, "%a"), is(SUNDAY.getDisplayName(TextStyle.SHORT, locale))); + }); + } + } + + public void withLocale(Locale locale, Consumer block) { + Locale backupLocale = Locale.getDefault(); + Locale.setDefault(locale); + block.accept(locale); + Locale.setDefault(backupLocale); + } +} diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java index b2d26b659..956bbc4d1 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java @@ -16,8 +16,6 @@ package org.javarosa.core.model.utils.test; -import static java.time.DayOfWeek.SUNDAY; -import static java.time.Month.JANUARY; import static org.hamcrest.Matchers.is; import static org.javarosa.core.model.utils.DateUtils.getXMLStringValue; import static org.junit.Assert.assertThat; @@ -25,11 +23,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.OffsetDateTime; -import java.time.format.TextStyle; import java.util.Date; -import java.util.Locale; -import java.util.function.Consumer; -import org.javarosa.core.model.utils.DateUtils; import org.junit.Test; public class DateUtilsTests { @@ -47,38 +41,4 @@ public void testGetXMLStringValueFormat() { assertThat(LocalDate.parse(nowXmlFormatterDate), is(nowDateTime.toLocalDate())); } - @Test - public void format_is_localized() { - class LangJanSun { - private LangJanSun(Locale locale) { - this.locale = locale; - } - - private Locale locale; - } - - LangJanSun langJanSuns[] = new LangJanSun[]{ - new LangJanSun(Locale.ENGLISH), - new LangJanSun(Locale.forLanguageTag("es-ES")), - new LangJanSun(Locale.FRENCH) - }; - - // Use a Sunday in January for our test - LocalDateTime localDateTime = LocalDateTime.parse("2018-01-07T10:20:30.400"); - Date date = Date.from(localDateTime.toInstant(OffsetDateTime.now().getOffset())); - - for (LangJanSun ljs : langJanSuns) { - withLocale(ljs.locale, locale -> { - assertThat(DateUtils.format(date, "%b"), is(JANUARY.getDisplayName(TextStyle.SHORT, locale))); - assertThat(DateUtils.format(date, "%a"), is(SUNDAY.getDisplayName(TextStyle.SHORT, locale))); - }); - } - } - - public void withLocale(Locale locale, Consumer block) { - Locale backupLocale = Locale.getDefault(); - Locale.setDefault(locale); - block.accept(locale); - Locale.setDefault(backupLocale); - } } From aae6935225a1ef2746b89485c5af5a80a3ab21e1 Mon Sep 17 00:00:00 2001 From: Guille Date: Thu, 30 Jan 2020 12:11:49 +0100 Subject: [PATCH 28/31] Simplify by replacing the wrapper class with a stream of Locale --- .../DateUtilsFormatLocalizationTests.java | 31 ++++++------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsFormatLocalizationTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsFormatLocalizationTests.java index cd6e2e869..f034b5de8 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsFormatLocalizationTests.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsFormatLocalizationTests.java @@ -19,46 +19,33 @@ import static java.time.DayOfWeek.SUNDAY; import static java.time.Month.JANUARY; import static org.hamcrest.Matchers.is; -import static org.javarosa.core.model.utils.DateUtils.getXMLStringValue; import static org.junit.Assert.assertThat; -import java.time.LocalDate; import java.time.LocalDateTime; import java.time.OffsetDateTime; import java.time.format.TextStyle; import java.util.Date; import java.util.Locale; import java.util.function.Consumer; +import java.util.stream.Stream; import org.javarosa.core.model.utils.DateUtils; import org.junit.Test; public class DateUtilsFormatLocalizationTests { @Test public void format_is_localized() { - class LangJanSun { - private LangJanSun(Locale locale) { - this.locale = locale; - } - - private Locale locale; - } - - LangJanSun langJanSuns[] = new LangJanSun[]{ - new LangJanSun(Locale.ENGLISH), - new LangJanSun(Locale.forLanguageTag("es-ES")), - new LangJanSun(Locale.FRENCH) - }; - // Use a Sunday in January for our test LocalDateTime localDateTime = LocalDateTime.parse("2018-01-07T10:20:30.400"); Date date = Date.from(localDateTime.toInstant(OffsetDateTime.now().getOffset())); - for (LangJanSun ljs : langJanSuns) { - withLocale(ljs.locale, locale -> { - assertThat(DateUtils.format(date, "%b"), is(JANUARY.getDisplayName(TextStyle.SHORT, locale))); - assertThat(DateUtils.format(date, "%a"), is(SUNDAY.getDisplayName(TextStyle.SHORT, locale))); - }); - } + Stream.of( + Locale.ENGLISH, + Locale.forLanguageTag("es-ES"), + Locale.FRENCH + ).forEach(locale -> withLocale(locale, l -> { + assertThat(DateUtils.format(date, "%b"), is(JANUARY.getDisplayName(TextStyle.SHORT, l))); + assertThat(DateUtils.format(date, "%a"), is(SUNDAY.getDisplayName(TextStyle.SHORT, l))); + })); } public void withLocale(Locale locale, Consumer block) { From e8343b467c9eba2127f3482e9fcf3173a8bf85c3 Mon Sep 17 00:00:00 2001 From: Guille Date: Thu, 30 Jan 2020 12:12:56 +0100 Subject: [PATCH 29/31] Rename for conformity --- ...{DateUtilsTests.java => DateUtilsGetXmlStringValueTest.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/test/java/org/javarosa/core/model/utils/test/{DateUtilsTests.java => DateUtilsGetXmlStringValueTest.java} (97%) diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsGetXmlStringValueTest.java similarity index 97% rename from src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java rename to src/test/java/org/javarosa/core/model/utils/test/DateUtilsGetXmlStringValueTest.java index 956bbc4d1..94f8df794 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsTests.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsGetXmlStringValueTest.java @@ -26,7 +26,7 @@ import java.util.Date; import org.junit.Test; -public class DateUtilsTests { +public class DateUtilsGetXmlStringValueTest { /** * This test ensures that the Strings returned * by the getXMLStringValue function are in From 8001c81bc20c0181f3dc877c8a7070da3a1d51e4 Mon Sep 17 00:00:00 2001 From: Guille Date: Thu, 30 Jan 2020 12:18:08 +0100 Subject: [PATCH 30/31] Extract method to helper class and reuse --- .../DateUtilsFormatLocalizationTests.java | 14 ++----- .../test/DateUtilsFormatSanityCheckTests.java | 8 +--- .../test/DateUtilsGetXmlStringValueTest.java | 2 +- .../test/DateUtilsParseDateTimeTests.java | 10 +---- .../utils/test/DateUtilsParseTimeTests.java | 10 +---- .../org/javarosa/test/utils/SystemHelper.java | 37 +++++++++++++++++++ 6 files changed, 47 insertions(+), 34 deletions(-) create mode 100644 src/test/java/org/javarosa/test/utils/SystemHelper.java diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsFormatLocalizationTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsFormatLocalizationTests.java index f034b5de8..e6cacc908 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsFormatLocalizationTests.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsFormatLocalizationTests.java @@ -18,15 +18,15 @@ import static java.time.DayOfWeek.SUNDAY; import static java.time.Month.JANUARY; +import static java.time.format.TextStyle.SHORT; import static org.hamcrest.Matchers.is; +import static org.javarosa.test.utils.SystemHelper.withLocale; import static org.junit.Assert.assertThat; import java.time.LocalDateTime; import java.time.OffsetDateTime; -import java.time.format.TextStyle; import java.util.Date; import java.util.Locale; -import java.util.function.Consumer; import java.util.stream.Stream; import org.javarosa.core.model.utils.DateUtils; import org.junit.Test; @@ -43,15 +43,9 @@ public void format_is_localized() { Locale.forLanguageTag("es-ES"), Locale.FRENCH ).forEach(locale -> withLocale(locale, l -> { - assertThat(DateUtils.format(date, "%b"), is(JANUARY.getDisplayName(TextStyle.SHORT, l))); - assertThat(DateUtils.format(date, "%a"), is(SUNDAY.getDisplayName(TextStyle.SHORT, l))); + assertThat(DateUtils.format(date, "%b"), is(JANUARY.getDisplayName(SHORT, l))); + assertThat(DateUtils.format(date, "%a"), is(SUNDAY.getDisplayName(SHORT, l))); })); } - public void withLocale(Locale locale, Consumer block) { - Locale backupLocale = Locale.getDefault(); - Locale.setDefault(locale); - block.accept(locale); - Locale.setDefault(backupLocale); - } } diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsFormatSanityCheckTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsFormatSanityCheckTests.java index 7daab6666..29d93b530 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsFormatSanityCheckTests.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsFormatSanityCheckTests.java @@ -21,6 +21,7 @@ import static org.javarosa.core.model.utils.DateUtils.FORMAT_ISO8601; import static org.javarosa.core.model.utils.DateUtils.formatDateTime; import static org.javarosa.core.model.utils.DateUtils.parseDateTime; +import static org.javarosa.test.utils.SystemHelper.withTimeZone; import static org.junit.Assert.assertThat; import java.util.Arrays; @@ -57,11 +58,4 @@ public void sanity_check_iso_format_and_parse_back() { ).forEach(timeZone -> withTimeZone(timeZone, () -> assertThat(parseDateTime(formatDateTime(input, FORMAT_ISO8601)), is(input)))); } - - private void withTimeZone(TimeZone timeZone, Runnable block) { - TimeZone backupZone = TimeZone.getDefault(); - TimeZone.setDefault(timeZone); - block.run(); - TimeZone.setDefault(backupZone); - } } diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsGetXmlStringValueTest.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsGetXmlStringValueTest.java index 94f8df794..baf1adbbd 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsGetXmlStringValueTest.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsGetXmlStringValueTest.java @@ -34,7 +34,7 @@ public class DateUtilsGetXmlStringValueTest { * parsed by LocalDate.parse() */ @Test - public void testGetXMLStringValueFormat() { + public void xml_string_is_well_formatted() { LocalDateTime nowDateTime = LocalDateTime.now(); Date nowDate = Date.from(nowDateTime.toInstant(OffsetDateTime.now().getOffset())); String nowXmlFormatterDate = getXMLStringValue(nowDate); diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsParseDateTimeTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsParseDateTimeTests.java index a3bb12eac..b6bd0c179 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsParseDateTimeTests.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsParseDateTimeTests.java @@ -18,6 +18,7 @@ import static java.util.TimeZone.getTimeZone; import static org.hamcrest.Matchers.is; +import static org.javarosa.test.utils.SystemHelper.withTimeZone; import static org.junit.Assert.assertThat; import java.time.Instant; @@ -57,7 +58,7 @@ public static Collection data() { } @Test - public void testTimeParses() { + public void parseDateTime_produces_expected_results_in_all_time_zones() { Stream.of( TimeZone.getDefault(), getTimeZone("UTC"), @@ -67,13 +68,6 @@ public void testTimeParses() { ).forEach(tz -> withTimeZone(tz, () -> assertThat(parseDateTime(input), is(expectedDateTime)))); } - private void withTimeZone(TimeZone timeZone, Runnable block) { - TimeZone backupZone = TimeZone.getDefault(); - TimeZone.setDefault(timeZone); - block.run(); - TimeZone.setDefault(backupZone); - } - /** * Returns a LocalDateTime or an OffsetTimeTime obtained from the result of * calling DateUtils.parseDateTime() with the provided input, ensuring that diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsParseTimeTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsParseTimeTests.java index 3004b338b..097dbf48f 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsParseTimeTests.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsParseTimeTests.java @@ -18,6 +18,7 @@ import static java.util.TimeZone.getTimeZone; import static org.hamcrest.Matchers.is; +import static org.javarosa.test.utils.SystemHelper.withTimeZone; import static org.junit.Assert.assertThat; import java.time.Instant; @@ -58,7 +59,7 @@ public static Collection data() { } @Test - public void testTimeParses() { + public void parseTime_produces_expected_results_in_all_time_zones() { // The tricky part of DateUtils.parseTime is that we allow for input time // values to include time offset declarations, which has issues at different // levels: @@ -86,13 +87,6 @@ public void testTimeParses() { ).forEach(tz -> withTimeZone(tz, () -> assertThat(parseTime(input), is(expectedTime)))); } - private void withTimeZone(TimeZone timeZone, Runnable block) { - TimeZone backupZone = TimeZone.getDefault(); - TimeZone.setDefault(timeZone); - block.run(); - TimeZone.setDefault(backupZone); - } - /** * Returns a LocalTime or a OffsetTime obtained from the result of * calling DateUtils.parseTime() with the provided input. diff --git a/src/test/java/org/javarosa/test/utils/SystemHelper.java b/src/test/java/org/javarosa/test/utils/SystemHelper.java new file mode 100644 index 000000000..3640b6f73 --- /dev/null +++ b/src/test/java/org/javarosa/test/utils/SystemHelper.java @@ -0,0 +1,37 @@ +/* + * Copyright 2020 Nafundi + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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.javarosa.test.utils; + +import java.util.Locale; +import java.util.TimeZone; +import java.util.function.Consumer; + +public class SystemHelper { + public static void withTimeZone(TimeZone timeZone, Runnable block) { + TimeZone backupZone = TimeZone.getDefault(); + TimeZone.setDefault(timeZone); + block.run(); + TimeZone.setDefault(backupZone); + } + + public static void withLocale(Locale locale, Consumer block) { + Locale backupLocale = Locale.getDefault(); + Locale.setDefault(locale); + block.accept(locale); + Locale.setDefault(backupLocale); + } +} From 362a6641049a5a9030029631e67165637e7a5571 Mon Sep 17 00:00:00 2001 From: Guille Date: Thu, 30 Jan 2020 12:57:34 +0100 Subject: [PATCH 31/31] Migrate test suite to use SystemHelper withLocale and withTimeZone For conformity --- .../data/test/TimeDataLimitationsTest.java | 123 ++++++++++-------- .../model/utils/test/DateUtilsSCTOTests.java | 83 +++++------- .../org/javarosa/test/utils/SystemHelper.java | 19 ++- .../org/javarosa/xpath/expr/ToDateTest.java | 40 ++---- .../javarosa/xpath/test/XPathEvalTest.java | 61 ++++----- 5 files changed, 151 insertions(+), 175 deletions(-) diff --git a/src/test/java/org/javarosa/core/model/data/test/TimeDataLimitationsTest.java b/src/test/java/org/javarosa/core/model/data/test/TimeDataLimitationsTest.java index 2bd0b14ac..9b4b51969 100644 --- a/src/test/java/org/javarosa/core/model/data/test/TimeDataLimitationsTest.java +++ b/src/test/java/org/javarosa/core/model/data/test/TimeDataLimitationsTest.java @@ -1,63 +1,54 @@ package org.javarosa.core.model.data.test; +import static org.javarosa.test.utils.SystemHelper.withTimeZone; +import static org.junit.Assert.assertEquals; + +import java.util.Date; +import java.util.TimeZone; import org.javarosa.core.model.data.TimeData; import org.javarosa.core.model.utils.DateUtils; -import org.junit.After; -import org.junit.Before; import org.junit.Test; -import java.util.Date; -import java.util.TimeZone; - -import static org.junit.Assert.assertEquals; /** - This test is intended to show the limitation of {@link TimeData}. - - Using this data type in countries which change their time (summer/winter - DST) will cause that forms saved during - wintertime and then edited during summertime (and vice versa) will be treated as saved in a neighbor's timezone. - It's because we have just time and time offset like: 10:00:00.000+02:00 but we don't know when the form has been - saved so we parse it using the current date. - - Example: - If we saved 10:00:00.000+02:00 during summertime (in Poland) and we are editing the form during winter time our - timezone is +01:00 not +02:00. As mentioned above javarosa doesn't know that the form has been saved in the same location - but different timezone because of DST so it treats the value like saved in the neighbor timezone - (in Kiev or London for example). - - Related issues: - https://github.com/opendatakit/javarosa/pull/478 - https://github.com/opendatakit/collect/issues/170 + * This test is intended to show the limitation of {@link TimeData}. + *

+ * Using this data type in countries which change their time (summer/winter - DST) will cause that forms saved during + * wintertime and then edited during summertime (and vice versa) will be treated as saved in a neighbor's timezone. + * It's because we have just time and time offset like: 10:00:00.000+02:00 but we don't know when the form has been + * saved so we parse it using the current date. + *

+ * Example: + * If we saved 10:00:00.000+02:00 during summertime (in Poland) and we are editing the form during winter time our + * timezone is +01:00 not +02:00. As mentioned above javarosa doesn't know that the form has been saved in the same location + * but different timezone because of DST so it treats the value like saved in the neighbor timezone + * (in Kiev or London for example). + *

+ * Related issues: + * https://github.com/opendatakit/javarosa/pull/478 + * https://github.com/opendatakit/collect/issues/170 */ public class TimeDataLimitationsTest { - - private TimeZone backupTimeZone; - - @Before - public void setUp() { - backupTimeZone = TimeZone.getDefault(); - } - - @After - public void tearDown() { - TimeZone.setDefault(backupTimeZone); - } + public static final TimeZone WARSAW = TimeZone.getTimeZone("Europe/Warsaw"); + public static final TimeZone KIEV = TimeZone.getTimeZone("Europe/Kiev"); @Test public void editingFormsSavedInDifferentTimezoneTest() { + StringWrapper savedTime = StringWrapper.empty(); // A user is in Warsaw (GMT+2) saved a form with the time question - TimeZone.setDefault(TimeZone.getTimeZone("Europe/Warsaw")); - - boolean isSummerTime = TimeZone.getDefault().inDaylightTime(new Date()); - String savedTime = isSummerTime ? "10:00:00.000+02:00" : "10:00:00.000+01:00"; - - // A user opens saved form in Warsaw as well - the hour should be the same - TimeData timeData = new TimeData(DateUtils.parseTime(savedTime)); - assertEquals("10:00", timeData.getDisplayText()); - + withTimeZone(WARSAW, () -> { + boolean isSummerTime = TimeZone.getDefault().inDaylightTime(new Date()); + savedTime.set(isSummerTime ? "10:00:00.000+02:00" : "10:00:00.000+01:00"); + + // A user opens saved form in Warsaw as well - the hour should be the same + TimeData timeData = new TimeData(DateUtils.parseTime(savedTime.get())); + assertEquals("10:00", timeData.getDisplayText()); + }); // A user travels to Kiev (GMT+3) and opens the saved form again - the hour should be edited +1h - TimeZone.setDefault(TimeZone.getTimeZone("Europe/Kiev")); - timeData = new TimeData(DateUtils.parseTime(savedTime)); - assertEquals("11:00", timeData.getDisplayText()); + withTimeZone(KIEV, () -> { + TimeData timeData = new TimeData(DateUtils.parseTime(savedTime.get())); + assertEquals("11:00", timeData.getDisplayText()); + }); + } @Test @@ -67,17 +58,37 @@ public void editingFormsSavedInTheSameLocationButAfterDSTChangeTest() { dateFields.month = 8; dateFields.day = 1; - // A user is in Warsaw (during summer time - GMT+2) and saved a form with the time question - TimeZone.setDefault(TimeZone.getTimeZone("Europe/Warsaw")); - String savedTime = "10:00:00.000+02:00"; + withTimeZone(WARSAW, () -> { + String savedTime = "10:00:00.000+02:00"; + + // A user opens saved form in Warsaw and during summertime as well - the hour should be the same + TimeData timeData = new TimeData(DateUtils.parseTimeWithFixedDate(savedTime, dateFields)); + assertEquals("10:00", timeData.getDisplayText()); + + // A user opens saved form in Warsaw as well but during wintertime - the hour is edited -1h (the mentioned limitation) + dateFields.month = 12; + timeData = new TimeData(DateUtils.parseTimeWithFixedDate(savedTime, dateFields)); + assertEquals("09:00", timeData.getDisplayText()); + }); + } + + static class StringWrapper { + private String value; + + StringWrapper(String value) { + this.value = value; + } + + static StringWrapper empty() { + return new StringWrapper(""); + } - // A user opens saved form in Warsaw and during summertime as well - the hour should be the same - TimeData timeData = new TimeData(DateUtils.parseTimeWithFixedDate(savedTime, dateFields)); - assertEquals("10:00", timeData.getDisplayText()); + public String get() { + return value; + } - // A user opens saved form in Warsaw as well but during wintertime - the hour is edited -1h (the mentioned limitation) - dateFields.month = 12; - timeData = new TimeData(DateUtils.parseTimeWithFixedDate(savedTime, dateFields)); - assertEquals("09:00", timeData.getDisplayText()); + public void set(String value) { + this.value = value; + } } } diff --git a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsSCTOTests.java b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsSCTOTests.java index f04620ac8..eae831008 100644 --- a/src/test/java/org/javarosa/core/model/utils/test/DateUtilsSCTOTests.java +++ b/src/test/java/org/javarosa/core/model/utils/test/DateUtilsSCTOTests.java @@ -6,67 +6,50 @@ package org.javarosa.core.model.utils.test; +import static org.javarosa.test.utils.SystemHelper.withTimeZone; import static org.junit.Assert.assertEquals; import java.util.Calendar; import java.util.Date; -import java.util.Locale; import java.util.SimpleTimeZone; import java.util.TimeZone; import org.javarosa.core.model.utils.DateUtils; -import org.junit.After; -import org.junit.Before; import org.junit.Ignore; import org.junit.Test; public class DateUtilsSCTOTests { - private Locale backupLocale; - private TimeZone backupZone; - - @Before - public void setUp() { - backupLocale = Locale.getDefault(); - backupZone = TimeZone.getDefault(); - } - - @After - public void tearDown() { - TimeZone.setDefault(backupZone); - Locale.setDefault(backupLocale); - } - @Test public void testParseDateTime() { - TimeZone.setDefault(TimeZone.getTimeZone("GMT+02")); + withTimeZone(TimeZone.getTimeZone("GMT+02"), () -> { + Date date = DateUtils.parseDateTime("2014-10-05T00:03:05.244+03"); + String str = DateUtils.formatDateTime(date, DateUtils.FORMAT_ISO8601); - Date date = DateUtils.parseDateTime("2014-10-05T00:03:05.244+03"); - String str = DateUtils.formatDateTime(date, DateUtils.FORMAT_ISO8601); - - assertEquals("2014-10-04T23:03:05.244+02:00", str); + assertEquals("2014-10-04T23:03:05.244+02:00", str); + }); } @Test public void testParseDateTime_withDST() { - applyDST(); - - Date date = DateUtils.parseDateTime("2014-10-05T00:03:05.244+03"); - String str = DateUtils.formatDateTime(date, DateUtils.FORMAT_ISO8601); + withTimeZone(buildDstTimeZone(), () -> { + Date date = DateUtils.parseDateTime("2014-10-05T00:03:05.244+03"); + String str = DateUtils.formatDateTime(date, DateUtils.FORMAT_ISO8601); - assertEquals("2014-10-05T00:03:05.244+03:00", str); + assertEquals("2014-10-05T00:03:05.244+03:00", str); + }); } @Test public void testParseTime() { - TimeZone.setDefault(TimeZone.getTimeZone("GMT+02")); + withTimeZone(TimeZone.getTimeZone("GMT+02"), () -> { + String time = "12:03:05.011+03"; - String time = "12:03:05.011+03"; + Date date = DateUtils.parseTime(time); - Date date = DateUtils.parseTime(time); + String formatted = DateUtils.formatTime(date, DateUtils.FORMAT_ISO8601); - String formatted = DateUtils.formatTime(date, DateUtils.FORMAT_ISO8601); - - assertEquals("11:03:05.011+02:00", formatted); + assertEquals("11:03:05.011+02:00", formatted); + }); } @Test @@ -77,27 +60,25 @@ public void testParseTime() { // - We're effectively binding all times to the EPOCH date // (1970-01-01, UTC), which has no DST public void testParseTime_withDST() { - applyDST(); - - String time = "12:03:05.011+03"; + withTimeZone(buildDstTimeZone(), () -> { + String time = "12:03:05.011+03"; - Date date = DateUtils.parseTime(time); + Date date = DateUtils.parseTime(time); - String formatted = DateUtils.formatTime(date, DateUtils.FORMAT_ISO8601); + String formatted = DateUtils.formatTime(date, DateUtils.FORMAT_ISO8601); - assertEquals("12:03:05.011+03", formatted); + assertEquals("12:03:05.011+03", formatted); + }); } - private void applyDST() { - // this is a timezone that operates DST every day of the year! - SimpleTimeZone dstTimezone = new SimpleTimeZone( - 2 * 60 * 60 * 1000, - "Europe/Athens", - Calendar.JANUARY, 1, 0, - 0, SimpleTimeZone.UTC_TIME, - Calendar.DECEMBER, 31, 0, - 24 * 60 * 60 * 1000, SimpleTimeZone.UTC_TIME, - 60 * 60 * 1000); - TimeZone.setDefault(dstTimezone); + private SimpleTimeZone buildDstTimeZone() { + return new SimpleTimeZone( + 2 * 60 * 60 * 1000, + "Europe/Athens", + Calendar.JANUARY, 1, 0, + 0, SimpleTimeZone.UTC_TIME, + Calendar.DECEMBER, 31, 0, + 24 * 60 * 60 * 1000, SimpleTimeZone.UTC_TIME, + 60 * 60 * 1000); } } diff --git a/src/test/java/org/javarosa/test/utils/SystemHelper.java b/src/test/java/org/javarosa/test/utils/SystemHelper.java index 3640b6f73..c10588925 100644 --- a/src/test/java/org/javarosa/test/utils/SystemHelper.java +++ b/src/test/java/org/javarosa/test/utils/SystemHelper.java @@ -18,20 +18,37 @@ import java.util.Locale; import java.util.TimeZone; +import java.util.function.BiConsumer; import java.util.function.Consumer; public class SystemHelper { public static void withTimeZone(TimeZone timeZone, Runnable block) { + withTimeZone(timeZone, __ -> block.run()); + } + + public static void withTimeZone(TimeZone timeZone, Consumer block) { TimeZone backupZone = TimeZone.getDefault(); TimeZone.setDefault(timeZone); - block.run(); + block.accept(timeZone); TimeZone.setDefault(backupZone); } + public static void withLocale(Locale locale, Runnable block) { + withLocale(locale, __ -> block.run()); + } + public static void withLocale(Locale locale, Consumer block) { Locale backupLocale = Locale.getDefault(); Locale.setDefault(locale); block.accept(locale); Locale.setDefault(backupLocale); } + + public static void withLocaleAndTimeZone(Locale locale, TimeZone timeZone, Runnable block) { + withLocaleAndTimeZone(locale, timeZone, (__, ___) -> block.run()); + } + + public static void withLocaleAndTimeZone(Locale locale, TimeZone timeZone, BiConsumer block) { + withLocale(locale, l -> withTimeZone(timeZone, () -> block.accept(l, timeZone))); + } } diff --git a/src/test/java/org/javarosa/xpath/expr/ToDateTest.java b/src/test/java/org/javarosa/xpath/expr/ToDateTest.java index 47d9f3a9d..bbc79827a 100644 --- a/src/test/java/org/javarosa/xpath/expr/ToDateTest.java +++ b/src/test/java/org/javarosa/xpath/expr/ToDateTest.java @@ -2,25 +2,23 @@ import static java.lang.Double.NEGATIVE_INFINITY; import static java.lang.Double.POSITIVE_INFINITY; +import static org.javarosa.test.utils.SystemHelper.withTimeZone; import static org.javarosa.xpath.expr.XPathFuncExpr.toDate; import static org.junit.Assert.assertEquals; +import java.time.ZoneId; import java.util.Date; import java.util.TimeZone; -import java.util.function.Consumer; import org.javarosa.xpath.XPathTypeMismatchException; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.LocalDateTime; -import org.junit.AfterClass; -import org.junit.BeforeClass; import org.junit.Test; +// TODO Migrate to java.time for conformity public class ToDateTest { private static final DateTime EPOCH = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeZone.UTC); - - public static final TimeZone TIME_ZONE = TimeZone.getTimeZone(DateTimeZone.UTC.getID()); - private static TimeZone backupTimeZone; + public static final TimeZone PST = TimeZone.getTimeZone(ZoneId.of("America/Los_Angeles")); private static Date date(int year, int month, int day) { return new LocalDateTime(year, month, day, 0, 0, 0, 0).toDate(); @@ -30,18 +28,6 @@ private static Date date(int year, int month, int day, int hour, int minute, int return new LocalDateTime(year, month, day, hour, minute, second, milli).toDate(); } - @BeforeClass - public static void forceUTCOffset() { - // All the tests run on the UTC offset by default. - backupTimeZone = TimeZone.getDefault(); - TimeZone.setDefault(TIME_ZONE); - } - - @AfterClass - public static void restoreTimeZone() { - TimeZone.setDefault(backupTimeZone); - } - @Test public void convertsISO8601DatesWithoutPreservingTime() { assertEquals( @@ -68,17 +54,15 @@ public void convertsISO8601DatesWithOffsetPreservingTime() { @Test public void convertsTimestampsWithoutPreservingTime() { - assertEquals( + withTimeZone(TimeZone.getTimeZone("Z"), () -> assertEquals( EPOCH.withZone(DateTimeZone.getDefault()).plusDays(365).toDate(), toDate(365d, false) - ); + )); } @Test public void convertsTimestampsWithoutPreservingTimeOnLocalTimeZone() { - runOnTimeZone( - DateTimeZone.forID("America/Los_Angeles"), // a.k.a. PST (joda doesn't like the short ids) - zoneId -> { + withTimeZone(PST, tz -> { DateTime utcEpoch = EPOCH.withZone(DateTimeZone.UTC); // 1970-01-01T00:00:00 UTC DateTime properExpectedDate = utcEpoch.plusDays(365); // 1971-01-01T00:00:00 UTC // Explanation for all this: @@ -92,7 +76,7 @@ public void convertsTimestampsWithoutPreservingTimeOnLocalTimeZone() { // the same local datetime in our target zone. // 1971-01-01T00:00:00 PST (or 1971-01-01T06:00:00 UTC which is the EPOCH plus // 365 days *and 6 hours*, but will make our code work) - DateTime expectedDate = properExpectedDate.withZoneRetainFields(zoneId); + DateTime expectedDate = properExpectedDate.withZoneRetainFields(DateTimeZone.forTimeZone(tz)); assertEquals( expectedDate.toDate(), toDate(365d, false) @@ -169,10 +153,4 @@ public void anyOtherTypeThrows() { toDate(2L, false); } - private void runOnTimeZone(DateTimeZone zoneId, Consumer block) { - TimeZone backup = TimeZone.getDefault(); - TimeZone.setDefault(TimeZone.getTimeZone(zoneId.getID())); - block.accept(zoneId); - TimeZone.setDefault(backup); - } -} \ No newline at end of file +} diff --git a/src/test/java/org/javarosa/xpath/test/XPathEvalTest.java b/src/test/java/org/javarosa/xpath/test/XPathEvalTest.java index 5bb6eda73..dd0b3e89c 100644 --- a/src/test/java/org/javarosa/xpath/test/XPathEvalTest.java +++ b/src/test/java/org/javarosa/xpath/test/XPathEvalTest.java @@ -24,6 +24,7 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.is; +import static org.javarosa.test.utils.SystemHelper.withLocaleAndTimeZone; import static org.javarosa.xpath.test.IFunctionHandlerHelpers.HANDLER_ADD; import static org.javarosa.xpath.test.IFunctionHandlerHelpers.HANDLER_CHECK_TYPES; import static org.javarosa.xpath.test.IFunctionHandlerHelpers.HANDLER_CONCAT; @@ -61,7 +62,6 @@ import org.javarosa.xpath.XPathUnsupportedException; import org.javarosa.xpath.expr.XPathExpression; import org.javarosa.xpath.parser.XPathSyntaxException; -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -69,11 +69,10 @@ @RunWith(Parameterized.class) public class XPathEvalTest { + public static final TimeZone GMT = TimeZone.getTimeZone("GMT"); @Parameterized.Parameter(value = 0) public Locale locale; - private Locale backupLocale; - private TimeZone backupTimeZone; private EvaluationContext ec; @Parameterized.Parameters(name = "Locale {0}") @@ -86,21 +85,9 @@ public static Iterable testParametersProvider() { @Before public void setUp() { - backupLocale = Locale.getDefault(); - Locale.setDefault(locale); - backupTimeZone = TimeZone.getDefault(); - TimeZone.setDefault(TimeZone.getTimeZone("GMT")); ec = new EvaluationContext(null); } - @After - public void tearDown() { - Locale.setDefault(backupLocale); - backupLocale = null; - TimeZone.setDefault(backupTimeZone); - backupTimeZone = null; - } - @Test public void counting_functions() { testEval("count(/data/path)", buildInstance(), null, 5.0); @@ -299,27 +286,29 @@ should be tested (particularly DST changes), but it's just too hard and dates cannot reliably be compared/used across time zones (an issue with the code) any time-of-day or DST should be ignored when comparing/using a date (an issue with testing) */ - ec.addFunctionHandler(HANDLER_CONVERTIBLE); - testEval("date('2000-01-01')", DateUtils.getDate(2000, 1, 1)); - testEval("date('1945-04-26')", DateUtils.getDate(1945, 4, 26)); - testEval("date('1996-02-29')", DateUtils.getDate(1996, 2, 29)); - testEval("date('1983-09-31')", new XPathTypeMismatchException()); - testEval("date('not a date')", new XPathTypeMismatchException()); - testEval("date(0)", DateUtils.getDate(1970, 1, 1)); - testEval("date(6.5)", DateUtils.getDate(1970, 1, 7)); - testEval("date(1)", DateUtils.getDate(1970, 1, 2)); - testEval("date(-1)", DateUtils.getDate(1969, 12, 31)); - testEval("date(14127)", DateUtils.getDate(2008, 9, 5)); - testEval("date(-10252)", DateUtils.getDate(1941, 12, 7)); - testEval("date(date('1989-11-09'))", DateUtils.getDate(1989, 11, 9)); - testEval("date(true())", new XPathTypeMismatchException()); - testEval("date(convertible())", null, ec, new XPathTypeMismatchException()); - testEval("format-date('2018-01-02T10:20:30.123', \"%Y-%m-%e %H:%M:%S\")", "2018-01-2 10:20:30"); - testEval("date-time('2000-01-01T10:20:30.000')", DateUtils.getDateTimeFromString("2000-01-01T10:20:30.000")); - testEval("decimal-date-time('2000-01-01T10:20:30.000')", 10957.430902777778); - testEval("decimal-time('2000-01-01T10:20:30.000+03:00')", .30590277777810115); - testEval("decimal-date-time('-1000')", new XPathTypeMismatchException()); - testEval("decimal-date-time('-01-2019')", new XPathTypeMismatchException()); + withLocaleAndTimeZone(locale, GMT, () -> { + ec.addFunctionHandler(HANDLER_CONVERTIBLE); + testEval("date('2000-01-01')", DateUtils.getDate(2000, 1, 1)); + testEval("date('1945-04-26')", DateUtils.getDate(1945, 4, 26)); + testEval("date('1996-02-29')", DateUtils.getDate(1996, 2, 29)); + testEval("date('1983-09-31')", new XPathTypeMismatchException()); + testEval("date('not a date')", new XPathTypeMismatchException()); + testEval("date(0)", DateUtils.getDate(1970, 1, 1)); + testEval("date(6.5)", DateUtils.getDate(1970, 1, 7)); + testEval("date(1)", DateUtils.getDate(1970, 1, 2)); + testEval("date(-1)", DateUtils.getDate(1969, 12, 31)); + testEval("date(14127)", DateUtils.getDate(2008, 9, 5)); + testEval("date(-10252)", DateUtils.getDate(1941, 12, 7)); + testEval("date(date('1989-11-09'))", DateUtils.getDate(1989, 11, 9)); + testEval("date(true())", new XPathTypeMismatchException()); + testEval("date(convertible())", null, ec, new XPathTypeMismatchException()); + testEval("format-date('2018-01-02T10:20:30.123', \"%Y-%m-%e %H:%M:%S\")", "2018-01-2 10:20:30"); + testEval("date-time('2000-01-01T10:20:30.000')", DateUtils.getDateTimeFromString("2000-01-01T10:20:30.000")); + testEval("decimal-date-time('2000-01-01T10:20:30.000')", 10957.430902777778); + testEval("decimal-time('2000-01-01T10:20:30.000+03:00')", .30590277777810115); + testEval("decimal-date-time('-1000')", new XPathTypeMismatchException()); + testEval("decimal-date-time('-01-2019')", new XPathTypeMismatchException()); + }); } @Test