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




4 Answers

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

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

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

EDIT: Остановить нажмите! История изменений ...

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

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

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

миллисекундах timezone

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

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

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



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

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



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

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

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

1



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

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

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

Новый пакет 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, которые используют backport JSR 310.




Related