使用Joda处理时区偏移过渡和夏令时

YB *_*uta 5 java timezone jodatime timezone-offset

我试图解析datetime字符串并创建Joda DateTime对象。

我的数据来自存储日期时间字符串而不指定时区/偏移量的旧数据库。尽管没有存储日期时间字符串的时区/偏移量,但这是旧系统的业务规则,即所有日期时间都存储在东部时间中。不幸的是,我无权更新旧版数据库存储日期时间字符串的方式。

因此,我使用JODA的“美国/东部”时区解析日期时间字符串。

当dateTime字符串落在启用夏令时时“消失”的小时之内,则此方法将引发llegalInstance异常。

我创建了以下示例代码来演示此行为并展示我建议的解决方法。

public class FooBar {
public static final DateTimeZone EST = DateTimeZone.forID("EST");
public static final DateTimeZone EASTERN = DateTimeZone.forID("US/Eastern");

public static final DateTimeFormatter EST_FORMATTER = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS").withZone(EST);
public static final DateTimeFormatter EASTERN_FORMATTER = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS").withZone(EASTERN);


public static void main(String[] args) {
    final String[] listOfDateTimeStrings = {"2014-03-09 02:00:00.000", "2014-03-08 02:00:00.000"}; 

    System.out.println(" *********** 1st attempt  *********** ");
    for (String dateTimeString: listOfDateTimeStrings){
        try{
            final DateTime dateTime = DateTime.parse(dateTimeString, EASTERN_FORMATTER);
            System.out.println(dateTime);       
        }
        catch(Exception e){
            System.out.println(e.getMessage());
        }
    }

    System.out.println(" *********** 2nd attempt  *********** ");
    for (String dateTimeString: listOfDateTimeStrings){
        try{
            final DateTime dateTime = DateTime.parse(dateTimeString, EST_FORMATTER);
            System.out.println(dateTime);       
        }
        catch(Exception e){
            System.out.println(e.getMessage());
        }
    }

    System.out.println(" *********** 3rd attempt  *********** ");
    for (String dateTimeString: listOfDateTimeStrings){
        try{
            DateTime dateTime = DateTime.parse(dateTimeString, EST_FORMATTER);
            dateTime = dateTime.withZone(EASTERN);
            System.out.println(dateTime);       
        }
        catch(Exception e){
            System.out.println(e.getMessage());
        }
    }       

}
Run Code Online (Sandbox Code Playgroud)

}

产生的输出:

 ***********第一次尝试*********** 
无法解析“ 2014-03-09 02:00:00.000”:由于时区偏移转换,瞬间不合法(美国/纽约)
2014-03-08T02:00:00.000-05:00
 ***********第二次尝试*********** 
2014-03-09T02:00:00.000-05:00
2014-03-08T02:00:00.000-05:00
 ***********第三次尝试*********** 
2014-03-09T03:00:00.000-04:00
2014-03-08T02:00:00.000-05:00

在“第三次尝试”中,我得到了预期的结果:第一个日期时间的偏移量为-04:00。因为它落在DST的2015年第一个小时之内。第二个时间戳的偏移量是-05:00,因为它不在DST范围内。

这样做是安全的:

DateTime dateTime = DateTime.parse(dateTimeString, A_FORMATTER_WITH_TIME_ZONE_A);
dateTime = dateTime.withZone(TIME_ZONE_B);
Run Code Online (Sandbox Code Playgroud)

我已经用日期时间字符串和时区的几种不同组合测试了此代码(到目前为止,它适用于所有测试用例),但是我想知道是否有更多Joda经验的人会发现这种方法有什么错误/危险。

还是可以选择:是否有更好的方法来处理Joda的时区偏移过渡?

Men*_*ild 3

当心。withZone(...)方法的行为记录如下:

返回具有不同时区的该日期时间的副本,保留毫秒时刻。

记住这一点,您必须了解 EST 和“America/New_York”(比过时的 ID“US/Eastern”更好)并不相同。第一个 (EST) 具有固定偏移(无 DST),但第二个具有 DST,包括可能的间隙。如果您确定,您应该只应用 EST 作为东部的替代品

a)您已经处于异常模式(通常应该接受东部区域解析的日期时间,而无需重复解析,否则应用 EST 会伪造解析的瞬间),

b) 您知道在第二次(和第三次)尝试中选择 EST 是选择 DST 转换后的瞬间。

关于此限制/约束,您的解决方法将起作用(但仅适用于特殊对 EST 与 America/New_York)。就我个人而言,我发现使用基于异常的逻辑来解决 Joda-Time 的严重限制是令人恐惧的。作为反例,新的 JSR-310 在处理间隙时不使用异常策略,而是选择间隙大小向前推动的间隙后的稍后时刻的策略(就像旧的java.util.Calendar-stuff 一样)。

