尝试将PDT中的日期时间解析为ZonedDateTime表示

Trying to parse a datetime in PDT to a ZonedDateTime representation

我应该如何解析pdt时区中的日期时间值?

1
06/24/2017 07:00 AM (PDT)

我想维护时区,这样我就可以根据网站访问者的偏好在其他时区表示时间。

我尝试使用ZonedDateTime,但得到一个分析错误:

1
   java.time.ZonedDateTime.parse("06/24/2017 07:00 AM (PDT)")

错误是:

1
2
3
4
5
java.time.format.DateTimeParseException: Text '06/24/2017 07:00 AM (PDT)' could not be parsed at index 0
   at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1949)
   at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1851)
   at java.time.ZonedDateTime.parse(ZonedDateTime.java:597)
   at java.time.ZonedDateTime.parse(ZonedDateTime.java:582)   ... 29 elided

另外,你同意我应该使用ZonedDateTime吗?


由于您的格式是非标准格式,因此需要将其指定给解析器:

1
2
3
4
ZonedDateTime.parse(
   "06/24/2017 07:00 AM (PDT)",
    DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm a (zzz)")
);

其他答案已经很好地涵盖了你所得到的错误。

Also, do you agree that I should be using a ZonedDateTime?

是和否。您的字符串应该被解析成一个ZonedDateTime。我建议你把它转换成一个Instant,然后储存起来。然后,当您需要根据用户的时区偏好将其呈现给用户时,您可以再次将Instant转换为ZonedDateTime,也可以使用带有所需默认时区的DateTimeFormatter对其进行格式化。

为什么这样做?首先,通常的做法是存储Instants。有些人更喜欢只存储自时代以来的毫秒,我认为这是一些(经常被误解的)性能度量。当然,这样的毫秒我很难辨认,而Instants可以在视力上被解读,至少大致上是这样。我唯一尊重的另一种选择是,当您确信您的应用程序不需要关注时区时(是否会发生这种情况?),有时使用LocalDateTime进行存储。

如果我正确理解您的情况,您需要存储时间点,以便显示到多个时区。您不需要存储最初输入时间的时区(如PDT,除了PDT不是真正的完整时区之外)。Instant是时区中性的,这也是我喜欢它而不是像ZonedDateTime那样在某个时区存储时间的原因之一。另外,Instant在概念上更简单,我猜想它在实现方面也更简单。

这里有两个更好的答案:当数据依赖于日期时间时,在数据库中保存日期时间和时区信息的最佳实践。


parse方法需要特定格式的String,如2007-12-03T10:15:30+01:00[Europe/Paris]。由于您的输入格式不同,因此需要一个DateTimeFormatter

需要注意的一个细节是,API使用IANA时区名称(始终采用Continent/City格式,如America/Sao_PauloEurope/Berlin格式)。避免使用3个字母的缩写(如CSTPST),因为它们不明确且不标准。

API对特定的ID进行了一些异常,并为它们提供了一些默认值。对于PDT,默认为America/Los_Angeles

另一个细节是在下面的例子中,我在模式中使用了小写的hh:格式有AM/PM指示,所以我认为hh是正确的模式,因为它的值是从1到12(有AM/PM指示时的常用值)。

如果使用大写的hh,它允许0到23之间的值(在AM/PM中不常用),并且如果输入包含像07:00 PM这样的小时,它将抛出异常。

所以代码如下:

1
2
3
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("MM/dd/yyyy hh:mm a (zzz)");
ZonedDateTime z = ZonedDateTime.parse("06/24/2017 07:00 AM (PDT)", fmt);
System.out.println(z);

输出是:

2017-06-24T07:00-07:00[America/Los_Angeles]

但并非所有3个字母的时区名称都能被API识别,并会引发异常。

不管怎样,还有其他的时区也在PDT中(如America/Vancouver),您可以通过致电ZoneId.getAvailableZoneIds()获得所有时区的列表。如果要使用其他时区作为默认时区,可以创建一组首选区域,并使用该组创建格式化程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
Set<ZoneId> preferredZones = new HashSet<>();
// set America/Vancouver as preferred zone
preferredZones.add(ZoneId.of("America/Vancouver"));
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
    // pattern
    .appendPattern("MM/dd/yyyy hh:mm a (")
    // append timezone with set of prefered zones
    .appendZoneText(TextStyle.SHORT, preferredZones)
    // finish the pattern
    .appendPattern(")")
    // create formatter
    .toFormatter();
System.out.println(ZonedDateTime.parse("06/24/2017 07:00 AM (PDT)", fmt));

API将使用首选区域集(在本例中为America/Vancouver)而不是默认区域集(America/Los_Angeles)。输出将是:

2017-06-24T07:00-07:00[America/Vancouver]

不清楚输入String来自何处。如果您不能控制它们的格式,那么您就没有选择:它们需要以这种方式进行解析。然后您可以使用withZoneSameInstant方法将其转换为另一个时区:

1
2
3
4
// parse the input string
ZonedDateTime z = ZonedDateTime.parse("06/24/2017 07:00 AM (PDT)", fmt);
// convert to another timezone
ZonedDateTime other = z.withZoneSameInstant(ZoneId.of("America/Sao_Paulo")); // 2017-06-24T11:00-03:00[America/Sao_Paulo]

other的值为2017-06-24T11:00-03:00[America/Sao_Paulo]

但是,如果您可以控制输出,那么最好(IMO)在内部与UTC(java.time.Instant合作),并且只有在向用户显示以下内容时才转换到某个时区:

1
2
3
4
5
6
7
8
// convert ZonedDateTime to instant
ZonedDateTime z = // parse input
// convert to UTC (Instant is always in UTC)
Instant instant = z.toInstant();
// internally work with instant (as it's always in UTC)

// convert instant to some timezone only when necessary (like displaying to users)
ZonedDateTime converted = instant.atZone(ZoneId.of("Europe/London"));