3

I have a function where I need to grab the current date, set to another time zone, and return that converted/formatted date as a Date object. I have code that works, however, the Date object does not set to the newly converted date, it returns the current date.

Here is the code:

public static Date getCurrentLocalDateTime() {

    Calendar currentdate = Calendar.getInstance();
    DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    TimeZone obj = TimeZone.getTimeZone("America/Denver");
    formatter.setTimeZone(obj);

    Logger.info("Local:: " + currentdate.getTime());

    String formattedDate = formatter.format(currentdate.getTime());

    Logger.info("America/Denver:: "+ formattedDate);

    Date finalDate = null;
    try {
        finalDate = formatter.parse(formattedDate);
    } catch (ParseException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    Logger.info("finalDate:: " + finalDate);

    return finalDate;
}

From the examples I have reviewed and tried, this should work correctly. One of the issues is that I need to return the Date object so it works with the current code.

The output looks like:

2017-07-03 17:08:24,499 [INFO] from application in application-akka.actor.default-dispatcher-3 -
                Local:: Mon Jul 03 17:08:24 UTC 2017
2017-07-03 17:08:24,501 [INFO] from application in application-akka.actor.default-dispatcher-3 -
                America/Denver:: 2017-07-03 11:08:24
2017-07-03 17:08:24,502 [INFO] from application in application-akka.actor.default-dispatcher-3 -
                finalDate:: Mon Jul 03 17:08:24 UTC 2017

As you can see, it formats the date correctly to the Mountain Time Zone, but then sets it back to the Calendar time.


EDIT --- Code solution:

public static Date getCurrentLocalDateTime() {
    Calendar currentdate = Calendar.getInstance();
    ZonedDateTime converted = currentdate.toInstant().atZone(ZoneId.of("America/Denver"))
            .withZoneSameLocal(ZoneOffset.UTC);
    Date finalDate = Date.from(converted.toInstant());
    return finalDate;
}
Dan
  • 940
  • 2
  • 14
  • 42
  • Why do you expect a different result? – PM 77-1 Jul 03 '17 at 17:25
  • I expect the date returned to, based on the log output above, to be `Mon Jul 03 11:08:24 UTC 2017` not `Mon Jul 03 17:08:24 UTC 2017` - since I formatted the Calendar object to Mountain Time. – Dan Jul 03 '17 at 17:42
  • IMHO some have not done their requirement specification right. In 2017 you should not be required to return an instance of the long outdated `Date` class. The Java date and time API (since Java 8, backported to Java 6 and 7) are so much better and nicer to work with, for you and for whoever receives your return value. And if you could return a `ZonedDateTime` object, it would have a time zone of your choice. – Ole V.V. Jul 03 '17 at 21:04

1 Answers1

1

A java.util.Date object has no timezone information. It has only a long value, which is the number of milliseconds from 1970-01-01T00:00:00Z (also known as "unix epoch" or just "epoch"). This value is absolutely independent of timezone (you can say "it's in UTC" as well).

When you call Logger.info("finalDate:: " + finalDate);, it calls the toString() method of java.util.Date, and this method uses the system's default timezone behind the scenes, giving the impression that the date object itself has a timezone - but it doesn't.

Check the values of finalDate.getTime() and currentdate.getTimeInMillis(), you'll see they are almost the same - "almost" because the SimpleDateFormat doesn't have the fraction of seconds, so you're losing the milliseconds precision (format method creates a String without the milliseconds, and the parse method sets it to zero when the field is not present). If I change the formatter to this, though:

// using ".SSS" to don't lose milliseconds when formatting
DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");

The output is:

Local:: Mon Jul 03 17:34:34 UTC 2017
America/Denver:: 2017-07-03 11:34:34.508
finalDate:: Mon Jul 03 17:34:34 UTC 2017

And both finalDate.getTime() and currentdate.getTimeInMillis() will have exactly the same values (Note that Date.toString() doesn't print the milliseconds, so you can't know what's their value - only by comparing getTime() values you know if they are the same).

Conclusion: just change your formatter to use the milliseconds (.SSS) and parsing/formatting will work. The fact that it shows the dates in another timezone is an implementation detail (toString() method uses system's default timezone), but the milliseconds value is correct.

If you want to get 11h at UTC, you must create another formatter and set its timezone to UTC:

DateFormat parser = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
parser.setTimeZone(TimeZone.getTimeZone("UTC"));
finalDate = parser.parse(formattedDate);

Then, finalDate's time will have the value of 11h at UTC:

finalDate:: Mon Jul 03 11:34:34 UTC 2017


New Java Date/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.

To do what you need, you can use a ZonedDateTime (a date and time + a timezone) and convert to another timezone keeping the same date/time values:

// current date/time in Denver
ZonedDateTime denverNow = ZonedDateTime.now(ZoneId.of("America/Denver"));
// convert to UTC, but keeping the same date/time values (like 11:34)
ZonedDateTime converted = denverNow.withZoneSameLocal(ZoneOffset.UTC);
System.out.println(converted); // 2017-07-03T11:34:34.508Z

The output will be:

2017-07-03T11:34:34.508Z

If you want a different format, use a DateTimeFormatter:

DateTimeFormatter fmt = new DateTimeFormatterBuilder()
    // pattern for day/hour
    .appendPattern("EEE MMM dd HH:mm:ss ")
    // UTC offset
    .appendOffset("+HHMM", "UTC")
    // year
    .appendPattern(" yyyy")
    // create formatter
    .toFormatter(Locale.ENGLISH);
System.out.println(fmt.format(converted));

The output will be:

Mon Jul 03 11:34:34 UTC 2017


If you still need to use java.util.Date, you can easily convert from/to the new API.

In Java >= 8:

// convert your Calendar object to ZonedDateTime
converted = currentdate.toInstant()
               .atZone(ZoneId.of("America/Denver"))
               .withZoneSameLocal(ZoneOffset.UTC);
// converted is equals to 2017-07-03T11:34:34.508Z

// from ZonedDateTime to Date and Calendar (date will be 11:34 UTC)
Date d = Date.from(converted.toInstant());
Calendar cal = Calendar.getInstance();
cal.setTime(d);

// to get a Date that corresponds to 11:34 in Denver
Date d = Date.from(converted.withZoneSameLocal(ZoneId.of("America/Denver")).toInstant());
Calendar cal = Calendar.getInstance();
cal.setTime(d);

In Java <= 7 (ThreeTen Backport), you can use the org.threeten.bp.DateTimeUtils class:

// convert Calendar to ZonedDateTime 
converted = DateTimeUtils.toInstant(currentdate)
                .atZone(ZoneId.of("America/Denver"))
                .withZoneSameLocal(ZoneOffset.UTC);
// converted is equals to 2017-07-03T11:34:34.508Z

// convert ZonedDateTime to Date (date will be 11:34 UTC)
Date d = DateTimeUtils.toDate(converted.toInstant());
Calendar c = DateTimeUtils.toGregorianCalendar(converted);

// to get a Date that corresponds to 11:34 in Denver
Date d = DateTimeUtils.toDate(converted.withZoneSameLocal(ZoneId.of("America/Denver")).toInstant());
Calendar c = DateTimeUtils.toGregorianCalendar(converted.withZoneSameLocal(ZoneId.of("America/Denver")));
  • Thank you - will this return the Date object of `Mon Jul 03 11:34:34 UTC 2017`? I need the time to be 11:00 AM, not 5:00 PM (17:00). – Dan Jul 03 '17 at 17:44
  • The Date object contains the value that corresponds to 11:34 in **America/Denver** (which is equivalent to 17:34 in UTC). If you want a `String` with 11:34, use a formatter with Denver timezone set (like the one you're already using). Or do you really want "11:34 UTC"? –  Jul 03 '17 at 17:46
  • I want to have the 11:34 UTC returned if possible. – Dan Jul 03 '17 at 17:48
  • 1
    @Dan I've updated the answer with 2 alternatives: using `SimpleDateFormat` or using the new date/time API - if this new API is available to you, I recommend using it, as it's much better and easier (and IMO, much more clear about what it's doing, like the method `withZoneSameLocal`, that does the conversion directly, without the need to format and parse in obscure ways) –  Jul 03 '17 at 18:06
  • thanks for updating the example. I would like to use the ZonedDateTime example. Is there a way to convert the `converted` object the into a Date object? Or will this just set it back to UTC value, not the Denver time zone? – Dan Jul 07 '17 at 15:02
  • @Dan You can convert a `ZonedDateTime` to `Date` in both UTC or Denver. I've updated the answer with this info –  Jul 07 '17 at 15:56
  • 1
    worked perfectly! I have updated my post with the code. – Dan Jul 10 '17 at 18:43