【问题标题】:Java DateFormat parse() doesn't respect the timezoneJava DateFormat parse() 不尊重时区
【发布时间】: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() 获得的日期时,它不遵守时区?

【问题讨论】:

    标签: java parsing date 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 代替uuuuI prefer u to y

        Trail: Date Time了解更多关于java.timemodern Date-Time API*


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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-06-14
          • 2013-03-22
          • 2016-09-23
          • 2011-03-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多