Java 使用SimpleDataFormat从具有偏移量的字符串分析时区

Java 使用SimpleDataFormat从具有偏移量的字符串分析时区,java,date,parsing,timezone,simpledateformat,Java,Date,Parsing,Timezone,Simpledateformat,我决定问这个问题,因为我在StackOverflow中找不到类似的例子 我想使用SimpleDataFormat解析日期字符串及其时区。我(希望)我仔细阅读了文档,并编写了这个程序来复制这个问题 import java.text.DateFormat; import java.util.Date; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Locale; public

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

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

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表示法时,日期millisconds正确返回为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”,还是我误用了SimpleDataFormat API?

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

现在有点奇怪了。如果您使用诸如GMT或PST之类的时区说明符,这实际上会改变DateFormat的内部时区。我的程序演示了这一点,但我不确定这是什么意思。我相信您错误地使用了API,期望DateFormat对象的时区反映“上次解析的”日期,但正如您的代码和我的代码所示,情况并非如此。事实上,for
DateFormat.parse()
没有提到格式化程序的
时区将如何更改,因此我们不应该依赖此“功能”

事实上,如果我们检查
Date
对象上可用的API,我们会发现时区支持非常差。注意,在我的程序中,
Date.toString()
提供相同的输出,而不管解析的输入字符串是什么。Java很早就认识到了这一点,它在Java1.1中弃用了
Date.getTimezoneOffset()
方法。结论是,
Date
对象只表示距离Epoch的毫秒-不支持时区

这种奇怪的行为,以及许多其他类似的例子,就是为什么您应该避免使用Java7日期和时间处理类的原因。从

一些日期和时间类[在Java7中]也表现出相当糟糕的API设计

如果您能够使用Java8,您会发现
Java.time
包中类的行为设计得更好。我在我的程序中给出了一个例子。如果您不能迁移到Java8,那么这是一条路要走。下面有一个Java8示例,显示了即使在解析之后,时区信息是如何被记住和尊重的。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

这是一个很好且全面的答案,它证实了我的观察。我不可能改用DateTimeFormatter或Joda,所以我必须找到另一种方法来处理这个问题。首先,我至少可以控制输入字符串的格式。