2

I want to test a method in a Date utility class that gets a Date from a String. The String that is passed is 1980-03-26T00:00:00.000+0200 and I want to compare the resulting date with assertEquals. The test failed with this output:

org.junit.ComparisonFailure: 
Expected :Wed Mar 26 00:00:00 PST 1980
Actual   :Wed Mar 26 00:00:00 SGT 1980

Here's my test:

INITIAL_DATA_DATE_FROM_STRING = "1980-03-26T00:00:00.000+0200";
EXPECTED_DATA_DATE_FROM_STRING = "Wed Mar 26 00:00:00 PST 1980";

// inside the method ...

date = DateUtils.getDateFromString(INITIAL_DATA_DATE_FROM_STRING);
assertEquals(EXPECTED_DATA_DATE_FROM_STRING, String.valueOf(date));

Here's the method that I am testing:

    public static Date getDateFromString(String dateAsString) {
        return getDateFromString(dateAsString, "dd/MM/yyyy");
    }

    public static Date getDateFromString(String dateAsString, String dateFormat) {
        Date formattedDate = null;
        if (StringUtils.notNull(dateAsString)) {
            DateFormat format = new SimpleDateFormat(dateFormat);
            try {
                formattedDate = parseString(dateAsString, format);
            } catch (ParseException e) {
                try {
                    formattedDate = parseString(dateAsString, new SimpleDateFormat("yyyy-MM-dd"));
                } catch (ParseException e1) {
                    // handle exception code
                }
            }
        }
        return formattedDate;
    }

So the unit test is not timezone independent. Is there anyway to set the default timezone just for unit testing?

Fawwaz Yusran
  • 1,260
  • 2
  • 19
  • 36
  • 2
    Locale and time zone are separate concepts. Do you mean locale? or do you mean time zone? – Matt Johnson-Pint Dec 16 '18 at 20:11
  • 1
    Also, it would help if you gave a [minimal, complete, and verifiable example](https://stackoverflow.com/help/mcve). – Matt Johnson-Pint Dec 16 '18 at 20:13
  • 1
    I recommend you avoid the `SimpleDateFormat` class. It is not only long outdated, it is also notoriously troublesome. Today we have so much better in [`java.time`, the modern Java date and time API](https://docs.oracle.com/javase/tutorial/datetime/). – Ole V.V. Dec 17 '18 at 10:57
  • 1
    Which PST are you using (expecting) and how can you expect it to agree with a UTC offset of +02:00? I know Philippine Standard Time, Pacific Standard Time and Pitcairn Standard Time, but none of those agrees. I suspect that your unit test may be correct in stating that your method returns the wrong result. – Ole V.V. Dec 17 '18 at 11:17
  • @MattJohnson I mean setting a timezone for the testing environment, so the result of the format is the same in any machine. – Fawwaz Yusran Dec 17 '18 at 11:43
  • @OleV.V. Thanks! I will definitely take a look, as I wasn't aware that SimpleDateFormat is outdated. I am expecting the Pacific Standard Time in this case, although the important thing is to set 1 timezone just for unit testing. – Fawwaz Yusran Dec 17 '18 at 11:48

1 Answers1

5

java.time or Joda-Time

The Date and SimpleDateFormat classes have design problems, so consider not using them. The first version of your question was tagged jodatime, and if you are using Joda-Time in your project, that’s already a sizable improvement. Joda-Time gives you all the functionality you need, so your method should probably return an approrpiate type from Joda-Time rather than an old-fashioned Date.

Joda-Time is also on its way to retirement, though, and its successor is java.time, the modern Java date and time API. So the latter is an option you may consider no matter if you were already using Joda-Time or not. Since your string contains a UTC offset, one option is to return an OffsetDateTime. To test a method that does this:

    assertEquals(OffsetDateTime.of(1980, 3, 26, 0, 0, 0, 0, ZoneOffset.ofHours(2)),
            DateUtils.getOffsetDateTimeFromString("1980-03-26T00:00:00.000+0200"));

Your example and your code may give the impression that you are only after the date from the string and don’t care about the time of day nor the offset. If this is correct, your method may return a LocalDate and be tested in this way:

    assertEquals(LocalDate.of(1980, Month.MARCH, 26),
            DateUtils.getLocalDateFromString("1980-03-26T00:00:00.000+0200"));

The latter will also free you from all time zone considerations. In both cases please note that I am passing date-time objects to assertEquals, not strings.

Your unit test is correct: your method is returning the wrong time

The failure you reported in your question was that while the expected Date from your method was Wed Mar 26 00:00:00 PST 1980, the actual value was Wed Mar 26 00:00:00 SGT 1980. It is correct that these differ. Midnight in Baja California, Yukon, Washington, Nevada, California and other places observing Pacific Time is not the same point in time as midnight in China.

Can I use java.time on Android?

Yes, java.time works nicely on older and newer Android devices. It just requires at least Java 6.

  • In Java 8 and later and on newer Android devices (from API level 26) the modern API comes built-in.
  • In Java 6 and 7 get the ThreeTen Backport, the backport of the new classes (ThreeTen for JSR 310; see the links at the bottom).
  • On (older) Android use the Android edition of ThreeTen Backport. It’s called ThreeTenABP. And make sure you import the date and time classes from org.threeten.bp with subpackages.

In case you don’t want your Android code to depend on a third-party library, you can still use java.time in your unit test. To test a method that returns an old-fashioned Date:

    Instant expectedInstant = LocalDate.of(1980, Month.MARCH, 26)
            .atStartOfDay(ZoneId.systemDefault())
            .toInstant();
    Date expectedDate = Date.from(expectedInstant);
    Date actualDate = DateUtils.getDateFromString("1980-03-26T00:00:00.000+0200");
    assertEquals(expectedDate, actualDate);

If using the backport (ThreeTen Backport and/or ThreeTenABP), the conversion from Instantot Date happens a little differently:

    Date expectedDate = DateTimeUtil.toDate(expectedInstant);

Again note that I am comparing Date objects, not strings.

How to set the timezone for a SimpleDateFormat?

To answer the question in your title: use the setTimeZone method:

    DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXX");
    df.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles")); // Pacific Time
    System.out.println(df.parseObject("1980-03-26T00:00:00.000+0200"));

In this particular case it won’t make any difference, though, because the offset in the string is parsed and takes precedence over the time zone of the formatter.

Is there any way to set the default timezone just for unit testing?

There are a couple of options.

    TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles"));

Putting this into the beforeClass or before method of your unit test will set the time zone of your JVM to Pacific Time. It’s not bullet-proof since other code may set it to something else before the test finishes, but you may be able to control that that doesn’t happen. Normally I would discourage the use of the outdated TimeZone class. It too has design problems, but is the natural outdated choice if the methods you are testing are using the outdated SimpleDateFormat. One of the problems is it doesn’t report if the string passed is invalid. To obtain proper validation just go through the modern class:

    TimeZone.setDefault(TimeZone.getTimeZone(ZoneId.of("America/Los_Angeles")));

Or using the backport:

    TimeZone.setDefault(DateTimeUtils.toTimeZone(ZoneId.of("America/Los_Angeles")));

Links

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161