1

I am facing some issues because of timezone in our apps.

We have some dependent services which send us EST time. We were in EST timezone so there were no any issues before. Recently, we moved to UTC time zone and started seeing the issue. For us, we might again go back and forth (EST/UTC).

So what I want to do is check my JVM time zone and if my timezone is UTC, I want to convert the dependent service's time to UTC and do nothing if we are in EST as dependent service always responds with EST. How do I achieve that?

Data1.java

XMLGregorianCalendar date;
XMLGregorianCalendar time;
//getter & setter

Util.java

String convertDate(Date d){
    SimpleDateFormat date = new SimpleDateFormat("yyyy-MM-dd");
            try {
                 String converted= dateFormat.format(d);
                return converted;
            } catch (ParseException e) {
                throw new RuntimeException(e);
            }
    }

    String convertTime(Date d){
            SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
            String Time = "";
            Time =  sdf.format(d);
            return Time;
    }
}

MyApp.java

String date = Util.convertDate(data1.getDate.toGregorianCalendar().getTime())
String time = Util.convertTime(data1.getDate.toGregorianCalendar().getTime())

Here, the date and time string here have the EST date and time which I want to change to UTC if the JVM timezone is UTC and keep it as JVM timezone is EST

Existing output example:

date: 2017-08-02
time: 21:04:04

Expected:

date: 2017-08-03
time: 01:04:04

user123475
  • 1,065
  • 4
  • 17
  • 29

2 Answers2

1

A java.util.Date object has no format nor any timezone information. It just keeps a long value that represents the number of milliseconds since unix epoch (1970-01-01T00:00Z).

When you format a Date using SimpleDateFormat, the formatter uses the system's default timezone to convert this milliseconds value to human readable values (day/month/year/hour/minute/second).

If you're getting 2017-08-02 21:04:04 it means that the default timezone in the JVM where the code is running is EST (technically speaking, EST is not actually a timezone, more on that below). If you want the output to be converted to UTC, you must set it in the formatter:

Date date = // Date corresponding to 2017-08-03 01:04:04 UTC (or 2017-08-02 21:04:04 EDT)

// date format
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
// set UTC
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
System.out.println(dateFormat.format(date)); // 2017-08-03

// time format
SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm:ss");
// set UTC
timeFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
System.out.println(timeFormat.format(date)); // 01:04:04

The output will be:

2017-08-03
01:04:04

To get the values for EST, just change the timezone:

// date format
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
// set to EST
dateFormat.setTimeZone(TimeZone.getTimeZone("America/New_York"));
System.out.println(dateFormat.format(date));

// time format
SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm:ss");
// set to EST
timeFormat.setTimeZone(TimeZone.getTimeZone("America/New_York"));
System.out.println(timeFormat.format(date));

The output will be:

2017-08-02
21:04:04

Note that I used America/New_York because EST produces incorrect results. The ideal is to always use IANA timezones names (always in the format Region/City, like America/New_York or Europe/Berlin). Avoid using the 3-letter abbreviations (like EST or PST) because they are ambiguous and not standard.

There are lots of different timezones that use EST as a "short name", so you can change America/New_York to a timezone that suits best to your system. You can get a list of available timezones by calling TimeZone.getAvailableIDs().

To check the default timezone, you can use TimeZone.getDefault().getID() and check if it's UTC or GMT (so you know which format to use - although it's recommended to internally work with UTC and only convert to a timezone when displaying the values to users, for example).

Depending on the default timezone is not ideal, because it can be changed at any time, even at runtime, or due to a misconfiguration made by someone else (I've faced situations like this before and it's not a good thing). But as it seems you don't have a choice, just check the value returned by getID() and decide what to do based on that.


Java new Date and Time API

The old classes (Date, Calendar and SimpleDateFormat) have lots of problems and design issues, and they're being replaced by the new APIs.

If you're using Java 8, consider using the new java.time API. It's easier, less bugged and less error-prone than the old APIs.

