Skip to content

Commit

Permalink
Check leap second awareness and interpret FILETIME accordingly
Browse files Browse the repository at this point in the history
Windows 10 1809 and Windows Server 2019 introduced support for leap
seconds (enabled by default, could be disabled system-wide). When leap
seconds are enabled, leap seconds after July 2018 are included in the
tick count of `FILETIME`. This is different from the old behavior that
`FILETIME` doesn't count leap seconds. In the future, the same
`FILETIME` value could represent different time points a few seconds
apart depending on whether leap second support is available and enabled.

This PR detects whether leap second support is enabled on the system by
performing trial conversion from `SYSTEMTIME` to `FILETIME`. The
following functions, which depend on interpretation of `FILETIME`, are
modified to correctly handle `FILETIME`:
- `file_clock::now()`
- `file_clock` conversions
- `file_clock` I/O
- `system_clock::now()`, which gets the current `FILETIME` internally
  - In C++20 mode, `system_clock::now()` uses C++20 <chrono> features to
    convert the current `FILETIME` to `sys_time`.
  - In C++14/C++17 mode, `system_clock::now()` calls
    `FileTimeToSystemTime` to determine how Windows interprets the
    current `FILETIME`.
  • Loading branch information
statementreply committed Apr 26, 2021
1 parent f675d68 commit 119d474
Show file tree
Hide file tree
Showing 9 changed files with 348 additions and 37 deletions.
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
159 changes: 128 additions & 31 deletions stl/inc/chrono

Large diffs are not rendered by default.

59 changes: 59 additions & 0 deletions stl/inc/xtime0.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// 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

// Gets the number of 100 ns ticks since 1601-01-01.
// Leap seconds before 2018-07-01 are excluded.
// Leap seconds since 2018-07-01 may or may not be included depending on OS version and system configuration.
_NODISCARD long long __stdcall __std_file_clock_now() noexcept;

// Gets the number of 100 ns ticks since 1970-01-01, excluding leap seconds.
// If this function is called during a leap second insertion, and the system is leap second aware,
// the largest representable value before the leap second insertion is returned.
_NODISCARD long long __stdcall __std_system_clock_now_cpp14() noexcept;

// Calls Windows SystemTimeToFileTime
_NODISCARD long long __stdcall __std_system_time_to_file_time(const __std_win_system_time* _System_time) noexcept;

_END_EXTERN_C

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

#endif // defined(_XTIME0_H)
2 changes: 1 addition & 1 deletion stl/inc/xtimec.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ _CRTIMP2_PURE int __cdecl xtime_get(xtime*, int);

_CRTIMP2_PURE long __cdecl _Xtime_diff_to_millis(const xtime*);
_CRTIMP2_PURE long __cdecl _Xtime_diff_to_millis2(const xtime*, const xtime*);
_CRTIMP2_PURE long long __cdecl _Xtime_get_ticks();
_CRTIMP2_PURE long long __cdecl _Xtime_get_ticks(); // TRANSITION, ABI

_CRTIMP2_PURE long long __cdecl _Query_perf_counter();
_CRTIMP2_PURE long long __cdecl _Query_perf_frequency();
Expand Down
9 changes: 7 additions & 2 deletions stl/src/xtime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <xtimec.h>

#include "awint.hpp"
#include "xtime_inl.hpp"

constexpr long _Nsec_per_sec = 1000000000L;
constexpr long _Nsec_per_msec = 1000000L;
Expand Down Expand Up @@ -47,14 +48,18 @@ constexpr long _Nsec100_per_sec = _Nsec_per_sec / 100;

_EXTERN_C

