2
votes

I would like to format dates, for a specific time zone, GMT, and I want the result of that formatting to be the same always, regardless of which time zone the application runs in.

E.g., create Calendar instance in GMT time zone and populate its fields:

TimeZone gmtTimeZone = TimeZone.getTimeZone( "GMT" );

Calendar calendar = Calendar.getInstance();
calendar.setTimeZone( gmtTimeZone );
calendar.set( Calendar.YEAR, 1982 );
calendar.set( Calendar.MONTH, Calendar.JANUARY );
calendar.set( Calendar.DAY_OF_MONTH, 23 );
calendar.set( Calendar.HOUR, 1 );
calendar.set( Calendar.MINUTE, 2 );
calendar.set( Calendar.SECOND, 3 );
calendar.set( Calendar.MILLISECOND, 4 );

Retrieve UTC timestamp from calendar:

Date utcDate = calendar.getTime();

From what I understand, utcDate is now the number of milliseconds between January 1, 1970, 00:00:00.000 GMT and January 23, 1982, 01:02:03.004 GMT. See Date Javadocs:

    /**
     * Returns the number of milliseconds since January 1, 1970, 00:00:00 GMT
     * represented by this <tt>Date</tt> object.
     *
     * @return the number of milliseconds since January 1, 1970, 00:00:00 GMT
     *          represented by this date.
     */

Create date formatter and set its time zone to GMT too:

SimpleDateFormat dateTimeFormat = new SimpleDateFormat( "yyyy-MM-dd' 'HH:mm:ss.SSSZ" );
dateTimeFormat.setTimeZone( gmtTimeZone );

Format date object into string:

String stringDate = dateTimeFormat.format( utcDate );

Now, when I do:

System.out.println( utcDate.getTime() );
System.out.println( stringDate );

I get:

> 380638923004
> 1982-01-23 13:02:03.004+0000

However, what I expected was (note 13 hours vs 01 hours):

> 1982-01-23 01:02:03.004+0000

I.e., because I set the time to 1 (1am) with calendar.set( Calendar.HOUR, 1 );, I expect the time to be 1 (1am) not 13 (1pm).

Where am I going wrong?

2
So instead of 1300 hours, you want 1pm? - MadProgrammer
So, according to the JavaDocs, "H - Hour in day (0-23)". Perhaps you should check the docs for a specifier which better meets your requirements - MadProgrammer
No, I expected 1am because calendar.set( Calendar.HOUR, 1 ); - Alex Averbuch
Now I understand what you meant, thanks @MadProgrammer - Alex Averbuch

2 Answers

4
votes
SimpleDateFormat dateTimeFormat = new SimpleDateFormat( "yyyy-MM-dd' 'hh:mm:ss.SSSZ" );
dateTimeFormat.setTimeZone( gmtTimeZone );

This will give you the expected output.

'h' in the date format string will display as hour in am/pm (1-12) according to the JavaDoc for SimpleDateFormat.

EDIT: Calendar.HOUR_OF_DAY should be used to set the hour for a 24-hour clock.

1
votes

As the correct Answer by Spencer Brett says, you should have used hh lowercase if you meant 12-hour clock rather than 24-hour.

java.time

When using Java 8 and later, you should avoid these notoriously troublesome old date-time classes (java.util.Date/.Calendar etc.). They have been supplanted by the java.time framework. See Tutorial.

Instant

An Instant is a moment on the timeline in UTC.

Instant instant = Instant.now();

OffsetDateTime

However an Instant has limited formatting, and limited ways to instantiate. So let's look at OffsetDateTime which you can think of as an Instant combined with an offset-from-UTC (a ZoneOffset). To specify our offset, we can use the constant java.time.ZoneOffset.UTC. To instantiate an OffsetDateTime we first build a date-only and a time-only, then combine with an offset.

Nanosecond Resolution

The java.time classes have nanosecond resolution. You intended milliseconds in your Question’s example. So we will convert using TimeUnit. You could do the multiplication yourself but that is error-prone. And TimeUnit is self-documenting.

LocalDate localDate = LocalDate.of( 1982 , 1 , 23 );
long nanoseconds = TimeUnit.NANOSECONDS.convert( 4 , TimeUnit.MILLISECONDS );
LocalTime localTime = LocalTime.of( 1 , 2 , 3 , nanoseconds );
OffsetDateTime odt = OffsetDateTime.of( localDate , localTime , ZoneOffset.UTC );

ISO 8601

The java.time classes use the ISO 8601 standard formats by default in their toString methods. So you need not specify a format as that is close to your desired output. Not quite the same as the standard format includes a T in the middle rather than a SPACE.

String output = odt.toString();

1982-01-23T01:02:03.004Z

Either replace the T with a SPACE, or search Stack Overflow for many examples of using DateTimeFormatter class in java.time.

String output = odt.toString().replace( "T" , " " );