skip to Main Content

We’ve recently transitioned our application from Java 11 to Java 17. Subsequent to the deployment, we’ve noticed that certain records exhibit a discrepancy where dates are displaying with a one-day difference (+1). This occurs specifically in records with years before 1940. Our application operates in the CEST timezone, and dates are stored in MongoDB as UTC. The following examples highlight instances where we’ve identified the issue.

DB Java 11 Java 17
1934-07-21T22:40:28.000+00:00 1934-07-22 1934-07-21
1897-08-06T23:40:28.000+00:00 1897-08-07 1897-08-06

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.

fun main() {  // Kotlin code
    val date = Date(-1118625572000) //1934-07-21T22:40:28.000+00:00
    println("Date: ${date.toInstant()?.atZone(ZoneId.of("Europe/Amsterdam"))!!.toLocalDate()}")
}

Java11 Output:
enter image description here

Java17 Output:
enter image description here

Is there a solution available in Java 17 to address issues related to these types of dates?

We attempted to rectify those records directly in the database to ensure the population of valid dates.

For example,

1934-07-21T22:40:28.000+00:00 --> 1934-07-22T00:00:00.000+00:00
1897-08-06T23:40:28.000+00:00 --> 1897-08-07T00:00:00.000+00:00

However, we need to explore whether there is a way to handle these types of dates specifically in Java 17.

2

Answers


  1. 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

    Login or Signup to reply.
  2. The issue you’re seeing is almost certainly due to the difference in time zone database being used in the two versions of Java. Time zone rules are updated over time, and it looks like you’ve found 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, but 1934-07-21 for Java 21.

    Demonstration

    String javaVersion = System.getProperty("java.vendor") + Runtime.version();
    String timeZoneDatabase = ZoneRulesProvider.getVersions("UTC")
            .keySet().stream().findFirst().orElseThrow();
    LocalDate localDate = new Date(-1118625572000L).toInstant()
            .atZone(ZoneId.of("Europe/Amsterdam")).toLocalDate();
    
    System.out.printf("Java: %s%n", javaVersion);
    System.out.printf("Time Zone Database: %s%n", timeZoneDatabase);
    System.out.printf("LocalDate (Europe/Amsterdam): %s%n", localDate);
    

    Output

    Java: AdoptOpenJDK11.0.10+9
    Time Zone Database: 2020d
    LocalDate (Europe/Amsterdam): 1934-07-22
    
    Java: Eclipse Adoptium17.0.2+8
    Time Zone Database: 2021e
    LocalDate (Europe/Amsterdam): 1934-07-22
    
    Java: Eclipse Adoptium21.0.1+12-LTS
    Time Zone Database: 2023c
    LocalDate (Europe/Amsterdam): 1934-07-21
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search