Hi,

Thanks for the report, I created a Jira issue to track the investigation.
https://bugs.openjdk.org/browse/JDK-8308302

Regards, Roger

On 5/13/23 2:43 PM, andr...@flueckiger.ch wrote:
Subject:
Leap second handling in Windows timestamps
From:
<andr...@flueckiger.ch>
Date:
5/13/23, 2:43 PM

To:
<core-libs-dev@openjdk.org>


Hello,

This is my first post to this mailing list. I've been exploring a problem 
concerning leap seconds that emerged with the Windows 10 October 2018 Update. 
The current implementation of InstantSource and other classes that interact 
with FILETIME structures seem to be affected. This problem extends beyond just 
leap second days and will occur on any future day where the UTC-TAI offset 
deviates from 37 seconds.

The Java code snippet below, which uses JNA to convert a Windows FILETIME to an 
Instant, represents my initial attempt to address this issue. This approach 
makes the assumption that no more than one leap second is added or removed in a 
day, which should hold true until at least 2035, and likely a few years beyond.

I'm not sure how this will impact performance, and I'm not certain about the 
exact performance requirements. Also, I'm not sure if my current level of 
experience and permissions allow me to contribute directly to the JDK codebase. 
Still, I hope this code can provide some direction towards refining the 
handling of Windows timestamps.

Kind regards,
Andreas

private static Instant fileTimeToInstant(long fileTime) {
   if (fileTime < 0L) {
     throw new DateTimeException("file time must not be negative");
   }

   // Calculate nano adjustment and round down fileTime to the nearest second
   int nanoAdjustment = (int) (fileTime % 10000000L);
   fileTime -= nanoAdjustment;
   nanoAdjustment *= 100;

   // Populate FILETIME structure
   FILETIME fileTimeStruct = new FILETIME();
   fileTimeStruct.dwHighDateTime = (int) (fileTime >>> 32);
   fileTimeStruct.dwLowDateTime = (int) (fileTime & 0xffffffffL);

   // Convert FILETIME structure to a SYSTEMTIME structure
   SYSTEMTIME systemTime = new SYSTEMTIME();
   if (!Kernel32.INSTANCE.FileTimeToSystemTime(fileTimeStruct, systemTime)) {
     throw new LastErrorException(Native.getLastError());
   }

   // Calculate epoch day and second of day
   long epochDay = LocalDate.of(systemTime.wYear, systemTime.wMonth, 
systemTime.wDay).toEpochDay();
   int secondOfDay = systemTime.wHour * 3600 + systemTime.wMinute * 60 + 
systemTime.wSecond;

   // Calculate UTC-SLS slew if necessary and only for dates before December 31,
   // 30827 (epochDay < 10540167). SystemTimeToFileTime does not support dates
   // after the year 30827.
   if (secondOfDay >= 85399 && epochDay < 10540167) {
     // If the actual second of the day is 86400 (leap second) and the process 
is in
     // "compatible mode", increment the secondOfDay variable. In compatible 
mode,
     // the clock slows down to half speed for two seconds at the '59' second 
mark,
     // and systemTime.wMilliseconds reaches 500 at the beginning of the leap 
second.
     // This ensures that the leap second is properly accounted for without 
depending
     // on the ProcessLeapSecondInfo option. Rounding down fileTime to the 
nearest
     // second ensures that this check works as intended.
     if (secondOfDay == 86399 && systemTime.wMilliseconds == 500) {
       secondOfDay++;
     }

     // Calculate leap adjustment
     int leapAdjustment;
     if (secondOfDay == 86400) {
       // In case of a leap second, set leap adjustment to 1
       // to avoid unnecessary further calculations
       leapAdjustment = 1;
     } else {
       // If it's not a leap second, calculate leap adjustment by
       // determining the difference to the beginning of the next day
       LocalDate nextDay = LocalDate.ofEpochDay(epochDay + 1);
       systemTime.wYear = (short) nextDay.getYear();
       systemTime.wMonth = (short) nextDay.getMonthValue();
       systemTime.wDay = (short) nextDay.getDayOfMonth();
       systemTime.wHour = 0;
       systemTime.wMinute = 0;
       systemTime.wSecond = 0;
       systemTime.wMilliseconds = 0;
       if (!Kernel32.INSTANCE.SystemTimeToFileTime(systemTime, fileTimeStruct)) 
{
         throw new LastErrorException(Native.getLastError());
       }
       long nextDayFileTime = (((long) fileTimeStruct.dwHighDateTime) << 32) | 
(fileTimeStruct.dwLowDateTime & 0xffffffffL);

       leapAdjustment = (int) ((nextDayFileTime - fileTime) / 10000000L + 
secondOfDay - 86400L);
     }

     // Adjust nanoseconds based on leap adjustment
     if (leapAdjustment != 0 && secondOfDay - leapAdjustment >= 85400) {
       nanoAdjustment -= ((secondOfDay - leapAdjustment - 85400) * 1000000000L 
+ nanoAdjustment) * leapAdjustment / 1000L;
     }
   }

   return Instant.ofEpochSecond(epochDay * 86400L + secondOfDay, 
nanoAdjustment);
}

Reply via email to