【问题标题】:Java Date Time Start and End of day without any TimeZone information没有任何 TimeZone 信息的 Java 日期时间开始和结束
【发布时间】:2019-06-28 20:12:58
【问题描述】:

我有两个字符串作为 startDate 和 endDate。这些值例如:

startDate="2019-09-29T06:00:00.000Z"
endDate="2019-10-06T05:59:59.999Z"

现在让我们说这些在山区时间作为 startDate 的一天的开始和 endDate 的一天的结束。但由于这些是字符串,我怎样才能让程序明白这些可以转换为山区当地时间,如:

startDate="2019-09-29T00:00:00.000Z"
endDate="2019-10-05T11:59:59.999Z"

时区是动态的。我可以从东部时间或任何其他时区获取时间戳字符串。
更新
我意识到我应该提供更多信息。
所以我输入的山区时间就是一个例子。它可以来自东部时间或任何其他时区。此方法知道的唯一事实是 st​​artDate 是某个时区的一天的开始,而 endDate 是该时区的一天的结束。我知道“Z”是 UTC 时间,但找出哪个时区在一天的开始(startDate)和一天的结束(endDate)生成了 UTC 时间并转换回相应的本地时间,这是我面临的挑战我正面临着。

【问题讨论】:

  • OffsetDateTime.parse 来自 java.time 包。
  • 您的第一个挑战是 MST 不明确(就像很多三个字母的时区缩写一样)。山地标准时间还是马来西亚标准时间?另外,山区标准时间是在 9 月使用吗?
  • 如果您的字符串以Z 结尾,则它们并非“没有任何时区信息”。 Z 是时区标识符。具体来说,祖鲁时间,也称为 UTC。
  • @OleV.V. - 更新为山区时间,以确保考虑夏令时。
  • 感谢您的澄清,它有帮助。我们可以假设一个美国时区吗?如果它可以是世界上的任何时区,那么在国际日期变更线附近会有日期不明确的情况,因为时间可能在该线的一侧或另一侧。在美洲,我认为没有任何歧义。

标签: java datetime jodatime


【解决方案1】:

末尾的Z 是时区信息,意思是offset 00:00 从UTC,又名零,又名祖鲁语,所以首先你将字符串解析为存储日期、时间和时间的类型Z 时区。

使用 Java 8 Time API,这将是 InstantOffsetDateTimeZonedDateTime
如果您的输入总是以Z 结尾,而不是其他偏移量,请使用Instant

然后您转换为所需的时区,美国山区时间称为America/Denver

例子

String startDate = "2019-09-29T06:00:00.000Z";
String endDate = "2019-10-06T05:59:59.999Z";
ZoneId zone = ZoneId.of("America/Denver");

System.out.println(Instant.parse(startDate).atZone(zone));
System.out.println(OffsetDateTime.parse(startDate).atZoneSameInstant(zone));
System.out.println(ZonedDateTime.parse(startDate).withZoneSameInstant(zone));

System.out.println(Instant.parse(endDate).atZone(zone));
System.out.println(OffsetDateTime.parse(endDate).atZoneSameInstant(zone));
System.out.println(ZonedDateTime.parse(endDate).withZoneSameInstant(zone));

输出

2019-09-29T00:00-06:00[America/Denver]
2019-09-29T00:00-06:00[America/Denver]
2019-09-29T00:00-06:00[America/Denver]
2019-10-05T23:59:59.999-06:00[America/Denver]
2019-10-05T23:59:59.999-06:00[America/Denver]
2019-10-05T23:59:59.999-06:00[America/Denver]

如果转换后不想保留时区,可以调用toLocalDateTime()去掉,例如

System.out.println(Instant.parse(endDate).atZone(zone).toLocalDateTime());

输出

2019-10-05T23:59:59.999

注意它最后没有Z

【讨论】:

  • 也许我不清楚。但问题是该方法不知道从哪个时区创建相应的 UTC 时间。山区时间只是一个例子。它也可以是 EST。唯一给定的事实是 st​​artDate 是某个时区的一天的开始,而 endDate 是该时区的一天的结束。这有意义还是我把它复杂化了?
  • @jsmtslch 您无法从纯 UTC 日期时间值确定时区,因为时区偏移量为 ±14 小时,因此存在重叠。
【解决方案2】:

