forked from pointfreeco/swift-parsing
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Date.swift
141 lines (125 loc) · 3.52 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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
import Benchmark
import Foundation
import Parsing
/// 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
/// 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
let timeDelim = OneOf {
"T".utf8
"t".utf8
" ".utf8
}
let nanoSecfrac = Prefix(while: (.init(ascii: "0") ... .init(ascii: "9")).contains)
.map { $0.prefix(9) }
let timeSecfrac = Parse {
".".utf8
nanoSecfrac
}
.compactMap { n in
Int(String(decoding: n, as: UTF8.self))
.map { $0 * Int(pow(10, 9 - Double(n.count))) }
}
let timeNumoffset = Parse {
OneOf {
"+".utf8.map { 1 }
"-".utf8.map { -1 }
}
Digits(2)
":".utf8
Digits(2)
}
let timeOffset = OneOf {
"Z".utf8.map { ( /*sign: */1, /*minute: */ 0, /*second: */ 0) }
timeNumoffset
}
.compactMap { TimeZone(secondsFromGMT: $0 * ($1 * 60 + $2)) }
let partialTime = Parse {
Digits(2)
":".utf8
Digits(2)
":".utf8
Digits(2)
Optionally {
timeSecfrac
}
}
let fullDate = Parse {
Digits(4)
"-".utf8
Digits(2)
"-".utf8
Digits(2)
}
let offsetDateTime = Parse {
fullDate
timeDelim
partialTime
timeOffset
}
.map { date, time, timeZone -> DateComponents in
let (year, month, day) = date
let (hour, minute, second, nanosecond) = time
return DateComponents(
timeZone: timeZone,
year: year, month: month, day: day,
hour: hour, minute: minute, second: second, nanosecond: nanosecond
)
}
let localDateTime = Parse {
fullDate
timeDelim
partialTime
}
.map { date, time -> DateComponents in
let (year, month, day) = date
let (hour, minute, second, nanosecond) = time
return DateComponents(
year: year, month: month, day: day,
hour: hour, minute: minute, second: second, nanosecond: nanosecond
)
}
let localDate =
fullDate
.map { DateComponents(year: $0, month: $1, day: $2) }
let localTime =
partialTime
.map { DateComponents(hour: $0, minute: $1, second: $2, nanosecond: $3) }
let dateTime = OneOf {
offsetDateTime
localDateTime
localDate
localTime
}
let input = "1979-05-27T00:32:00Z"
let expected = Date(timeIntervalSince1970: 296_613_120)
var output: Date!
let dateTimeParser = dateTime.compactMap(Calendar.current.date(from:))
suite.benchmark("Parser") {
var input = input[...].utf8
output = try dateTimeParser.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)
}
}
}