Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8308302

Leap second handling in Windows timestamps

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Unresolved
    • Icon: P4 P4
    • tbd
    • 26
    • core-libs
    • None

      There may be a problem of offsets between system time and file time related to leapsecond adjustments.

      See core-libs-dev mail archive:
      https://mail.openjdk.org/pipermail/core-libs-dev/2023-May/105717.html

      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);
      }

            rriggs Roger Riggs
            rriggs Roger Riggs
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Created:
              Updated: