Home > Mobile >  Achieve DateTimeFormatter Y10K output equal to SimpleDateFormat?
Achieve DateTimeFormatter Y10K output equal to SimpleDateFormat?

Time:09-22

While writing unit tests for some date helpers i stumbled across a particular behaviour of DateTimeFormatter that i would like to understand how to get around.

When outputting years >9999, it always adds a plus sign in front of the year number. Some quick code to illustrate this:

LocalDate localDate = LocalDate.of(9999, 1, 1);
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
cal.set(9999, 0, 1, 12, 0 , 0);

// following assertion passes as both strings are "01-01-9999"
Assertions.assertEquals(
    new SimpleDateFormat("dd-MM-yyyy").format(cal.getTime()),
    localDate.format(DateTimeFormatter.ofPattern("dd-MM-yyyy"))
);


localDate = localDate.plusDays(365);
cal.add(Calendar.DAY_OF_MONTH, 365);

// following assertion passes (lengthy workaround using SimpleDateFormat)
Assertions.assertEquals(
    new SimpleDateFormat("dd-MM-yyyy").format(cal.getTime()),
    new SimpleDateFormat("dd-MM-yyyy").format(Timestamp.valueOf(localDate.atTime(LocalTime.MIDNIGHT)))
);

// following assertion fails:
// Expected : "01-01-10000"
// Actual   : "01-01- 10000"
Assertions.assertEquals(
    new SimpleDateFormat("dd-MM-yyyy").format(cal.getTime()),
    localDate.format(DateTimeFormatter.ofPattern("dd-MM-yyyy"))
);

Now the docs states for year patterns that:

If the count of letters is less than four (but not two), then the sign is only output for negative years as per SignStyle.NORMAL. Otherwise, the sign is output if the pad width is exceeded, as per SignStyle.EXCEEDS_PAD.

So this gives a hint, but i'm still clueless about:

How to make DateTimeFormatter output exactly the same string for Y10K dates that SimpleDateFormat.format() does in my example (=unsigned for positive years >9999)?

CodePudding user response:

ISO 8601 does permit this year format. From Wikipedia:

To represent years before 0000 or after 9999, the standard also permits the expansion of the year representation but only by prior agreement between the sender and the receiver. An expanded year representation [±YYYYY] must have an agreed-upon number of extra year digits beyond the four-digit minimum, and it must be prefixed with a or − sign instead of the more common AD/BC (or CE/BCE) notation;

However, since it only permits this "by prior agreement between the sender and the receiver", it is quite strange that adding the sign is the default behaviour of LocalDate.toString.

According to the docs:

Year: The count of letters determines the minimum field width below which padding is used. If the count of letters is two, then a reduced two digit form is used. For printing, this outputs the rightmost two digits. For parsing, this will parse using the base value of 2000, resulting in a year within the range 2000 to 2099 inclusive. If the count of letters is less than four (but not two), then the sign is only output for negative years as per SignStyle.NORMAL. Otherwise, the sign is output if the pad width is exceeded, as per SignStyle.EXCEEDS_PAD.

So if you don't want the sign, you can use 3 "y"s, or just 1 "y", since 3 and 1 are both "less than four (but not two)".

Also, since "y" means "year of era", there won't be any negative years, so you don't need to worry about it outputting a sign for negative years either.

Example:

System.out.println(
    LocalDate.of(10000, 1, 1)
        .format(DateTimeFormatter.ofPattern("dd-MM-yyy"))
); // 01-01-10000

More generally, you can specify the sign style using the appendValue(TemporalField, int, int, SignStyle) method in DateTimeFormatterBuilder. You can specify SignStyle.NEVER to make it never output a sign.

  • Related