Почему вычитание этих двух раз (в 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

8 ответов


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

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

просто еще один эпизод в загадочный и прекрасный мир часовой пояс.

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

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


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

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

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


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

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

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

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


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

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

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

1

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

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

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


как объясняли другие, там есть разрыв во времени. Существует два возможных смещения часового пояса для 1927-12-31 23:54:08 at 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 backport.


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

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

до Процесс Сообщества Java члены рекомендую:

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

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