Это не столько проблема форматирования (здесь просто симптом), сколько проблема создания экземпляра LocalDateTime
. Основная причина заключается просто в том, что LocalDateTime.now()
в некоторых редких случаях выдает день месяца, полностью выходящий за пределы. Эта проблема, вероятно, связана с этой проблемой в системе отслеживания ошибок threeten-bp.
LocalDate.ofEpochDay(x) иногда возвращает неправильный или недопустимый результат вместо создания исключения для больших значений x . Например, LocalDate.ofEpochDay(9223371671611556645L) возвращает дату с отрицательным значением для d.getDayOfMonth() вместо создания DateTimeException .
Имейте в виду, что метод now()
должен выполнять преобразование эпохи в фоновом режиме и, наконец, вызывать LocalDate.ofEpochDay(...)
. Поэтому, если ваши часы выдают необычное значение эпохи в миллисекундах с эпохи Unix, это также может повлиять на now()
. И ваш форматер просто извлекает день месяца из вашего LocalDateTime
, фактически вызывая getDayOfMonth()
(на самом деле через доступ к полю в TemporalAccessor
). Рассматриваемый исходный код:
281 public static LocalDate ofEpochDay(long epochDay) {
282 long zeroDay = epochDay + DAYS_0000_TO_1970;
283 // find the march-based year
284 zeroDay -= 60; // adjust to 0000-03-01 so leap day is at end of four year cycle
285 long adjust = 0;
286 if (zeroDay < 0) {
287 // adjust negative years to positive for calculation
288 long adjustCycles = (zeroDay + 1) / DAYS_PER_CYCLE - 1;
289 adjust = adjustCycles * 400;
290 zeroDay += -adjustCycles * DAYS_PER_CYCLE;
291 }
292 long yearEst = (400 * zeroDay + 591) / DAYS_PER_CYCLE;
293 long doyEst = zeroDay - (365 * yearEst + yearEst / 4 - yearEst / 100 + yearEst / 400);
294 if (doyEst < 0) {
295 // fix estimate
296 yearEst--;
297 doyEst = zeroDay - (365 * yearEst + yearEst / 4 - yearEst / 100 + yearEst / 400);
298 }
299 yearEst += adjust; // reset any negative year
300 int marchDoy0 = (int) doyEst;
301
302 // convert march-based values back to january-based
303 int marchMonth0 = (marchDoy0 * 5 + 2) / 153;
304 int month = (marchMonth0 + 2) % 12 + 1;
305 int dom = marchDoy0 - (marchMonth0 * 306 + 5) / 10 + 1;
306 yearEst += marchMonth0 / 10;
307
308 // check year now we are certain it is correct
309 int year = YEAR.checkValidIntValue(yearEst);
310 return new LocalDate(year, month, dom);
311 }
Наиболее интересным и подозрительным является тот факт, что проверяется только год, а не месяц или день месяца. И действительно, взгляните на этот причудливый результат, состоящий из четырех частей, разделенных минус-символами (???):
LocalDate d = LocalDate.ofEpochDay(9223371671611556645L);
System.out.println(d); // -999999999-02-0-30
System.out.println(d.getDayOfMonth()); // -30
Очевидно, код библиотеки сломан для некоторых экзотических чисел эпохи-дня, которые, к сожалению, могут быть созданы вашими часами. Я также протестировал тот же код на Java-8 с тем же неверным результатом.
Обновление:
Исходный код LocalDate.ofEpochDay(long)
, показанный до сих пор, определенно сломан, в том числе из-за того, что нет проверки числового/арифметического переполнения. Например: ввод типа Long.MAX_VALUE
вызывает переполнение выражения epochDay + DAYS_0000_TO_1970
и изменение знака на отрицательный. Аналогично, ввод Long.MIN_VALUE
окончательно переполнится при использовании выражения 400 * zeroDay
. И я боюсь, что это не единственная проблема показанного кода. Для сравнения: правильная реализация григорианского алгоритма скорее выглядела бы как в моя библиотека времени.
Примечание:
С помощью моей библиотеки Time4J я проанализировал, что приведенный выше тестовый ввод даст год далеко за пределы, поскольку также определяется в тридесяти битах (диапазон от -999999999 до +999999999):
PlainDate date = PlainDate.of(9223371671611556645L, EpochDays.UNIX);
// java.lang.IllegalArgumentException: Year out of range: 25252733927766555
Я не совсем уверен, что вы можете сделать, чтобы решить проблему.
Первое, что нужно сделать, это записать все входные данные, производимые вашими часами, связать их с наблюдаемым ошибочным поведением threeten-bp и провести некоторое исследование, почему ваши часы иногда сходят с ума.
По поводу бага в threeten-bp (и Java-8!), можно только надеяться, что команда threeten-bp-project скоро его исправит (точнее Oracle!). Входные данные, вызывающие проблемы, вероятно, в любом случае неверны, поэтому вам лучше всего перехватить исключение и зарегистрировать его с дополнительным сообщением о том, что часы неверны (в качестве основной причины).
person
Meno Hochschild
schedule
30.01.2016