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

时间:2016-01-05 17:00:34

标签: java timezone jodatime timezone-offset

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

我的数据来自遗留数据库,该数据库存储日期时间字符串而不指定时区/偏移量。虽然未存储日期时间字符串的时区/偏移量,但遗留系统的业务规则是所有日期时间都存储在东部时间。遗憾的是,我没有权限更新旧数据库存储日期时间字符串的方式。

因此,我使用JODA" US / Eastern"解析日期时间字符串。时区。

当dateTime字符串落在"消失的时间内时,此方法会抛出illegalInstance异常。当夏令时开启时。

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

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

}

}

产生的结果:

 *********** 1st attempt  *********** 
Cannot parse "2014-03-09 02:00:00.000": Illegal instant due to time zone offset transition (America/New_York)
2014-03-08T02:00:00.000-05:00
 *********** 2nd attempt  *********** 
2014-03-09T02:00:00.000-05:00
2014-03-08T02:00:00.000-05:00
 *********** 3rd attempt  *********** 
2014-03-09T03:00:00.000-04:00
2014-03-08T02:00:00.000-05:00

第三次尝试"我得到了预期的结果:第一个datetime的偏移量为-04:00。因为它属于2015年夏令时的第一个小时。第二个时间戳的偏移量为-05:00,因为它超出了夏令时。

这样做是安全的:

DateTime dateTime = DateTime.parse(dateTimeString, A_FORMATTER_WITH_TIME_ZONE_A);
dateTime = dateTime.withZone(TIME_ZONE_B);

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

或者说:有没有更好的方法来处理Joda的时区偏移过渡?

2 个答案:

答案 0 :(得分:3)

你的问题归结为

  

我必须处理技术上无效的时间戳值并一致地解释它们

你用它们做什么完全取决于你的要求。如果您正在为雇主编写软件,则项目所有者必须是做出该决定的人。如果设计人员/架构师未指定无效输入,那么您作为开发人员尚无权决定如何处理无效输入。

我建议您回到项目所有者/经理,告知他们问题(输入包含实际不存在的日期/时间戳)并让他们决定如何处理它们。 / p>

答案 1 :(得分: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 - 东西)。 / p>

我建议你首先按照@Jim Garrison的建议,看看在应用这样的解决方法之前是否可以纠正这些废弃的数据(我的upvote答案)。

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

如果遗留系统的规范说所有时间都存储在EST 中,那么您应该以这种方式解析它,而不是使用“America / New_York”进行解析。相反,您可以在第二阶段将解析的EST时刻转换为New-York-time(使用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

在评论和澄清OP之后更新:

现在确认遗留系统不会在EST中存储时间戳(固定偏移量为UTC-05,但在EASTERN区域(“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");

=>在你的解析方法

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

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

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

  

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

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

result = ldt.toDateTime(EASTERN).withEarlierOffsetAtOverlap();

但是,它在这里没有任何伤害,为了记录起见,您可能更喜欢它。另一方面:调用异常处理(间隙)

没有任何意义
result = ldt.toDateTime(EDT).withEarlierOffsetAtOverlap();

因为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))。

相关问题