获取午夜和当前时间之间的时间范围 JDK 8

Getting time range between midnight and current time JDK 8

我有这种方法可以将午夜时间和当前时间计算为长值:

/**
 * Returns the time range between the midnight and current time in milliseconds.
 *
 * @param zoneId time zone ID.
 * @return a {@code long} array, where at index: 0 - midnight time; 1 - current time.
 */

public static long[] todayDateRange(ZoneId zoneId) {
    long[] toReturn = new long[2];

    LocalTime midnight = LocalTime.MIDNIGHT;
    LocalDate today = LocalDate.now(zoneId);
    LocalDateTime todayMidnight = LocalDateTime.of(today, midnight);
    ZonedDateTime todayMidnightZdt = todayMidnight.atZone(zoneId);
    toReturn[0] = todayMidnightZdt.toInstant().toEpochMilli();

    ZonedDateTime nowZdt = LocalDateTime.now().atZone(zoneId);
    toReturn[1] = nowZdt.toInstant().toEpochMilli();
    return toReturn;
}

也许有更简单的方法?

你也可以这样做:

ZonedDateTime nowZdt = ZonedDateTime.now(zoneId);

ZonedDateTime todayAtMidnightZdt = nowZdt.with(LocalTime.MIDNIGHT);

我想不出更简单的方法。


LocalDateTime 与 ZonedDateTime

LocalDateTime.now().atZone(zoneId)ZonedDateTime.now(zoneId) 之间有一个(棘手的)区别。

对于下面的代码,我使用了一个 JVM,其中 默认时区 America/Sao_Paulo 并将尝试获取另一个时区的当前日期和时间(Europe/London)。目前我 运行 这个代码,是 2017 年 8 月 20th,但在圣保罗时间是 17:56 并且在伦敦 21:56.

当我这样做时:

LocalDateTime nowLdt = LocalDateTime.now();

它使用 JVM 默认时区 中的当前日期和时间 创建一个 LocalDateTime。在这种情况下,它将获取圣保罗时区的当前日期和时间(即 2017 年 8 月 20th,在 17:56) :

2017-08-20T17:56:05.159

当我调用 atZone 方法时,它会创建一个 ZonedDateTime 对应于指定区域中的 this 日期和时间:

ZoneId zoneId = ZoneId.of("Europe/London");
ZonedDateTime nowAtZone = nowLdt.atZone(zoneId);

nowAtZone 变量将是:

2017-08-20T17:56:05.159+01:00[Europe/London]

相同的日期(2017年8月20日th)和时间(17:56 ) 在伦敦时区。请注意,它 不是 当前在伦敦的 date/time。如果我得到等效的 epochMilli:

System.out.println(nowAtZone.toInstant().toEpochMilli());

它将是:

1503248165159

现在,如果我不使用 LocalDateTime 而是直接使用 ZonedDateTime

ZonedDateTime nowZdt = ZonedDateTime.now(zoneId);

它将获取伦敦的当前日期和时间,即:

2017-08-20T21:56:05.170+01:00[Europe/London]

请注意时间已更改(现在是 21:56)。那是因为此时此刻,就是伦敦的当前时间。如果我得到 epochMilli 值:

System.out.println(nowZdt.toInstant().toEpochMilli());

值为:

1503262565170

请注意,它与使用 LocalDateTime 的第一种情况不同(即使您忽略毫秒值的差异,因为 小时 是不同的)。如果你想要指定时区的当前日期和时间,你必须使用 ZonedDateTime.now(zoneId).

使用 LocalDateTime.now().atZone() 不仅会得到不同的结果,而且如果您在不同的 JVM 中 运行,或者如果 JVM 默认时区发生变化(有人可能会错误配置它,或者另一个应用程序),它也会发生变化运行ning 在同一个 VM 中调用 TimeZone.setDefault()).


夏令时

仅提醒一下由于 DST(夏令时)问题导致的极端情况。我将使用我居住的时区作为示例 (America/Sao_Paulo)。

在圣保罗,夏令时从 2016 年 10 月 16 日th开始:午夜,时钟从午夜向前偏移 1 小时到凌晨 1 点(偏移量从 -03:00 变为 -02:00)。所以 00:00 和 00:59 之间的所有当地时间在这个时区都不存在(你也可以认为时钟从 23:59:59.999999999 直接变成了 01:00)。如果我在这个时间间隔内创建本地日期,它会调整到下一个有效时刻:

ZoneId zone = ZoneId.of("America/Sao_Paulo");

// October 16th 2016 at midnight, DST started in Sao Paulo
LocalDateTime d = LocalDateTime.of(2016, 10, 16, 0, 0, 0, 0);
ZonedDateTime z = d.atZone(zone);
System.out.println(z);// adjusted to 2017-10-15T01:00-02:00[America/Sao_Paulo]

夏令时结束时:2017 年 2 月 19 日th 午夜,时钟偏移 1 小时,从午夜到 [=23 PM 18th(偏移量从-02:00变为-03:00)。所以从 23:00 到 23:59 的所有本地时间都存在 两次 (在两个偏移量中:-03:00-02:00),你必须决定你要哪一个。 默认情况下,它使用夏令时结束前的偏移量,但您可以使用 withLaterOffsetAtOverlap() 方法获取夏令时结束后的偏移量:

// February 19th 2017 at midnight, DST ends in Sao Paulo
// local times from 23:00 to 23:59 at 18th exist twice
LocalDateTime d = LocalDateTime.of(2017, 2, 18, 23, 0, 0, 0);
// by default, it gets the offset before DST ends
ZonedDateTime beforeDST = d.atZone(zone);
System.out.println(beforeDST); // before DST end: 2018-02-17T23:00-02:00[America/Sao_Paulo]

// get the offset after DST ends
ZonedDateTime afterDST = beforeDST.withLaterOffsetAtOverlap();
System.out.println(afterDST); // after DST end: 2018-02-17T23:00-03:00[America/Sao_Paulo]

请注意,夏令时结束前后的日期有不同的偏移量(-02:00-03:00)。这会影响 epochMilli 的值。

如果您使用with方法调整时间,也会发生上述情况。

修改后的代码现在简单多了:

/**
 * Returns the time range between the midnight and current time in milliseconds.
 *
 * @param zoneId time zone ID.
 * @return a {@code long} array, where at index: 0 - midnight time; 1 - current time.
 */
public static long[] todayDateRange(ZoneId zoneId) {
    long[] toReturn = new long[2];

    //ZonedDateTime nowZdt = LocalDateTime.now().atZone(zoneId);
    ZonedDateTime nowZdt = ZonedDateTime.now(zoneId);//As suggested by Hugo (tested). 
    ZonedDateTime midZdt = nowZdt.with(LocalTime.MIDNIGHT);
    toReturn[0] = midZdt.toInstant().toEpochMilli();
    toReturn[1] = nowZdt.toInstant().toEpochMilli();
    return toReturn;
}