Java ZonedDateTime MST 到 UTC 的转换使用夏令时

Java ZonedDateTime MST to UTC conversion uses daylight savings

提问人:Dima Garbar 提问时间:5/18/2023 最后编辑:AnonymousDima Garbar 更新时间:5/23/2023 访问量:203

问:

我需要将时间戳转换为UTC。 时间戳具有 MST 时区,该时区在时间戳中指定

观察到的转化示例:

  • 2023-03-20 08:53:19 MST -> 2023-03-20T14:53:19 (-6)
  • 2023-01-20 08:53:19 MST -> 2023-01-20T15:53:19 (-7)
static DateTimeFormatter EVENT_TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss zzz");

static LocalDateTime timestampConverter(String timestamp) {
    return ZonedDateTime.parse(timestamp, EVENT_TIMESTAMP_FORMATTER)
            .withZoneSameInstant(ZoneOffset.UTC)
            .toLocalDateTime();
}

MDT 与 UTC 的偏移量为 -6:00 小时

MST 与 UTC 的偏移量为 -7:00 小时

问题: Java 转换将 MST 视为遵守夏令时的 MDT

例如,JS 库没有这个问题:

console.log(new Date("2023-03-20 08:53:19 MST").toUTCString()); // Mon, 20 Mar 2023 15:53:19 GMT

问题: 为什么逻辑与标准行为不同?

日期时间 java-time zoneddatetime datetime-conversion

评论

0赞 HariHaravelan 5/18/2023
在Java中有这样的代码,而不是MST尝试使用城市ZoneInfoaliases.put("MST", "America/Denver");
0赞 Andrey B. Panfilov 5/18/2023
java 的输出是正确的:为同一个 TZ 使用不同名称的目的(在您的情况下为 MDT/MST)只是为了向人类提供额外的信息,无论指定的日期/时间是否受 DST 影响。
1赞 VGR 5/18/2023
发生这种情况是因为 ZonedDateTime.parse 使用 ZonedDateTime.from,其文档指出:“转换将首先从临时对象获取 ZoneId,如有必要,回退到 ZoneOffset。因此,“MST”将转换为 ZoneId,该 ZoneId 始终具有有效的 DST 规则。
0赞 Anonymous 5/19/2023
您不能使用 DateTimeFormatterBuilder.appendZoneText(TextStyle, Set) 给美国/凤凰城或其他不使用夏令时 (DST) 的时区来解决这个问题吗?我相信是这样,并会推荐它。
0赞 Dima Garbar 5/21/2023
好建议。不幸的是,此方法添加而不是替换具有尝试解析无效索引的现有类new ZoneTextPrinterParserpreferredZone=null

答:

0赞 Stephen Olujare 5/18/2023 #1

首先,让我们在提供的代码片段中定义错误。

我意识到上面的代码片段将 MST 时区视为 MDT,并且不考虑夏令时。我希望你明白这一点。

现在,让我们解决这个问题,您可以使用 ZoneId 类来指定夏令时。使用此更新版本的 timestampConverter 方法,该方法应适用于 MST 和 MDT:

static LocalDateTime timestampConverter(String timestamp) {
ZonedDateTime zonedDateTime = ZonedDateTime.parse(timestamp,EVENT_TIMESTAMP_FORMATTER);
    if(zonedDateTime.getZone().getRules().isDaylightSavings(zonedDateTime.toInstant())){
    zonedDateTime.toInstant().atZone(ZoneOffset.UTC).toLocalDateTime();
    }else{
        return zonedDateTime.withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime();
    }
}

这应该可以解决您目前正在经历的挑战!如果您需要更多说明...不要犹豫。祝您编码愉快!

评论

