首先,如果您要开始一个新项目,我建议您使用新的日期时间 API 而不是 joda-time(更多内容见下文)。无论如何,这是两者的解决方案。
乔达时间
问题在于模式Z是偏移量(格式如+0000或-0100),但字符串EST是时区短名称,由模式z解析(更多详情请查看jodatime javadoc)。
因此,您需要一个带有可选部分的模式,它可以同时接收一个或另一个。您可以使用 org.joda.time.format.DateTimeFormatterBuilder 类来做到这一点。
首先,您需要创建两个org.joda.time.format.DateTimeParser 实例(一个用于Z,另一个用于z),并将它们添加为可选解析器。然后使用下面的代码创建org.joda.time.format.DateTimeFormatter。请注意,我还使用了java.util.Locale,只是为了确保它正确解析工作日和月份名称(这样您就不必依赖默认语言环境,这可能因每个系统/机器而异):
// offset parser (for "+0000")
DateTimeParser offsetParser = new DateTimeFormatterBuilder().appendPattern("Z").toParser();
// timezone name parser (for "EST")
DateTimeParser zoneNameParser = new DateTimeFormatterBuilder().appendPattern("z").toParser();
// formatter for both patterns
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
// append common pattern
.appendPattern("EEE, d MMM yyyy HH:mm:ss ")
// optional offset
.appendOptional(offsetParser)
// optional timezone name
.appendOptional(zoneNameParser)
// create formatter (use English Locale to make sure it parses weekdays and month names independent of JVM config)
.toFormatter().withLocale(Locale.ENGLISH)
// make sure the offset "+0000" is parsed
.withOffsetParsed();
// parse the strings
DateTime est = fmt.parseDateTime("Sat, 10 Jun 2017 12:49:45 EST");
DateTime utc = fmt.parseDateTime("Sun, 11 Jun 2017 18:18:23 +0000");
System.out.println(est);
System.out.println(utc);
输出将是:
2017-06-10T12:49:45.000-04:00
2017-06-11T18:18:23.000Z
如果它们与您的预期不完全一样(或者您仍然遇到错误),请参阅下面的说明。
注意事项:
-
请注意,EST 被打印为带有偏移量 -0400 的日期/时间。那是因为EST 内部变成了America/New_York 时区,现在是夏令时,它的偏移量是-0400(我可以通过DateTimeZone.forTimeZone(TimeZone.getTimeZone("EST")) 来解决这个问题。问题是:这些三个字母的名称是@987654322 @,而 joda-time 只是为它们假设一个“默认值”。因此,如果您不期望这个时区,并且您不想依赖默认值,您可以使用具有自定义值的地图,如下所示:
// mapping EST to some other timezone (I know it's wrong and Chicago is not EST, it's just an example)
Map<String, DateTimeZone> map = new LinkedHashMap<>();
map.put("EST", DateTimeZone.forID("America/Chicago"));
// parser for my custom map
DateTimeParser customTimeZoneParser = new DateTimeFormatterBuilder().appendTimeZoneShortName(map).toParser();
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
// append common pattern
.appendPattern("EEE, d MMM yyyy HH:mm:ss ")
// optional offset
.appendOptional(offsetParser)
// optional custom timezone name
.appendOptional(customTimeZoneParser)
// optional timezone name (accepts all others that are not in the map)
.appendOptional(zoneNameParser)
// create formatter (use English Locale to make sure it parses weekdays and month names independent of JVM config)
.toFormatter().withLocale(Locale.ENGLISH)
// make sure the offset "+0000" is parsed
.withOffsetParsed();
System.out.println(fmt.parseDateTime("Sat, 10 Jun 2017 12:49:45 EST"));
这会将EST 解析为America/Chicago(我知道这是错误的,芝加哥不是EST,这只是一个示例,说明如何使用地图更改默认值),输出将是:
2017-06-10T12:49:45.000-05:00
如果您在上面的第一个代码中遇到错误,您也可以使用它,将 EST 映射到所需的时区(取决于您使用的 jodatime 和 Java 版本,EST 可能不会映射到默认值并引发异常,因此使用自定义映射可以避免这种情况)。
新的日期时间 API
正如@Ole V.V.'s comment 中所说的(我昨天没有时间写),joda-time 正在被new Java's Date and Time API 取代,这要优于compared to the old Date and SimpleDateFormat classes。
如果您使用的是 Java >= 8,则 java.time 包已经是 JDK 的一部分。对于 Java ThreeTen Backport。对于Android,还有ThreeTenABP(更多关于如何使用它here)。
如果您要开始一个新项目,请考虑使用新 API 而不是 joda-time,因为在 joda's website 中它说:请注意,Joda-Time 被认为是一个基本上“完成”的项目。没有计划进行重大改进。如果使用 Java SE 8,请迁移到 java.time (JSR-310)。
下面的代码适用于两者。唯一的区别是包名(在 Java 8 中是 java.time,而在 ThreeTen Backport(或 Android 的 ThreeTenABP)中是 org.threeten.bp),但类和方法 names 是相同的。
这个想法与jodatime非常相似,只是有细微的差别:
- 您可以使用可选的部分分隔符
[]
- 需要一组具有自定义时区名称(将
EST 映射到某个有效的非模糊时区)(因为EST 未映射到任何默认值)
- 使用了一个新类:
ZonedDateTime,它表示带有时区的日期和时间(因此它涵盖了您的两种情况)
只是提醒这些类在 java.time 包中(或在 org.threeten.bp 中,具体取决于您使用的 Java 版本,如上所述):
// set with custom timezone names
Set<ZoneId> set = new HashSet<>();
// when parsing, ambiguous EST uses to New York
set.add(ZoneId.of("America/New_York"));
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
// append pattern, with optional offset (delimited by [])
.appendPattern("EEE, d MMM yyyy HH:mm:ss[ Z]")
// append optional timezone name with custom set for EST
.optionalStart().appendLiteral(" ").appendZoneText(TextStyle.SHORT, set).optionalEnd()
// create formatter using English locale to make sure it parses weekdays and month names correctly
.toFormatter(Locale.ENGLISH);
ZonedDateTime est = ZonedDateTime.parse("Sat, 10 Jun 2017 12:49:45 EST", fmt);
ZonedDateTime utc = ZonedDateTime.parse("Sun, 11 Jun 2017 18:18:23 +0000", fmt);
System.out.println(est); // 2017-06-10T12:49:45-04:00[America/New_York]
System.out.println(utc); // 2017-06-11T18:18:23Z
输出将是:
2017-06-10T12:49:45-04:00[美国/纽约]
2017-06-11T18:18:23Z
请注意,在第一种情况下,EST 被设置为 America/New_York(由自定义集配置)。 appendZoneText 可以做到这一点,它使用自定义集中的值来解决模棱两可的情况。
第二种情况设置为UTC,因为偏移量是+0000。
如果您想将第一个对象转换为 UTC,这很简单:
System.out.println(est.withZoneSameInstant(ZoneOffset.UTC)); // 2017-06-10T16:49:45Z
输出将是纽约的日期/时间转换为 UTC:
2017-06-10T16:49:45Z
除了ZoneOffset.UTC,您当然可以使用任何您想要的时区或偏移量(使用ZoneId 和ZoneOffset 类,查看javadoc 了解更多详细信息)。