Почему вычитание этих двух раз (в 1927 году) дает странный результат? - PullRequest
6470 голосов
/ 27 июля 2011

Если я запускаю следующую программу, которая анализирует две строки даты, ссылающиеся на раз в 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

Ответы [ 9 ]

10389 голосов
/ 27 июля 2011

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

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

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

РЕДАКТИРОВАТЬ: Стоп!История изменений ...

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

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

РЕДАКТИРОВАТЬ: История снова изменилась ...

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

РЕДАКТИРОВАТЬ: Чтобы ответить на вопрос о переходе в 1900 ... похоже, что реализация часового пояса Java обрабатывает все часовые пояса просто находятся в их стандартном времени в любой момент до начала 1900 UTC:

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

1540 голосов
/ 27 июля 2011

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

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

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

626 голосов
/ 28 июля 2011

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

  • По возможности используйте даты и время в UTC.
  • Если вы не можете отобразить дату или время в UTC, всегда указывайте время-zone.
  • Если вам не требуется указывать дату / время в формате UTC, укажите явно указанный часовой пояс.
346 голосов
/ 30 июля 2011

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

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

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

290 голосов
/ 16 мая 2012

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

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

и видите результат:

1
211 голосов
/ 18 февраля 2014

Мне очень жаль говорить, но разрыв по времени немного сдвинулся в

JDK 6 два года назад и в JDK 7 совсем недавно в обновление 25 .

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

188 голосов
/ 03 января 2014

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

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

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

Новый *Пакет 1010 * на 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);

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

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

154 голосов
/ 26 ноября 2014

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

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

Членам процесса сообщества Java я рекомендую:

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

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

6 голосов
/ 11 февраля 2019

Как говорили другие, в Шанхае в 1927 году изменилось время, как говорили другие.

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...