Java DateFormat parse() 不尊重时区

Posted

技术标签:

【中文标题】Java DateFormat parse() 不尊重时区【英文标题】:Java DateFormat parse() doesn't respect the timezone 【发布时间】:2011-11-23 21:36:27 【问题描述】:
Calendar cal = Calendar.getInstance(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();

结果如下:

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

2011 年 9 月 24 日星期六 20:10:51 CEST

为什么当我解析从 format() 获得的日期时,它不遵守时区?

【问题讨论】:

【参考方案1】:

您正在打印调用Date.toString() 的结果,始终使用默认时区。基本上,您不应该将Date.toString() 用于除调试以外的任何事情。

不要忘记Date 没有时区 - 它代表一个瞬间,以 Unix 纪元(UTC 1970 年 1 月 1 日午夜)以来的毫秒数为单位。

如果您再次使用格式化程序格式化日期,应该会得出与以前相同的答案。

顺便说一句,如果您在 Java 中进行大量日期/时间工作,我建议使用 Joda Time 而不是 Date/Calendar;这是一个非常更好的 API。

【讨论】:

没错...尝试打印System.out.println(df.format(df.parse(df.format(cal.getTime()))));,它会按预期工作:-) 好吧,显然问题出在 toString() 上?感谢您的建议。 @MaximeLaval:特别是来自Date.toString,是的 - 您需要注意Date 只是一个瞬间,而不是任何特定的时区或日历。 除了显示/调试之外,永远不要依赖.toString 方法。另请注意,SimpleDateFormatter 对区域设置敏感。 @ChristopheRoussy:当然,除非行为被记录为你想做的事。【参考方案2】:

DateFormat 是日期/时间格式子类的抽象类 它以独立于语言的方式格式化和解析日期或时间 方式。日期/时间格式化子类,例如 SimpleDateFormat, 允许格式化(即日期 -> 文本),解析(文本 -> 日期), 和规范化。 日期表示为 Date 对象或 自 1970 年 1 月 1 日 00:00:00 GMT 以来的毫秒数

从spec返回EPOCH时间

【讨论】:

【参考方案3】:

DateFormat.parse() 不是查询(返回值且不改变系统状态的东西)。这是一个具有更新内部Calendar 对象的副作用的命令。调用parse() 后,您必须通过访问DateFormatCalendar 或调用DateFormat.getTimeZone() 来访问时区。除非您想丢弃原始时区并使用本地时间,否则不要使用从parse() 返回的Date 值。而是在解析后使用日历对象。格式方法也是如此。如果要格式化日期,请在调用format() 之前将带有时区信息的日历传递到DateFormat 对象。以下是如何将一种格式转换为另一种保留原始时区的格式:

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

这很麻烦但很有必要,因为parse() 不返回保留时区的值,而format() 不接受定义时区的值(Date 类)。

【讨论】:

【参考方案4】:

java.util.Date 对象不是像 modern Date-Time types 这样的真实日期时间对象;相反,它表示自称为“纪元”的标准基准时间以来的毫秒数,即January 1, 1970, 00:00:00 GMT(或 UTC)。由于它不包含任何格式和时区信息,因此它应用格式 EEE MMM dd HH:mm:ss z yyyy 和 JVM 的时区来返回从该毫秒值派生的 Date#toString 的值。如果您需要以不同的格式和时区打印日期时间,则需要使用具有所需格式和适用时区的SimpleDateFormat,例如

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

请注意,java.util 日期时间 API 及其格式化 API SimpleDateFormat 已过时且容易出错。建议完全停止使用,改用modern Date-Time API*

使用现代 API java.time 的解决方案:

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

输出:

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

在这里,您可以使用yyyy 代替uuuu 但I prefer u to y

Trail: Date Time了解更多关于java.time的modern Date-Time API*


* 出于任何原因,如果您必须坚持使用 Java 6 或 Java 7,您可以使用 ThreeTen-Backport,它将大部分 java.time 功能向后移植到 Java 6 和 7 . 如果您正在为一个 android 项目工作并且您的 Android API 级别仍然不符合 Java-8,请检查 Java 8+ APIs available through desugaring 和 How to use ThreeTenABP in Android Project。

【讨论】:

以上是关于Java DateFormat parse() 不尊重时区的主要内容,如果未能解决你的问题,请参考以下文章

java_DateFormat类中format方法parse方法使用

d3 dateformat parse返回null

java 使用Date类和DateFormat抽象类和Calendar抽象类实现日历

DateFormat类中的format方法和parse方法

java与模式,这段代码怎么反映简单工厂模式了?

Java日期到sql日期