endryha
endryha

Reputation: 7256

DateTimeFormatter doesn't parse custom date format

I have an issue with java DataTimeFormmater. I feel that I missed something but cannot figure out what exactly.

String format = "yyyy-MM-dd'T'HH:mm:ss[.S]'T'zxxx";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);

String date = "2017-07-05T12:28:36.4TGMT+03:00";

System.out.println(formatter.format(ZonedDateTime.now()));
System.out.println(formatter.parse(date));

The code above produces string of the current ZonedDateTime and attempts to parse date time string with the same date formatter. Result that it successfully produces 2017-07-05T06:07:51.0TCDT-05:00 but fails to parse 2017-07-05T12:28:36.4TGMT+03:00

My goal is to parse 2017-07-05T12:28:36.4TGMT+03:00 and come up with appropriate DateTimeFormatter.

Upvotes: 6

Views: 8776

Answers (2)

user7605325
user7605325

Reputation:

You must change the format to:

String format = "yyyy-MM-dd'T'HH:mm:ss[.S]'T'[zzz][xxx]";

Both [zzz] and [xxx] are in optional sections because zzz can parse either the whole GMT+03:00 part or just the zone short name (such as CDT), and xxx parses only the offset part (such as -05:00 - so it's not needed if a GMT+03:00 is found).

Just reminding that formatter.parse(date) returns a TemporalAccessor object. If you want to create a specific type, it's better to use the class's respective parse method:

System.out.println(ZonedDateTime.parse(date, formatter)); // 2017-07-05T12:28:36.400+03:00[GMT+03:00]

PS: the only problem with this formatter is that, when formatting, it prints all the optional sections. So, if you do something like this:

String date = "2017-07-05T12:28:36.4TGMT+03:00";
ZonedDateTime z  = ZonedDateTime.parse(date, formatter);
System.out.println(formatter.format(z));

The output will be:

2017-07-05T12:28:36.4TGMT+03:00+03:00

That's because the GMT+03:00 is the result of zzz and the second +03:00 is the result of xxx. If you don't want this, I recomment using 2 different DateTimeFormatter's (one for parsing, another for formatting).

Or (an "uglier" approach), use 2 different formatters:

DateTimeFormatter noGMT = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss[.S]'T'zzzxxx");
DateTimeFormatter gmt = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss[.S]'TGMT'xxx");

Then, you try to parse with the first - if you get an exception, try with the second (or you check if your input contains GMT to know which one to use).

I personally don't like this because GMT is part of the zone name and should't be treated as a literal. But in the end, you get a ZonedDateTime with the correct offset, so I'm not sure of how wrong this approach is.


Timezone abbreviations

Please be aware that you should avoid (as possible) using the 3-letter abbreviations (like CDT or PST) because they are ambiguous and not standard. CDT can be both Central Daylight Time (UTC-05:00), Cuba Daylight Time (UTC-04:00) or even China Daylight Time (UTC+09:00).

Prefer, if possible, to use IANA timezones names (always in the format Continent/City, like America/Sao_Paulo or Europe/Berlin). Based on that list, there are more than 40 timezones that uses (or had used somewhere in the past) the CDT abbreviation.

CDT works for this case because some abbreviations have default values configured, probably due to retro-compatibility reasons, but you shouldn't rely on them for all cases.

To make sure your timezone abbreviations always work (in case you can't avoid using them), you can create a formatter that uses a set of prefered zones. In this case, I'm using America/Chicago (so, CST or CDT will be parsed as Chicago's timezone):

Set<ZoneId> preferedZones = new HashSet<>();
preferedZones.add(ZoneId.of("America/Chicago"));
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
    // append first part of pattern (before timezone)
    .appendPattern("yyyy-MM-dd'T'HH:mm:ss[.S]'T'")
    // append zone name, use prefered zones (optional)
    .optionalStart().appendZoneText(TextStyle.SHORT, preferedZones).optionalEnd()
    // offset (optional)
    .appendPattern("[xxx]")
    // create formatter
    .toFormatter();

This formatter works the same way as above, for both your inputs (with and without GMT), and uses America/Chicago as default timezone when CDT is in the input. You can add as many zones you want in the set, according to your use cases.

Just reminding that this formatter has the same issues regarding the output (it prints all optional sections), as already stated above.

Upvotes: 7

Basil Bourque
Basil Bourque

Reputation: 340230

tl;dr

OffsetDateTime.parse(
    "2017-07-05T12:28:36.4TGMT+03:00".replace( "TGMT" , "" ) 
)

Details

Your format is bizarre, like a strange misunderstanding or corruption of the standard ISO 8601 formats.

If all your inputs have “TGMT" in the last part, strip that out to comply with ISO 8601.

The java.time classes use standard formats by default when parsing/generating strings. So no need to define a formatting pattern.

OffsetDateTime odt = OffsetDateTime.parse( "2017-07-05T12:28:36.4TGMT+03:00".replace( "TGMT" , "" ) ) ;

And never use the 3-4 letter pseudo-timezones like CMT, EST, and IST. These are not actual time zones, not standardized, and not even unique(!). Real time zone names are in the format of continent/region such as America/Montreal or Pacific/Auckland.

Upvotes: 2

Related Questions