it-swarm.com.ru

Почему вычитание этих двух раз (в 1927 году) дает странный результат?

Если я запускаю следующую программу, которая анализирует две строки даты, ссылаясь на раз в 1 секунду, и сравнивает их:

public static void main(String[] args) throws ParseException {
    SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
    String str3 = "1927-12-31 23:54:07";  
    String str4 = "1927-12-31 23:54:08";  
    Date sDt3 = sf.parse(str3);  
    Date sDt4 = sf.parse(str4);  
    long ld3 = sDt3.getTime() /1000;  
    long ld4 = sDt4.getTime() /1000;
    System.out.println(ld4-ld3);
}

Вывод:

353

Почему ld4-ld3 не 1 (как и следовало ожидать из разницы времени в одну секунду), а 353?

Если я изменю даты на время 1 секунду позже:

String str3 = "1927-12-31 23:54:08";  
String str4 = "1927-12-31 23:54:09";  

Тогда ld4-ld3 будет 1.


Java версия:

Java version "1.6.0_22"
Java(TM) SE Runtime Environment (build 1.6.0_22-b04)
Dynamic Code Evolution Client VM (build 0.2-b02-internal, 19.0-b04-internal, mixed mode)

Timezone(`TimeZone.getDefault()`):

Sun.util.calendar.ZoneInfo[id="Asia/Shanghai",
offset=28800000,dstSavings=0,
useDaylight=false,
transitions=19,
lastRule=null]

Locale(Locale.getDefault()): zh_CN
6294
Freewind

Это изменение часового пояса 31 декабря в Шанхае.

Смотрите эту страницу для деталей 1927 года в Шанхае. В основном в полночь в конце 1927 года часы вернулись на 5 минут и 52 секунды. Таким образом, «1927-12-31 23:54:08» фактически происходило дважды, и похоже, что Java анализирует его как позже возможный момент для этой локальной даты/времени - отсюда и разница.

Просто еще один эпизод в часто странном и чудесном мире часовых поясов.

Правка: Стоп нажмите! История меняется ...

Исходный вопрос больше не будет демонстрировать совершенно такое же поведение, если его перестроить с версией 2013a из TZDB . В 2013a результат будет 358 секунд с временем перехода 23:54:03 вместо 23:54:08.

Я заметил это только потому, что собираю подобные вопросы в Noda Time в виде модульных тестов ... Тест теперь изменен, но он просто показывает, что даже исторические данные не являются безопасными.

EDIT: История снова изменилась ...

В TZDB 2014f время изменения сместилось на 1900-12-31, и теперь это всего лишь 343 секундное изменение (так что время между t и t+1 составляет 344 секунды, если вы понимаете, что я имею в виду).

EDIT: Чтобы ответить на вопрос о переходе в 1900 году ... похоже, что реализация часового пояса Java обрабатывает все часовые пояса как просто их стандартное время в любой момент до начала 1900 года. УНИВЕРСАЛЬНОЕ ГЛОБАЛЬНОЕ ВРЕМЯ:

import Java.util.TimeZone;

public class Test {
    public static void main(String[] args) throws Exception {
        long startOf1900Utc = -2208988800000L;
        for (String id : TimeZone.getAvailableIDs()) {
            TimeZone zone = TimeZone.getTimeZone(id);
            if (zone.getRawOffset() != zone.getOffset(startOf1900Utc - 1)) {
                System.out.println(id);
            }
        }
    }
}

Приведенный выше код не производит вывод на мой компьютер с Windows. Таким образом, любой часовой пояс с любым смещением, отличным от стандартного в начале 1900 года, будет учитываться как переходный. Сам TZDB имеет некоторые данные, возвращающиеся раньше, и не полагается на идею «фиксированного» стандартного времени (то, что getRawOffset предполагает допустимым понятием), поэтому другие библиотеки не должны вводить этот искусственный переход.

10176
Jon Skeet

Вы столкнулись с разрывом по местному времени :

Когда местное стандартное время должно было достичь воскресенья, 1 января 1928 года 00:00:00 часы были переведены назад с 0:05:52 часов до субботы, 31 . Декабрь 1927, 23:54:08 вместо местного времени

Это не особенно странно и происходило практически везде когда-то, когда часовые пояса менялись или менялись из-за политических или административных действий.

1523
Michael Borgwardt

Мораль этой странности такова:

  • Используйте даты и время в UTC, где это возможно.
  • Если вы не можете отобразить дату или время в формате UTC, всегда указывайте часовой пояс.
  • Если вам не требуется вводить дату/время в формате UTC, укажите явно указанный часовой пояс.
611
Raedwald

При увеличении времени вы должны преобразовать обратно в UTC, а затем добавить или вычесть. Используйте местное время только для отображения.

Таким образом, вы сможете пройти любые периоды, когда часы или минуты встречаются дважды.

Если вы конвертировали в UTC, добавляйте каждую секунду и переводите в местное время для отображения. Вы бы прошли 11:54:08 вечера. LMT - 11:59:59 вечера LMT, а затем 11:54:08 CST - 11:59:59 вечера CST.

338
PatrickO

Вместо того, чтобы конвертировать каждую дату, вы используете следующий код 

long difference = (sDt4.getTime() - sDt3.getTime()) / 1000;
System.out.println(difference);

И посмотрим на результат:

1
284
Rajshri

Прошу прощения, но разрыв времени немного сдвинулся

JDK 6 два года назад и в JDK 7 только недавно в обновление 25 .

Урок, который нужно выучить: избегайте времени, отличного от UTC, любой ценой, за исключением, возможно, демонстрации.