long long _Xtime_get_ticks() { // get system time in 100-nanosecond intervals since the epoch
long long _Xtime_get_ticks() { // get system time in 100-nanosecond intervals since the epoch, excluding leap seconds
return system_clock_now_cpp14();
}

unsigned long long _Xtime_get_ticks_old() { // get system time in 100-nanosecond intervals since the epoch
FILETIME ft;
__crtGetSystemTimePreciseAsFileTime(&ft);
return ((static_cast<long long>(ft.dwHighDateTime)) << 32) + static_cast<long long>(ft.dwLowDateTime) - _Epoch;
}

static void sys_get_time(xtime* xt) { // get system time with nanosecond resolution
unsigned long long now = _Xtime_get_ticks();
unsigned long long now = _Xtime_get_ticks_old();
xt->sec = static_cast<__time64_t>(now / _Nsec100_per_sec);
xt->nsec = static_cast<long>(now % _Nsec100_per_sec) * 100;
}
Expand Down
37 changes: 37 additions & 0 deletions stl/src/xtime0.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// 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.

#include <chrono>
#include <cstdlib>
#include <xtime0.h>

#include "awint.hpp"
#include "xtime_inl.hpp"

_EXTERN_C

_NODISCARD long long __stdcall __std_file_clock_now() noexcept {
return file_clock_now();
}

_NODISCARD long long __stdcall __std_system_clock_now_cpp14() noexcept {
return system_clock_now_cpp14();
}

_NODISCARD long long __stdcall __std_system_time_to_file_time(
const __std_win_system_time* const _System_time) noexcept {
FILETIME ft;
if (SystemTimeToFileTime(reinterpret_cast<const SYSTEMTIME*>(_System_time), &ft) == FALSE) {
_STL_ASSERT(false, "SystemTimeToFileTime failed");
return -1;
}

return ((static_cast<long long>(ft.dwHighDateTime)) << 32) + static_cast<long long>(ft.dwLowDateTime);
}

_END_EXTERN_C
90 changes: 90 additions & 0 deletions stl/src/xtime_inl.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// 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 XTIME_INL_HPP
#define XTIME_INL_HPP

#include <chrono>
#include <cstdlib>
#include <xtime0.h>

#include "awint.hpp"

static_assert(sizeof(__std_win_system_time) == sizeof(SYSTEMTIME));
static_assert(alignof(__std_win_system_time) == alignof(SYSTEMTIME));

_NODISCARD inline long long file_clock_now() noexcept {
FILETIME ft;
__crtGetSystemTimePreciseAsFileTime(&ft);
return ((static_cast<long long>(ft.dwHighDateTime)) << 32) + static_cast<long long>(ft.dwLowDateTime);
}

_NODISCARD inline long long system_clock_now_cpp14() noexcept {
using namespace std::chrono;
constexpr int ticks_per_sec = 10'000'000u;
static_assert(system_clock::duration{ticks_per_sec} == 1s);

// check how Windows interprets the current second
const long long ticks = file_clock_now();
auto sub_sec_ticks = static_cast<int>(ticks % ticks_per_sec);
const long long sec_ticks = ticks - sub_sec_ticks;

const FILETIME sec_ft{
.dwLowDateTime = static_cast<DWORD>(sec_ticks),
.dwHighDateTime = static_cast<DWORD>(sec_ticks >> 32),
};
SYSTEMTIME st;
if (FileTimeToSystemTime(&sec_ft, &st) == FALSE) {
_STL_ASSERT(false, "FileTimeToSystemTime failed");
std::abort();
}

if (st.wSecond == 60) {
// during leap second insertion
// system is leap second aware
// process is leap second aware
st.wSecond = 59;
sub_sec_ticks = ticks_per_sec - 1;
} else if (st.wMilliseconds != 0) {
_STL_INTERNAL_CHECK(st.wSecond == 59 && st.wMilliseconds == 500);

if (st.wSecond == 59 && st.wMilliseconds == 500) {
// during leap second insertion
// system is leap second aware
// process is leap second unaware, 23:59:60.000 UTC is reported as 23:59:59.500 in SYSTEMTIME
sub_sec_ticks = ticks_per_sec - 1;
} else {
// fail-safe fallback in case leap second smearing behavior changes in future Windows versions
constexpr int ticks_per_ms = ticks_per_sec / 1000;

const FILETIME ft{
.dwLowDateTime = static_cast<DWORD>(ticks),
.dwHighDateTime = static_cast<DWORD>(ticks >> 32),
};
if (FileTimeToSystemTime(&ft, &st) == FALSE) {
_STL_ASSERT(false, "FileTimeToSystemTime failed");
std::abort();
}

if (st.wSecond == 60) {
st.wSecond = 59;
sub_sec_ticks = ticks_per_sec - 1;
} else {
sub_sec_ticks = st.wMilliseconds * ticks_per_ms;
}
}
}

const system_clock::time_point now =
sys_days{year{st.wYear} / month{st.wMonth} / day{st.wDay}}
+ (hours{st.wHour} + minutes{st.wMinute} + seconds{st.wSecond} + system_clock::duration{sub_sec_ticks});
return now.time_since_epoch().count();
}

#endif // defined(XTIME_INL_HPP)
23 changes: 22 additions & 1 deletion tests/std/tests/P0355R7_calendars_and_time_zones_clocks/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,25 @@ void test_utc_clock_to_sys(const leap_second& leap) {
assert(utc_clock::from_sys(utc_clock::to_sys(u)) == u); // just after
}

template <class Duration>
void test_file_clock_to_sys(const leap_second& leap) { // tests internal machinery used by system_clock::now()
static_assert(is_same_v<filesystem::file_time_type::clock, file_clock>);

const tzdb& db = get_tzdb();

auto u = utc_clock::from_sys(leap.date() - Duration{1}); // just before leap second
assert(file_clock::_To_sys(file_clock::from_utc(u), db) == utc_clock::to_sys(u));

u += Duration{1};

if (leap.value() == 1s) {
assert(file_clock::_To_sys(file_clock::from_utc(u), db) == utc_clock::to_sys(u)); // during
u += 1s;
}

assert(file_clock::_To_sys(file_clock::from_utc(u), db) == utc_clock::to_sys(u)); // just after
}

template <class Duration>
void test_file_clock_from_utc(const leap_second& leap) {
static_assert(is_same_v<filesystem::file_time_type::clock, file_clock>);
Expand Down Expand Up @@ -391,7 +410,8 @@ tzdb copy_tzdb() {
return time_zone_link{link.name(), link.target()};
});

return {my_tzdb.version, move(zones), move(links), my_tzdb.leap_seconds, my_tzdb._All_ls_positive};
return {my_tzdb.version, move(zones), move(links), my_tzdb.leap_seconds, my_tzdb._All_ls_positive,
my_tzdb._Ls_awareness};
}

