Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 534 fix date utils test #535

Merged
merged 31 commits into from
Feb 3, 2020
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
c2753c2
Change test offsets to something that will fail in all systems
ggalmazor Jan 30, 2020
cbe9268
Fix the test by refreshing the base value we use to define expectations
ggalmazor Jan 30, 2020
fe64bb2
Use the same assertions for consistency
ggalmazor Jan 30, 2020
6592037
Migrate test to java.time
ggalmazor Jan 30, 2020
0a48fa4
Migrate assertions to something that's easier to reason about
ggalmazor Jan 30, 2020
0d3538e
Move the parseTime test to its own test class
ggalmazor Jan 30, 2020
2252a57
Add default zone to the list and remove unnecessary before/after methods
ggalmazor Jan 30, 2020
72b6797
Migrate DateUtils.parseDateTime tests to use OffsetDateTime.parse
ggalmazor Jan 30, 2020
8e22fa0
Extract the DateUtils.parseDateTime to their own test class
ggalmazor Jan 30, 2020
3d934b7
Add a case without input time zone for completeness
ggalmazor Jan 30, 2020
c3bc3b2
Remove ignored test
ggalmazor Jan 30, 2020
218d603
These members are only used in a test. Move to local
ggalmazor Jan 30, 2020
9ae9636
Simplify by reusing the same test date time
ggalmazor Jan 30, 2020
f06f589
Simplify. XML date strings can be parsed by LocalDate
ggalmazor Jan 30, 2020
7f49465
Remove indirection for easier understanding of the test
ggalmazor Jan 30, 2020
9558475
Simplify by removing repetition
ggalmazor Jan 30, 2020
294f5bf
Extract sanity check to its own test class
ggalmazor Jan 30, 2020
cfbe477
Now the test method's name is more precise and there is no indirection
ggalmazor Jan 30, 2020
5a4ead2
Improve by using human readable dates and java.time parse.
ggalmazor Jan 30, 2020
3df902f
Migrate formatting test to use Locales and values that depend on the JVM
ggalmazor Jan 30, 2020
16f998f
Simplify by leaving only the moving parts
ggalmazor Jan 30, 2020
e8e9dc1
Wrap block execution to ensure it runs in a certain locale safely
ggalmazor Jan 30, 2020
86a2c08
Simplify by requiring less state and making the assertion more explicit
ggalmazor Jan 30, 2020
863da8d
Refactor: move creation of variable to where it's used
ggalmazor Jan 30, 2020
f5e0a0f
Migrate assertions to assertThat for conformity and natural reading
ggalmazor Jan 30, 2020
79c2014
Extract static part from the assertions block
ggalmazor Jan 30, 2020
5f2c10d
Extract test about DateUtils.format localization to its own class
ggalmazor Jan 30, 2020
aae6935
Simplify by replacing the wrapper class with a stream of Locale
ggalmazor Jan 30, 2020
e8343b4
Rename for conformity
ggalmazor Jan 30, 2020
8001c81
Extract method to helper class and reuse
ggalmazor Jan 30, 2020
362a664
Migrate test suite to use SystemHelper withLocale and withTimeZone
ggalmazor Jan 30, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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<Object[]> 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
ggalmazor marked this conversation as resolved.
Show resolved Hide resolved
// 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.
* <p>
* 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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@
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.joda.time.LocalDateTime;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
Expand All @@ -43,8 +44,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
Expand All @@ -64,7 +65,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)));
}

Expand Down Expand Up @@ -121,74 +122,6 @@ private void testDateTime(String in, long test) {
assertEquals("Fail: " + in + "(" + TimeZone.getDefault().getDisplayName() + ")", test, value);
}

@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

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();

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));

TimeZone offsetTwoHours = TimeZone.getTimeZone("GMT+02");

TimeZone.setDefault(offsetTwoHours);

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));

TimeZone offsetMinusTwoHours = TimeZone.getTimeZone("GMT-02");

TimeZone.setDefault(offsetMinusTwoHours);

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));


TimeZone offsetPlusHalf = TimeZone.getTimeZone("GMT+0230");

TimeZone.setDefault(offsetPlusHalf);

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);
}

private void testTime(String in, long test) {
Date d = DateUtils.parseTime(in);

Expand All @@ -197,13 +130,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() {

Expand Down Expand Up @@ -294,8 +220,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.")
};

Expand Down