使用SimpleDateFormat从具有偏移量的字符串解析时区

时间:2015-08-04 20:18:25

标签: java date parsing timezone simpledateformat

决定问这个,因为我在StackOverflow中找不到类似的例子。

我想使用SimpleDateFormat解析日期字符串及其时区。我(希望)我仔细阅读了文档,并编写了这个复制问题的程序。

import java.text.DateFormat;
import java.util.Date;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Locale;

public class SDF {

  private static final String FORMAT = "EEE, d MMM yyyy HH:mm:ss Z";  

  public static void main(String[] args) throws ParseException {
    DateFormat formatter = new SimpleDateFormat(FORMAT, Locale.ENGLISH);
    formatter.setLenient(false);
    String dateString = args[0];

    System.out.println(" Format: " + FORMAT);
    Date date = formatter.parse(dateString);
    System.out.println(" Parsed time in millis: " + date.getTime());
    System.out.println(" Parsed timezone: " +  formatter.getTimeZone().getDisplayName());
    System.out.println(" Parsed offset: " + formatter.getTimeZone().getRawOffset() / 1000 / 60 / 60 + "hrs.");
    }
}

如果我在输入字符串中使用“EDT”或任何其他支持的时区指示符,则dateFormat对象上的调用getTimeZone()将返回正确的时区和GMT的原始偏移量。

$ java SDF "Thu, 1 Jan 2015 00:00:00 EDT"
  Parsed time in millis: 1420084800000
  Parsed timezone: Eastern Standard Time
  Parsed offset: -5hrs.

$ java SDF "Thu, 1 Jan 2015 00:00:00 PST"
  Parsed time in millis: 1420099200000
  Parsed timezone: Pacific Standard Time
  Parsed offset: -8hrs.

但是当我使用+0000表示法时,日期毫秒被正确地返回为UTC中纪元以来的时间,但时区始终默认为我的本地时区。

$ java SDF "Thu, 1 Jan 2015 00:00:00 +0000"
  Parsed time in millis: 1420070400000
  Parsed timezone: Central European Time
  Parsed offset: 1hrs.

$ java SDF "Thu, 1 Jan 2015 00:00:00 -0800"
  Parsed time in millis: 1420099200000
  Parsed timezone: Central European Time
  Parsed offset: 1hrs.

这是否意味着我只能在输入字符串中使用“EDT”,“BST”或者我滥用SimpleDateFormat API?

1 个答案:

答案 0 :(得分:1)

当使用时区说明符(如“+0000”和“-0800”)时,DateFormat类将解析日期字符串并将尊重ZoneOffset,正如您通过比较自Epoch以来的毫秒数所证明的那样。但是,DateFormat的内部TimeZone将不会更改!

现在来了奇怪的一点。如果您使用GMT或PST等时区说明符,则实际上会更改DateFormat的内部TimeZone。我的程序证明了这一点,但我不确定这是什么意思。我相信你滥用API是因为期望DateFormat对象的TimeZone反映“最后解析的”日期,但是正如你的代码和我的代码都显示的那样,情况并非如此。实际上,DateFormat.parse()的{​​{3}}未提及格式化程序的TimeZone将如何更改,因此我们不应该依赖此“功能”。

事实上,如果我们检查Date对象上可用的API,我们发现TimeZone支持非常糟糕。请注意,在我的程序中,无论解析的输入字符串如何,Date.toString()都会提供相同的输出。 Java很早就通过弃用Java 1.1中的Date.getTimezoneOffset()方法来认识到这一点。 结论是Date对象仅表示来自Epoch的毫秒 - 没有TimeZone支持!

这种奇怪的行为以及许多其他此类示例是您应该避免Java 7日期和时间处理类的原因。来自Java Doc

  

[Java 7]中的一些日期和时间类也表现出相当差的API设计。

如果您能够使用Java 8,您会发现java.time包中的类的行为设计得更好。我在我的程序中给出了一个例子。如果您无法迁移到Java 8,那么Oracle website on Java 7 Dates:就是您的选择。下面有一个Java 8示例,显示了在解析之后,TimeZone信息是如何被记住和尊重的。 Joda Time也同样适用,这些方法中的任何一种都可以避免您在这里遇到的问题。

我的节目:

package com.company;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.Locale;

public class Main {

    private static final String FORMAT = "EEE, d MMM yyyy HH:mm:ss Z";

    public static void main(String[] args) throws ParseException {
        DateFormat formatter = new SimpleDateFormat(FORMAT, Locale.ENGLISH);
        formatter.setLenient(false);
        String dateString = "Thu, 1 Jan 2015 00:00:00 EDT";
        String dateString2 = "Thu, 1 Jan 2015 00:00:00 PST";
        String dateString3 = "Thu, 1 Jan 2015 00:00:00 +0000";
        String dateString4 = "Thu, 1 Jan 2015 00:00:00 -0800";


        runParseTest(dateString, formatter);
        runParseTest(dateString2, formatter);
        runParseTest(dateString3, formatter);
        runParseTest(dateString4, formatter);

        runJava8Test();
    }

    private static void runParseTest(String dateString, DateFormat formatter) throws ParseException {
        System.out.println(" Format: " + FORMAT);
        System.out.println(" Formatter time zone: " + formatter.getTimeZone().getDisplayName());
        System.out.println(" Parse String: " + dateString);
        Date date = formatter.parse(dateString);
        System.out.println(" Formatter time zone: " + formatter.getTimeZone().getDisplayName());
        System.out.println(" Parsed time in millis: " + date.getTime());
        System.out.println(" Parsed date: " +  date.toString());
        System.out.println();
    }

    private static void runJava8Test() {
        OffsetDateTime parsed = OffsetDateTime
                .parse(
                        "Thu, 1 Jan 2015 00:00:00 -1300",
                        DateTimeFormatter.ofPattern(FORMAT)
                );

        System.out.println(" Java 8 parsed offset: " + parsed.getOffset().toString());
    }
}

输出:

 Format: EEE, d MMM yyyy HH:mm:ss Z
 Formatter time zone: Greenwich Mean Time
 Parse String: Thu, 1 Jan 2015 00:00:00 EDT
 Formatter time zone: Eastern Standard Time
 Parsed time in millis: 1420084800000
 Parsed date: Thu Jan 01 04:00:00 GMT 2015

 Format: EEE, d MMM yyyy HH:mm:ss Z
 Formatter time zone: Eastern Standard Time
 Parse String: Thu, 1 Jan 2015 00:00:00 PST
 Formatter time zone: Pacific Standard Time
 Parsed time in millis: 1420099200000
 Parsed date: Thu Jan 01 08:00:00 GMT 2015

 Format: EEE, d MMM yyyy HH:mm:ss Z
 Formatter time zone: Pacific Standard Time
 Parse String: Thu, 1 Jan 2015 00:00:00 +0000
 Formatter time zone: Pacific Standard Time
 Parsed time in millis: 1420070400000
 Parsed date: Thu Jan 01 00:00:00 GMT 2015

 Format: EEE, d MMM yyyy HH:mm:ss Z
 Formatter time zone: Pacific Standard Time
 Parse String: Thu, 1 Jan 2015 00:00:00 -0800
 Formatter time zone: Pacific Standard Time
 Parsed time in millis: 1420099200000
 Parsed date: Thu Jan 01 08:00:00 GMT 2015

 Java 8 parsed offset: -13:00