如何处理由于夏令时时钟回滚引起的时间歧义

时间:2019-06-14 18:53:28

标签: java datetime dst

我有这种情况,我想看看是否有更好的解决方案:

我从系统A收到了一个时间戳,格式为“ yyyy-MM-dd-HH-mm” -ss”,没有时区,但他们可以将其设置为UTC。

然后我需要通过SSH进入系统B以获取“过去的同步时间”,该时间可能是过去的时间,并且它不返回任何时区,甚至也不返回年份(通过备份)产品),下面是一个示例:

“时间同步:Sun Nov 3 01:13”

系统B是运行的备份设备一些专有软件,并且有一个内部调度程序,该调度程序每15分钟唤醒一次,以检查是否需要从源同步任何数据。如果先前的同步仍在运行,它将使当前的同步结束而无需采取进一步的措施。该“最后同步时间”将在每次同步后进行更新,因此在最坏的情况下,它不应比当前系统时间晚几个小时。

我需要比较这两个时间戳,以确保B上的“最后同步时间”晚于A。否则,我需要等待并重试查询,直到B​​晚于A才可以继续下一步。

我将Java程序放在Linux机器上,当A发出请求时,它向B查询“最后同步时间”,然后立即向B发出“日期”命令以获取当前系统时间,以“ EEE MMM dd kk:mm:ss z yyyy”的格式返回当前系统时间,例如

“ Fri Jun 14 21:07:07 EDT 2019 ”,

,以便程序可以应用年份和该输出的时区来完成“上次同步时间”的时间戳。该程序确实可以处理“上一个snync时间”和“日期”输出跨年度边界的情况。

除了夏季夏令时结束并且时钟回滚(从凌晨2点回到凌晨1点)时,它工作正常,只有一个小时的时间窗口是不确定的:

例如,我从系统A得到一个1:20 am的时间映射(已经从UTC转换为本地),然后当我从系统B得到一个1:30 am的时间时,我无法确定B是否晚于A。时钟更改前1:30。系统B的当前时区,并不确定“上次同步时间”所在的时区。

我们要求用户将所有系统都切换为UTC时间,因此我们不必担心关于时钟调整。但这对环境影响太大。

一种替代方法是,我专门处理一个小时,然后等到“上次同步时间”时间戳记超过凌晨2点。为此,我将需要在代码中每次进行时间戳比较时检查这是否是特定时间,因此我认为这不是最佳选择。但是我想不出更好的方法。

任何建议都将不胜感激。如果这是可行的方法,那么是否有一个不错的Java库来确定夏令时的转换日期?谢谢一百万!

2 个答案:

答案 0 :(得分:1)

我认为您无法完全确定。 “太阳11月3日”可能是在2002年,2013年或2019年,甚至更早了。一种方法是,如果您可以假设上次同步时间不超过您当前获得的当前系统B系统时间的几年(并且也不是该时间的之后),并且如果可以检测到不在几年内,请报告错误。

夏令时(夏令时)结束时的诀窍是,如果有任何歧义,则总是假设较早的时间。这样,当您检测到同步时间晚于A的时间戳时,无论如何解释不明确的同步时间,都是如此。这也意味着您将等到您建议的最后一个同步时间在凌晨2点之后。从比较的角度来看,我不希望这会过高地花费,但是如果您需要确定的话,就需要自己进行测量和判断。

    final DateTimeFormatter timestampFormatter
            = DateTimeFormatter.ofPattern("uuuu-MM-dd-HH-mm-ss");
    final ZoneId systemBTimeZone = ZoneId.of("America/New_York");
    final String syncFormatPattern = "'Sync''ed-as-of time:' EEE MMM d HH:mm";
    final DateTimeFormatter currentSystemBTiemFormatter = new DateTimeFormatterBuilder()
            .appendPattern("EEE MMM dd HH:mm:ss ")
            .appendZoneText(TextStyle.SHORT, Collections.singleton(systemBTimeZone))
            .appendPattern(" yyyy")
            .toFormatter(Locale.US);
    final Period maxSyncAge = Period.ofYears(2);

    String systemATimestampString = "2019-11-03-06-05-55";
    String lastSyncMsg = "Sync'ed-as-of time: Sun Nov 3 01:13";
    String currentSystemBTime = "Sun Nov 03 01:13:07 EDT 2019";
    OffsetDateTime systemATimestamp = LocalDateTime.parse(systemATimestampString, timestampFormatter)
            .atOffset(ZoneOffset.UTC);
    ZonedDateTime maxLastSyncTime
            = ZonedDateTime.parse(currentSystemBTime, currentSystemBTiemFormatter);
    ZonedDateTime minLatSyncTime = maxLastSyncTime.minus(maxSyncAge);
    int candidateYear = maxLastSyncTime.getYear();
    ZonedDateTime syncTime;
    while (true) {
        DateTimeFormatter syncFormatter = new DateTimeFormatterBuilder()
                .appendPattern(syncFormatPattern)
                .parseDefaulting(ChronoField.YEAR, candidateYear)
                .toFormatter(Locale.US);
        try {
            syncTime = LocalDateTime.parse(lastSyncMsg, syncFormatter)
                    .atZone(systemBTimeZone)
                    .withEarlierOffsetAtOverlap();
            if (syncTime.isBefore(minLatSyncTime) || syncTime.isAfter(maxLastSyncTime)) {
                throw new IllegalStateException("Last sync time is out of range");
            }
            break;
        } catch (DateTimeParseException dtpe) {
            // Ignore; try next earlier year
        }
        candidateYear--;
    }
    System.out.println("Last sync time: " + syncTime);
    System.out.println("Timestamp from system A: " + systemATimestamp);
    System.out.println("Is OK? " + syncTime.toOffsetDateTime().isAfter(systemATimestamp));

如摘录所示,其输出为:

Last sync time: 2019-11-03T01:13-04:00[America/New_York]
Timestamp from system A: 2019-11-03T06:05:55Z
Is OK? false

您会看到它已选择将01:13的最后一个同步时间解释为夏令时,偏移量是-04:00,这将导致检查失败。时间也可能是标准时间,偏移量为-05:00,在这种情况下,UTC等效时间将为06:13,并且检查成功。但是,正如所讨论的那样,为了安全起见,我们宁愿等到同步时间在02:00之后再重新确定。

while循环假设不同的年份继续解析字符串,直到遇到星期几匹配的年份为止。

答案 1 :(得分:0)

在马特·约翰逊(Matt Johnson)的评论中得到了这一点,我认为这是最好的解决方案:

现代Java代码应使用java.time软件包,该软件包自Java 8开始内置。ZoneRules.getValidOffsets将告诉您在特定时区中适用于给定LocalDateTime的偏移量。如果返回多个偏移量,则说明您处于歧义期。