【问题标题】:Trying to parse a datetime in PDT to a ZonedDateTime representation尝试将 PDT 中的日期时间解析为 ZonedDateTime 表示
【发布时间】:2024-04-24 18:30:01
【问题描述】:

我应该如何解析这个位于 PDT 时区的日期时间值?

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

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

我尝试使用ZonedDateTime,但出现解析错误:

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

错误是:

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吗?

【问题讨论】:

  • “你同意我应该使用 ZonedDateTime 吗?”:取决于要实现的目标。如果目标是从运行代码的区域获取java.util.Date,那么使用java.text.SimpleDateFormat 可能会更好。
  • 当您可以使用现代日期和时间 API(ZonedDateTime 所属的地方)时,您不会想要获得 java.util.Date。你肯定想避免SimpleDateFormat。 @AxelRichter

标签: java datetime timezone datetime-parsing zoneddatetime


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

    parse method expects a String in a specific format,如2007-12-03T10:15:30+01:00[Europe/Paris]。由于您的输入格式不同,您需要DateTimeFormatter

    需要注意的一个细节是 API 使用 IANA timezones names(始终采用 Continent/City 格式,例如 America/Sao_PauloEurope/Berlin)。 避免使用三个字母的缩写(如CSTPST),因为它们是ambiguous and not standard

    API 使用特定 ID 进行一些例外处理,并为它们提供一些默认值。对于PDT,默认为America/Los_Angeles

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

    如果你使用大写的HH,它允许从0到23的值(在上午/下午使用这个值并不常见),并且如果输入包含像@987654339这样的小时,它会抛出异常@

    所以代码会是这样的:

    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[美国/洛杉矶]

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

    无论如何,PDT 中还有其他时区(例如 America/Vancouver) - 您可以通过调用 ZoneId.getAvailableZoneIds() 获取所有时区的列表。如果您想使用不同的时区作为默认时区,您可以创建一组首选时区并使用该组构建格式化程序:

    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[美国/温哥华]


    目前尚不清楚输入 String 的来源。如果您无法控制它们的格式,那么您别无选择:它们需要以这种方式解析。然后您可以使用withZoneSameInstant 方法将其转换为另一个时区:

    // 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),并且仅在向用户显示时转换为某个时区:

    // 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"));
    

    【讨论】:

      【解决方案3】:

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

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

      是和不是。您的字符串绝对应该被解析为ZonedDateTime。我建议您将其转换为 Instant 并存储它。然后,当您需要根据用户的时区偏好将其呈现给用户时,您可以再次将Instant 转换为ZonedDateTime,或者使用具有所需默认时区的DateTimeFormatter 对其进行格式化。

      为什么要这样?首先,通常的做法是存储Instants。有些人更喜欢存储自纪元以来的毫秒数,我认为这是一些(经常被误解的)性能度量。当然,这样的毫秒我很难读懂,而Instants 可以用肉眼来破译,至少粗略。我尊重的唯一另一种选择是,当您确定您的应用程序永远不需要关注时区(这曾经发生过吗?)时,有时LocalDateTime 用于存储。

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

      这里有几个更好的答案:Best practices with saving datetime & timezone info in database when data is dependant on datetime

      【讨论】:

      • 为什么瞬时超过 zonedddatetime,b/c 它更通用?
      • @Blankman,我试图在我的编辑中解释(从假期直接回家,我的编程自己还没有达到全速,所以希望它有意义)。