Как справиться с неопределенностью времени, вызванной откатом летнего времени - PullRequest
1 голос
/ 14 июня 2019

У меня есть такой сценарий, который я хотел бы увидеть, если есть лучшее решение:

Я получаю метку времени из системы A в формате "гггг-мм-дд-чч-мм" -ss ", без часового пояса, но они могут исправить это до UTC.

А затем мне нужно подключиться по SSH к системе B, чтобы получить «время последней синхронизации», которое может быть в прошлом, и оно не возвращает ни часовой пояс, даже год (из резервной копии). продукт), и вот пример:

«Синхронизировано по времени: вс 3 ноября 01:13»

Система B - это устройство резервного копирования, которое работает некоторое проприетарное программное обеспечение, и есть внутренний планировщик, который просыпается каждые 15 минут, чтобы проверить, есть ли какие-либо данные, которые должны быть синхронизированы из источника. Если предыдущая синхронизация все еще выполняется, текущая синхронизация будет завершена без каких-либо дальнейших действий. Это «время последней синхронизации» будет обновляться после каждой синхронизации, поэтому в худшем случае оно не должно отставать от текущего системного времени более чем на пару часов.

Мне нужно сравнить эти две временные метки, чтобы убедиться, что «время последней синхронизации» на B больше, чем A. Если нет, мне нужно подождать и повторить запрос, пока B не станет больше, чем A, чтобы перейти к следующий шаг.

У меня есть java-программа, установленная на Linux-машине, которая при поступлении запроса от A запрашивает у B «время последней синхронизации», а после этого сразу же выдает команду «date» для B, чтобы получить текущее системное время, которое возвращает текущее системное время в формате «EEE MMM dd kk: mm: ss z yyyy», например,

«Пт Июн 14 21:07:07 EDT 2019 msgstr ",

чтобы программа могла применить год и часовой пояс этого выхода для завершения временной метки" времени последней синхронизации ". Программа обрабатывает случай, когда выходные данные «последнего времени синхронизации» и «даты» распространяются через границу года.

Это прекрасно работает, за исключением случаев, когда летнее время заканчивается, а часы возвращаются назад (2 часа ночи назад к 1 часу ночи), есть одночасовое окно, время будет неоднозначным:

Например, я получаю временную карту 1:20 из системы A (преобразованную из UTC уже в локальную), а затем, когда я получаю 1:30 из системы B, я не могу сказать, является ли B более поздней, чем A. Это может быть 1:30 до смены часов. Текущий часовой пояс системы B не уточняет, в каком часовом поясе было «время последней синхронизации».

Мы попросили пользователя переключить все системы на время UTC, поэтому нам не о чем беспокоиться о настройке часов. Но это слишком сильно влияет на окружающую среду.

Одна из альтернатив заключается в том, что я обрабатываю этот час специально и жду, пока отметка времени «последней синхронизации» не превысит 2 часа ночи. Чтобы сделать это, мне нужно будет проверять в коде, является ли это конкретным часом каждый раз, когда я делаю сравнение с отметкой времени, которое я считаю неоптимальным. Но я не могу придумать лучшего пути.

Будем благодарны за любые предложения. И если это так, то есть ли хорошая библиотека Java для определения даты перехода на летнее время? Спасибо миллион!

Ответы [ 2 ]

1 голос
/ 16 июня 2019

Я не вижу, чтобы ты был полностью уверен. «Солнце 3 ноября» может быть в 2002, 2013 или 2019 году или даже в далеком прошлом. Одним из способов будет, если вы предположите, что время последней синхронизации не превышает, скажем, несколько лет до текущего системного времени системы B, которое вы получите (а также не после этого времени), и сообщите ошибка, если вы можете обнаружить, что это не в течение этих нескольких лет.

Хитрость, когда заканчивается летнее время (переход на летнее время), состоит в том, чтобы всегда предполагать более раннее время, если есть какая-либо неопределенность. Таким образом, когда вы обнаруживаете, что время синхронизации позже, чем отметка времени от A, это верно независимо от того, как интерпретируется неоднозначное время синхронизации. Это также означает, что вы будете ждать, пока время последней синхронизации не наступит после 2:00, как вы предложили. Я не ожидал бы, что это будет слишком дорого с точки зрения сравнения, но если вам нужно быть уверенным, вам нужно сделать свои собственные измерения и суждения.

    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 продолжает синтаксический анализ строки, принимая разные годы, пока не достигнет года, в котором совпадает день недели.

0 голосов
/ 15 июня 2019

Получил это в комментарии от Мэтта Джонсона, который я считаю лучшим решением:

Современный код Java должен использовать пакет java.time, который был встроен начиная с Java 8. ZoneRules.getValidOffsets сообщит вам о смещениях, которые применяются для данного LocalDateTime в конкретном часовом поясе. Если возвращается более одного смещения, вы находитесь в неоднозначном периоде.

...