From 7bcde35651619f5990234d3e084dcbfdc8e69db4 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 26 Oct 2022 16:05:49 +0100 Subject: [PATCH] Heavily improve time handling on Windows for `time.now()` and `os.File_Info` --- core/os/dir_windows.odin | 4 +-- core/os/stat_windows.odin | 20 +++++------ core/sys/windows/kernel32.odin | 10 ++++++ core/sys/windows/types.odin | 13 ++++++- core/time/time.odin | 66 +++++++++++++++++----------------- core/time/time_windows.odin | 13 +++++-- 6 files changed, 77 insertions(+), 49 deletions(-) diff --git a/core/os/dir_windows.odin b/core/os/dir_windows.odin index 89a09d403..cf1452abd 100644 --- a/core/os/dir_windows.odin +++ b/core/os/dir_windows.odin @@ -41,9 +41,7 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F // fi.mode |= file_type_mode(h); } - fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)) - fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)) - fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) + window_set_file_info_times(&fi, d) fi.is_dir = fi.mode & File_Mode_Dir != 0 return diff --git a/core/os/stat_windows.odin b/core/os/stat_windows.odin index 79bb8c42e..54f8003d6 100644 --- a/core/os/stat_windows.odin +++ b/core/os/stat_windows.odin @@ -228,6 +228,13 @@ file_mode_from_file_attributes :: proc(FileAttributes: win32.DWORD, h: win32.HAN return } +@(private) +window_set_file_info_times :: proc(fi: ^File_Info, d: ^$T) { + fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)) + fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)) + fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) +} + @(private) file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string) -> (fi: File_Info, e: Errno) { fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) @@ -235,9 +242,7 @@ file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_ fi.mode |= file_mode_from_file_attributes(d.dwFileAttributes, nil, 0) fi.is_dir = fi.mode & File_Mode_Dir != 0 - fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)) - fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)) - fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) + window_set_file_info_times(&fi, d) fi.fullpath, e = full_path_from_name(name) fi.name = basename(fi.fullpath) @@ -252,9 +257,7 @@ file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string) fi.mode |= file_mode_from_file_attributes(d.dwFileAttributes, nil, 0) fi.is_dir = fi.mode & File_Mode_Dir != 0 - fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)) - fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)) - fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) + window_set_file_info_times(&fi, d) fi.fullpath, e = full_path_from_name(name) fi.name = basename(fi.fullpath) @@ -290,10 +293,7 @@ file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HAN fi.mode |= file_mode_from_file_attributes(ti.FileAttributes, h, ti.ReparseTag) fi.is_dir = fi.mode & File_Mode_Dir != 0 - fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)) - fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)) - fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) - + window_set_file_info_times(&fi, &d) return fi, ERROR_NONE } diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin index 304710be2..4f7d24ce8 100644 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -249,6 +249,16 @@ foreign kernel32 { GetModuleHandleA :: proc(lpModuleName: LPCSTR) -> HMODULE --- GetSystemTimeAsFileTime :: proc(lpSystemTimeAsFileTime: LPFILETIME) --- GetSystemTimePreciseAsFileTime :: proc(lpSystemTimeAsFileTime: LPFILETIME) --- + FileTimeToSystemTime :: proc(lpFileTime: ^FILETIME, lpSystemTime: ^SYSTEMTIME) -> BOOL --- + SystemTimeToTzSpecificLocalTime :: proc( + lpTimeZoneInformation: ^TIME_ZONE_INFORMATION, + lpUniversalTime: ^SYSTEMTIME, + lpLocalTime: ^SYSTEMTIME, + ) -> BOOL --- + SystemTimeToFileTime :: proc( + lpSystemTime: ^SYSTEMTIME, + lpFileTime: LPFILETIME, + ) -> BOOL --- CreateEventW :: proc( lpEventAttributes: LPSECURITY_ATTRIBUTES, bManualReset: BOOL, diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index af28a9060..62b7d25a7 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -2267,9 +2267,10 @@ FILETIME :: struct { FILETIME_as_unix_nanoseconds :: proc "contextless" (ft: FILETIME) -> i64 { t := i64(u64(ft.dwLowDateTime) | u64(ft.dwHighDateTime) << 32) - return (t - 0x019db1ded53e8000) * 100 + return (t - 116444736000000000) * 100 } + OVERLAPPED :: struct { Internal: ^c_ulong, InternalHigh: ^c_ulong, @@ -2943,6 +2944,16 @@ SYSTEMTIME :: struct { milliseconds: WORD, } +TIME_ZONE_INFORMATION :: struct { + Bias: LONG, + StandardName: [32]WCHAR, + StandardDate: SYSTEMTIME, + StandardBias: LONG, + DaylightName: [32]WCHAR, + DaylightDate: SYSTEMTIME, + DaylightBias: LONG, +} + @(private="file") IMAGE_DOS_HEADER :: struct { diff --git a/core/time/time.odin b/core/time/time.odin index 6c6e47dc0..74c80c8f7 100644 --- a/core/time/time.odin +++ b/core/time/time.odin @@ -17,7 +17,7 @@ MAX_DURATION :: Duration(1<<63 - 1) IS_SUPPORTED :: _IS_SUPPORTED Time :: struct { - _nsec: i64, // zero is 1970-01-01 00:00:00 + _nsec: i64, // Measured in UNIX nanonseconds } Month :: enum int { @@ -59,36 +59,36 @@ sleep :: proc "contextless" (d: Duration) { _sleep(d) } -stopwatch_start :: proc(using stopwatch: ^Stopwatch) { +stopwatch_start :: proc "contextless" (using stopwatch: ^Stopwatch) { if !running { _start_time = tick_now() running = true } } -stopwatch_stop :: proc(using stopwatch: ^Stopwatch) { +stopwatch_stop :: proc "contextless" (using stopwatch: ^Stopwatch) { if running { _accumulation += tick_diff(_start_time, tick_now()) running = false } } -stopwatch_reset :: proc(using stopwatch: ^Stopwatch) { +stopwatch_reset :: proc "contextless" (using stopwatch: ^Stopwatch) { _accumulation = {} running = false } -stopwatch_duration :: proc(using stopwatch: Stopwatch) -> Duration { +stopwatch_duration :: proc "contextless" (using stopwatch: Stopwatch) -> Duration { if !running { return _accumulation } return _accumulation + tick_diff(_start_time, tick_now()) } -diff :: proc(start, end: Time) -> Duration { +diff :: proc "contextless" (start, end: Time) -> Duration { d := end._nsec - start._nsec return Duration(d) } -since :: proc(start: Time) -> Duration { +since :: proc "contextless" (start: Time) -> Duration { return diff(start, now()) } @@ -117,8 +117,8 @@ duration_hours :: proc "contextless" (d: Duration) -> f64 { return f64(hour) + f64(nsec)/(60*60*1e9) } -duration_round :: proc(d, m: Duration) -> Duration { - _less_than_half :: #force_inline proc(x, y: Duration) -> bool { +duration_round :: proc "contextless" (d, m: Duration) -> Duration { + _less_than_half :: #force_inline proc "contextless" (x, y: Duration) -> bool { return u64(x)+u64(x) < u64(y) } @@ -146,45 +146,45 @@ duration_round :: proc(d, m: Duration) -> Duration { return MAX_DURATION } -duration_truncate :: proc(d, m: Duration) -> Duration { +duration_truncate :: proc "contextless" (d, m: Duration) -> Duration { return d if m <= 0 else d - d%m } -date :: proc(t: Time) -> (year: int, month: Month, day: int) { +date :: proc "contextless" (t: Time) -> (year: int, month: Month, day: int) { year, month, day, _ = _abs_date(_time_abs(t), true) return } -year :: proc(t: Time) -> (year: int) { +year :: proc "contextless" (t: Time) -> (year: int) { year, _, _, _ = _date(t, true) return } -month :: proc(t: Time) -> (month: Month) { +month :: proc "contextless" (t: Time) -> (month: Month) { _, month, _, _ = _date(t, true) return } -day :: proc(t: Time) -> (day: int) { +day :: proc "contextless" (t: Time) -> (day: int) { _, _, day, _ = _date(t, true) return } clock :: proc { clock_from_time, clock_from_duration, clock_from_stopwatch } -clock_from_time :: proc(t: Time) -> (hour, min, sec: int) { +clock_from_time :: proc "contextless" (t: Time) -> (hour, min, sec: int) { return clock_from_seconds(_time_abs(t)) } -clock_from_duration :: proc(d: Duration) -> (hour, min, sec: int) { +clock_from_duration :: proc "contextless" (d: Duration) -> (hour, min, sec: int) { return clock_from_seconds(u64(d/1e9)) } -clock_from_stopwatch :: proc(s: Stopwatch) -> (hour, min, sec: int) { +clock_from_stopwatch :: proc "contextless" (s: Stopwatch) -> (hour, min, sec: int) { return clock_from_duration(stopwatch_duration(s)) } -clock_from_seconds :: proc(nsec: u64) -> (hour, min, sec: int) { +clock_from_seconds :: proc "contextless" (nsec: u64) -> (hour, min, sec: int) { sec = int(nsec % SECONDS_PER_DAY) hour = sec / SECONDS_PER_HOUR sec -= hour * SECONDS_PER_HOUR @@ -193,11 +193,11 @@ clock_from_seconds :: proc(nsec: u64) -> (hour, min, sec: int) { return } -read_cycle_counter :: proc() -> u64 { +read_cycle_counter :: proc "contextless" () -> u64 { return u64(intrinsics.read_cycle_counter()) } -unix :: proc(sec: i64, nsec: i64) -> Time { +unix :: proc "contextless" (sec: i64, nsec: i64) -> Time { sec, nsec := sec, nsec if nsec < 0 || nsec >= 1e9 { n := nsec / 1e9 @@ -208,20 +208,20 @@ unix :: proc(sec: i64, nsec: i64) -> Time { sec -= 1 } } - return Time{(sec*1e9 + nsec) + UNIX_TO_INTERNAL} + return Time{(sec*1e9 + nsec)} } to_unix_seconds :: time_to_unix -time_to_unix :: proc(t: Time) -> i64 { +time_to_unix :: proc "contextless" (t: Time) -> i64 { return t._nsec/1e9 } to_unix_nanoseconds :: time_to_unix_nano -time_to_unix_nano :: proc(t: Time) -> i64 { +time_to_unix_nano :: proc "contextless" (t: Time) -> i64 { return t._nsec } -time_add :: proc(t: Time, d: Duration) -> Time { +time_add :: proc "contextless" (t: Time, d: Duration) -> Time { return Time{t._nsec + i64(d)} } @@ -231,7 +231,7 @@ time_add :: proc(t: Time, d: Duration) -> Time { // On Windows it depends but is comparable with regular sleep in the worst case. // To get the same kind of accuracy as on Linux, have your program call `win32.time_begin_period(1)` to // tell Windows to use a more accurate timer for your process. -accurate_sleep :: proc(d: Duration) { +accurate_sleep :: proc "contextless" (d: Duration) { to_sleep, estimate, mean, m2, count: Duration to_sleep = d @@ -279,19 +279,19 @@ ABSOLUTE_TO_UNIX :: -UNIX_TO_ABSOLUTE @(private) -_date :: proc(t: Time, full: bool) -> (year: int, month: Month, day: int, yday: int) { +_date :: proc "contextless" (t: Time, full: bool) -> (year: int, month: Month, day: int, yday: int) { year, month, day, yday = _abs_date(_time_abs(t), full) return } @(private) -_time_abs :: proc(t: Time) -> u64 { +_time_abs :: proc "contextless" (t: Time) -> u64 { return u64(t._nsec/1e9 + UNIX_TO_ABSOLUTE) } @(private) -_abs_date :: proc(abs: u64, full: bool) -> (year: int, month: Month, day: int, yday: int) { - _is_leap_year :: proc(year: int) -> bool { +_abs_date :: proc "contextless" (abs: u64, full: bool) -> (year: int, month: Month, day: int, yday: int) { + _is_leap_year :: proc "contextless" (year: int) -> bool { return year%4 == 0 && (year%100 != 0 || year%400 == 0) } @@ -352,9 +352,11 @@ _abs_date :: proc(abs: u64, full: bool) -> (year: int, month: Month, day: int, y return } -datetime_to_time :: proc(year, month, day, hour, minute, second: int, nsec := int(0)) -> (t: Time, ok: bool) { - divmod :: proc(year: int, divisor: int) -> (div: int, mod: int) { - assert(divisor > 0) +datetime_to_time :: proc "contextless" (year, month, day, hour, minute, second: int, nsec := int(0)) -> (t: Time, ok: bool) { + divmod :: proc "contextless" (year: int, divisor: int) -> (div: int, mod: int) { + if divisor <= 0 { + intrinsics.debug_trap() + } div = int(year / divisor) mod = year % divisor return diff --git a/core/time/time_windows.odin b/core/time/time_windows.odin index 20863c323..378b914b0 100644 --- a/core/time/time_windows.odin +++ b/core/time/time_windows.odin @@ -7,9 +7,16 @@ _IS_SUPPORTED :: true _now :: proc "contextless" () -> Time { file_time: win32.FILETIME - win32.GetSystemTimeAsFileTime(&file_time) - ns := win32.FILETIME_as_unix_nanoseconds(file_time) - return Time{_nsec=ns} + + ns: i64 + + // monotonic + win32.GetSystemTimePreciseAsFileTime(&file_time) + + dt := u64(transmute(u64le)file_time) // in 100ns units + ns = i64((dt - 116444736000000000) * 100) // convert to ns + + return unix(0, ns) } _sleep :: proc "contextless" (d: Duration) {