202
user1050755

Как объяснили другие, там есть временной разрыв. Существует два возможных смещения часового пояса для 1927-12-31 23:54:08 в Asia/Shanghai, но только одно смещение для 1927-12-31 23:54:07. Таким образом, в зависимости от того, какое смещение используется, разница составляет либо одну секунду, либо разницу в 5 минут и 53 секунды.

Это небольшое смещение смещений вместо привычного для нас перехода на летнее время в течение одного часа (летнее время), к которому мы привыкли, немного затеняет проблему.

Обратите внимание, что обновление 2013a базы данных часовых поясов переместило этот разрыв на несколько секунд раньше, но эффект все еще будет заметен.

Новый пакет Java.time в Java 8 позволяет использовать это более наглядно и предоставляет инструменты для его обработки. Дано:

DateTimeFormatterBuilder dtfb = new DateTimeFormatterBuilder();
dtfb.append(DateTimeFormatter.ISO_LOCAL_DATE);
dtfb.appendLiteral(' ');
dtfb.append(DateTimeFormatter.ISO_LOCAL_TIME);
DateTimeFormatter dtf = dtfb.toFormatter();
ZoneId shanghai = ZoneId.of("Asia/Shanghai");

String str3 = "1927-12-31 23:54:07";  
String str4 = "1927-12-31 23:54:08";  

ZonedDateTime zdt3 = LocalDateTime.parse(str3, dtf).atZone(shanghai);
ZonedDateTime zdt4 = LocalDateTime.parse(str4, dtf).atZone(shanghai);

Duration durationAtEarlierOffset = Duration.between(zdt3.withEarlierOffsetAtOverlap(), zdt4.withEarlierOffsetAtOverlap());

Duration durationAtLaterOffset = Duration.between(zdt3.withLaterOffsetAtOverlap(), zdt4.withLaterOffsetAtOverlap());

Тогда durationAtEarlierOffset будет одна секунда, а durationAtLaterOffset будет пять минут и 53 секунды.

Кроме того, эти два смещения одинаковы:

// Both have offsets +08:05:52
ZoneOffset zo3Earlier = zdt3.withEarlierOffsetAtOverlap().getOffset();
ZoneOffset zo3Later = zdt3.withLaterOffsetAtOverlap().getOffset();

Но эти два разные:

// +08:05:52
ZoneOffset zo4Earlier = zdt4.withEarlierOffsetAtOverlap().getOffset();

// +08:00
ZoneOffset zo4Later = zdt4.withLaterOffsetAtOverlap().getOffset();

Вы можете увидеть ту же проблему, сравнивая 1927-12-31 23:59:59 с 1928-01-01 00:00:00, хотя в этом случае более раннее смещение вызывает более длинную расходимость, и более ранняя дата имеет два возможных смещения.

Другой способ подойти к этому - проверить, происходит ли переход. Мы можем сделать это так:

// Null
ZoneOffsetTransition zot3 = shanghai.getRules().getTransition(ld3.toLocalDateTime);

// An overlap transition
ZoneOffsetTransition zot4 = shanghai.getRules().getTransition(ld3.toLocalDateTime);

Вы можете проверить, является ли переход перекрытием - в этом случае существует более одного допустимого смещения для этой даты/времени - или разрывом - в этом случае эта дата/время недопустима для идентификатора этой зоны - с помощью isOverlap() и isGap() методы на zot4.

Я надеюсь, что это поможет людям справиться с такой проблемой, как только Java 8 станет широко доступной, или тем, кто использует Java 7, которые используют бэкпорт JSR 310.

180
Daniel C. Sobral

ИМХО, повсеместная локализация implicit в Java - это ее самый большой недостаток дизайна. Это может быть предназначено для пользовательских интерфейсов, но, честно говоря, кто действительно использует Java для пользовательских интерфейсов сегодня, за исключением некоторых IDE, где вы можете в основном игнорировать локализацию, потому что программисты не являются целевой аудиторией. Вы можете исправить это (особенно на серверах Linux):

  • экспорт LC_ALL = C TZ = UTC
  • установите системные часы на UTC
  • никогда не используйте локализованные реализации, за исключением случаев, когда это абсолютно необходимо (т.е. только для отображения)

Для процесса сообщества Java участники, которые я рекомендую:

  • сделать локализованные методы не по умолчанию, но требовать от пользователя явного запроса локализации.
  • вместо этого используйте UTF-8/UTC в качестве FIXED по умолчанию, потому что сегодня это просто значение по умолчанию. Нет причин делать что-то еще, кроме случаев, когда вы хотите создавать такие потоки.

Я имею в виду, да ладно, разве глобальные статические переменные не являются шаблоном анти-OO? Ничто иное не является теми распространенными значениями по умолчанию, которые дают некоторые элементарные переменные среды .......

148
user1050755

Это изменение времени в 1927 году в Шанхае, как говорили другие. 

По сути, это 23:54:07 в Шанхае, местное стандартное время, через некоторое время оно перешло на следующий день в 00:00:00. Но затем он переключился обратно на 23:54:08 по местному стандартному времени. Вот почему разница составляет 343 секунды, а не 1 секунду.

Это также может испортить другие места, такие как США. В США у них есть летнее время. Когда начинается летнее время, время идет вперед на 1 час. Но через некоторое время летнее время заканчивается, оно возвращается назад на 1 час назад к стандартному часовому поясу. Поэтому иногда при сравнении времени в США разница составляет 3600 секунд, а не 1 секунду.

Лучше использовать UTC, где время не меняется, если только не нужно использовать время не UTC, как на дисплее.

0
zixuan