39

I've got a SimpleDateFormat to parse a String into a Date:

SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ssZ");

When I'm parsing this:

format.parse("2011-08-29T12:44:00+0200");

The result will be, when using Date.toLocaleString:

29 aug. 2011 00:44:00

This should be ofcourse:

29 aug. 2011 12:44:00

And when I'm parsing this:

format.parse("2011-08-29T13:44:00+0200");

Then the result is as expected:

29 aug. 2011 13:44:00

How can I fix this?

nhaarman
  • 98,571
  • 55
  • 246
  • 278
  • For new readers to this question I recommend you don’t use `SimpleDateFormat` and `Date`. Those classes are poorly designed and long outdated, the former in particular notoriously troublesome. Instead use `OffsetDateTime` and `DateTimeFormatter`, both from [java.time, the modern Java date and time API](https://docs.oracle.com/javase/tutorial/datetime/). – Ole V.V. Dec 05 '21 at 10:04

2 Answers2

117

Use HH instead of hh for the hours pattern:

H   Hour in day (0-23)  Number  0
k   Hour in day (1-24)  Number  24
K   Hour in am/pm (0-11)    Number  0
h   Hour in am/pm (1-12)    Number  12
martin clayton
  • 76,436
  • 32
  • 213
  • 198
1

java.time through desugaring

I suggest that you use java.time, the modern Java date and time API, for your date and time work.

Your string is in ISO 8601 format. Define a formatter for it:

private static final DateTimeFormatter ISO_FORMATTER
        = new DateTimeFormatterBuilder()
                .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
                .appendOffset("+HHMM", "Z")
                .toFormatter(Locale.ROOT);

It could have been shorter with a format pattern string, but I prefer to reuse the built-in ISO_LOCAL_DATE_TIME, which already handles ISO 8601. As your question testifies, writing a format pattern string is error-prone. I have also specified that an offset of zero from UTC should be accepted as Z in accordance with ISO 8601 (the parser will accept +0000 too).

Parse like this:

    String isoString = "2011-08-29T12:44:00+0200";
    OffsetDateTime dateTime = OffsetDateTime.parse(isoString, ISO_FORMATTER);
    System.out.println(dateTime);

Output is:

2011-08-29T12:44+02:00

To obtain a string formatted for the user’s locale use a second formatter:

private static final DateTimeFormatter LOCALE_FORMATTER
        = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM);

We still need not write any format pattern string. Format like this:

    String localeString = dateTime.format(LOCALE_FORMATTER);
    System.out.println(localeString);

Output is exactly what it should be. I ran in nl_NL locale and got:

29 aug. 2011 12:44:00

Please skip the next section.

What if I need an old-fashioned java.util.Date?

No one should use the Date class anymore. Only if you indispensably need a Date for a legacy API that you cannot afford to upgrade to java.time right now, convert:

    Date oldfashionedDate = Date.from(dateTime.toInstant());
    System.out.println(oldfashionedDate);

Funnily output will be time zone dependent. In my time zone I got:

Mon Aug 29 12:44:00 CEST 2011

Question: Doesn’t java.time require Android API level 26?

java.time works nicely on both 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 non-Android Java 6 and 7 get the ThreeTen Backport, the backport of the modern classes (ThreeTen for JSR 310; see the links at the bottom).
  • On older Android either use desugaring or the Android edition of ThreeTen Backport. It’s called ThreeTenABP. In the latter case make sure you import the date and time classes from org.threeten.bp with subpackages.

Links

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