简体   繁体   中英

Setting and formatting time zone with Calendar object in Java and then returning a Date object

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;
}

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")));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM