From 2896cd51562102af459138628199bf323ad9c274 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 21 Dec 2017 01:01:27 +0100 Subject: [PATCH] Implement Crystal::System::Time for win32 * `compute_utc_offset`: Always returns the **current** UTC offset for now. There seems to be no easy method to access UTC offset at an arbitrary point in time. * `compute_utc_seconds_and_nanoseconds`: Precision is currently only 100 nanoseconds because `FILE_TIME` has no higher resolution. * `monotonic` --- src/crystal/system/win32/time.cr | 45 +++++++++++++++++++++- src/lib_c/x86_64-windows-msvc/c/win_def.cr | 4 ++ src/lib_c/x86_64-windows-msvc/c/win_nt.cr | 2 + src/lib_c/x86_64-windows-msvc/c/winbase.cr | 34 ++++++++++++++++ 4 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 src/lib_c/x86_64-windows-msvc/c/win_def.cr diff --git a/src/crystal/system/win32/time.cr b/src/crystal/system/win32/time.cr index 6755adb0a71a..55c6d147f39d 100644 --- a/src/crystal/system/win32/time.cr +++ b/src/crystal/system/win32/time.cr @@ -1,9 +1,50 @@ +require "c/winbase" +require "winerror" + module Crystal::System::Time + # Win32 epoch is 1601-01-01 00:00:00 UTC + WINDOWS_EPOCH_IN_SECONDS = 50_491_123_200_i64 + + # Resolution of FILETIME is 100 nanoseconds + NANOSECONDS_PER_FILETIME_TICK = 100 + + NANOSECONDS_PER_SECOND = 1_000_000_000 + FILETIME_TICKS_PER_SECOND = NANOSECONDS_PER_SECOND / NANOSECONDS_PER_FILETIME_TICK + + # TODO: For now, this method returns the UTC offset currently in place, ignoring *seconds*. def self.compute_utc_offset(seconds : Int64) : Int32 - raise NotImplementedError.new("Crystal::System::Time.compute_utc_offset") + ret = LibC.GetTimeZoneInformation(out zone_information) + raise WinError.new("GetTimeZoneInformation") if ret == -1 + + zone_information.bias.to_i32 * -60 end def self.compute_utc_seconds_and_nanoseconds : {Int64, Int32} - raise NotImplementedError.new("Crystal::System::Time.compute_utc_seconds_and_nanoseconds") + # TODO: Needs a check if `GetSystemTimePreciseAsFileTime` is actually available (only >= Windows 8) + # and use `GetSystemTimeAsFileTime` as fallback. + LibC.GetSystemTimePreciseAsFileTime(out filetime) + since_epoch = (filetime.dwHighDateTime.to_u64 << 32) | filetime.dwLowDateTime.to_u64 + + seconds = (since_epoch / FILETIME_TICKS_PER_SECOND).to_i64 + WINDOWS_EPOCH_IN_SECONDS + nanoseconds = since_epoch.remainder(FILETIME_TICKS_PER_SECOND).to_i32 * NANOSECONDS_PER_FILETIME_TICK + + {seconds, nanoseconds} + end + + @@performance_frequency : Int64 = begin + ret = LibC.QueryPerformanceFrequency(out frequency) + if ret == 0 + raise WinError.new("QueryPerformanceFrequency") + end + + frequency + end + + def self.monotonic : {Int64, Int32} + if LibC.QueryPerformanceCounter(out ticks) == 0 + raise WinError.new("QueryPerformanceCounter") + end + + {ticks / @@performance_frequency, (ticks.remainder(NANOSECONDS_PER_SECOND) * NANOSECONDS_PER_SECOND / @@performance_frequency).to_i32} end end diff --git a/src/lib_c/x86_64-windows-msvc/c/win_def.cr b/src/lib_c/x86_64-windows-msvc/c/win_def.cr new file mode 100644 index 000000000000..56258f7501fc --- /dev/null +++ b/src/lib_c/x86_64-windows-msvc/c/win_def.cr @@ -0,0 +1,4 @@ +lib LibC + alias WORD = UInt16 + alias BOOL = Int32 +end diff --git a/src/lib_c/x86_64-windows-msvc/c/win_nt.cr b/src/lib_c/x86_64-windows-msvc/c/win_nt.cr index bc378a4211e1..ab7aa9256a48 100644 --- a/src/lib_c/x86_64-windows-msvc/c/win_nt.cr +++ b/src/lib_c/x86_64-windows-msvc/c/win_nt.cr @@ -1,3 +1,5 @@ lib LibC alias LPSTR = Char* + alias LONG = Int32 + alias WCHAR = UInt16 end diff --git a/src/lib_c/x86_64-windows-msvc/c/winbase.cr b/src/lib_c/x86_64-windows-msvc/c/winbase.cr index c1936a92259f..18586c8d5fa5 100644 --- a/src/lib_c/x86_64-windows-msvc/c/winbase.cr +++ b/src/lib_c/x86_64-windows-msvc/c/winbase.cr @@ -1,4 +1,5 @@ require "c/win_nt" +require "c/win_def" require "c/int_safe" lib LibC @@ -13,4 +14,37 @@ lib LibC fun FormatMessageA(dwFlags : DWORD, lpSource : Void*, dwMessageId : DWORD, dwLanguageId : DWORD, lpBuffer : LPSTR, nSize : DWORD, arguments : Void*) : DWORD + + struct FILETIME + dwLowDateTime : DWORD + dwHighDateTime : DWORD + end + + struct SYSTEMTIME + wYear : WORD + wMonth : WORD + wDayOfWeek : WORD + wDay : WORD + wHour : WORD + wMinute : WORD + wSecond : WORD + wMilliseconds : WORD + end + + struct TIME_ZONE_INFORMATION + bias : LONG + standardName : StaticArray(WCHAR, 32) + standardDate : SYSTEMTIME + standardBias : LONG + daylightName : StaticArray(WCHAR, 32) + daylightDate : SYSTEMTIME + daylightBias : LONG + end + + fun GetTimeZoneInformation(tz_info : TIME_ZONE_INFORMATION*) : DWORD + fun GetSystemTimeAsFileTime(time : FILETIME*) + fun GetSystemTimePreciseAsFileTime(time : FILETIME*) + + fun QueryPerformanceCounter(performance_count : Int64*) : BOOL + fun QueryPerformanceFrequency(frequency : Int64*) : BOOL end