0赞 VGR 5/18/2023
问题不在于如何补偿这种行为。问题是询问如何正确解析其时区 ID 明确声明它不在夏令时的字符串。(旁注:您应该修复答案的格式。
0赞 Anonymous 5/23/2023
将 MST 时区视为 MDT,并且不考虑夏令时从问题中可能并不清晰,但问题恰恰相反:即使在 MST 声明不应考虑的情况下,代码也会考虑夏令时。因此,您的代码无法修复任何内容。你的部分和你的部分是等价的,所以你的/是无用的。ifelseifelse
2赞 VGR 5/19/2023 #2

发生这种情况是因为 ZonedDateTime.parse 使用 ZonedDateTime.from,其文档指出:

转换将首先从临时对象获取 ZoneId,并在必要时回退到 ZoneOffset。

因此,将转换为始终具有有效 DST 规则的 ZoneId。MST

我曾希望接受首选区域覆盖的 DateTimeFormatterBuilder.appendZoneText 版本可能会有所帮助,但事实并非如此。首选区域仅针对被视为不明确的区域 ID,并且显然 MST 不被视为不明确。

我找不到任何方法让单个 DateTimeFormatter 做你想要的,但你至少可以在显式查找区域时提供区域覆盖,使用双参数 ZoneId.of 方法

static DateTimeFormatter EVENT_TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

private static final Map<String, String> nonDSTOverrides = Map.of(
        "PST", "UTC-08:00",
        "MST", "UTC-07:00",
        "CST", "UTC-06:00",
        "EST", "UTC-05:00");

static LocalDateTime timestampConverter(String timestamp) {
    int lastSpace = timestamp.lastIndexOf(' ');
    if (lastSpace < 0) {
        throw new DateTimeParseException(
            "Text must contain a space before the timezone.", timestamp, 0);
    }

    String localPart = timestamp.substring(0, lastSpace);
    String zonePart = timestamp.substring(lastSpace + 1);

    return ZonedDateTime.of(
            LocalDateTime.parse(localPart, EVENT_TIMESTAMP_FORMATTER),
            ZoneId.of(zonePart, nonDSTOverrides))
        .withZoneSameInstant(ZoneOffset.UTC)
        .toLocalDateTime();
}

评论

0赞 Anonymous 5/23/2023
很好的解释。超载对我有用。我想知道为什么它不适合你。这是我的在线演示DateTimeFormatterBuilder.appendZoneText](TextStyle, Set<ZoneId>)
0赞 VGR 5/23/2023
@OleV.V.我没有看过源代码,但我确实在文档中看到时区的分辨率是基于语言环境的。也许 MST 对我来说被认为是明确的,因为我使用的是美国语言环境,但在其他地方被认为是模棱两可的。
0赞 Anonymous 5/23/2023
感谢您的思考。好吧,我明确指定了对我有用的格式化程序。那么,Java 版本呢?我相信我在早期的 Java 版本中也有过这种工作。而且很多区域都打印为 MST(超过 5 个),所以“不模棱两可”对我来说没有多大意义。我会一直想知道。:-)(是的,存在特定于区域设置的时区名称和缩写,我们显然也应该能够解析。Locale.US;
0赞 Anonymous 5/23/2023
即使您将美国/亚利桑那州指定为首选,您是否只是获得了美国/丹佛?(如果您尝试解析成 a 并打印它)。ZonedDateTime
0赞 VGR 5/23/2023
@OleV.V.是的,我得到美国/丹佛,但我没有指定美国/凤凰城。我正在使用 ZoneId.of(“MST”, Map.of(“MST”, “UTC-07:00”)),我现在看到它返回一个 ZoneId,它根本不与 ID“MST”相关联。使用美国/凤凰城绝对可以解决这个特殊问题。我想我希望有一个通用的解决方案可以应用于任何希望抑制使用 DST 的时区。
0赞 Dima Garbar 5/19/2023 #3

我一直使用此代码来实现 -07:00 转换。

    LocalDateTime timestampConverter(String timestamp) {
        return ZonedDateTime.parse(timestamp, EVENT_TIMESTAMP_FORMATTER)
                // Java conversion treats MST as MDT which observe daylight saving
                .withZoneSameLocal(ZoneId.of(ZONE_OFFSET))
                .withZoneSameInstant(ZoneOffset.UTC)
                .toLocalDateTime();
    }

评论

0赞 Anonymous 5/20/2023
Thamks 的贡献。恕我直言,这是一个非常丑陋的解决方案。如果你想要的只是一个,这比需要的要复杂得多。LocalDateTime
0赞 Dima Garbar 5/21/2023
@OleV.V.如果您能提供比这更好的工作解决方案,我将很高兴。或者至少定义为什么这是丑陋的
0赞 Anonymous 5/23/2023
感谢您的请求。这是我推荐的方式。我不喜欢您的代码的是,您假设偏移量为 -07:00,而没有验证它在字符串中是否说。如果字符串在某些日子带有不同的时区,您的转换可能会很遥远,并且没有人可能会注意到,或者当他们注意到时,他们将不知所措,不知道如何以及为什么。我的代码也没有验证,但它也可以从其他时区正确转换。MST