Java8 time library does not interpret BST timezone correctly

Viewed 799

I am in the process of migrating my code to use the java.time package but I found that DateTimeFormatter does not interpret the time zone "BST" (British Summer Time) correctly.

Instead of making it UTC+0100, it converted it to Pacific/Bougainville timezone.

Does anybody know how I can fix this without going back to the old SimpleDateFormat, or use an explicit timezone? My code needs to run in multiple regions in the world.

This timestamp format is obtained by querying another system so I won't be able to change it. It seems SimpleDateFormat can recognize the timezone properly. My test code is below:

String sTime = "Fri Jun 07 14:07:07 BST 2019";
DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("EEE MMM dd kk:mm:ss z yyyy");
ZonedDateTime zd = ZonedDateTime.parse(sTime, FORMATTER);
System.out.println("The time zone: " + zd.getZone());
FileTime ftReturn = FileTime.from(zd.toEpochSecond(), TimeUnit.SECONDS);
System.out.println("File time is: " + ftReturn);

DateFormat df = new SimpleDateFormat("EEE MMM dd kk:mm:ss z yyyy", Locale.ENGLISH);
Date dtDD = df.parse(sTime);
Calendar calendar = Calendar.getInstance();
calendar.setTime(dtDD);
FileTime ftReturn1 = FileTime.fromMillis(calendar.getTimeInMillis());
System.out.println("File time1 is:  " + ftReturn1);

Test result:
The time zone: Pacific/Bougainville
File time is: 2019-06-07T03:07:07Z
File time1 is: 2019-06-07T13:07:07Z

3 Answers

First, you’re trying to solve a task that cannot be solved. Three letter time zone abbreviations are ambiguous, I think more often than not. So if you solve the issue for Europe/London, you will have it again when you meet EST, IST, CST, CDT, PST, WST, and so on and so forth. Or when you meet a string where BST was intended to mean Brazil Summer Time, Bangladesh Standard Time or Bougainville Standard Time. Still more interpretations exist. Instead get an unambiguous string like one with a UTC offset rather than a time zone abbreviation, best a string in ISO 8601 format like 2019-06-07T14:07:07+01:00.

But if you’re sure that BST will always mean British Summer Time in your world, the short-sighted solution may be to tell the DateTimeFormatter which time zone/s you prefer:

    String sTime = "Fri Jun 07 14:07:07 BST 2019";
    ZoneId preferredZone = ZoneId.of("Europe/London");
    DateTimeFormatter FORMATTER = new DateTimeFormatterBuilder()
            .appendPattern("EEE MMM dd HH:mm:ss ")
            .appendZoneText(TextStyle.SHORT, Collections.singleton(preferredZone))
            .appendPattern(" yyyy")
            .toFormatter(Locale.ROOT);
    ZonedDateTime zd = ZonedDateTime.parse(sTime, FORMATTER);
    System.out.println("The time zone: " + zd.getZone());
    FileTime ftReturn = FileTime.from(zd.toEpochSecond(), TimeUnit.SECONDS);
    System.out.println("File time is: " + ftReturn);

Output is:

The time zone: Europe/London
File time is: 2019-06-07T13:07:07Z

Links

BST is ambiguous, as are many such abbreviations for timezones.

This solution is not great, but if you absolutely have to give precedence to BST to mean British Summer Time then you could just check whether the string contains that zone, and if it does, remove the zone and apply it manually.

String s = "Fri Jun 07 14:07:07 BST 2019";
if (s.contains(" BST ")) { // Check could be improved
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("EEE MMM dd kk:mm:ss yyyy");
    s = s.replace(" BST ", " ");
    ZonedDateTime dateTime = LocalDateTime.parse(s, formatter).atZone(ZoneOffset.ofHours(1));
}
else {
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("EEE MMM dd kk:mm:ss z yyyy");
    // Normal parsing
}

In future, consider representing your dates (either on-disk, or across an API) as offset strings instead, for example +01:00

Related