forked from pointfreeco/swift-parsing
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Date.swift
113 lines (107 loc) · 3.41 KB
/
Date.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import Benchmark
import Foundation
import Parsing
#if swift(>=5.8)
struct DateTime: Parser {
var body: some Parser<Substring.UTF8View, Date> {
Parse(Date.init(year:month:day:hour:minute:second:nanosecond:timeZone:)) {
Digits(4)
"-".utf8
Digits(2).filter { (1...12).contains($0) }
"-".utf8
Digits(2).filter { (1...31).contains($0) }
"T".utf8
Digits(2).filter { $0 < 24 }
":".utf8
Digits(2).filter { $0 < 60 }
":".utf8
Digits(2).filter { $0 <= 60 }
Parse {
".".utf8
Prefix(1...9, while: (UInt8(ascii: "0")...UInt8(ascii: "9")).contains)
.compactMap { n in Int(Substring(n)).map { $0 * Int(pow(10, 9 - Double(n.count))) } }
}
.replaceError(with: 0)
OneOf {
"Z".utf8.map { 0 }
Parse {
OneOf {
"+".utf8.map { 1 }
"-".utf8.map { -1 }
}
Digits(2).filter { $0 < 24 }.map { $0 * 60 * 60 }
":".utf8
Digits(2).filter { $0 < 60 }.map { $0 * 60 }
}
.map { $0 * ($1 + $2) }
}
}
}
}
extension Date {
fileprivate init(
year: Int,
month: Int,
day: Int,
hour: Int,
minute: Int,
second: Int,
nanosecond: Int,
timeZone: Int
) {
var components = tm(
tm_sec: Int32(second),
tm_min: Int32(minute),
tm_hour: Int32(hour),
tm_mday: Int32(day),
tm_mon: Int32(month - 1),
tm_year: Int32(year - 1900),
tm_wday: 0,
tm_yday: 0,
tm_isdst: 0,
tm_gmtoff: 0,
tm_zone: nil
)
let time = timegm(&components)
var timeIntervalSince1970 = TimeInterval(time - timeZone)
timeIntervalSince1970 += TimeInterval(nanosecond) / 1_000_000_000
self.init(timeIntervalSince1970: timeIntervalSince1970)
}
}
#endif
/// This benchmarks implements an [RFC-3339-compliant](https://www.ietf.org/rfc/rfc3339.txt) date
/// parser in a relatively naive way and pits it against `DateFormatter` and `ISO8601DateFormatter`.
///
/// Not only is the parser faster than both formatters, it is more flexible and accurate: it will
/// parse fractional seconds and time zone offsets automatically, and it will parse to the
/// nanosecond, while the formatters do not parse beyond the millisecond.
let dateSuite = BenchmarkSuite(name: "Date") { suite in
#if swift(>=5.8)
let input = "1979-05-27T00:32:00Z"
let expected = Date(timeIntervalSince1970: 296_613_120)
var output: Date!
suite.benchmark("Parser") {
output = try DateTime().parse(input)
} tearDown: {
precondition(output == expected)
}
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)!
suite.benchmark("DateFormatter") {
output = dateFormatter.date(from: input)
} tearDown: {
precondition(output == expected)
}
if #available(macOS 10.12, *) {
let iso8601DateFormatter = ISO8601DateFormatter()
iso8601DateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
suite.benchmark("ISO8601DateFormatter") {
output = iso8601DateFormatter.date(from: input)
} tearDown: {
precondition(output == expected)
}
}
#endif
}