void test() {
Expand All @@ -411,6 +431,7 @@ void test() {
test_utc_clock_to_sys<seconds>(leap);
test_utc_clock_to_sys<milliseconds>(leap);
test_utc_clock_to_sys<duration<double>>(leap);
test_file_clock_to_sys<system_clock::duration>(leap);
test_file_clock_from_utc<seconds>(leap);
test_file_clock_from_utc<milliseconds>(leap);
test_utc_clock_from_sys(leap, offset);
Expand Down
4 changes: 2 additions & 2 deletions tests/std/tests/P0355R7_calendars_and_time_zones_io/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -884,8 +884,8 @@ void insert_leap_second(const sys_days& date, const seconds& value) {

auto leap_vec = my_tzdb.leap_seconds;
leap_vec.emplace_back(date, value == 1s, leap_vec.back()._Elapsed());
get_tzdb_list()._Emplace_front(
tzdb{my_tzdb.version, move(zones), move(links), move(leap_vec), my_tzdb._All_ls_positive && (value == 1s)});
get_tzdb_list()._Emplace_front(tzdb{my_tzdb.version, move(zones), move(links), move(leap_vec),
my_tzdb._All_ls_positive && (value == 1s), my_tzdb._Ls_awareness});
}

void parse_timepoints() {
Expand Down

0 comments on commit 119d474

Please sign in to comment.