On Wed, 8 Jul 2026 22:08:26 GMT, Naoto Sato <[email protected]> wrote:

>> Also covers: [8249280](https://bugs.openjdk.org/browse/JDK-8249280)
>> 
>> I will first give a quick summary of the problem.
>> Put simply, the `LocalDate` form of the `java.sql.Date` is derived using the 
>> `getYear` method of `java.util.Date`. This in turn returns the year of the 
>> normalised internal calendar.
>> However, the internal calendar `getYear` has an extra layer of complexity.
>> The calendar has an additional era field, which captures BC/AD.
>> `getYear` therefore just returns the year _of that era_.
>> For example, the year 6BC and the year 6AD both return `getYear` as 6.
>> 
>> **This means that for BC dates, our `LocalDate` conversion loses the sign of 
>> the year.**
>> 
>> This leads to additional problems down the line, as the year 1BC is for 
>> calculations sake is considered to be year 0 (and 2BC us considered year -1 
>> and so on). As a result, the various leap year calculations are WRONG for 
>> these years, causing year format validation failures in situations like 
>> marshalling/unmarshalling the dates with a DB.
>> 
>> There are two seemingly obvious fixes here, however I will attempt to 
>> explain why I did not proceed with them.
>> 
>> Firstly, it seems sensible is to derive the `LocalDate` from an `Instant` 
>> created from the millisecond representation of the `Date`. After all, why we 
>> are having to use the deprecated `getYear`, `getMonth` and `getDay` methods 
>> anyway?
>> The answer lies in [8061577](https://bugs.openjdk.org/browse/JDK-8061577).
>> The underlying millisecond representation between `java.time.Instant` and 
>> `java.util.Date` is fundamentally different. Read that ticket for a greater 
>> explanation.
>> Ultimately though, it means that the for older dates, the only real way to 
>> bridge between the two calendar systems is to use these year/month/day 
>> methods.
>> 
>> This is where the second possible solution appears.
>> The underlying calendar representation that `java.util.Date` uses actually 
>> does have a year method which gives you the correctly signed year, that 
>> being `getNormalizedYear`.
>> In fact, `java.util.Date` uses the setter counterpart `setNormalizedYear` is 
>> its `setYear` method.
>> Given this, it seems natural that `getYear` should similarly call 
>> `getNormalizedYear`.
>> I think this would be my ideal solution, however I recognise that `get`Year 
>> only returning a positive year is very long standing behaviour. Given how 
>> widely spread `java.util.Date` is, I felt it was perhaps better not to rock 
>> the boat too much.
>> 
>> I have therefore taken the decision to add an ...
>
> Hi,
> 
> I am not sure that adding a protected method to `java.util.Date` is the right 
> approach. Since `Date` is extensible, an existing subclass may already 
> declare a `getNormalizedYear()` method. With this change, that method would 
> unintentionally override the new method and could change the behavior of 
> `toLocalDate()` or `toLocalDateTime()`.
> Instead, I would suggest creating a `GregorianCalendar` initialized from the 
> `Date` and deriving the proleptic year from its `ERA` and `YEAR` fields (BC = 
> 1 - YEAR). This would avoid adding a new overrideable API to `Date`.

> @naotoj Understood. I have gone with the `GregorianCalendar` approach as 
> recommended. I just want to double check that there aren't any possible 
> timezone issues when constructing the calendar?

I now think this approach would be significantly slower, although it would 
still handle the situation correctly, as it creates GregorianCalendar each time 
the method is called  (time zone is not an issue here). Accessing the internal 
calendar via shared secrets might be possible, but I am not sure that is the 
right approach either.

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

PR Comment: https://git.openjdk.org/jdk/pull/31808#issuecomment-4929435652

Reply via email to