我无法将手指从这个上移开,这很有趣。据我了解,您不知道开始和结束字符串来自哪个时区,它可能是任何时区。除了依次尝试所有这些,我们还能做什么?

    String startDate = "2019-09-29T06:00:00.000Z";
    String endDate = "2019-10-06T05:59:59.999Z";

    LocalTime dayStart = LocalTime.MIN;
    LocalTime dayEndEarliset = LocalTime.of(23, 59, 59);

    Instant startInstant = Instant.parse(startDate);
    Instant endInstant = Instant.parse(endDate);
    // Just out of curiosity find candidate time zones
    Set<ZoneId> candidateZones = ZoneId.getAvailableZoneIds()
            .stream()
            .map(ZoneId::of)
            .filter(zid -> startInstant.atZone(zid).toLocalTime().equals(dayStart)
                    && ! endInstant.atZone(zid).toLocalTime().isBefore(dayEndEarliset))
            .collect(Collectors.toSet());
    System.out.println("Potential time zones: " + candidateZones);
    // Real work: find candidate date(s)
    Set<LocalDate> candidateDates = ZoneId.getAvailableZoneIds()
            .stream()
            .map(ZoneId::of)
            .filter(zid -> startInstant.atZone(zid).toLocalTime().equals(dayStart)
                    && ! endInstant.atZone(zid).toLocalTime().isBefore(dayEndEarliset))
            .map(zid -> startInstant.atZone(zid).toLocalDate())
            .collect(Collectors.toSet());
    if (candidateDates.isEmpty()) {
        System.out.println("Cannot identify a date from the instants");
    } else if (candidateDates.size() > 1) {
        System.out.println("Ambiguous date, candidates are " + candidateDates);
    } else {
        System.out.println("The date is " + candidateDates.iterator().next());
    }

打印出来:

Potential time zones: [America/Inuvik, America/Yellowknife, America/Regina, America/Boise, SystemV/MST7MDT, America/El_Salvador, America/Costa_Rica, America/Shiprock, America/Guatemala, America/Denver, America/Belize, America/Swift_Current, America/Managua, Mexico/BajaSur, Canada/Mountain, America/Cambridge_Bay, Navajo, America/Chihuahua, America/Ojinaga, MST7MDT, Pacific/Galapagos, America/Mazatlan, US/Mountain, America/Edmonton, America/Tegucigalpa, Canada/Saskatchewan, Etc/GMT+6, SystemV/CST6]
The date is 2019-09-29

是的,美国/丹佛是预期的候选时区之一。到目前为止,我已经找到了开始日期。您可以以类似的方式找到开始日期和结束日期对。与流操作相比,您可能更喜欢老式循环。

但是,日期并不总是明确的。让我们尝试在 Pacific/Pago_Pago 时区构建一天的间隔。它位于偏移量 -11:00。

    ZoneId zone = ZoneId.of("Pacific/Pago_Pago");
    LocalDate testDate = LocalDate.of(2019, Month.OCTOBER, 2);
    String startDate = testDate.atStartOfDay(zone).toInstant().toString();
    String endDate = testDate.atTime(LocalTime.of(23, 59, 59, 999_000_000))
            .atZone(zone)
            .toInstant()
            .toString();

现在程序的输出是:

Potential time zones: [Antarctica/McMurdo, Pacific/Niue, Pacific/Samoa, Pacific/Tongatapu, Pacific/Enderbury, Etc/GMT+11, NZ, Antarctica/South_Pole, Etc/GMT-13, Pacific/Pago_Pago, Pacific/Midway, Pacific/Fakaofo, US/Samoa, Pacific/Auckland]
Ambiguous date, candidates are [2019-10-03, 2019-10-02]

这是因为也有偏移 +13:00 的时区。我们可以判断偏移量必须是 -11:00 或 +13:00,但我们无法判断两者中的哪一个。所以两个日期出来了。

时间使用半开间隔

我不确定结束是否总是在相关时区的 23:59:59.999,或者有时可能是 23:59:59.999999 或其他时间。此外,无论我们添加多少个小数,理论上仍然存在在该结束时间之后但仍在同一日期内的时刻。在代码中,我保守地接受 23:59:59 及以后的任何时间作为结束时间。完全避免怀疑的要点是:使用半开区间。将间隔的开始表示为 00:00:00,就像您已经做的那样,在最后一个日期 after 的日期中将结束表示为 00:00:00。并将开始日期视为包含日期,并将结束日期视为不包含

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-09-06
    • 1970-01-01
    • 2020-10-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多