从Joda时间库迁移到Java时间(Java 8)

5

我正在尝试从Joda time库迁移到Java时间(Java 8)。 我无法找到java.time中等效的ISODateTimeFormat.dateOptionalTimeParser()

Joda ISO格式化程序有很好的解析器:

ISODateTimeFormat.dateTimeParser():通用-根据解析的字符串选择解析器。 类似地: ISODateTimeFormat.dateOptionalTimeParser()

我发现将Joda时间更改为Java时间很困难。 能否有人指导我?

示例:

String dateTimeString = "2015-01-01T12:29:22+00:00"; 
String dateTimeString2 = "2015-01-01T12:29:22";

当我使用joda time解析这个字符串时,
ISODateTimeFormat.dateTimeParser().withZone("EST")

可以处理两者而不会出现问题。在Java中,这相当于什么时间?

使用Java 8,ZonedDateTime与ISO_Zoned_date_time无法同时处理两者。


请问您为什么想要迁移?在 Joda-Time 中是否有特殊的问题,您希望通过迁移来解决?正如您在我的回答中所看到的,迁移在实际操作中可能是一个挑战。如果您在 Joda-Time 中没有任何特别的问题,只是想迁移因为 Java-8 更加“现代”,那么这可能不值得所有的努力。 - Meno Hochschild
是的,你说得对。我们想要使用Java 8的所有现代特性,这就是为什么我们正在迁移到Java 8的原因。 - Shishir
3
从joda-time网站上可以看到:从Java SE 8开始,建议用户迁移到java.time(JSR-310)。此外,注意到Joda-Time被认为是一个大体“完成”的项目,不再计划进行重大的增强。这些建议都出现在主页上。如果您使用Java 8或更高版本,则最好进行迁移。 - Bill Turner
@BillTurner 我非常清楚这些官方建议,这使得Joda-Time事实上已经过时。然而,在一个基于Joda的大型软件上进行额外的迁移工作仍然需要有正当理由,不仅仅是因为“转向更现代的库”。支持可能会丢失的功能(例如间隔或持续时间/期间格式)以及详细信息中的意外行为变化(请参见我的答案)也是重要问题。 - Meno Hochschild
2个回答

11

你不能使用预定义的格式化程序,但是可以使用以下模式构建自己的格式化程序(并将其分配给静态常量):

static final DateTimeFormatter DATE_TIME_OPTIONAL_OFFSET =
    DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss[xxx]");

注意:如果您解析的输入只包含日期和时间,但没有偏移量(也没有任何偏移量/区域默认值),那么结果只能是LocalDateTime,而不是全局时间戳。

还请注意withZone(...)方法的不同行为。

Joda-Time

When parsing, this zone will be set on the parsed datetime.     
A null zone means of no-override. If both an override chronology
and an override zone are set, the override zone will take precedence
over the zone in the chronology.

Java-8 (JSR-310)

When parsing, there are two distinct cases to consider.
If a zone has been parsed directly from the text, perhaps because 
DateTimeFormatterBuilder.appendZoneId() was used, then this override Zone
has no effect. If no zone has been parsed, then this override zone will
be included in the result of the parse where it can be used to build
instants and date-times.

顺便提一下:Joda-Time的方法withOffsetParsed()更接近于Java-8的行为。

更新:我现在已经进行了自己的测试。请查看有时会令人惊讶的结果。

System.out.println(System.getProperty("java.version")); // 1.8.0_31

// parsing s1 with offset = UTC
String s1 = "2015-01-01T12:29:22+00:00"; 

OffsetDateTime odt1 = DATE_TIME_OPTIONAL_OFFSET.parse(s1, OffsetDateTime::from);
System.out.println(odt1); // 2015-01-01T12:29:22Z --- OK

LocalDateTime ldt1 = DATE_TIME_OPTIONAL_OFFSET.parse(s1, LocalDateTime::from);
System.out.println(ldt1); // 2015-01-01T12:29:22 --- OK

