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.