Skip to content

Commit e5bd338

Browse files
committed
Support: add some helpers for time conversion
This adds some conversion helpers for `FILETIME` and `SYSTEMTIME`. These are intended to help support adding accessors for the `DatePicker` control.
1 parent d433fa2 commit e5bd338

File tree

3 files changed

+144
-0
lines changed

3 files changed

+144
-0
lines changed

Package.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ let SwiftWin32 = Package(
9797
name: "CoreGraphicsTests",
9898
dependencies: ["SwiftWin32"]
9999
),
100+
.testTarget(
101+
name: "SupportTests",
102+
dependencies: ["SwiftWin32"]
103+
),
100104
.testTarget(
101105
name: "UICoreTests",
102106
dependencies: ["SwiftWin32", "TestUtilities"]
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright © 2021 Saleem Abdulrasool <[email protected]>
2+
// SPDX-License-Identifier: BSD-3-Clause
3+
4+
import WinSDK
5+
6+
import struct Foundation.Date
7+
import struct Foundation.TimeInterval
8+
9+
// 100 nanosecond ticks
10+
@_transparent
11+
internal var WindowsTick: Double { 10_000_000 }
12+
13+
// NT to Unix Epoch (seconds)
14+
@_transparent
15+
internal var NTToUnixEpochBias: Double { 11_644_473_600 }
16+
17+
extension FILETIME {
18+
@inline(__always)
19+
internal init(_ systemTime: SYSTEMTIME) {
20+
self = FILETIME()
21+
withUnsafePointer(to: systemTime) {
22+
guard SystemTimeToFileTime($0, &self) else {
23+
fatalError("SystemTimeToFileTime: \(Error(win32: GetLastError()))")
24+
}
25+
}
26+
}
27+
28+
@inline(__always)
29+
internal init(timeIntervalSince1970 interval: TimeInterval) {
30+
let value = UInt64((interval + NTToUnixEpochBias) * WindowsTick)
31+
self = FILETIME(dwLowDateTime: DWORD((value >> 0) & 0xffffffff),
32+
dwHighDateTime: DWORD((value >> 32) & 0xffffffff))
33+
}
34+
35+
@inline(__always)
36+
internal var timeIntervalSince1970: TimeInterval {
37+
var ulTime: ULARGE_INTEGER = ULARGE_INTEGER()
38+
ulTime.LowPart = self.dwLowDateTime
39+
ulTime.HighPart = self.dwHighDateTime
40+
return Double(ulTime.QuadPart) / WindowsTick - NTToUnixEpochBias
41+
}
42+
}
43+
44+
extension SYSTEMTIME {
45+
@inline(__always)
46+
internal init(_ fileTime: FILETIME) {
47+
self = SYSTEMTIME()
48+
withUnsafePointer(to: fileTime) {
49+
guard FileTimeToSystemTime($0, &self) else {
50+
fatalError("FileTimeToSystemTime: \(Error(win32: GetLastError()))")
51+
}
52+
}
53+
}
54+
}

Tests/SupportTests/DateTests.swift

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Copyright © 2021 Saleem Abdulrasool <[email protected]>
2+
// SPDX-License-Identifier: BSD-3-Clause
3+
4+
import XCTest
5+
import WinSDK
6+
import struct Foundation.Date
7+
@testable import SwiftWin32
8+
9+
final class DateTests: XCTestCase {
10+
func testFileTimeConstruction() {
11+
XCTAssertEqual(FILETIME(timeIntervalSince1970: 0).timeIntervalSince1970, 0)
12+
}
13+
14+
func testUnixEpoch() {
15+
let ftUnixEpoch: FILETIME = FILETIME(timeIntervalSince1970: 0)
16+
XCTAssertEqual(ftUnixEpoch.dwLowDateTime, 3577643008)
17+
XCTAssertEqual(ftUnixEpoch.dwHighDateTime, 27111902)
18+
19+
let stUnixEpoch: SYSTEMTIME = SYSTEMTIME(ftUnixEpoch)
20+
XCTAssertEqual(stUnixEpoch.wYear, 1970)
21+
XCTAssertEqual(stUnixEpoch.wMonth, 1)
22+
XCTAssertEqual(stUnixEpoch.wDayOfWeek, 4)
23+
XCTAssertEqual(stUnixEpoch.wDay, 1)
24+
XCTAssertEqual(stUnixEpoch.wHour, 0)
25+
XCTAssertEqual(stUnixEpoch.wMinute, 0)
26+
XCTAssertEqual(stUnixEpoch.wSecond, 0)
27+
XCTAssertEqual(stUnixEpoch.wMilliseconds, 0)
28+
}
29+
30+
func testSystemTimeConversion() {
31+
let stTimeStamp: SYSTEMTIME =
32+
SYSTEMTIME(wYear: 2020, wMonth: 11, wDayOfWeek: 0, wDay: 7,
33+
wHour: 0, wMinute: 0, wSecond: 0, wMilliseconds: 0)
34+
XCTAssertEqual(FILETIME(stTimeStamp).timeIntervalSince1970,
35+
1604707200)
36+
}
37+
38+
func testFileTimeConversion() {
39+
let ftTimeStamp: FILETIME = FILETIME(timeIntervalSince1970: 1604707200)
40+
let stTimeStamp: SYSTEMTIME = SYSTEMTIME(ftTimeStamp)
41+
42+
XCTAssertEqual(stTimeStamp.wYear, 2020)
43+
XCTAssertEqual(stTimeStamp.wMonth, 11)
44+
45+
// We cannot check the day of week because it is ignored on the conversion
46+
// and we are going from UTC to local time zone to UTC, which changes the
47+
// day.
48+
/* XCTAssertEqual(stTimeStamp.wDayOfWeek, 0) */
49+
50+
XCTAssertEqual(stTimeStamp.wDay, 7)
51+
XCTAssertEqual(stTimeStamp.wHour, 0)
52+
XCTAssertEqual(stTimeStamp.wMinute, 0)
53+
XCTAssertEqual(stTimeStamp.wSecond, 0)
54+
XCTAssertEqual(stTimeStamp.wMilliseconds, 0)
55+
}
56+
57+
func testRoundTrip() {
58+
let ftSystemTimeInitial: SYSTEMTIME =
59+
SYSTEMTIME(wYear: 2020, wMonth: 11, wDayOfWeek: 6, wDay: 7,
60+
wHour: 0, wMinute: 0, wSecond: 0, wMilliseconds: 0)
61+
let ftSystemTimeConverted: SYSTEMTIME =
62+
SYSTEMTIME(FILETIME(ftSystemTimeInitial))
63+
64+
XCTAssertEqual(ftSystemTimeInitial.wYear, ftSystemTimeConverted.wYear)
65+
XCTAssertEqual(ftSystemTimeInitial.wMonth, ftSystemTimeConverted.wMonth)
66+
67+
// We cannot check the day of week because it is ignored on the conversion
68+
// and we are going from UTC to local time zone to UTC, which changes the
69+
// day.
70+
/* XCTAssertEqual(ftSystemTimeInitial.wDayOfWeek, ftSystemTimeConverted.wDayOfWeek) */
71+
72+
XCTAssertEqual(ftSystemTimeInitial.wDay, ftSystemTimeConverted.wDay)
73+
XCTAssertEqual(ftSystemTimeInitial.wHour, ftSystemTimeConverted.wHour)
74+
XCTAssertEqual(ftSystemTimeInitial.wMinute, ftSystemTimeConverted.wMinute)
75+
XCTAssertEqual(ftSystemTimeInitial.wSecond, ftSystemTimeConverted.wSecond)
76+
XCTAssertEqual(ftSystemTimeInitial.wMilliseconds, ftSystemTimeConverted.wMilliseconds)
77+
}
78+
79+
static var allTests = [
80+
("testFileTimeConstruction", testFileTimeConstruction),
81+
("testUnixEpoch", testUnixEpoch),
82+
("testSystemTimeConversion", testSystemTimeConversion),
83+
("testFileTimeConversion", testFileTimeConversion),
84+
("testRoundTrip", testRoundTrip),
85+
]
86+
}

0 commit comments

Comments
 (0)