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

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

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

5961


источник


Ответы:


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

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

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

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

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

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

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

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

РЕДАКТИРОВАТЬ: Чтобы ответить на вопрос о переходе в 1900 году ... похоже, все часовые пояса, как просто находящиеся в свое стандартное время для любого момента до начала 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предполагает, что это правильная концепция), поэтому другим библиотекам не нужно вводить этот искусственный переход.


9644



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

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

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


1450



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

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

568



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

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

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


311



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

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

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

1

256



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

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

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


180



As explained by others, there's a time discontinuity there. There are two possible timezone offsets for 1927-12-31 23:54:08 at Asia/Shanghai, but only one offset for 1927-12-31 23:54:07. So, depending on which offset is used, there's either a one second difference or a 5 minutes and 53 seconds difference.

This slight shift of offsets, instead of the usual one-hour daylight savings (summer time) we are used to, obscures the problem a bit.

Note that the 2013a update of the timezone database moved this discontinuity a few seconds earlier, but the effect would still be observable.

The new java.time package on Java 8 let use see this more clearly, and provide tools to handle it. Given:

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());

Then durationAtEarlierOffset will be one second, while durationAtLaterOffset will be five minutes and 53 seconds.

Also, these two offsets are the same:

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

But these two are different:

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

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

You can see the same problem comparing 1927-12-31 23:59:59 with 1928-01-01 00:00:00, though, in this case, it is the earlier offset that produce the longer divergence, and it is the earlier date that has two possible offsets.

Another way to approach this is to check whether there's a transition going on. We can do this like this:

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

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

You can check whether the transition is an overlap - in which case there's more than one valid offset for that date/time - or a gap - in which case that date/time is not valid for that zone id - by using the isOverlap() and isGap() methods on zot4.

I hope this helps people handle this sort of issue once Java 8 becomes widely available, or to those using Java 7 who adopt the JSR 310 backport.


160



IMHO the pervasive, implicit localization in Java is its single largest design flaw. It may be intended for user interfaces, but frankly, who really uses Java for user interfaces today except for some IDEs where you can basically ignore localization because programmers aren't exactly the target audience for it. You can fix it (especially on linux servers) by:

  • export LC_ALL=C TZ=UTC
  • set your system clock to UTC
  • never use localized implementations unless absolutely necessary (ie for display only)

To the Java Community Process members I recommend:

  • make localized methods not the default, but require the user to explicitly request localization.
  • use UTF-8/UTC as the FIXED default instead because that's simply the default today. There is no reason to do something else, except if you want to produce threads like this.

I mean, come on, aren't global static variables an anti-OO pattern? Nothing else are those pervasive defaults given by some rudimentary environment variables.......


129



This is because a time zone change on December 31st, 1927. The clocks went back 5 minutes and 52 seconds at the end of the year, so, 12:54:08 on December 31st, 1927, it actually happened twice and this looks like Java is parsing this time as the later possible instant of the time.


1