从字符串中提取TimeZone对象的最佳方法是什么?

9

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

2008年9月26日星期五晚上8:30美国东部夏令时

我可以使用SimpleDateFormat轻松解析它作为Date。

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

我想做的是从这个字符串中提取一个TimeZone对象。在JVM中,应用程序运行的默认时区是GMT,因此我不能使用上面解析的Date的.getTimezoneOffset()(因为它将返回默认的TimeZone)。
除了对原始字符串进行标记化并找到时区字符串的起始位置(因为我知道格式将始终为EEEE,MMMM dd,yyyy hh:mm aa zzzz),是否有一种使用DateFormat/SimpleDateFormat/Date/Calendar API来提取TimeZone对象的方法 - 它将具有与我使用DateFormat.parse()分解的字符串相同的时区?
Java API中关于Date和Calendar的一个问题是,Calendar应该在所有地方替换Date...但然后他们决定,嘿,让我们仍然在DateFormat类中使用Date。

你解决了这个问题吗? - pranjaljain
关于最后一段,DateCalendar类是一个混乱的烂摊子:设计不良、令人困惑和麻烦。避免使用它们,改用java.time类。 - Basil Bourque
7个回答

2
我发现以下内容:
        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));

生成以下内容:

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运行的。


我也对此感到惊讶,并且由于在日期实例化之前出现时区滑动问题而遇到了麻烦。 - 18Rabbit
只是好奇 - 您的计算机上 TimeZone.getDefault() 的结果是什么? - matt b

1

tl;dr

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对象以匹配您的数据。
DateTimeFormatter f = DateTimeFormatter.ofPattern( "EEEE, MMMM d, uuuu h:m a zzzz" );

指定一个 Locale 来确定日期名称和月份名称所使用的人类语言,以及其他格式问题的文化规范。

f = f.withLocale( Locale.US );

最后,进行解析以获取一个ZonedDateTime对象。
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]

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

ZoneId z = zdt.getZone();

在IdeOne.com上亲自查看

ISO 8601

避免使用这种糟糕的日期时间格式进行交换。不要假设英语,不要用像星期几这样的东西来装饰你的输出,也永远不要使用伪时区,比如Eastern Daylight Time

对于时区:以continent/region的格式指定正确的时区名称,例如{{link3:America/Montreal}},{{link4:Africa/Casablanca}}或Pacific/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类?

ThreeTen-Extra项目通过增加类扩展了java.time。该项目是java.time可能未来添加内容的试验场。您可能会在此处找到一些有用的类,例如Interval, YearWeek, YearQuarter以及更多


1

@Ed Thomas:

我尝试了与你的例子非常相似的东西,但结果却非常不同:

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

输出:

默认时区为:东部标准时间

解析前的日期格式时区为:东部标准时间

将 [Friday, September 26, 2008 8:30 PM Pacific Standard Time] 解析为日期:Sat Sep 27 00:30:00 EDT 2008

解析后的日期格式时区为:东部标准时间

看起来 DateFormat.getTimeZone()parse() 前后返回的时区是相同的... 即使在调用 parse() 前加入了一个显式的 setTimeZone()

查看 DateFormat 和 SimpleDateFormat 的源代码,似乎 getTimeZone() 只是返回底层日历的时区... 如果您没有指定要使用的特定时区,它将默认为默认语言环境/时区的日历。


Java版本“1.4.2_14” Java(TM) 2标准版运行时环境(版本1.4.2_14-b05) Java HotSpot(TM)客户端虚拟机(版本1.4.2_14-b05,混合模式) - matt b

1

Joda-Time 项目现已进入维护模式。其主要作者 Stephen Colebourne 现已领导 JSR 310 及其实现,即 OpenJDK 中的 java.time 类,这是 Joda-Time 的继任者。 - Basil Bourque

0
主要区别在于Date和Calendar之间,Date只是一个没有修改方法的值对象。因此,它被设计用于在某个地方存储日期/时间信息。如果您使用Calendar对象,则可以在将其设置为执行一些业务逻辑的持久实体后对其进行修改日期/时间信息。这非常危险,因为实体无法识别此更改。
Calendar类是为日期/时间操作而设计的,例如添加天数或类似操作。
通过您的示例进行测试,我得到了以下结果:
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="欧洲/柏林"... sun.util.calendar.ZoneInfo[id="非洲/亚的斯亚贝巴"...

这是你想要的结果吗?


0

Ed 是对的。在解析时间后,您需要在 DateFormat 对象上设置 timeZone。

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

生成

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

2
你可能想要使用除了“Eastern Daylight Time”以外的其他日期字符串进行测试 - dbFormatter.getTimeZone() 只会返回默认的(你的)时区。 - matt b

0

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

我对Java时区不太了解,无法帮您解决最后一部分。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接