The issue you're seeing is due to the difference in time zone database being used in the two versions of Java. Time zone rules are updated over time, and you've encountered an old date that is treated differently under newer rules.
For the versions of Java on my machine, I'm getting 1934-07-22 for Java 11 & 17 (time zone database versions 2020d & 2021e, respectively), but 1934-07-21 for Java 21 (time zone database version 2023c).
The specific reason for this is that 2020d & 2021e have a zone offset of +01:19:32 for this instant in Europe/Amsterdam. On the other hand, 2023c has a zone offset of +01:00 in Europe/Amsterdam. Adding 1:19:32 to 1934-07-21T22:40:28Z results in midnight the next day (1934-07-22), but adding 1:00:00 results in 23:40:28 the same day (1934-07-21).
Time zone database change
I did some Internet sleuthing, and per the time zone database mailing list, it looks like this change was made in time zone database version 2022b. So 2022a and earlier use the +01:19:32 offset, and 2022b and later use the +01:00 offset.
2022b merged the Amsterdam & Brussels time zones, which are identical after 1970, but differ in sufficiently early historical dates. It looks like Amsterdam at one point used local mean time, hence the strange-looking offset. However, it seems that Brussels did not.
Between 2022a an b the timezone info for Europe/Amstedam is removed:
Z Europe/Amsterdam 0:19:32 - LMT 1835
The timezone info for Brussels around that date differs from Amsterdams timezone info.Determining the timezone information for dates around that period on my Linux system in python returns since the latest update an offset of 0 minutes as opposed to the 19 minutes previously.
Is the removal for this timezone information intentional or a bug?
The maintainer has merged the Amsterdam and Brussels timezone information by using the Brussels rules for Amsterdam, as the rules don't differ since 1970, in the default data package. This wipes out Amsterdam rules from before 1970, including local mean time.
Demonstration
String javaVersion = System.getProperty("java.vendor") + Runtime.version();
String timeZoneDatabase = ZoneRulesProvider.getVersions("UTC")
.keySet().stream().findFirst().orElseThrow();
Date date = new Date(-1118625572000L);
ZoneId timeZone = ZoneId.of("Europe/Amsterdam");
ZonedDateTime zonedDateTime = date.toInstant().atZone(timeZone);
LocalDate localDate = zonedDateTime.toLocalDate();
System.out.printf("Java: %s%n", javaVersion);
System.out.printf("Time Zone Database: %s%n", timeZoneDatabase);
System.out.printf("Instant (UTC): %s%n", date.toInstant());
System.out.printf("LocalDate (Europe/Amsterdam): %s%n", localDate);
System.out.printf("Time zone offset: %s%n", zonedDateTime.getOffset());
Output
Java: AdoptOpenJDK11.0.10+9
Time Zone Database: 2020d
Instant (UTC): 1934-07-21T22:40:28Z
LocalDate (Europe/Amsterdam): 1934-07-22
Time zone offset: +01:19:32
Java: Eclipse Adoptium17.0.2+8
Time Zone Database: 2021e
Instant (UTC): 1934-07-21T22:40:28Z
LocalDate (Europe/Amsterdam): 1934-07-22
Time zone offset: +01:19:32
Java: Eclipse Adoptium21.0.1+12-LTS
Time Zone Database: 2023c
Instant (UTC): 1934-07-21T22:40:28Z
LocalDate (Europe/Amsterdam): 1934-07-21
Time zone offset: +01:00
Answer from M. Justin on Stack OverflowThe issue you're seeing is due to the difference in time zone database being used in the two versions of Java. Time zone rules are updated over time, and you've encountered an old date that is treated differently under newer rules.
For the versions of Java on my machine, I'm getting 1934-07-22 for Java 11 & 17 (time zone database versions 2020d & 2021e, respectively), but 1934-07-21 for Java 21 (time zone database version 2023c).
The specific reason for this is that 2020d & 2021e have a zone offset of +01:19:32 for this instant in Europe/Amsterdam. On the other hand, 2023c has a zone offset of +01:00 in Europe/Amsterdam. Adding 1:19:32 to 1934-07-21T22:40:28Z results in midnight the next day (1934-07-22), but adding 1:00:00 results in 23:40:28 the same day (1934-07-21).
Time zone database change
I did some Internet sleuthing, and per the time zone database mailing list, it looks like this change was made in time zone database version 2022b. So 2022a and earlier use the +01:19:32 offset, and 2022b and later use the +01:00 offset.
2022b merged the Amsterdam & Brussels time zones, which are identical after 1970, but differ in sufficiently early historical dates. It looks like Amsterdam at one point used local mean time, hence the strange-looking offset. However, it seems that Brussels did not.
Between 2022a an b the timezone info for Europe/Amstedam is removed:
Z Europe/Amsterdam 0:19:32 - LMT 1835
The timezone info for Brussels around that date differs from Amsterdams timezone info.Determining the timezone information for dates around that period on my Linux system in python returns since the latest update an offset of 0 minutes as opposed to the 19 minutes previously.
Is the removal for this timezone information intentional or a bug?
The maintainer has merged the Amsterdam and Brussels timezone information by using the Brussels rules for Amsterdam, as the rules don't differ since 1970, in the default data package. This wipes out Amsterdam rules from before 1970, including local mean time.
Demonstration
String javaVersion = System.getProperty("java.vendor") + Runtime.version();
String timeZoneDatabase = ZoneRulesProvider.getVersions("UTC")
.keySet().stream().findFirst().orElseThrow();
Date date = new Date(-1118625572000L);
ZoneId timeZone = ZoneId.of("Europe/Amsterdam");
ZonedDateTime zonedDateTime = date.toInstant().atZone(timeZone);
LocalDate localDate = zonedDateTime.toLocalDate();
System.out.printf("Java: %s%n", javaVersion);
System.out.printf("Time Zone Database: %s%n", timeZoneDatabase);
System.out.printf("Instant (UTC): %s%n", date.toInstant());
System.out.printf("LocalDate (Europe/Amsterdam): %s%n", localDate);
System.out.printf("Time zone offset: %s%n", zonedDateTime.getOffset());
Output
Java: AdoptOpenJDK11.0.10+9
Time Zone Database: 2020d
Instant (UTC): 1934-07-21T22:40:28Z
LocalDate (Europe/Amsterdam): 1934-07-22
Time zone offset: +01:19:32
Java: Eclipse Adoptium17.0.2+8
Time Zone Database: 2021e
Instant (UTC): 1934-07-21T22:40:28Z
LocalDate (Europe/Amsterdam): 1934-07-22
Time zone offset: +01:19:32
Java: Eclipse Adoptium21.0.1+12-LTS
Time Zone Database: 2023c
Instant (UTC): 1934-07-21T22:40:28Z
LocalDate (Europe/Amsterdam): 1934-07-21
Time zone offset: +01:00
tl;dr
Your issue of differing dates likely has nothing to do with MongoDB, and nothing to do with versions of Java. Your issue is probably due to using different time zones.
The same moment has different dates when seen through different time zones.
Your code:
println("Date: ${date.toInstant()?.atZone(ZoneId.systemDefault())!!.toLocalDate()}")
… uses default time zone. I suspect that default varied at runtime.
Do not do that, do not rely upon such defaults. Specify your expected/desired time zone explicitly. For example:
Instant.ofEpochMilli( x ).atZone( ZoneId.of( "Europe/Paris" ) ).toLocalDate()
Details
We've recently transitioned our application from Java 11 to Java 17.
Time zone rules change quite often. Most every version and update of Java includes a fresh tzdata.
Subsequent to the deployment, we've noticed that certain records exhibit a discrepancy where dates are displaying with a one-day difference (+1).
Sounds like a time zone issue.
This occurs specifically in records with years before 1940.
By the way… The time zone rules were retroactively documented mainly for 1970 and later. And even then the rules have some holes and inaccuracies. Going further back in decades is even more likely to be less accurate. Fixes are always possible in later versions of tzdata.
Our application operates in the CEST timezone
No such thing as CEST time zone.
Text such as CEST is a pseudo-zone that gives a hint about what might be the intended time zone, and whether Daylight Saving Time (DST) is in effect. Pseudo-zones are not standardized, and are not even unique(!). Never use these pseudo-zones for storing, exchanging, or processing date-time values. Use pseudo-zones only for presentation to the user.
Real time zones have a name in format of Continent/Region such as Europe/Paris, America/Edmonton, and Pacific/Auckland.
dates are stored in MongoDB as UTC.
Exactly what data type did you use? Not covered in your Question.
The provided code snippet, written in Kotlin, produces satisfactory results in Java 11. However, when executed with Java 17, the same code fails to function as expected.
The most likely explanation is that your issue (differing dates) has nothing to do with versions of Java… the issue involves a change in your JVMs’ current default time zone.
For any given moment, the date varies around the globe by time zone. The same moment can be “tomorrow” in Tokyo Japan while simultaneously “yesterday” in Los Angeles US.
Run this to verify the current default time zone (in Java syntax):
System.out.println( ZoneId.systemDefault() ) ;
Depending on such defaults is a great way to mess up your tests. Instead, specify your expected/desired time zone.
System.out.println( Runtime.version() ) ;
Instant instant = Instant.ofEpochMilli( -1_118_625_572_000L ) ; // Always has an offset of zero hours-minutes-seconds from UTC.
ZoneId zLosAngeles = ZoneId.of( "America/Los_Angeles" ) ;
ZoneId zTokyo = ZoneId.of( "Asia/Tokyo" ) ;
ZonedDateTime zdtLosAngeles = instant.atZone( zLosAngeles ) ;
ZonedDateTime zdtTokyo = instant.atZone( zTokyo ) ;
System.out.println( "Same moment, different dates." );
System.out.println( zdtLosAngeles ) ;
System.out.println( zdtTokyo ) ;
See this code run at Ideone.com. Notice the differing dates in effect for the same simultaneous point on the timeline. Alice in LA looks up to her wall calendar to see the 21st, while at the very same moment Bob in Tokyo looks up to his wall clock/calendar to see the 22nd.
12.0.1+12
Same moment, different dates.
1934-07-21T14:40:28-08:00[America/Los_Angeles]
1934-07-22T07:40:28+09:00[Asia/Tokyo]
Verify inputs
Let's run your examples through Java 12 at Ideone.com.
System.out.println( Runtime.version() ) ;
String v1934_input = "1934-07-21T22:40:28.000+00:00" ;
String v1897_input = "1897-08-06T23:40:28.000+00:00" ;
Instant v1934_instant = Instant.parse ( v1934_input ) ;
Instant v1897_instant = Instant.parse ( v1897_input ) ;
long v1934_millis = v1934_instant.toEpochMilli() ;
long v1897_millis = v1897_instant.toEpochMilli() ;
System.out.println( v1934_input + " = " + v1934_instant + " = " + v1934_millis ) ;
System.out.println( v1897_input + " = " + v1897_instant + " = " + v1897_millis ) ;
12.0.1+12
1934-07-21T22:40:28.000+00:00 = 1934-07-21T22:40:28Z = -1118625572000
1897-08-06T23:40:28.000+00:00 = 1897-08-06T23:40:28Z = -2284762772000
java.util.Date and java.util.Calendar are not deprecated, and still work as they always have. However, how they work is difficult to use, which is why java.time classes are recommended instead.
What you are seeing is the difference between WEEK_OF_YEAR (which depends on the locale) and ALIGNED_WEEK_OF_YEAR (which is the same in all locales).
When setting or getting the
WEEK_OF_MONTHorWEEK_OF_YEARfields,Calendarmust determine the first week of the month or year as a reference point. The first week of a month or year is defined as the earliest seven day period beginning ongetFirstDayOfWeek()and containing at leastgetMinimalDaysInFirstWeek()days of that month or year. Weeks numbered ..., -1, 0 precede the first week; weeks numbered 2, 3,... follow it. Note that the normalized numbering returned byget()may be different. For example, a specific Calendar subclass may designate the week before week 1 of a year as week n of the previous year.
ALIGNED_WEEK_OF_YEARrepresents concept of the count of weeks within the period of a year where the weeks are aligned to the start of the year. This field is typically used withALIGNED_DAY_OF_WEEK_IN_YEAR.For example, in a calendar systems with a seven day week, the first aligned-week-of-year starts on day-of-year 1, the second aligned-week starts on day-of-year 8, and so on. Thus, day-of-year values 1 to 7 are in aligned-week 1, while day-of-year values 8 to 14 are in aligned-week 2, and so on.
And for the locale difference:
// returns DayOfWeek.MONDAY
WeekFields.of(Locale.forLanguageTag("de-DE")).getFirstDayOfWeek();
// returns DayOfWeek.SUNDAY
WeekFields.of(Locale.forLanguageTag("en-ZA")).getFirstDayOfWeek();
To get the unaligned week of year using the current system local with java.time:
LocalDate.now().get(WeekFields.of(Locale.getDefault()).weekOfYear())
If you want to be locale-independent, then there is ISO.weekOfYear() if you want the week to start on a Monday, and SUNDAY_START.weekOfYear() for a Sunday.
The old, much-derided Date and Calendar classes have always been confusing and difficult to use properly, particularly in a multi-threaded context.Java 8’s JSR 310 implementation offers specific classes for:

The old date library included only a single time representation class – java.util.Date, which despite its name, is actually a timestamp. It only stores the number of milliseconds elapsed since the Unix epoch.