0

I'm currently at a loss about the following simple usage of the SimpleDateFormatter:

 import java.text.ParseException;
 import java.text.SimpleDateFormat;

 public static void main(String[] args) throws ParseException {
    System.out.println(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX").parse("2018-12-04T22:22:01+1000"));
 }

I'm running this example with JDK 1.8.0_192.

My PC is located at CET (+1000), so timezone is equal. So the expected result would be:

Tue Dec 04 22:22:01 CET 2018

But I get the following output:

Tue Dec 04 13:22:01 CET 2018

Does anyone have an Idea what is happening here?

xingbin
  • 27,410
  • 9
  • 53
  • 103
Dennis Kriechel
  • 3,719
  • 14
  • 40
  • 62

2 Answers2

4

You give it 2018-12-04T22:22:01+1000, which is 2018-12-04T12:22:01 in UTC. While CET is 1 hour ahead UTC, so you get hour 13.

xingbin
  • 27,410
  • 9
  • 53
  • 103
1

tl;dr

Your original problem was a typo +1000 vs +0100. Nevertheless, all the advice below still applies. You are using terrible old classes that should be avoided.

OffsetDateTime.parse( 
    "2018-12-04T22:22:01+1000" ,   // Input in standard ISO 8601, with the COLON omitted from the offset as allowed by the standard but breaking some libraries such as `OffsetDateTime.parse`. 
    DateTimeFormatter.ofPattern( 
        "uuuu-MM-dd'T'HH:mm:ssX" 
    )
)                                  // Returns a `OffsetDateTime` object.
.toInstant()                       // Adjust into UTC. Returns an `Instant` object. Same moment, different wall-clock time.
.atZone(                           // Adjust from UTC to some time zone. Same moment, different wall-clock time.
    ZoneId.of( "Europe/Brussels" ) 
)                                  // Returns a `ZonedDateTime` object.
.toString()                        // Generate text representing this `ZonedDateTime` object in standard ISO 8601 format but wisely extending the standard by appending the name of the time zone in square brackets.

18-12-04T13:22:01+01:00[Europe/Brussels]

Avoid legacy date-time classes

You are using terrible old date-time classes bundled with the earliest versions of Java. Supplanted years ago by the java.time classes.

Use proper time zones

FYI, CET is not a real time zone.

Specify a proper time zone name in the format of continent/region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 2-4 letter abbreviation such as EST or IST as they are not true time zones, not standardized, and not even unique(!).

ZoneId z = ZoneId.of( "America/Montreal" ) ;  

You likely mean a time zone such as Europe/Brussels, Europe/Paris, Europe/Berlin, Africa/Tunis, or Europe/Oslo.

ISO 8601

Your input string 2018-12-04T22:22:01+1000 is in standard format, defined by ISO 8601.

The last part +1000 is an offset-from-UTC, meaning a moment ten hours ahead of UTC. So this value was intended for the wall-clock time used by people is some region in the Pacific, such as time zone Australia/Lindeman.

Do not abbreviate the offset notation

That string +1000 is an abbreviation of an offset, omitting the COLON character delimiter between hours and minutes (and seconds, if any). While the standard allows this omission, I suggest always including the COLON: 2018-12-04T22:22:01+10:00. In my experience, some libraries and protocols break when encountering such strings. And the COLON’s inclusion makes the string more readable for humans.

OffsetDateTime

Indeed, the java.time.OffsetDateTime class meant to parse such standard strings by default has a bug in this regard, failing to parse when the COLON is omitted. Discussed at:

Workaround:

OffsetDateTime odt = 
    OffsetDateTime.parse( 
        "2018-12-04T22:22:01+1000" , 
        DateTimeFormatter.ofPattern( "uuuu-MM-dd'T'HH:mm:ssX" )
    )
;

See code example running live at IdeOne.com.

odt.toString(): 2018-12-04T22:22:01+10:00

Adjust that value into UTC by extracting an Instant object. Instant is always in UTC by definition.

Instant instant = odt.toString() ;

instant.toString(): 2018-12-04T12:22:01Z

Finally, we can adjust into your own parochial time zone.

By CET I assume you meant a time zone such as Europe/Paris.

ZoneId z = ZoneId.of( "Europe/Paris" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;

When calling ZonedDateTime::toString, text is generated in standard ISO 8601 format, but wisely extending the standard to append the name of the time zone in square brackets.

zdt.toString(): 2018-12-04T13:22:01+01:00[Europe/Paris]

All three of these objects (odt, instant, & zdt) refer to the same simultaneous moment, the very same point on the timeline. Their only difference is the wall-clock time. If three people on a conference call in Australia, France, and Iceland (always in UTC) all looked up simultaneously to read the current moment from their respective clock hanging on their local wall, they would read three different values for the same simultaneous moment.

See all this code run live at that IdeOne.com page.


About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.

Where to obtain the java.time classes?

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154