/
iso_8601.cr
189 lines (157 loc) · 4.46 KB
/
iso_8601.cr
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
struct Time::Format
module Pattern
def date_time_iso_8601
year_month_day_iso_8601
char? 'T'
time_iso_8601
end
def time_iso_8601
hour_minute_second_iso8601
time_zone_z_or_offset
end
end
struct Parser
def year_month_day_iso_8601
year
extended_format = char? '-'
if current_char == 'W'
# week date
next_char
week = consume_number(2)
extended_format ? char('-') : char?('-')
day_of_week = consume_number(1)
date = Time.week_date(@year, week, day_of_week, location: Time::Location::UTC)
@year = date.year
@month = date.month
@day = date.day
else
month_zero_padded
if @reader.peek_next_char.ascii_number? || !current_char.ascii_number?
# calendar date
extended_format ? char('-') : char?('-')
day_of_month_zero_padded
else
# ordinal date
day_of_the_year = @month * 10 + current_char.to_i
@month = 0
next_char
days_per_month = Time.leap_year?(@year) ? Time::DAYS_MONTH_LEAP : Time::DAYS_MONTH
days_per_month.each_with_index do |days, month|
if day_of_the_year > days
day_of_the_year -= days
else
@day = day_of_the_year
@month = month
break
end
end
end
end
end
def hour_minute_second_iso8601
hour_24_zero_padded
decimal_seconds = Time::SECONDS_PER_HOUR
extended_format = char? ':'
if current_char.ascii_number?
minute
decimal_seconds = Time::SECONDS_PER_MINUTE
has_colon = char?(':')
if current_char.ascii_number?
if extended_format && !has_colon
raise "Unexpected char: #{current_char.inspect} (#{@reader.pos})"
end
second
if current_char == '.' || current_char == ','
next_char
second_fraction
end
return
end
end
if current_char == '.' || current_char == ','
next_char
pos = @reader.pos
# Consume at most 12 digits as i64
decimals = consume_number_i64(12)
digits = @reader.pos - pos
if digits > 6
# make sure to avoid overflow
decimals = decimals // 10_i64 ** (digits - 6)
digits = 6
end
@nanosecond_offset = decimals.to_i64 * 10 ** 9 // 10 ** digits * decimal_seconds
end
end
end
struct Formatter
def year_month_day_iso_8601
year_month_day
end
def hour_minute_second_iso8601
twenty_four_hour_time_with_seconds
end
end
# The ISO 8601 date format.
module ISO_8601_DATE
# Parses a string into a `Time`.
def self.parse(string, location : Time::Location? = Time::Location::UTC) : Time
parser = Parser.new(string)
parser.year_month_day_iso_8601
parser.time(location)
end
# Formats a `Time` into the given *io*.
def self.format(time : Time, io : IO)
formatter = Formatter.new(time, io)
formatter.year_month_day_iso_8601
io
end
# Formats a `Time` into a `String`.
def self.format(time : Time)
String.build do |io|
format(time, io)
end
end
end
# The ISO 8601 date time format.
module ISO_8601_DATE_TIME
# Parses a string into a `Time`.
def self.parse(string, location : Time::Location? = Time::Location::UTC) : Time
parser = Parser.new(string)
parser.date_time_iso_8601
parser.time(location)
end
# Formats a `Time` into the given *io*.
def self.format(time : Time, io : IO)
formatter = Formatter.new(time, io)
formatter.rfc_3339
io
end
# Formats a `Time` into a `String`.
def self.format(time : Time)
String.build do |io|
format(time, io)
end
end
end
# The ISO 8601 time format.
module ISO_8601_TIME
# Parses a string into a `Time`.
def self.parse(string, location : Time::Location? = Time::Location::UTC) : Time
parser = Parser.new(string)
parser.time_iso_8601
parser.time(location)
end
# Formats a `Time` into the given *io*.
def self.format(time : Time, io : IO)
formatter = Formatter.new(time, io)
formatter.time_iso_8601
io
end
# Formats a `Time` into a `String`.
def self.format(time : Time)
String.build do |io|
format(time, io)
end
end
end
end