开发者

Java DateFormat parse() doesn't respect the timezone

开发者 https://www.devze.com 2023-04-08 13:18 出处:网络
Calendar cal = Calendar.getIn开发者_C百科stance(TimeZone.getTimeZone("America/New_York"));
Calendar cal = Calendar.getIn开发者_C百科stance(TimeZone.getTimeZone("America/New_York"));
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
df.setTimeZone(TimeZone.getTimeZone("America/New_York"));

try {
    System.out.println(df.format(cal.getTime()));
    System.out.println(df.parse(df.format(cal.getTime())));
} catch (ParseException e) {
    e.printStackTrace();
}

Here is the result:

2011-09-24 14:10:51 -0400

Sat Sep 24 20:10:51 CEST 2011

Why when I parse a date I get from format() it doesn't respect the timezone?


You're printing the result of calling Date.toString(), which always uses the default time zone. Basically, you shouldn't use Date.toString() for anything other than debugging.

Don't forget that a Date doesn't have a time zone - it represents an instant in time, measured as milliseconds since the Unix epoch (midnight on January 1st 1970 UTC).

If you format the date using your formatter again, that should come up with the same answer as before.

As an aside, I would recommend the use of Joda Time instead of Date/Calendar if you're doing any significant amount of date/time work in Java; it's a much nicer API.


DateFormat.parse() is NOT a query (something that returns a value and doesn't change the state of the system). It is a command which has the side-effect of updating an internal Calendar object. After calling parse() you have to access the timezone either by accessing the DateFormat's Calendar or calling DateFormat.getTimeZone(). Unless you want to throw away the original timezone and use local time, do not use the returned Date value from parse(). Instead use the calendar object after parsing. And the same is true for the format method. If you are going to format a date, pass the calendar with the timezone info into the DateFormat object before calling format(). Here is how you can convert one format to another format preserving the original timezone:

    DateFormat originalDateFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss Z yyyy");
    DateFormat targetDateFormat = new SimpleDateFormat("EEE., MMM. dd, yyyy");

    originalDateFormat.parse(origDateString);
    targetDateFormat.setCalendar(originalDateFormat.getCalendar());
    return targetDateFormat.format(targetDateFormat.getCalendar().getTime());

It's messy but necessary since parse() doesn't return a value that preserves timezone and format() doesn't accept a value that defines a timezone (the Date class).


DateFormat is an abstract class for date/time formatting subclasses which formats and parses dates or time in a language-independent manner. The date/time formatting subclass, such as SimpleDateFormat, allows for formatting (i.e., date -> text), parsing (text -> date), and normalization. The date is represented as a Date object or as the milliseconds since January 1, 1970, 00:00:00 GMT.

From the spec, it return EPOCH time


A java.util.Date object is not a real Date-Time object like the modern Date-Time types; rather, it represents the number of milliseconds since the standard base time known as "the epoch", namely January 1, 1970, 00:00:00 GMT (or UTC). Since it does not hold any format and timezone information, it applies the format, EEE MMM dd HH:mm:ss z yyyy and the JVM's timezone to return the value of Date#toString derived from this milliseconds value. If you need to print the Date-Time in a different format and timezone, you will need to use a SimpleDateFormat with the desired format and the applicable timezone e.g.

Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", Locale.ENGLISH);
sdf.setTimeZone(TimeZone.getTimeZone("America/New_York"));
String strDate = sdf.format(date);
System.out.println(strDate);

Note that the java.util Date-Time API and their formatting API, SimpleDateFormat are outdated and error-prone. It is recommended to stop using them completely and switch to the modern Date-Time API*.

Solution using java.time, the modern API:

import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

public class Main {
    public static void main(String[] args) {
        ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("America/New_York"));
        System.out.println(zdt);

        // With timezone offset
        OffsetDateTime odt = zdt.toOffsetDateTime();
        System.out.println(odt);

        // In a custom format
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss Z", Locale.ENGLISH);
        String formatted = dtf.format(odt);
        System.out.println(formatted);
    }
}

Output:

2021-06-04T14:25:08.266940-04:00[America/New_York]
2021-06-04T14:25:08.266940-04:00
2021-06-04 14:25:08 -0400

Here, you can use yyyy instead of uuuu but I prefer u to y.

Learn more about java.time, the modern Date-Time API* from Trail: Date Time.


* For any reason, if you have to stick to Java 6 or Java 7, you can use ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7. If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and How to use ThreeTenABP in Android Project.

0

精彩评论

暂无评论...
验证码 换一张
取 消