Weird org.threeten.bp.DateTimeException thrown?

2019-02-23 08:04发布

问题:

My code was working just fine. Today suddenly I started getting this exception - org.threeten.bp.DateTimeException: Field DayOfMonth cannot be printed as the value 1872095944 max width is 2 This is my simple code :

LocalDateTime date = LocalDateTime.now();
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd - MM - uuuu");
    String sDate = date.format(formatter);//EXCEPTION THROWN HERE

Why this problem suddenly?

EDIT

This seems to be an intermediate problem. It crashes sometimes and works fine on other times. No clues as to what is happening. A

回答1:

It is less a formatting problem (here just a symptom) but rather a problem how an instance of LocalDateTime is created. Root cause is simply that LocalDateTime.now() seems to produce a day-of-month completely out of bounds in some rare cases. This problem is probably related to this issue on the issue tracker of threeten-bp.

LocalDate.ofEpochDay(x) sometimes returns a wrong or illegal result instead of throwing an exception for large values of x . For instance, LocalDate.ofEpochDay(9223371671611556645L) returns a date with a negative value for d.getDayOfMonth() instead of throwing a DateTimeException .

Keep in mind that the method now() must do an epoch conversion in the background and finally call LocalDate.ofEpochDay(...). So if your clock produces an unusal epoch value in millis since Unix epoch then this can affect now(), too. And your formatter just fetches the day-of-month from your LocalDateTime by effectively calling getDayOfMonth() (really via field access in TemporalAccessor). The source code in question:

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     } 

Most interesting and suspicious is the fact that only the year is validated, but not the month or day-of-month. And indeed, have a look at this bizarre result containing four parts separated by minus-chars (???):

LocalDate d = LocalDate.ofEpochDay(9223371671611556645L);
System.out.println(d); // -999999999-02-0-30
System.out.println(d.getDayOfMonth()); // -30

Obviously the library code is broken for some exotic epoch-day-numbers which might be produced by your clock unfortunately. I have also tested the same code in Java-8, with the same wrong result.

Update:

The original code of LocalDate.ofEpochDay(long) shown so far is definitely broken, also because of the fact that there is no check for numerical/arithmetic overflow. For example: An input like Long.MAX_VALUE causes the expression epochDay + DAYS_0000_TO_1970 to overflow and to change the sign to negative. Similar, the input Long.MIN_VALUE will finally overflow when using the expression 400 * zeroDay. And I fear that this is not the only problem of shown code. For comparison: A correct implementation of the gregorian algorithm would rather look like in my own time library.

Side note:

By help of my library Time4J I have analyzed that given test input above would yield a year far out of bounds as defined in threeten-bp, too (range is -999999999 until +999999999):

PlainDate date = PlainDate.of(9223371671611556645L, EpochDays.UNIX);
// java.lang.IllegalArgumentException: Year out of range: 25252733927766555

I am not quite sure what you can do to solve the problem.

First thing is surely to log all inputs produced by your clock, relate them to the observed buggy behaviour of threeten-bp and to do some research why your clock goes sometimes mad.

About the bug in threeten-bp (and Java-8!), you can just hope that the threeten-bp-project team will soon fix it (or rather Oracle!). The input causing the problems is probably wrong anyway so you should best catch the exception and log it with the extra message that the clock is wrong (as root cause).



回答2:

Are there any recent changes in the api. I don't see any u option in allowed pattern list.

Symbol  Meaning                     Presentation       Examples

  ------  -------                     ------------      -------

   G       era                         number/text       1; 01; AD; Anno 
Domini

   y       year                        year              2004; 04

   D       day-of-year                 number            189

   M       month-of-year               number/text       7; 07; Jul; July; J

   d       day-of-month                number            10


   Q       quarter-of-year             number/text       3; 03; Q3


   Y       week-based-year             year              1996; 96


   w       week-of-year                number            27

   W       week-of-month               number            27

   e       localized day-of-week       number            2; Tue; Tuesday; T

   E       day-of-week                 number/text       2; Tue; Tuesday; T

   F       week-of-month               number            3


   a       am-pm-of-day                text              PM

   h       clock-hour-of-am-pm (1-12)  number            12

   K       hour-of-am-pm (0-11)        number            0

   k       clock-hour-of-am-pm (1-24)  number            0

   H       hour-of-day (0-23)          number            0

   m       minute-of-hour              number            30


   s       second-of-minute            number            55

   S       fraction-of-second          fraction          978

   A       milli-of-day                number            1234

   n       nano-of-second              number            987654321

   N       nano-of-day                 number            1234000000

   V       time-zone ID                zone-id America/Los_Angeles; Z; -08:30

   z       time-zone name              zone-name  Pacific Standard Time; PST

   X       zone-offset 'Z' for zero    offset-X          Z; -08; -0830; -08:30; -083015; -08:30:15;

   x       zone-offset                 offset-x          +0000; -08; -0830; -08:30; -083015; -08:30:15;

   Z       zone-offset                 offset-Z          +0000; -0800; -08:00;


   p       pad next                    pad modifier      1

   '       escape for text             delimiter


   ''      single quote                literal           '

[       optional section start

 ]       optional section end

{}      reserved for future use