处理夏令时并在 Date 对象中表示 UTC

Handle daylight savings time and representing UTC in Date object

我有一个函数 returns Date 对象中的当前 UTC 时间,很简单。我们所做的是找到 UTC 的当前 Instant,将其放入 Date 对象和 return。注意:这里的本地主机时间是New York/America.

我们现在面临的问题是 Date 拒绝存储 March 13 2:02 AM,因为时间不存在(时钟从 2 跳了一个小时纽约时间三月的第二个星期日上午到凌晨 3 点),但 UTC 时间也是如此,我们需要 UTC 时间。

有什么方法可以在 java 日期对象中表示 "20220313 02:02:00.000"

这是纽约时间(当地时间)"20220312 21:02.00.000"

public static Date getCurrUtc() throws ParseException {
    Instant instant = Instant.now();
    LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);
    Date date = new Date();
    int year = ldt.getYear(),
        month = ldt.getMonthValue() - 1,
        day = ldt.getDayOfMonth(),
        hour = ldt.getHour(),
        minute = ldt.getMinute(),
        second = ldt.getSecond(),
        milliseconds = (int) date.getTime() % 1000;
    SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
    isoFormat.setTimeZone(TimeZone.getTimeZone("America/New_York"));
    date = isoFormat.parse(String.format("%d-%d-%dT%d:%d:%d.%d", year, month, day, hour, minute, second, milliseconds));
    return date;
}

您目前正在混合调用两个非常不同的 API(来自 java.time 的旧的和过时的 java.util.Date 和 类,例如 LocalDateTime)。我会坚持使用较新的 API,因为如果您想在两个不同的时区表达同一瞬间,它会让生活变得更加轻松。

您仍然可以使用 LocalDateTime 来解析 String 的值,然后添加 ZoneId 使其代表真实的时间。

这是一个例子:

public static void main(String[] args) {
    // your example datetime String
    String datetime = "20220313 02:02:00.000";
    // a pattern representing the format of your datetime String
    String formatPattern = "uuuuMMdd HH:mm:ss.SSS";
    // a formatter using that pattern (can parse and format)
    DateTimeFormatter dtf = DateTimeFormatter.ofPattern(formatPattern);
    // the two time zones involved
    ZoneId americaNewYork = ZoneId.of("America/New_York");
    ZoneId utc = ZoneId.of("UTC");
    // the date and time of day without any zone or offset
    LocalDateTime zonelessDateTime = LocalDateTime.parse(datetime, dtf);
    // that date and time of day in New York
    ZonedDateTime newYorkTime = zonelessDateTime.atZone(americaNewYork);
    System.out.println(newYorkTime.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
    // the same instant in UTC (same date, different time of day, no offset
    ZonedDateTime utcTime = zonelessDateTime.atZone(utc);
    System.out.println(utcTime.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
}

如您在输出中所见,一天中的时间不同,区域也不同:

2022-03-13T03:02:00-04:00[America/New_York]
2022-03-13T02:02:00Z[UTC]

如果您必须 produce/consume java.util.Dates,您可以使用出于类似您的原因而实施的兼容性方法:处理大量遗留代码。

短句:一个java.time.ZonedDateTime和一个java.time.OffsetDateTime表示时间瞬间。还好有s a java.time.Instant, too, and you can convert a java.util.Datefrom/to anInstant. There's Date.from(Instant)andDate.toInstant(). Here's how you would use the results of the code example above in order to have the values as 日期`:

// convert the Instants represented by the ZonedDateTimes to Dates
Date newYorkDate = Date.from(newYorkTime.toInstant());
Date utcDate = Date.from(utcTime.toInstant());
// print the Date values
System.out.println(newYorkDate);
System.out.println(utcDate);

这些行将产生以下输出:

Sun Mar 13 08:02:00 CET 2022
Sun Mar 13 03:02:00 CET 2022

请仔细查看 java.util.Date 的值。
区域隐式更改 值调整(即使 Date 并没有真正的区域)。您基本上无法真正控制时区转换和时间转换。
在 Java 8 中引入一个全新的和不同的日期时间 API 有几个原因……提到的只是其中之一。


有什么方法可以在 java 日期对象中表示“20220313 02:02:00.000”?

是的,有...您可以创建 Date 和 return 它。 如何将此 Date 实例表示为 String 取决于所使用的 TimeZone。看到这个:

// create the date
Date date = Date.from(
                LocalDateTime.parse("20220313 02:02:00.000", dtf)
                             .atZone(utc)
                             .toInstant()
            );
// get the milliseconds since 1970-01-01, the only numerical value stored by a Date
long epochMillis = date.getTime();
// create a format for visualization
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
// add a time zone to the format
isoFormat.setTimeZone(TimeZone.getTimeZone("America/New_York"));
// and use it to print the date in that zone
System.out.println(epochMillis + " in New York: " + isoFormat.format(date));
// set the time zone of the format to UTC
isoFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
// and use it to print the date in a different zone
System.out.println(epochMillis + " in UTC:      " + isoFormat.format(date));

输出如下,注意使用相同的Date

1647136920000 in New York: 2022-03-12T21:02:00.000
1647136920000 in UTC:      2022-03-13T02:02:00.000

好吧,格式没有存储在 Date 变量中,但基础值至少可以用具有不同时区的不同格式表示。

要考虑的事情:
您认为时区应该是格式的一部分而不是日期时间对象本身的一部分吗?这个问题的答案可以映射到问题 Do you want to use java.util.Date or java.time? ;-)