Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

<chrono>: make system_clock::now() and file_clock aware of Windows leap second handling #1588

Closed
Closed
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b925e16
Implement `file_time` ↔ UTC conversion with leap second handling
statementreply May 1, 2021
375fe5a
Code cleanup
statementreply May 4, 2021
319e518
Move _Days_from_civil out for use in C++14 mode
statementreply May 4, 2021
22ccb75
Implement `system_clock::now`
statementreply May 4, 2021
2a9b591
Update `_To_xtime_10_day_clamped` to call `_Xtime_get_ticks` directly
statementreply May 4, 2021
1fa5f71
Add tests for `Clock::now`
statementreply May 4, 2021
5c6f625
_Uglify import lib code
statementreply May 4, 2021
c3038de
Shorten link to fit in 120 columns
statementreply May 4, 2021
7f2a5bd
Move bit operator overloads out of `extern C`
statementreply May 4, 2021
4f3d01c
Merge branch 'main' into windows-leap-second
statementreply Jul 26, 2021
a698c6d
Implement `file_time` ↔ `sys_time` conversion
statementreply Jul 26, 2021
49bce5a
Code review comments
statementreply Jul 26, 2021
7373b98
`nextafter` requires `#include <cmath>`
statementreply Jul 26, 2021
44d4bbb
Merge branch 'main' into windows-leap-second
statementreply Aug 21, 2021
1a22caf
Remove `#include <chrono>` from import library source
statementreply Aug 21, 2021
e4e88ec
Centralize FILETIME offset value
statementreply Aug 21, 2021
4296d97
Explain the magic tolerance
statementreply Aug 21, 2021
1336e1f
Merge branch 'main' into windows-leap-second
statementreply Aug 28, 2021
c5ee629
Implement `file_time` ↔ `utc_time` conversion
statementreply Sep 4, 2021
96d8501
Fix tests
statementreply Sep 4, 2021
0c3c998
Fix tests
statementreply Sep 5, 2021
cb7a139
Merge remote-tracking branch 'upstream/main' into windows-leap-second
statementreply Oct 2, 2021
5683bc0
Implement `file_time` formatting
statementreply Oct 2, 2021
4767cfe
Merge remote-tracking branch 'upstream/main' into windows-leap-second
statementreply Oct 22, 2021
d573b1e
Merge remote-tracking branch 'upstream/main' into windows-leap-second
statementreply Nov 21, 2021
29f5789
Merge remote-tracking branch 'upstream/main' into windows-leap-second
statementreply Jan 26, 2022
5b33476
Merge remote-tracking branch 'upstream/main' into windows-leap-second
statementreply May 4, 2022
e609aaa
Merge remote-tracking branch 'upstream/main' into windows-leap-second
statementreply Jun 25, 2022
fd4ce85
usual_winsdk_matrix.lst no longer exists
statementreply Jun 25, 2022
bdae556
Merge remote-tracking branch 'upstream/main' into windows-leap-second
statementreply Jul 31, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions stl/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ set(HEADERS
${CMAKE_CURRENT_LIST_DIR}/inc/xstddef
${CMAKE_CURRENT_LIST_DIR}/inc/xstring
${CMAKE_CURRENT_LIST_DIR}/inc/xthreads.h
${CMAKE_CURRENT_LIST_DIR}/inc/xtime0.h
${CMAKE_CURRENT_LIST_DIR}/inc/xtimec.h
${CMAKE_CURRENT_LIST_DIR}/inc/xtr1common
${CMAKE_CURRENT_LIST_DIR}/inc/xtree
Expand All @@ -256,6 +257,7 @@ set(IMPLIB_SOURCES
${CMAKE_CURRENT_LIST_DIR}/src/syserror_import_lib.cpp
${CMAKE_CURRENT_LIST_DIR}/src/vector_algorithms.cpp
${CMAKE_CURRENT_LIST_DIR}/src/xonce2.cpp
${CMAKE_CURRENT_LIST_DIR}/src/xtime0.cpp
)

# The following files are linked in msvcp140[d][_clr].dll.
Expand Down
75 changes: 59 additions & 16 deletions stl/inc/chrono
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <limits>
#include <ratio>
#include <utility>
#include <xtime0.h>
#include <xtimec.h>

#if _HAS_CXX20
Expand Down Expand Up @@ -663,6 +664,29 @@ namespace chrono {
}

// CLOCKS

// _Civil_from_days and _Days_from_civil perform conversions between the dates in the (proleptic) Gregorian
// calendar and the continuous count of days since 1970-01-01.

// See comments above year_month_day::_Civil_from_days for an explanation of the algorithm.

// courtesy of Howard Hinnant
// https://howardhinnant.github.io/date_algorithms.html#days_from_civil
_NODISCARD constexpr int _Days_from_civil(
const int _Year, const unsigned int _Month, const unsigned int _Day) noexcept {
static_assert(numeric_limits<unsigned int>::digits >= 18,
"This algorithm has not been ported to a 16 bit unsigned integer");
static_assert(
numeric_limits<int>::digits >= 26, "This algorithm has not been ported to a 16 bit signed integer");
const int _Yp = static_cast<int>(_Year) - (_Month <= 2);
statementreply marked this conversation as resolved.
Show resolved Hide resolved
const int _Century = (_Yp >= 0 ? _Yp : _Yp - 99) / 100;
const unsigned int _Mp = _Month + (_Month > 2 ? static_cast<unsigned int>(-3) : 9); // [0, 11]
// Formula 1
const int _Day_of_year = static_cast<int>(((979 * _Mp + 19) >> 5) + _Day) - 1;
// Formula 2
return ((1461 * _Yp) >> 2) - _Century + (_Century >> 2) + _Day_of_year - 719468;
}

struct system_clock { // wraps GetSystemTimePreciseAsFileTime/GetSystemTimeAsFileTime
using rep = long long;
using period = ratio<1, 10'000'000>; // 100 nanoseconds
Expand All @@ -671,7 +695,29 @@ namespace chrono {
static constexpr bool is_steady = false;

_NODISCARD static time_point now() noexcept { // get current time
return time_point(duration(_Xtime_get_ticks()));
constexpr long long __std_fs_file_time_epoch_adjustment = 0x19DB1DED53E8000LL; // TRANSITION, ABI
statementreply marked this conversation as resolved.
Show resolved Hide resolved

const duration _File_time{_Xtime_get_ticks() + __std_fs_file_time_epoch_adjustment};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: I'm unsure of how _Xtime_get_ticks() is affected by the leap-seconds-related changes in different OS versions. Specifically this function contains

FILETIME ft;
__crtGetSystemTimePreciseAsFileTime(&ft);

where __crtGetSystemTimePreciseAsFileTime(&ft) wraps either GetSystemTimeAsFileTime or GetSystemTimePreciseAsFileTime. I notice that neither of these functions is listed as an "affected API" in this blog. It would make sense to me that they would be affected in similar ways, but I just want to doublecheck / ask for some clarity here.

Copy link
Contributor Author

@statementreply statementreply Jul 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That blog is written in a "FILETIME centric view". The listed "affected APIs" are those whose behavior would change in different OS versions even when the inputs/clocks represent time points with the same FILETIME value on each corresponding machine. GetSystemTimeAsFileTime and GetSystemTimePreciseAsFileTime return the current FILETIME, which by definition doesn't change for the same FILETIME, but would map to different time points in the UTC time scale.

const auto _File_seconds = _CHRONO duration_cast<seconds>(_File_time);

__std_utc_components_1s _Utc;
const __std_file_time_to_utc_errc _Ec = __std_file_seconds_to_utc_components(_File_seconds.count(), &_Utc);
_STL_INTERNAL_CHECK(_Ec == __std_file_time_to_utc_errc::_Success);
if (_Ec == __std_file_time_to_utc_errc::_Invalid_parameter) {
return time_point{_File_time - duration{__std_fs_file_time_epoch_adjustment}};
}

using _Days = _CHRONO duration<int, ratio<86'400>>;
using _Int_seconds = _CHRONO duration<int>;
const duration _Ymd = _Days{_Days_from_civil(_Utc._Year, _Utc._Month, _Utc._Day)};
const duration _Hms = hours{_Utc._Hour} + minutes{_Utc._Minute} + _Int_seconds{_Utc._Second};

if (_Utc._Second < 60) {
return time_point{_Ymd + _Hms + (_File_time - _File_seconds)};
} else {
// positive leap second
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment "// positive leap second" made me wonder how/whether we need to handle potential future negative leap seconds. I didn't see negative leap seconds referenced in either Dan's blogpost or Daniel's blogpost so I'm wondering if there is any documentation for how a negative leap second would be represented in the Windows API/structs/*?

Copy link
Contributor Author

@statementreply statementreply Jul 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not aware of any documentation explicitly mentioning the handling of negative leap seconds by Windows APIs. My experiments showed that FileTimeToSystemTime jumps from 23:59:58.999 to 00:00:00.000 and that SystemTimeToFileTime rejects 23:59:59.xxx deleted by a fake negative leap second, whether or not PROCESS_LEAP_SECOND_INFO_FLAG_ENABLE_SIXTY_SECOND is set for the process.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code for handling negative leap seconds:

STL/stl/src/xtime0.cpp

Lines 102 to 109 in 7373b98

if (_St.wSecond == 59) {
--_St.wSecond;
if (FILETIME _Prev_sec_ft; SystemTimeToFileTime(&_St, &_Prev_sec_ft) != FALSE) {
// negative leap second
const long long _Prev_sec_ticks = _File_time_to_ticks(_Prev_sec_ft);
return {._Ticks = _Prev_sec_ticks + _Ticks_per_sec, ._Ec = _Nonexistent};
}
}

return time_point{_Ymd + _Hms - duration{1}};
}
}

_NODISCARD static __time64_t to_time_t(const time_point& _Time) noexcept { // convert to __time64_t
Expand Down Expand Up @@ -1589,19 +1635,10 @@ namespace chrono {
const unsigned int _Month = _Mp + (_Mp < 10 ? 3 : static_cast<unsigned int>(-9)); // [1, 12]
return year_month_day{_CHRONO year{_Yp + (_Month <= 2)}, _CHRONO month{_Month}, _CHRONO day{_Day}};
}
// courtesy of Howard Hinnant
// https://howardhinnant.github.io/date_algorithms.html#days_from_civil

_NODISCARD constexpr days _Days_from_civil() const noexcept {
static_assert(numeric_limits<unsigned int>::digits >= 18);
static_assert(numeric_limits<int>::digits >= 26);
const unsigned int _Mo = static_cast<unsigned int>(_Month); // [1, 12]
const int _Yp = static_cast<int>(_Year) - (_Mo <= 2);
const int _Century = (_Yp >= 0 ? _Yp : _Yp - 99) / 100;
const unsigned int _Mp = _Mo + (_Mo > 2 ? static_cast<unsigned int>(-3) : 9); // [0, 11]
// Formula 1
const int _Day_of_year = static_cast<int>(((979 * _Mp + 19) >> 5) + static_cast<unsigned int>(_Day)) - 1;
// Formula 2
return days{((1461 * _Yp) >> 2) - _Century + (_Century >> 2) + _Day_of_year - 719468};
return days{_CHRONO _Days_from_civil(
static_cast<int>(_Year), static_cast<unsigned int>(_Month), static_cast<unsigned int>(_Day))};
}
};

Expand Down Expand Up @@ -6523,15 +6560,15 @@ namespace chrono {

// HELPERS
template <class _Rep, class _Period>
_NODISCARD bool _To_xtime_10_day_clamped(_CSTD xtime& _Xt, const _CHRONO duration<_Rep, _Period>& _Rel_time) noexcept(
is_arithmetic_v<_Rep>) {
_NODISCARD bool _To_xtime_10_day_clamped_v2(
_CSTD xtime& _Xt, const _CHRONO duration<_Rep, _Period>& _Rel_time) noexcept(is_arithmetic_v<_Rep>) {
// Convert duration to xtime, maximum 10 days from now, returns whether clamping occurred.
// If clamped, timeouts will be transformed into spurious non-timeout wakes, due to ABI restrictions where
// the other side of the DLL boundary overflows int32_t milliseconds.
// Every function calling this one is TRANSITION, ABI
constexpr _CHRONO nanoseconds _Ten_days{_CHRONO hours{24} * 10};
constexpr _CHRONO duration<double> _Ten_days_d{_Ten_days};
_CHRONO nanoseconds _Tx0 = _CHRONO system_clock::now().time_since_epoch();
_CHRONO nanoseconds _Tx0 = _CHRONO system_clock::duration{_Xtime_get_ticks()};
const bool _Clamped = _Ten_days_d < _Rel_time;
if (_Clamped) {
_Tx0 += _Ten_days;
Expand All @@ -6546,6 +6583,12 @@ _NODISCARD bool _To_xtime_10_day_clamped(_CSTD xtime& _Xt, const _CHRONO duratio
return _Clamped;
}

template <class _Rep, class _Period>
_NODISCARD bool _To_xtime_10_day_clamped(_CSTD xtime& _Xt, const _CHRONO duration<_Rep, _Period>& _Rel_time) noexcept(
is_arithmetic_v<_Rep>) {
return _To_xtime_10_day_clamped_v2(_Xt, _Rel_time);
}
statementreply marked this conversation as resolved.
Show resolved Hide resolved

// duration LITERALS
inline namespace literals {
inline namespace chrono_literals {
Expand Down
4 changes: 2 additions & 2 deletions stl/inc/condition_variable
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public:
// TRANSITION, ABI: The standard says that we should use a steady clock,
// but unfortunately our ABI speaks struct xtime, which is relative to the system clock.
_CSTD xtime _Tgt;
const bool _Clamped = _To_xtime_10_day_clamped(_Tgt, _Rel_time);
const bool _Clamped = _To_xtime_10_day_clamped_v2(_Tgt, _Rel_time);
const cv_status _Result = _Wait_until(_Lck, &_Tgt);
if (_Clamped) {
return cv_status::no_timeout;
Expand Down Expand Up @@ -221,7 +221,7 @@ public:
// TRANSITION, ABI: The standard says that we should use a steady clock,
// but unfortunately our ABI speaks struct xtime, which is relative to the system clock.
_CSTD xtime _Tgt;
(void) _To_xtime_10_day_clamped(_Tgt, _Rel_time);
(void) _To_xtime_10_day_clamped_v2(_Tgt, _Rel_time);
const int _Res = _Cnd_timedwait(_Mycnd(), _Myptr->_Mymtx(), &_Tgt);
_Guard.unlock();

Expand Down
6 changes: 3 additions & 3 deletions stl/inc/mutex
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,7 @@ public:
// TRANSITION, ABI: The standard says that we should use a steady clock,
// but unfortunately our ABI speaks struct xtime, which is relative to the system clock.
_CSTD xtime _Tgt;
const bool _Clamped = _To_xtime_10_day_clamped(_Tgt, _Rel_time);
const bool _Clamped = _To_xtime_10_day_clamped_v2(_Tgt, _Rel_time);
const cv_status _Result = wait_until(_Lck, &_Tgt);
if (_Clamped) {
return cv_status::no_timeout;
Expand Down Expand Up @@ -653,7 +653,7 @@ public:
}

_CSTD xtime _Tgt;
(void) _To_xtime_10_day_clamped(_Tgt, _Abs_time - _Now);
(void) _To_xtime_10_day_clamped_v2(_Tgt, _Abs_time - _Now);
const cv_status _Result = wait_until(_Lck, &_Tgt);
if (_Result == cv_status::no_timeout) {
return cv_status::no_timeout;
Expand Down Expand Up @@ -736,7 +736,7 @@ private:
}

_CSTD xtime _Tgt;
const bool _Clamped = _To_xtime_10_day_clamped(_Tgt, _Abs_time - _Now);
const bool _Clamped = _To_xtime_10_day_clamped_v2(_Tgt, _Abs_time - _Now);
if (wait_until(_Lck, &_Tgt) == cv_status::timeout && !_Clamped) {
return _Pred();
}
Expand Down
2 changes: 1 addition & 1 deletion stl/inc/thread
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ namespace this_thread {
}

_CSTD xtime _Tgt;
(void) _To_xtime_10_day_clamped(_Tgt, _Abs_time - _Now);
(void) _To_xtime_10_day_clamped_v2(_Tgt, _Abs_time - _Now);
_Thrd_sleep(&_Tgt);
}
}
Expand Down
99 changes: 99 additions & 0 deletions stl/inc/xtime0.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// xtime0.h internal header

// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

// This must be as small as possible, because its contents are
// injected into the msvcprt.lib and msvcprtd.lib import libraries.
// Do not include or define anything else here.
// In particular, basic_string must not be included here.

#pragma once
#ifndef _XTIME0_H
#define _XTIME0_H

#include <yvals_core.h>

#pragma pack(push, _CRT_PACKING)
#pragma warning(push, _STL_WARNING_LEVEL)
#pragma warning(disable : _STL_DISABLED_WARNINGS)
_STL_DISABLE_CLANG_WARNINGS
#pragma push_macro("new")
#undef new

_EXTERN_C

// clang-format off
struct __std_win_system_time { // typedef struct _SYSTEMTIME {
unsigned short _Year; // WORD wYear;
unsigned short _Month; // WORD wMonth;
unsigned short _Day_of_week; // WORD wDayOfWeek;
unsigned short _Day; // WORD wDay;
unsigned short _Hour; // WORD wHour;
unsigned short _Minute; // WORD wMinute;
unsigned short _Second; // WORD wSecond;
unsigned short _Milliseconds; // WORD wMilliseconds;
}; // } SYSTEMTIME, *PSYSTEMTIME, *LPSYSTEMTIME;
// clang-format on

struct __std_utc_components_1s {
short _Year;
unsigned char _Month;
unsigned char _Day;
unsigned char _Weekday;
unsigned char _Hour;
unsigned char _Minute;
unsigned char _Second;
};

// SystemTimeToFileTime
// returns -1 on failure
_NODISCARD long long __stdcall __std_win_system_time_to_file_time(const __std_win_system_time* _System_time) noexcept;

// FileTimeToSystemTime
_NODISCARD bool __stdcall __std_win_file_time_to_system_time(
long long _File_time, __std_win_system_time* _Out_system_time) noexcept;

enum class __std_utc_to_file_time_errc {
_Success = 0,

// input is invalid regardless of whether there are leap seconds
// _Out_file_seconds is not set
_Invalid_parameter = 0b001,

// __std_utc_components_to_file_seconds only
// second 60 without a positive leap second, or second 59 deleted by a negative leap second
// sets _Out_file_seconds to the value corresponding to second 00 of the next minute
_Nonexistent = 0b010,

// SystemTimeToFileTime returns unexpected sub-second values
_Unknown_smear = 0b100,
};

enum class __std_file_time_to_utc_errc {
_Success = 0,

_Invalid_parameter = static_cast<int>(__std_utc_to_file_time_errc::_Invalid_parameter),

// FileTimeToSystemTime returns unexpected sub-second values
_Unknown_smear = static_cast<int>(__std_utc_to_file_time_errc::_Unknown_smear),
};

// converts UTC (whole seconds) into file_time<seconds>
_NODISCARD __std_utc_to_file_time_errc __stdcall __std_utc_components_to_file_seconds(
const __std_utc_components_1s* _Utc_time, long long* _Out_file_seconds) noexcept;

// converts file_time<seconds> into UTC
_NODISCARD __std_file_time_to_utc_errc __stdcall __std_file_seconds_to_utc_components(
long long _File_seconds, __std_utc_components_1s* _Out_utc_time) noexcept;

_END_EXTERN_C

_BITMASK_OPS(__std_utc_to_file_time_errc)
statementreply marked this conversation as resolved.
Show resolved Hide resolved

#pragma pop_macro("new")
_STL_RESTORE_CLANG_WARNINGS
#pragma warning(pop)
#pragma pack(pop)

#endif // defined(_XTIME0_H)
Loading