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

Java 8 从Joda时间库迁移到Java时间(Java 8),java-8,jodatime,Java 8,Jodatime,我正在尝试从Joda时间库迁移到Java时间(Java8)。 在java.time Joda ISO格式化程序有很好的解析器: ISODateTimeFormat.dateTimeParser():泛型-根据解析的字符串选择解析器。 同样地: ISODateTimeFormat.dateOptionalTimeParser() 我发现很难将Joda time更改为java.time。 有人能指引我吗 例如: String dateTimeString = "2015-01-01T12:29:22

我正在尝试从Joda时间库迁移到Java时间(Java8)。 在
java.time

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

ISODateTimeFormat.dateTimeParser()
:泛型-根据解析的字符串选择解析器。 同样地:
ISODateTimeFormat.dateOptionalTimeParser()

我发现很难将Joda time更改为java.time。 有人能指引我吗

例如:

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,带ISO_Zoned_date_time的ZonedDateTime无法同时处理这两个问题。

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

static final DateTimeFormatter DATE_TIME_OPTIONAL_OFFSET =
    DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss[xxx]");
注意:如果解析的输入只包含日期和时间,但没有偏移量(并且没有任何偏移量/区域默认值),则结果只能是
LocalDateTime
,而不是全局时间戳

还请注意方法
与区域(…)
的不同行为

乔达时间

Java-8(JSR-310)

旁注:Joda-Time方法
withOffsetParsed()
更接近Java-8-Behavior

更新:我现在已经完成了自己的测试。看到有时令人惊讶的结果

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()
的行为可能已得到纠正-请参阅,但对于8u31,似乎缺少后端口修复程序?!)。您还应该注意,在我的测试中,您的标有“EST”的“时区”被替换为“America/New_York”,因为“EST”不是一个公认的时区id(在美国是一个本地化的时区名称缩写)

更新-最终解决方案

经过额外测试后,该代码似乎在Java 8u31中工作(假设输入中缺少偏移量时UTC为默认值):


我在尝试将ISODateTimeFormat.dateTimeParser().parseDateTime(“…”)转换为基于Java8Java.time工具的等效版本时遇到了类似的问题。 最后,我未能使用DateTimeFormatter重现Joda Time的ISODateTimeFormat的行为,而是选择了基于regexp的方法:

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");
    }
}
private静态最终模式ISO_8601_PARSE=Pattern.compile(
“(?\\d{1,4})-(?\\d{1,2})-(?\\d{1,2})”
+“(T((?\\d{1,2})(\\:(?\\d{1,2})(\\:(?\\d{1,2})(\\.(?\\d{1,3}))?Z?);
公共静态日期parseIso8601Date(字符串日期)引发IllegalArgumentException{
Matcher Matcher=ISO_8601_PARSE.Matcher(日期);
if(matcher.matches()){
试一试{
字符串日期=matcher.group(“日期”);
字符串month=matcher.group(“月”);
字符串年份=匹配器组(“年份”);
字符串hour=matcher.group(“hour”);
字符串minute=matcher.group(“minute”);
字符串second=matcher.group(“second”);
字符串millis=matcher.group(“millis”);
返回日期。从(ZoneDateTime.of)(
整数值(年),
整数。值of(月),
整数值(天),
hasText(小时)?整数值(小时):0,
hasText(分钟)?整数值(分钟):0,
hasText(秒)?整数值(秒):0,
(hasText(毫秒)?整数值(毫秒):0)*1000000,//毫微秒
UTC.toInstant());
}捕获(数字格式){
抛出新的IllegalArgumentException(“未能解析[“+date+”]:”+e,e);
}
}否则{
抛出新的IllegalArgumentException(“未能解析[“+date+”];与模式yyyy MM ddThh:MM:ss[.SSS]Z]不匹配”);
}
}

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

我可以问一下为什么要迁移吗?在Joda时代,您是否希望通过迁移解决一个特殊的问题?正如您在我的回答中所看到的,迁移在实际操作中可能是一个挑战。如果您对Joda Time没有任何特别的问题,只是因为Java-8更“现代”而想迁移,那么这可能不值得所有的努力。是的,您是对的。我们希望使用Java 8的所有现代功能,这就是我们迁移到Java 8的原因。@MenoHochschild从Java SE 8开始的joda time站点,要求用户迁移到Java.time(JSR-310)。此外,从现场可以看出,Joda Time被认为是一个基本上“已完成”的项目。没有计划进行重大改进。如果使用JavaSE8,请迁移到Java.time(JSR-310)。这些建议都出现在主页上。如果使用Java8(或更高版本),迁移是明智的。@BillTurner我非常清楚这些官方建议,它们使Joda Time事实上过时了。尽管如此,在一个更大的基于Joda的软件上进行额外的迁移工作仍然是一个需要证明的决定,不仅仅是“去一个更现代化的图书馆”。支持可能丢失的功能(例如间隔或持续时间/期间格式)和细节上的意外行为更改(参见我的答案)也是重要的问题。
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
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
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");
    }
}