If you're using Java <= 7, you can use the ThreeTen Backport, a great backport for Java 8's new date/time classes. And for Android, there's the ThreeTenABP (more on how to use it here).

The code below works for both. The only difference is the package names (in Java 8 is java.time and in ThreeTen Backport (or Android's ThreeTenABP) is org.threeten.bp), but the classes and methods names are the same.

First you convert the GregorianCalendar to an Instant:

// convert calendar
Instant inst = Instant.ofEpochMilli(data1.getDate.toGregorianCalendar().getTimeInMillis());

In Java 8, GregorianCalendar has a method to do the conversion directly:

Instant inst = data1.getDate.toGregorianCalendar().toInstant();

Then you can convert the instant to a timezone:

// convert to UTC
ZonedDateTime zdt = inst.atZone(ZoneOffset.UTC);

// or convert to a timezone
ZonedDateTime zdt = inst.atZone(ZoneId.of("America/New_York"));

Then you can use a DateTimeFormatter to format it:

DateTimeFormatter dateFmt = DateTimeFormatter.ofPattern("yyyy-MM-dd");
DateTimeFormatter timeFmt = DateTimeFormatter.ofPattern("HH:mm:ss");

System.out.println(dateFmt.format(zdt));
System.out.println(timeFmt.format(zdt));

This will produce the same output as above.

You can also check the JVM default timezone using ZoneId.systemDefault().getId() and checking if it's UTC or GMT.

1

EST is not a time zone

The 3-4 letter pseudo-zones seen in the media are not true time zones. They are not standardized, and are not even unique(!).

Specify a proper time zone name in the format of continent/region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland.

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

Avoid XMLGregorianCalendar

When possible, avoid using XMLGregorianCalendar. Like the legacy Date & Calendar & GregorianCalendar classes, these are now supplanted by the superior java.time classes.

Never depend on default time zone

Bad practice to depend on the host OS’ default time zone as that is out of your control as a programmer.

Similarly bad practice to depend on the JVM’s current default time zone as that is also outside your control. Any code in any thread of any app within the JVM can change the current default during runtime.

Sounds like your people are expecting to use the host OS’ current default time zone as a signal to alter your app’s behavior. Very strange and clumsy strategy. You have so many other routes available: configuration files, JMX, JNDI, and so on.

Getting current default time zone

I know of no way for the JVM to directly access the host OS’ current default time zone. You could go some roundabout way via command-line utility or some such.

You can get the JVM’s current default. Most implementations of Java with which I am familiar set the JVM’s to the host OS’ default, by default.

ZoneId z = ZoneId.systemDefault() ;

Convert to java.time

If you are given a XMLGregorianCalendar object, convert to java.time.ZonedDateTime via GregorianCalendar.

GregorianCalendar gc = myXMLGregorianCalendar.toGregorianCalendar() ;
ZonedDateTime zdt = gc.toZonedDateTime() ;

Adjust into desired/expected time zone

From that ZonedDateTime extract a Instant. The Instant class represents a moment on the timeline in UTC with a resolution of nanoseconds (up to nine (9) digits of a decimal fraction).

Instant instant = zdt.toInstant() ;

Apply any time zone you desire. For UTC, use OffsetDateTime.

OffsetDateTime odt = instant.atOffset( ZoneOffset.UTC ) ;

Get your date & time strings in standard ISO 8601 format.

String outputDate = odt.toLocalDate().toString() ;
String outputTime = odt.toLocalTime().toString() ;

For a time zone rather than mere offset-from-UTC, use ZonedDateTime. For east coast of US, perhaps you want America/New_York.

ZonedDateTime zdt = instant.atZone( ZoneId.of( "America/New_York" ) ) ;

Generate strings in standard ISO 8601 format.

String outputDate = zdt.toLocalDate().toString() ;
String outputTime = zdt.toLocalTime().toString() ;

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