关于java:从字符串中提取TimeZone对象的最佳方法?

关于java:从字符串中提取TimeZone对象的最佳方法?

Best way to extract TimeZone object from a String?

我有一个包含原始日期字段(存储为字符数据)的数据库字段,例如

Friday, September 26, 2008 8:30 PM Eastern Daylight Time

我可以使用SimpleDateFormat轻松将其解析为Date

1
2
DateFormat dbFormatter = new SimpleDateFormat("EEEE, MMMM dd, yyyy hh:mm aa zzzz");
Date scheduledDate = dbFormatter.parse(rawDate);

我想做的是从此字符串中提取一个TimeZone对象。 运行此应用程序的JVM中的默认TimeZone是GMT,所以我不能使用上面解析的Date中的.getTimezoneOffset()(因为它将返回默认的TimeZone)。

除了标记原始字符串并找到时区字符串的开始位置(因为我知道格式将始终为EEEE, MMMM dd, yyyy hh:mm aa zzzz)之外,还有一种方法可以使用DateFormat / SimpleDateFormat / Date / Calendar API提取TimeZone对象- 与我用DateFormat.parse()解析过的String相同的TimeZone?

关于Java API中的DateCalendar的一件令我烦恼的事情是,应该在所有位置替换Date ...但是他们决定,哦,还是让我们继续使用DateDateFormat类中。


我发现以下内容:

1
2
3
4
5
6
7
8
9
        DateFormat dbFormatter = new SimpleDateFormat("EEEE, MMMM dd, yyyy hh:mm aa zzzz");
        dbFormatter.setTimeZone(TimeZone.getTimeZone("America/Chicago"));
        Date scheduledDate = dbFormatter.parse("Friday, September 26, 2008 8:30 PM Eastern Daylight Time");
        System.out.println(scheduledDate);
        System.out.println(dbFormatter.format(scheduledDate));
        TimeZone tz = dbFormatter.getTimeZone();
        System.out.println(tz.getDisplayName());
        dbFormatter.setTimeZone(TimeZone.getTimeZone("America/Chicago"));
        System.out.println(dbFormatter.format(scheduledDate));

产生以下内容:

1
2
3
4
Fri Sep 26 20:30:00 CDT 2008
Friday, September 26, 2008 08:30 PM Eastern Standard Time
Eastern Standard Time
Friday, September 26, 2008 08:30 PM Central Daylight Time

我实际上发现这有些令人惊讶。但是,我想这表明您的问题的答案是在解析后仅在格式化程序上调用getTimeZone。

编辑:
以上是与Sun的JDK 1.6一起运行的。


TL;博士

1
2
3
4
ZonedDateTime.parse(
   "Friday, September 26, 2008 8:30 PM Eastern Daylight Time" ,
    DateTimeFormatter.ofPattern("EEEE, MMMM d, uuuu h:m a zzzz" )
).getZone()

java.time

现代方法是使用java.time类。"问题"和"其他答案"使用麻烦的旧旧日期时间类或Joda-Time项目,现在这两个类都已被java.time类取代。

用格式模式定义DateTimeFormatter对象以匹配您的数据。

1
DateTimeFormatter f = DateTimeFormatter.ofPattern("EEEE, MMMM d, uuuu h:m a zzzz" );

分配Locale可以指定日期名称和月份名称的人类语言,以及其他格式问题的文化规范。

1
f = f.withLocale( Locale.US );

最后,进行解析以获得ZonedDateTime对象。

1
2
String input ="Friday, September 26, 2008 8:30 PM Eastern Daylight Time" ;
ZonedDateTime zdt = ZonedDateTime.parse( input , f );

zdt.toString(): 2008-09-26T20:30-04:00[America/New_York]

您可以从ZonedDateTime(表示为ZoneId对象)中请求时区。如果需要有关时区的更多信息,则可以查询ZoneId

1
ZoneId z = zdt.getZone();

在IdeOne.com上自行查看。

ISO 8601

避免以这种糟糕的格式交换日期时间数据。不要假设英语,不要使用诸如" day-of-day"之类的名称来为输出添加内容,也不要使用诸如Eastern Daylight Time之类的伪时区。

对于时区:以continent/region格式指定正确的时区名称,例如America/MontrealAfrica/CasablancaPacific/Auckland。切勿使用3-4个字母的缩写,例如ESTIST,因为它们不是真实的时区,不是标准化的,甚至不是唯一的(!)。

要将日期时间值序列化为文本,请仅使用ISO 8601格式。解析/生成字符串以表示其值时,java.time类默认使用这些格式。

关于java.time

java.time框架内置于Java 8及更高版本中。这些类取代了麻烦的旧式传统日期时间类,例如java.util.DateCalendarSimpleDateFormat

