Питання Чому вираховує ці два рази (у 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

6025
2017-07-27 08:15


походження


Чи дійсно ти натрапив на таку ситуацію в реальному сценарії, чи було це питання лише означати, щоб стати головоломкою - просто для задоволення від цього? - Costi Ciudatu
@Costi Ciudatu: FWIW, я міг легко уявити, що це виникає внаслідок зменшення більшої помилки - наприклад, "Чому ці два дати здаються окремо не точно на рік?" - Brooks Moses
Реальна відповідь полягає в тому, щоб завжди, завжди використовувати секунди з епохи реєстрації, подібно до епохи Unix, з 64-бітним цілим представленням (підписано, якщо ви хочете дозволити штампи до епохи). Будь-яка реальна система часу має деяку нелінійну, немонотонну поведінку, як години стрибка або денне світло. - Phil H
спочатку опубліковано як Oracle Bug ID 7070044 23 липня `11 - Arno
Як @Costi Ciudatu, я дійсно дивувався, з якого звіту про помилку OP зробив це. Я також впевнений, що крім того, що це не є корисним для 99.9 (додати 9 цифр) відсотків користувачів. Він отримує медаль за репутацію "гру". - Menelaos Bakopoulos


Відповіді:


31 грудня в Шанхаї змінено часовий пояс.

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

Ще один епізод у часто дивному і чудовому світі часових поясів.

EDIT: Зупинити прес! Історія змінюється ...

Початкове питання більше не буде демонструвати зовсім таку саму поведінку, якщо її буде перебудовано з версією 2013a TZDB. У 2013 році результат буде становити 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 вважає, що це дійсна концепція), тому інші бібліотеки не повинні вводити цей штучний перехід.


9729
2017-07-27 08:31



@ Гарат: Ні, але перевірка подробиць переходів Шанхайської часової зони в цей період була моїм першим портом дзвінка. І я нещодавно працював над тимчасовими переходами для Noda Time, тому можливість двозначності в значній мірі лежить на передньому краї моїх думок у будь-якому випадку ... - Jon Skeet
@ Йоханнес: Щоб зробити це більш загальнозазвичайним часовим поясом, я вважаю - в результаті чого зсув UTC + 8. Наприклад, в 1911 році Париж зробив те ж саме: timeanddate.com/worldclock/clockchange.html?n=195&year=1911 - Jon Skeet
@Чарлс: тоді подорожні знали очікувати, що місцевий час буде різним всюди (тому що це було). Окрім того, годинники були механічними та швидкими темпами, тому люди, які ми використовували, щоб регулювати їх за місцевими ключами кожні пару днів у будь-якому випадку, навіть якщо вони не подорожували. Так як же встановили годинник вежі (який теж дрейфував)? Найлегше встановити їх до 12:00, коли сонце досягало свого щоденного максимуму ... який був різним у будь-якому місці, не в тій же довготі. Це була норма майже скрізь, поки залізничні розклади вимагали певної стандартизації. - Michael Borgwardt
Але тоді: як на Землі такого роду знання витримали століттями, так що майже століття тому він реалізується програмним забезпеченням? У 2011 році кожен, хто згадує дивовижні часові пояси індивідуумам, що не є розробником програмного забезпечення, розглядається як "ботанік". (І дійсно, люди чекають, що все програмне забезпечення абстрагує його, і вони не дають прокляття, якщо це двозначне, коли вони говорять "полудень", ми програмний інженер має справу з цим). Але уявити собі, хтось у Шанхаї, в грудні 1927 року, думаючи, що було б важливо відзначити таку річ, і що якось ця інформація ніколи не була втрачена, вилучена, нічого ... розум роздутий. - phtrivier
@ Eds Це фактично зайняло його лише 15 хвилин, причина, чому вона показує 16-хвилинну різницю, пояснюється невідповідністю часового поясу 27 липня 2011 року, викликаного тим, наскільки дивовижним є Джон Скет. - JessieArr


Ви зіткнулися з розрив місцевого часу:

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

Це не особливо дивно, і це траплялось майже скрізь в той чи інший час, оскільки часові пояси були змінені чи змінені через політичні або адміністративні дії.


1463
2017-07-27 08:38



@ Джейсон: Для читання перед сном я б запропонував базу даних часового поясу IANA (яку раніше керував милий хлопець, названий Олсоном, я думаю), було б чудовим ресурсом: iana.org/time-zones. Наскільки я знаю, більшість із світових джерел відкритого джерела (згадані бібліотеки) використовують це як основне джерело даних часового поясу. - Sune Rasmussen


Мораль цієї дивності є:

  • Використовуйте дати та години в UTC, де це можливо.
  • Якщо ви не можете відобразити дату або час в UTC, завжди вказуйте часову зону.
  • Якщо ви не можете вимагати дату / час введення в UTC, потрібно вказати явно вказану часову зону.

580
2017-07-28 11:50



Перетворення / зберігання в UTC дійсно не допомогло б описаній проблемі, оскільки ви зіткнулися з розривом у перетворенні в UTC. - unpythonic
@ Марк Манн: якщо ваша програма використовує UTC всередині країни, перетворюючи її в / в місцеву часову зону лише в інтерфейсі користувача, ви б не турбота про такі розриви. - Raedwald
@Reedwald: Звичайно, ви хочете - Який час UTC для 1927-12-31 23:54:08? (Ігнорування на даний момент, що UTC навіть не існувало в 1927 році). В дещо вкажіть, що цей час і дата надходять у вашу систему, і ви повинні вирішити, що з ним робити. Якщо вказувати користувачеві, що вони повинні вводити час у UTC, просто переміщує проблему користувачеві, це не усуває його. - Nick Bastin
Я відчуваю себе обгрунтованим за обсягом активності в цій темі, працюючи над датуванням / виправленням часу великого додатку вже майже рік. Якщо ви робите щось подібне до календаря, ви не можете просто "зберігати" UTC ", оскільки визначення часових поясів, в яких воно може бути відтворене, з часом змінюватиметься. Ми зберігаємо "user intent time" - місцевий час користувача та часовий пояс, а також UTC для пошуку та сортування, а також при кожному оновленні бази даних IANA ми перераховуємо всі часові UTC. - taiganaut


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

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

Якщо ви перетворили на UTC, додати кожну секунду та перетворити в місцевий час для відображення. Ви пройдете через 11:54:08 за стор. LMT - 11:59:59 ранку LMT, а потім 11:54:08. КНТ - 11:59:59 ранку КНТ.


320
2017-07-30 16:55





Замість перетворення кожної дати використовується наступний код

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

І побачите результат:

1

265
2018-05-16 05:31



Боюсь, це не справа. Ви можете спробувати свій код у вашій системі, вона буде виводитись 1, тому що у нас різні місця. - Freewind
Це справедливо лише тому, що ви не вказали мову аналогового входу. Це поганий стиль кодування та величезний дизайнерський недолік в Java - його властива локалізація. Особисто я ставлю "TZ = UTC LC_ALL = C" скрізь, де я використовую Java, щоб уникнути цього. Крім того, ви повинні уникати будь-якої локалізованої версії реалізації, якщо ви не безпосередньо взаємодієте з користувачем і явно цього не хочете. Не використовуйте будь-які розрахунки, включаючи локалізацію, завжди використовуйте часові пояси Locale.ROOT та UTC, якщо це не є абсолютно необхідним. - user1050755


Мені шкода сказати це, але розрив часу трохи змінився

JDK 6 два роки тому і в Росії JDK 7 нещодавно в Росії оновлення 25.

Урок для вивчення: уникайте будь-яких часів, за винятком, можливо, для відображення.


188
2018-02-17 22:44



Це неправильно. Розрив не є помилкою - лише в останній версії TZDB дані дещо відрізняються. Наприклад, на моєму комп'ютері з Java 8, якщо ви трохи зміните код, використовуючи "1927-12-31 23:54:02" та "1927-12-31 23:54:03", ви все одно побачите розрив - але тепер 358 секунд, а не 353. Навіть більш пізні версії TZDB мають ще одну різницю - подивіться мою відповідь на деталі. Тут немає реальної помилки, просто рішення дизайну про те, як аналізуються неоднозначні текстові значення дат / час. - Jon Skeet
Реальна проблема полягає в тому, що програмісти не розуміють, що перетворення між локальним та універсальним часом (в будь-якому напрямку) не є і не може бути надійним на 100%. Для старих міток часу дані, які ми маємо на які місцеві часи, були хиткими в кращому випадку. Для майбутніх часових позначень політичні дії можуть змінити те, що загальний час, який належить до певного місцевого часу. Для поточних і недавніх минулих часових міток ви можете мати проблему, що процес оновлення бази даних tz та розгортання змін може бути повільніше, ніж графік виконання законів. - plugwash


Як пояснюють інші, там існує розрив часу. Є два можливі зсуви часового поясу для 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.


168
2018-01-03 14:43



Привіт Деніел, я запустив ваш шматок коду, але він не дає вихід, як очікувалося. як lengthAtEarlierOffset і durationAtLaterOffset обидва лише 1 секунда, а також zot3 і zot4 обидва нульові. Я наказав просто скопіювати та запустити цей код на моїй машині. чи є тут щось, що потрібно зробити? Дайте мені знати, якщо ви хочете побачити шматок коду. Ось код tutorialspoint.com/... Ви можете дати мені знати, що відбувається тут. - vineeshchauhan
@vineeshchauhan Це залежить від версії Java, тому що це змінилося в tzdata, і різні версії JDK розставляють різні версії tzdata. На моєму встановленому Java час є 1900-12-31 23:54:16 і 1900-12-31 23:54:17, але це не працює на веб-сайті, яким ви ділилися, тому вони використовують іншу версію Java, ніж я. - Daniel C. Sobral


ІМХО всепроникаюче, неявний Локалізація в Java є її єдиним найбільшим дизайнерським недоліком. Він може бути призначений для користувальницьких інтерфейсів, але, чесно кажучи, хто дійсно використовує Java для користувацьких інтерфейсів сьогодні, за винятком деяких IDE, де ви в принципі можете ігнорувати локалізацію, оскільки програмісти не є саме цільовою аудиторією для неї. Ви можете виправити це (особливо на серверах Linux):

  • експорт LC_ALL = C TZ = UTC
  • встановіть системні годинники до UTC
  • ніколи не використовуйте локалізовані реалізації, якщо це не є абсолютно необхідним (тобто для показу тільки)

До Процес спільноти Java члени я рекомендую:

  • зробити локалізовані методи не за замовчуванням, але вимагати від користувача явного запиту локалізації.
  • використовувати UTF-8 / UTC як Виправлено замість цього, оскільки це сьогодні просто за замовчуванням. Немає причин робити щось інше, крім випадків, коли ви хочете створити подібні теми.

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


137
2017-11-26 15:58