Search code examples
javaandroiddatesimpledateformattimestamp-with-timezone

Print date in original timezone


Let's say that my device is in Australia (GMT+10:00).

I have a date "2016-04-22T17:45:00+02:00" received from an API.

I want to display it nicely in the original time zone (GMT+02:00).

So, I used SimpleDateFormat with "yyyy-MM-dd'T'HH:mm:ssZZZZZ".

String date = "2016-04-22T17:45:00+02:00";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZZZZ");
Calendar cal = Calendar.getInstance();
cal.setTime(sdf.parse(date));

But unfortunately, I can't find a clean way to display the date:

sdf.format(cal.getTime());

uses the device time zone.

So I parsed "manually" the date to extract the time zone to set it in the calendar.

cal.setTimeZone(TimeZone.getTimeZone("GMT" + date.substring(date.length() - 6, date.length())));

But it doesn't seems to do the trick.

How can I do this?


Solution

  • Time Zone = Offset + Rules

    in the original time zone (GMT+02:00).

    No, incorrect. The text GMT+02:00 represents an offset-from-UTC, not a time zone. A time zone is an offset plus a set of rules for handling anomalies such as Daylight Saving Time (DST).

    A time zone is named in the format of continent/region such as America/Montreal or Asia/Kolkata. Always use these proper names. Never the 3-4 abbreviations seen in mainstream media; these are neither standardized nor unique.

    UTC is the new GMT, in practical terms.

    Avoid old date-time classes

    The old date-time classes bundled with the earliest versions of Java have proven to be poorly designed, confusing, and troublesome. Avoid them. These classes were supplanted by the highly successful Joda-Time library and its successor the java.time framework.

    The java.time framework is built into Java 8 and later. Much of its functionality was back-ported to Java 6 & 7 in the ThreeTen-Backport project, and further adapted to Android in the ThreeTenABP.

    Learn more at the Oracle Tutorial.

    OffsetDateTime

    The input string has only an offset-from-UTC, not a full time zone. So we use the OffsetDateTime class to represent this kind of value.

    Your input string is fortunately in standard ISO 8601 format. The java.time classes use ISO 8601 formats by default when parsing/generating textual representations of date-time values. So we can directly parse without bothering to define a formatting pattern.

    String input = "2016-04-22T17:45:00+02:00";
    OffsetDateTime odt = OffsetDateTime.parse( input );
    

    Call OffsetDateTime::toString() to generate a string in the same format.

    String output = odt.toString(); // ISO 8601 formatted string.
    

    Instant

    Programmers should not be thinking in these kind of local date-times. Generally you should do your business logic, database storage, and data-exchange all in UTC.

    In java.time, an Instant represents a moment on the timeline in UTC with a resolution in nanoseconds. We can extract an Instant from our OffsetDateTime.

    Instant instant = odt.toInstant();
    

    ZonedDateTime

    Always better to use a time zone than just an offset because of the rules for handling anomalies such as Daylight Saving Time.

    You can easily adjust either an Instant or an OffsetDateTime into a time zone by applying a ZoneId to get a ZonedDateTime. For more discussion, see my longer answer and diagram on the general topic of converting/adjusting between these classes.

    ZoneId zoneId_Montreal = ZoneId.of( "America/Montreal" );
    ZonedDateTime zdt = ZonedDateTime.ofInstant( instant , zoneId_Montreal );
    

    …or…

    ZonedDateTime zdt_Montreal = odt.atZoneSameInstant( zoneId_Montreal );
    

    Be aware that when applying a time zone you may see the time-of-day and even the date change. Adjustments may need to be made according to that zone’s rules to accommodate Daylight Saving Time (DST) or other issues.

    You can adjust further into other time zones. You mention Australia. That country has many time zones, so you must choose which is appropriate to you (or your user) as they may have different rules either now or in the past or future.

    ZoneId zoneId_Melbourne = ZoneId.of( "Australia/Melbourne" );
    ZonedDateTime zdt_Melbourne = zdt_Montreal.withZoneSameInstant( zoneId_Melbourne );
    

    You will notice when calling ZonedDateTime::toString that the method extends the standard ISO 8601 format by appending the name of the time zone in square brackets.

    To generate strings in other formats, search Stack Overflow for topics of java.time.format and DateTimeFormatter. The ofLocalized… methods along with a Locale can automatically localize the formatting including translating human language day/month names, and following cultural norms for order of elements, period-versus-comma, and so on.


    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.