我建议您首先遵循@Jim Garrison 的建议,看看是否可以纠正错误的数据,然后再应用这样的解决方法(我对他的答案投赞成票)。

阅读原始规格要求后更新(已过时 - 见下文):

如果遗留系统的规范规定所有时间都存储在 EST 中,那么您应该以这种方式解析它,并且根本不使用“America/New_York”进行解析。相反,您可以在第二阶段将解析的 EST 时刻转换为纽约时间(使用withZone(EASTERN))。这样您将不会有任何异常逻辑,因为(解析的)时刻始终可以以明确的方式转换为本地时间表示(类型 的解析瞬间DateTime,转换后的结果包含本地时间)代码示例:

public static final DateTimeZone EST = DateTimeZone.forID("EST");
public static final DateTimeZone EASTERN = DateTimeZone.forID("US/Eastern");
public static final DateTimeFormatter EST_FORMATTER = 
  DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS").withZone(EST);

// in your parsing method...
String input = "2014-03-09 02:00:00.000";
DateTime dt = EST_FORMATTER.parseDateTime(input);
System.out.println(dt); // 2014-03-09T02:00:00.000-05:00
System.out.println(dt.withZone(EASTERN)); // 2014-03-09T03:00:00.000-04:00
Run Code Online (Sandbox Code Playgroud)

OP 评论和澄清后更新:

现在已确认旧系统不会在 EST 中存储时间戳(具有固定偏移量 UTC-05,但位于东部区域(“America/New_York”,具有 EST 或 EDT 的可变偏移量)。首先应采取的措施是联系供应商无效的时间戳,以查看他们是否可以纠正数据。否则,您可以使用以下解决方法:

关于您的输入包含没有任何偏移或区域信息的时间戳这一事实,我建议首先解析为LocalDateTime.

=> 静态初始化部分

// Joda-Time cannot parse "EDT" so we use hard-coded offsets
public static final DateTimeZone EST = DateTimeZone.forOffsetHours(-5);
public static final DateTimeZone EDT = DateTimeZone.forOffsetHours(-4);

public static final DateTimeZone EASTERN = DateTimeZone.forID("America/New_York");
public static final org.joda.time.format.DateTimeFormatter FORMATTER = 
    org.joda.time.format.DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS");
Run Code Online (Sandbox Code Playgroud)

=> 在你的解析方法中

String input = "2014-03-09 02:00:00.000";
LocalDateTime ldt = FORMATTER.parseLocalDateTime(input); // always working
System.out.println(ldt); // 2014-03-09T02:00:00.000
DateTime result;

try {
    result = ldt.toDateTime(EASTERN);
} catch (IllegalInstantException ex) {
    result = ldt.plusHours(1).toDateTime(EDT); // simulates a PUSH-FORWARD-strategy at gap
    // result = ldt.toDateTime(EST); // the same instant but finally display with EST offset
}
System.out.println(result); // 2014-03-09T03:00:00.000-04:00
// if you had chosen <<<ldt.toDateTime(EST)>>> then: 2014-03-09T02:00:00.000-05:00
Run Code Online (Sandbox Code Playgroud)

由于OP最后评论的另一个澄清:

生成 a的方法toDateTime(DateTimeZone)DateTime记录如下:

在夏令时重叠的情况下,当相同的当地时间出现两次时,此方法返回第一次出现的当地时间。

换句话说,它会在重叠的情况下(秋季)选择较早的偏移量。所以不需要打电话

result = ldt.toDateTime(EASTERN).withEarlierOffsetAtOverlap();
Run Code Online (Sandbox Code Playgroud)

但是,它在这里不会造成任何损害,并且出于文档的原因,您可能更喜欢它。另一方面:调用异常处理(对于间隙)没有任何意义

result = ldt.toDateTime(EDT).withEarlierOffsetAtOverlap();
Run Code Online (Sandbox Code Playgroud)

因为 EDT(以及 EST)是一个固定的偏移量,永远不会发生重叠。所以这里的方法withEarlierOffsetAtOverlap()不做任何事情。此外:在 EDT 的情况下忽略修正ldt.plusHours(1)是不行的,并且会产生另一个瞬间。在写这个额外的解释之前,我已经测试过,但是当然,您可以使用替代方案ldt.toDateTime(EST)来实现您想要的(EDT!= EST,但经过更正,plusHours(1)您会得到相同的瞬间)。我刚刚注意到 EDT 示例演示了如何精确建模标准 JDK 行为。在解决差距(EDT 或 EST)时,您更喜欢哪种偏移取决于您,但在这里获得相同的时刻至关重要(ldt.plusHours(1).toDateTime(EDT)result = ldt.toDateTime(EST))。

  • 虽然“EST”技术上意味着 UTC-5,但尚不清楚 OP 是否理解这一点。他可能确实意味着数据存储在“东部时间”,包括东部时间和东部时间。如果能得到澄清就好了。 (2认同)