curious1
curious1

Reputation: 14737

Joda: convert the system date and time to date/time in another zone

I read many posts at SO and tested most of them. None of them is working for me. Here is my code:

DateTimeZone fromTimeZone = DateTimeZone.forID("America/New_York");
DateTimeZone toTimeZone = DateTimeZone.forID("US/Central");  
Date now = new Date();        
DateTime dateTime = new DateTime(now, fromTimeZone); 
DateTime newDateTime = dateTime.withZone(toTimeZone);
System.out.println(dateTime.toDate() + "--" + newDateTime.toDate());

Here is what I got in print:

Tue Aug 22 13:08:13 EDT 2017--Tue Aug 22 13:08:13 EDT 2017

I am hoping to display "Tue Aug 22 12:08:13 CDT 2017" for the second time zone.

Upvotes: 1

Views: 672

Answers (1)

user7605325
user7605325

Reputation:

A java.util.Date doesn't have timezone information. Joda's DateTime has, but it's wrapped into a Chronology to translate this instant to "human readable" date/time fields.

But in the end, both objects just represent points (instants) in the time-line.

Just check the values of dateTime.getMillis(), newDateTime.getMillis(), dateTime.toDate().getTime() and newDateTime.toDate().getTime(). They will all be exactly the same, and this value represents the number of milliseconds since epoch (1970-01-01T00:00Z).

The timezone passed to the DateTime object just affects the output of toString() (when this milliseconds value is "translated" to a local date and time), but it doesn't change the milliseconds value itself. So if you do:

DateTime dateTime = new DateTime(now, fromTimeZone);
System.out.println(dateTime);

It will print the date and time that's equivalent to the milliseconds value, but converted to the fromTimeZone (America/New_York):

2017-08-22T13:33:08.345-04:00

The withZone method just sets to a different timezone, but keeps the same milliseconds value:

DateTime newDateTime = dateTime.withZone(toTimeZone);
System.out.println(newDateTime);

The code above keeps the instant (the milliseconds value), but prints the equivalent date and time in the toTimeZone (US/Central):

2017-08-22T12:33:08.345-05:00

The .toDate() method returns a java.util.Date, which just contains the same milliseconds value, and no timezone information. Then, System.out.println implicity calls Date::toString() method, and this converts the milliseconds value to the JVM's default timezone. In this case both will be:

Tue Aug 22 13:33:08 EDT 2017

Because both dates represent the same instant (the same number of milliseconds since epoch).


If you want to get a String that contains the date in a specific format, you can use a org.joda.time.format.DateTimeFormatter:

DateTimeFormatter fmt = DateTimeFormat.forPattern("EEE MMM dd HH:mm:ss z yyyy").withLocale(Locale.ENGLISH);
System.out.println(fmt.print(new DateTime(DateTimeZone.forID("US/Central"))));

There's no need to convert dates objects, because actually no conversion is really happening: all methods above don't change the milliseconds value.

Also note that I used a java.util.Locale to make sure the month and day of week are in English. If you don't specify a locale, the JVM default will be used, and it's not guaranteed to always be English (and it can also be changed, even at runtime, so it's better to always specify it).

Then I get the current date and set the timezone to be used when printing it. Note that you can get a DateTime directly, there's no need to create a java.util.Date.

The output will be:

Tue Aug 22 12:33:08 CDT 2017

To get exactly the same output you want (with both dates), you can do:

DateTimeFormatter fmt = DateTimeFormat.forPattern("EEE MMM dd HH:mm:ss z yyyy").withLocale(Locale.ENGLISH);
DateTime nowNy = new DateTime(DateTimeZone.forID("America/New_York"));
DateTime nowCentral = nowNy.withZone(DateTimeZone.forID("US/Central"));
System.out.println(fmt.print(nowNy) + "--" + fmt.print(nowCentral));

The output will be:

Tue Aug 22 13:33:08 EDT 2017--Tue Aug 22 12:33:08 CDT 2017


Java new Date/Time API

Joda-Time is in maintainance mode and being replaced by the new APIs, so I don't recommend start a new project with it. Even in joda's website it says: "Note that Joda-Time is considered to be a largely “finished” project. No major enhancements are planned. If using Java SE 8, please migrate to java.time (JSR-310)." (if you don't want to or can't migrate from Joda to another API, you can desconsider this section).

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.

The relevant classes are DateTimeFormatter (to format the date to a String in a specific format), ZonedDateTime (which represents a date and time in a specific timezone) and a ZoneId (which represents a timezone):

// formatter - use English locale for month and day of week
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss z yyyy", Locale.ENGLISH);

// current date/time in New York timezone
ZonedDateTime nowNy = ZonedDateTime.now(ZoneId.of("America/New_York"));
// convert to another timezone (US/Central)
ZonedDateTime nowCentral = nowNy.withZoneSameInstant(ZoneId.of("US/Central"));

// format dates
System.out.println(fmt.format(nowNy) + "--" + fmt.format(nowCentral));

The output is the same as above.

Upvotes: 1

Related Questions