ZonedDateTime zdt1 = DATE_TIME_OPTIONAL_OFFSET.withZone(ZoneId.of("America/New_York")).parse(s1, ZonedDateTime::from);
System.out.println(zdt1); // 2015-01-01T12:29:22-05:00[America/New_York] --- seems to be a bug compared with the spec above, the parsed offset was overridden!!!

// now parsing s2 without offset
String s2 = "2015-01-01T12:29:22";

OffsetDateTime odt2 = DATE_TIME_OPTIONAL_OFFSET.parse(s2, OffsetDateTime::from);
System.out.println(odt2); // 2015-01-01T12:29:22Z --- questionable, the offset Z is invented/guessed here

LocalDateTime ldt2 = DATE_TIME_OPTIONAL_OFFSET.parse(s2, LocalDateTime::from);
System.out.println(ldt2); // 2015-01-01T12:29:22 --- OK

DATE_TIME_OPTIONAL_OFFSET.withZone(ZoneId.of("America/New_York")).parse(s2, ZonedDateTime::from);
// throws an exception --- seems to be a bug compared with the spec above, the zone set was not accepted

结论:

在迁移时要小心。魔鬼就在细节中。也许更新的Java版本8u40已经修复了一些问题(至少withZone()的行为可能已经得到了纠正-请参见JDK-issue 8033662,但对于8u31,回退修复似乎缺失?!)。您还应该注意,在我的测试中,标记为“EST”的“时区”已被替换为“America / New_York”,因为“EST”不是一个被认可的时区ID(它只是美国本地化的时区名称缩写)。

更新-最终解决方案

经过额外的测试,此代码似乎在Java 8u31中运行良好(假设默认情况下为UTC,如果输入中缺少偏移量):

static final DateTimeFormatter DATE_TIME_OPTIONAL_OFFSET =
    DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss[xxx]");      
OffsetDateTime odt = 
  DATE_TIME_OPTIONAL_OFFSET.withZone(ZoneOffset.UTC).parse(input, OffsetDateTime::from);
ZonedDateTime zdt = odt.toZonedDateTime(); // containing a fixed offset

0
我曾经遇到过类似的问题,试图将ISODateTimeFormat.dateTimeParser().parseDateTime("...")转换为基于Java 8 java.time设施的等效格式。最终,我未能使用DateTimeFormatter复制Joda-Time的ISODateTimeFormat行为,而是选择了基于正则表达式的方法:
private static final Pattern ISO_8601_PARSE = Pattern.compile(
        "(?<year>\\d{1,4})-(?<month>\\d{1,2})-(?<day>\\d{1,2})"
        + "(T((?<hour>\\d{1,2})(\\:(?<minute>\\d{1,2})(\\:(?<second>\\d{1,2})(\\.(?<millis>\\d{1,3}))?Z?)?)?)?)?");

public static Date parseIso8601Date(String date) throws IllegalArgumentException {
    Matcher matcher = ISO_8601_PARSE.matcher(date);
    if (matcher.matches()) {
        try {
            String day = matcher.group("day");
            String month = matcher.group("month");
            String year = matcher.group("year");
            String hour = matcher.group("hour");
            String minute = matcher.group("minute");
            String second = matcher.group("second");
            String millis = matcher.group("millis");
            return Date.from(ZonedDateTime.of(
                    Integer.valueOf(year),
                    Integer.valueOf(month),
                    Integer.valueOf(day),
                    hasText(hour) ? Integer.valueOf(hour) : 0,
                    hasText(minute) ? Integer.valueOf(minute) : 0,
                    hasText(second) ? Integer.valueOf(second) : 0,
                    (hasText(millis) ? Integer.valueOf(millis) : 0) * 1000000, // nanoOfSecond
                    ZoneOffset.UTC).toInstant());
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("Failed to parse [" + date + "]: " + e, e);
        }
    } else {
        throw new IllegalArgumentException("Failed to parse [" + date + "]; does not match pattern yyyy-MM-ddThh:mm:ss[.SSS]Z");
    }
}

这还不是100%等效的(即它不支持“+00:00”样式的时区偏移,而是假定为UTC),但在解析字符串时它非常宽容,相当接近。


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