3

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

Joachim Sauer
  • 302,674
  • 57
  • 556
  • 614
Libby Shen
  • 65
  • 5
  • It’s not a duplicate. That other question is about `ZoneId.SHORT_IDS`, while this one is about `DateTimeFormatter`. They don’t interpret BST in the same way, and the way to persuade each to interpret it as British Summer Time is quite different. – Ole V.V. Jun 16 '19 at 19:41
  • Are you sure you want `kk` for hour from 01 through 24 in your format pattern string? `HH` for hour from 00 through 23 would be more conventional. – Ole V.V. Jun 16 '19 at 19:43

3 Answers3

5

According to https://docs.oracle.com/javase/8/docs/api/java/time/ZoneId.html#SHORT_IDS:

BST - Asia/Dhaka

So I guess you should not use that abbreviation.

EDIT: Just found this question, which answers it.

So don't use Bangladesh Standard Time ;) Instead use ZoneId.of("Europe/London")

sfiss
  • 2,119
  • 13
  • 19
  • As I said in the post, I do not get to choose the format. I query a system and this is the format it returned, in the 3 letter timezone abbreviation. I am just wondering what is the most graceful way to handle this without explicitly set the timezone. My code needs to run in multiple time zones across the world. Thanks! – Libby Shen Jun 14 '19 at 17:32
  • @LibbyShen Find and replace "BST" with "Europe/London" before parsing. Inform the owners of the API that their timestamp is ambiguous. – Michael Jun 14 '19 at 17:40
  • Your suggestion to replace "BST" with "Europe/London" before parsing worked. Thanks a lot! – Libby Shen Jun 14 '19 at 17:56
1

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

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

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

Michael
  • 41,989
  • 11
  • 82
  • 128