现在处于维护模式的Joda-Time项目建议迁移到java.time。

要了解更多信息,请参见Oracle教程。并在Stack Overflow中搜索许多示例和说明。规格为JSR 310。

在哪里获取java.time类?

  • Java SE 8和SE 9及更高版本

    • 内置。
    • 标准Java API的一部分,具有捆绑的实现。
    • Java 9添加了一些次要功能和修复。
  • Java SE 6和SE 7

    • java.time的许多功能在ThreeTen-Backport中都被反向移植到Java 6和7。
  • Android的

    • ThreeTenABP项目专门针对Android改编了ThreeTen-Backport(如上所述)。
    • 请参阅如何使用…。

ThreeTen-Extra项目使用其他类扩展了java.time。该项目为将来可能在java.time中添加内容提供了一个试验场。您可能会在这里找到一些有用的类,例如IntervalYearWeekYearQuarter等。


我建议您查看Joda Time日期和时间API。我最近被转换为对此的信奉者,因为它往往比Java中对日期和时间的内置支持要优越得多。特别是,您应该检出DateTimeZone类。希望这可以帮助。

http://joda-time.sourceforge.net/

http://joda-time.sourceforge.net/api-release/index.html


@Ed Thomas:

我尝试了与您的示例非常相似的方法,但结果却截然不同:

1
2
3
4
5
6
7
8
9
10
11
12
String testString ="Friday, September 26, 2008 8:30 PM Pacific Standard Time";
DateFormat df = new SimpleDateFormat("EEEE, MMMM dd, yyyy hh:mm aa zzzz");

System.out.println("The default TimeZone is:" + TimeZone.getDefault().getDisplayName());

System.out.println("DateFormat timezone before parse:" + df.getTimeZone().getDisplayName());

Date date = df.parse(testString);

System.out.println("Parsed [" + testString +"] to Date:" + date);

System.out.println("DateFormat timezone after parse:" + df.getTimeZone().getDisplayName());

输出:

The default TimeZone is: Eastern Standard Time

DateFormat timezone before parse: Eastern Standard Time

Parsed [Friday, September 26, 2008 8:30 PM Pacific Standard Time] to Date: Sat Sep 27 00:30:00 EDT 2008

DateFormat timezone after parse: Eastern Standard Time

似乎DateFormat.getTimeZone()parse()之前和之后返回相同的TimeZone ...,即使我在调用parse()之前抛出了明确的setTimeZone()

查看DateFormat和SimpleDateFormat的源代码,似乎getTimeZone()只是返回基础Calendar的TimeZone ...,它将默认为默认Locale / TimeZone的Calendar,除非您指定要使用的特定日历。


埃德说得对。您需要在解析时间后在DateFormat对象上使用timeZone。

1
2
3
4
5
6
7
 String rawDate ="Friday, September 26, 2008 8:30 PM Eastern Daylight Time";
 DateFormat dbFormatter = new SimpleDateFormat("EEEE, MMMM dd, yyyy hh:mm aa zzzz");
 Date scheduledDate = dbFormatter.parse(rawDate);

 System.out.println(rawDate);
 System.out.println(scheduledDate);
 System.out.println(dbFormatter.getTimeZone().getDisplayName());

产生

1
2
3
Friday, September 26, 2008 8:30 PM Eastern Daylight Time
Fri Sep 26 20:30:00 CDT 2008
Eastern Standard Time

日期和日历之间的主要区别在于,日期只是一个值对象,没有修改它的方法。因此,它旨在将日期/时间信息存储在某个地方。如果使用Calendar对象,则可以在将其设置为使用日期/时间信息执行某些业务逻辑的持久实体后,对其进行修改。这非常危险,因为实体无法识别此更改。
Calendar类设计用于按日期/时间进行操作,例如添加天数之类。

玩一下您的示例,我得到以下信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;

public class TimeZoneExtracter {

    public static final void main(String[] args) throws ParseException {
        DateFormat dbFormatter = new SimpleDateFormat("EEEE, MMMM dd, yyyy hh:mm aa zzzz");
        System.out.println(dbFormatter.getTimeZone());
        dbFormatter.parse("Fr, September 26, 2008 8:30 PM Eastern Daylight Time");
        System.out.println(dbFormatter.getTimeZone());
    }

}

输出:

sun.util.calendar.ZoneInfo[id="Europe/Berlin"...
sun.util.calendar.ZoneInfo[id="Africa/Addis_Ababa"...

这是您想要的结果吗?


作为部分解决方案,您可以使用RegEx匹配来获取时区,因为您之前总是会有相同的文本。上午或下午。

我对Java时区了解不足,无法完全了解它。


推荐阅读