/src/serenity/Userland/Libraries/LibJS/Runtime/Date.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2020-2023, Linus Groh <linusg@serenityos.org> |
3 | | * Copyright (c) 2022-2023, Tim Flynn <trflynn89@serenityos.org> |
4 | | * |
5 | | * SPDX-License-Identifier: BSD-2-Clause |
6 | | */ |
7 | | |
8 | | #include <AK/NumericLimits.h> |
9 | | #include <AK/StringBuilder.h> |
10 | | #include <AK/Time.h> |
11 | | #include <LibJS/Runtime/AbstractOperations.h> |
12 | | #include <LibJS/Runtime/Date.h> |
13 | | #include <LibJS/Runtime/GlobalObject.h> |
14 | | #include <LibJS/Runtime/Temporal/ISO8601.h> |
15 | | #include <LibTimeZone/TimeZone.h> |
16 | | #include <time.h> |
17 | | |
18 | | namespace JS { |
19 | | |
20 | | JS_DEFINE_ALLOCATOR(Date); |
21 | | |
22 | | static Crypto::SignedBigInteger const s_one_billion_bigint { 1'000'000'000 }; |
23 | | static Crypto::SignedBigInteger const s_one_million_bigint { 1'000'000 }; |
24 | | static Crypto::SignedBigInteger const s_one_thousand_bigint { 1'000 }; |
25 | | |
26 | | Crypto::SignedBigInteger const ns_per_day_bigint { static_cast<i64>(ns_per_day) }; |
27 | | |
28 | | NonnullGCPtr<Date> Date::create(Realm& realm, double date_value) |
29 | 0 | { |
30 | 0 | return realm.heap().allocate<Date>(realm, date_value, realm.intrinsics().date_prototype()); |
31 | 0 | } |
32 | | |
33 | | Date::Date(double date_value, Object& prototype) |
34 | 0 | : Object(ConstructWithPrototypeTag::Tag, prototype) |
35 | 0 | , m_date_value(date_value) |
36 | 0 | { |
37 | 0 | } |
38 | | |
39 | 0 | Date::~Date() = default; |
40 | | |
41 | | ErrorOr<String> Date::iso_date_string() const |
42 | 0 | { |
43 | 0 | int year = year_from_time(m_date_value); |
44 | |
|
45 | 0 | StringBuilder builder; |
46 | 0 | if (year < 0) |
47 | 0 | builder.appendff("-{:06}", -year); |
48 | 0 | else if (year > 9999) |
49 | 0 | builder.appendff("+{:06}", year); |
50 | 0 | else |
51 | 0 | builder.appendff("{:04}", year); |
52 | 0 | builder.append('-'); |
53 | 0 | builder.appendff("{:02}", month_from_time(m_date_value) + 1); |
54 | 0 | builder.append('-'); |
55 | 0 | builder.appendff("{:02}", date_from_time(m_date_value)); |
56 | 0 | builder.append('T'); |
57 | 0 | builder.appendff("{:02}", hour_from_time(m_date_value)); |
58 | 0 | builder.append(':'); |
59 | 0 | builder.appendff("{:02}", min_from_time(m_date_value)); |
60 | 0 | builder.append(':'); |
61 | 0 | builder.appendff("{:02}", sec_from_time(m_date_value)); |
62 | 0 | builder.append('.'); |
63 | 0 | builder.appendff("{:03}", ms_from_time(m_date_value)); |
64 | 0 | builder.append('Z'); |
65 | |
|
66 | 0 | return builder.to_string(); |
67 | 0 | } |
68 | | |
69 | | // 21.4.1.3 Day ( t ), https://tc39.es/ecma262/#sec-day |
70 | | double day(double time_value) |
71 | 0 | { |
72 | | // 1. Return 𝔽(floor(ℝ(t / msPerDay))). |
73 | 0 | return floor(time_value / ms_per_day); |
74 | 0 | } |
75 | | |
76 | | // 21.4.1.4 TimeWithinDay ( t ), https://tc39.es/ecma262/#sec-timewithinday |
77 | | double time_within_day(double time) |
78 | 0 | { |
79 | | // 1. Return 𝔽(ℝ(t) modulo ℝ(msPerDay)). |
80 | 0 | return modulo(time, ms_per_day); |
81 | 0 | } |
82 | | |
83 | | // 21.4.1.5 DaysInYear ( y ), https://tc39.es/ecma262/#sec-daysinyear |
84 | | u16 days_in_year(i32 y) |
85 | 0 | { |
86 | | // 1. Let ry be ℝ(y). |
87 | 0 | auto ry = static_cast<double>(y); |
88 | | |
89 | | // 2. If (ry modulo 400) = 0, return 366𝔽. |
90 | 0 | if (modulo(ry, 400.0) == 0) |
91 | 0 | return 366; |
92 | | |
93 | | // 3. If (ry modulo 100) = 0, return 365𝔽. |
94 | 0 | if (modulo(ry, 100.0) == 0) |
95 | 0 | return 365; |
96 | | |
97 | | // 4. If (ry modulo 4) = 0, return 366𝔽. |
98 | 0 | if (modulo(ry, 4.0) == 0) |
99 | 0 | return 366; |
100 | | |
101 | | // 5. Return 365𝔽. |
102 | 0 | return 365; |
103 | 0 | } |
104 | | |
105 | | // 21.4.1.6 DayFromYear ( y ), https://tc39.es/ecma262/#sec-dayfromyear |
106 | | double day_from_year(i32 y) |
107 | 0 | { |
108 | | // 1. Let ry be ℝ(y). |
109 | 0 | auto ry = static_cast<double>(y); |
110 | | |
111 | | // 2. NOTE: In the following steps, each _numYearsN_ is the number of years divisible by N that occur between the |
112 | | // epoch and the start of year y. (The number is negative if y is before the epoch.) |
113 | | |
114 | | // 3. Let numYears1 be (ry - 1970). |
115 | 0 | auto num_years_1 = ry - 1970; |
116 | | |
117 | | // 4. Let numYears4 be floor((ry - 1969) / 4). |
118 | 0 | auto num_years_4 = floor((ry - 1969) / 4.0); |
119 | | |
120 | | // 5. Let numYears100 be floor((ry - 1901) / 100). |
121 | 0 | auto num_years_100 = floor((ry - 1901) / 100.0); |
122 | | |
123 | | // 6. Let numYears400 be floor((ry - 1601) / 400). |
124 | 0 | auto num_years_400 = floor((ry - 1601) / 400.0); |
125 | | |
126 | | // 7. Return 𝔽(365 × numYears1 + numYears4 - numYears100 + numYears400). |
127 | 0 | return 365.0 * num_years_1 + num_years_4 - num_years_100 + num_years_400; |
128 | 0 | } |
129 | | |
130 | | // 21.4.1.7 TimeFromYear ( y ), https://tc39.es/ecma262/#sec-timefromyear |
131 | | double time_from_year(i32 y) |
132 | 0 | { |
133 | | // 1. Return msPerDay × DayFromYear(y). |
134 | 0 | return ms_per_day * day_from_year(y); |
135 | 0 | } |
136 | | |
137 | | // 21.4.1.8 YearFromTime ( t ), https://tc39.es/ecma262/#sec-yearfromtime |
138 | | i32 year_from_time(double t) |
139 | 0 | { |
140 | | // 1. Return the largest integral Number y (closest to +∞) such that TimeFromYear(y) ≤ t. |
141 | 0 | if (!Value(t).is_finite_number()) |
142 | 0 | return NumericLimits<i32>::max(); |
143 | | |
144 | | // Approximation using average number of milliseconds per year. We might have to adjust this guess afterwards. |
145 | 0 | auto year = static_cast<i32>(floor(t / (365.2425 * ms_per_day) + 1970)); |
146 | |
|
147 | 0 | auto year_t = time_from_year(year); |
148 | 0 | if (year_t > t) |
149 | 0 | year--; |
150 | 0 | else if (year_t + days_in_year(year) * ms_per_day <= t) |
151 | 0 | year++; |
152 | |
|
153 | 0 | return year; |
154 | 0 | } |
155 | | |
156 | | // 21.4.1.9 DayWithinYear ( t ), https://tc39.es/ecma262/#sec-daywithinyear |
157 | | u16 day_within_year(double t) |
158 | 0 | { |
159 | 0 | if (!Value(t).is_finite_number()) |
160 | 0 | return 0; |
161 | | |
162 | | // 1. Return Day(t) - DayFromYear(YearFromTime(t)). |
163 | 0 | return static_cast<u16>(day(t) - day_from_year(year_from_time(t))); |
164 | 0 | } |
165 | | |
166 | | // 21.4.1.10 InLeapYear ( t ), https://tc39.es/ecma262/#sec-inleapyear |
167 | | bool in_leap_year(double t) |
168 | 0 | { |
169 | | // 1. If DaysInYear(YearFromTime(t)) is 366𝔽, return 1𝔽; else return +0𝔽. |
170 | 0 | return days_in_year(year_from_time(t)) == 366; |
171 | 0 | } |
172 | | |
173 | | // 21.4.1.11 MonthFromTime ( t ), https://tc39.es/ecma262/#sec-monthfromtime |
174 | | u8 month_from_time(double t) |
175 | 0 | { |
176 | | // 1. Let inLeapYear be InLeapYear(t). |
177 | 0 | auto in_leap_year = static_cast<unsigned>(JS::in_leap_year(t)); |
178 | | |
179 | | // 2. Let dayWithinYear be DayWithinYear(t). |
180 | 0 | auto day_within_year = JS::day_within_year(t); |
181 | | |
182 | | // 3. If dayWithinYear < 31𝔽, return +0𝔽. |
183 | 0 | if (day_within_year < 31) |
184 | 0 | return 0; |
185 | | |
186 | | // 4. If dayWithinYear < 59𝔽 + inLeapYear, return 1𝔽. |
187 | 0 | if (day_within_year < (59 + in_leap_year)) |
188 | 0 | return 1; |
189 | | |
190 | | // 5. If dayWithinYear < 90𝔽 + inLeapYear, return 2𝔽. |
191 | 0 | if (day_within_year < (90 + in_leap_year)) |
192 | 0 | return 2; |
193 | | |
194 | | // 6. If dayWithinYear < 120𝔽 + inLeapYear, return 3𝔽. |
195 | 0 | if (day_within_year < (120 + in_leap_year)) |
196 | 0 | return 3; |
197 | | |
198 | | // 7. If dayWithinYear < 151𝔽 + inLeapYear, return 4𝔽. |
199 | 0 | if (day_within_year < (151 + in_leap_year)) |
200 | 0 | return 4; |
201 | | |
202 | | // 8. If dayWithinYear < 181𝔽 + inLeapYear, return 5𝔽. |
203 | 0 | if (day_within_year < (181 + in_leap_year)) |
204 | 0 | return 5; |
205 | | |
206 | | // 9. If dayWithinYear < 212𝔽 + inLeapYear, return 6𝔽. |
207 | 0 | if (day_within_year < (212 + in_leap_year)) |
208 | 0 | return 6; |
209 | | |
210 | | // 10. If dayWithinYear < 243𝔽 + inLeapYear, return 7𝔽. |
211 | 0 | if (day_within_year < (243 + in_leap_year)) |
212 | 0 | return 7; |
213 | | |
214 | | // 11. If dayWithinYear < 273𝔽 + inLeapYear, return 8𝔽. |
215 | 0 | if (day_within_year < (273 + in_leap_year)) |
216 | 0 | return 8; |
217 | | |
218 | | // 12. If dayWithinYear < 304𝔽 + inLeapYear, return 9𝔽. |
219 | 0 | if (day_within_year < (304 + in_leap_year)) |
220 | 0 | return 9; |
221 | | |
222 | | // 13. If dayWithinYear < 334𝔽 + inLeapYear, return 10𝔽. |
223 | 0 | if (day_within_year < (334 + in_leap_year)) |
224 | 0 | return 10; |
225 | | |
226 | | // 14. Assert: dayWithinYear < 365𝔽 + inLeapYear. |
227 | 0 | VERIFY(day_within_year < (365 + in_leap_year)); |
228 | | |
229 | | // 15. Return 11𝔽. |
230 | 0 | return 11; |
231 | 0 | } |
232 | | |
233 | | // 21.4.1.12 DateFromTime ( t ), https://tc39.es/ecma262/#sec-datefromtime |
234 | | u8 date_from_time(double t) |
235 | 0 | { |
236 | | // 1. Let inLeapYear be InLeapYear(t). |
237 | 0 | auto in_leap_year = static_cast<unsigned>(JS::in_leap_year(t)); |
238 | | |
239 | | // 2. Let dayWithinYear be DayWithinYear(t). |
240 | 0 | auto day_within_year = JS::day_within_year(t); |
241 | | |
242 | | // 3. Let month be MonthFromTime(t). |
243 | 0 | auto month = month_from_time(t); |
244 | | |
245 | | // 4. If month is +0𝔽, return dayWithinYear + 1𝔽. |
246 | 0 | if (month == 0) |
247 | 0 | return day_within_year + 1; |
248 | | |
249 | | // 5. If month is 1𝔽, return dayWithinYear - 30𝔽. |
250 | 0 | if (month == 1) |
251 | 0 | return day_within_year - 30; |
252 | | |
253 | | // 6. If month is 2𝔽, return dayWithinYear - 58𝔽 - inLeapYear. |
254 | 0 | if (month == 2) |
255 | 0 | return day_within_year - 58 - in_leap_year; |
256 | | |
257 | | // 7. If month is 3𝔽, return dayWithinYear - 89𝔽 - inLeapYear. |
258 | 0 | if (month == 3) |
259 | 0 | return day_within_year - 89 - in_leap_year; |
260 | | |
261 | | // 8. If month is 4𝔽, return dayWithinYear - 119𝔽 - inLeapYear. |
262 | 0 | if (month == 4) |
263 | 0 | return day_within_year - 119 - in_leap_year; |
264 | | |
265 | | // 9. If month is 5𝔽, return dayWithinYear - 150𝔽 - inLeapYear. |
266 | 0 | if (month == 5) |
267 | 0 | return day_within_year - 150 - in_leap_year; |
268 | | |
269 | | // 10. If month is 6𝔽, return dayWithinYear - 180𝔽 - inLeapYear. |
270 | 0 | if (month == 6) |
271 | 0 | return day_within_year - 180 - in_leap_year; |
272 | | |
273 | | // 11. If month is 7𝔽, return dayWithinYear - 211𝔽 - inLeapYear. |
274 | 0 | if (month == 7) |
275 | 0 | return day_within_year - 211 - in_leap_year; |
276 | | |
277 | | // 12. If month is 8𝔽, return dayWithinYear - 242𝔽 - inLeapYear. |
278 | 0 | if (month == 8) |
279 | 0 | return day_within_year - 242 - in_leap_year; |
280 | | |
281 | | // 13. If month is 9𝔽, return dayWithinYear - 272𝔽 - inLeapYear. |
282 | 0 | if (month == 9) |
283 | 0 | return day_within_year - 272 - in_leap_year; |
284 | | |
285 | | // 14. If month is 10𝔽, return dayWithinYear - 303𝔽 - inLeapYear. |
286 | 0 | if (month == 10) |
287 | 0 | return day_within_year - 303 - in_leap_year; |
288 | | |
289 | | // 15. Assert: month is 11𝔽. |
290 | 0 | VERIFY(month == 11); |
291 | | |
292 | | // 16. Return dayWithinYear - 333𝔽 - inLeapYear. |
293 | 0 | return day_within_year - 333 - in_leap_year; |
294 | 0 | } |
295 | | |
296 | | // 21.4.1.13 WeekDay ( t ), https://tc39.es/ecma262/#sec-weekday |
297 | | u8 week_day(double t) |
298 | 0 | { |
299 | 0 | if (!Value(t).is_finite_number()) |
300 | 0 | return 0; |
301 | | |
302 | | // 1. Return 𝔽(ℝ(Day(t) + 4𝔽) modulo 7). |
303 | 0 | return static_cast<u8>(modulo(day(t) + 4, 7)); |
304 | 0 | } |
305 | | |
306 | | // 21.4.1.14 HourFromTime ( t ), https://tc39.es/ecma262/#sec-hourfromtime |
307 | | u8 hour_from_time(double t) |
308 | 0 | { |
309 | 0 | if (!Value(t).is_finite_number()) |
310 | 0 | return 0; |
311 | | |
312 | | // 1. Return 𝔽(floor(ℝ(t / msPerHour)) modulo HoursPerDay). |
313 | 0 | return static_cast<u8>(modulo(floor(t / ms_per_hour), hours_per_day)); |
314 | 0 | } |
315 | | |
316 | | // 21.4.1.15 MinFromTime ( t ), https://tc39.es/ecma262/#sec-minfromtime |
317 | | u8 min_from_time(double t) |
318 | 0 | { |
319 | 0 | if (!Value(t).is_finite_number()) |
320 | 0 | return 0; |
321 | | |
322 | | // 1. Return 𝔽(floor(ℝ(t / msPerMinute)) modulo MinutesPerHour). |
323 | 0 | return static_cast<u8>(modulo(floor(t / ms_per_minute), minutes_per_hour)); |
324 | 0 | } |
325 | | |
326 | | // 21.4.1.16 SecFromTime ( t ), https://tc39.es/ecma262/#sec-secfromtime |
327 | | u8 sec_from_time(double t) |
328 | 0 | { |
329 | 0 | if (!Value(t).is_finite_number()) |
330 | 0 | return 0; |
331 | | |
332 | | // 1. Return 𝔽(floor(ℝ(t / msPerSecond)) modulo SecondsPerMinute). |
333 | 0 | return static_cast<u8>(modulo(floor(t / ms_per_second), seconds_per_minute)); |
334 | 0 | } |
335 | | |
336 | | // 21.4.1.17 msFromTime ( t ), https://tc39.es/ecma262/#sec-msfromtime |
337 | | u16 ms_from_time(double t) |
338 | 0 | { |
339 | 0 | if (!Value(t).is_finite_number()) |
340 | 0 | return 0; |
341 | | |
342 | | // 1. Return 𝔽(ℝ(t) modulo ℝ(msPerSecond)). |
343 | 0 | return static_cast<u16>(modulo(t, ms_per_second)); |
344 | 0 | } |
345 | | |
346 | | // 21.4.1.18 GetUTCEpochNanoseconds ( year, month, day, hour, minute, second, millisecond, microsecond, nanosecond ), https://tc39.es/ecma262/#sec-getutcepochnanoseconds |
347 | | Crypto::SignedBigInteger get_utc_epoch_nanoseconds(i32 year, u8 month, u8 day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond) |
348 | 0 | { |
349 | | // 1. Let date be MakeDay(𝔽(year), 𝔽(month - 1), 𝔽(day)). |
350 | 0 | auto date = make_day(year, month - 1, day); |
351 | | |
352 | | // 2. Let time be MakeTime(𝔽(hour), 𝔽(minute), 𝔽(second), 𝔽(millisecond)). |
353 | 0 | auto time = make_time(hour, minute, second, millisecond); |
354 | | |
355 | | // 3. Let ms be MakeDate(date, time). |
356 | 0 | auto ms = make_date(date, time); |
357 | | |
358 | | // 4. Assert: ms is an integral Number. |
359 | 0 | VERIFY(ms == trunc(ms)); |
360 | | |
361 | | // 5. Return ℤ(ℝ(ms) × 10^6 + microsecond × 10^3 + nanosecond). |
362 | 0 | auto result = Crypto::SignedBigInteger { ms }.multiplied_by(s_one_million_bigint); |
363 | 0 | result = result.plus(Crypto::SignedBigInteger { static_cast<i32>(microsecond) }.multiplied_by(s_one_thousand_bigint)); |
364 | 0 | result = result.plus(Crypto::SignedBigInteger { static_cast<i32>(nanosecond) }); |
365 | 0 | return result; |
366 | 0 | } |
367 | | |
368 | | static i64 clip_bigint_to_sane_time(Crypto::SignedBigInteger const& value) |
369 | 0 | { |
370 | 0 | static Crypto::SignedBigInteger const min_bigint { NumericLimits<i64>::min() }; |
371 | 0 | static Crypto::SignedBigInteger const max_bigint { NumericLimits<i64>::max() }; |
372 | | |
373 | | // The provided epoch (nano)seconds value is potentially out of range for AK::Duration and subsequently |
374 | | // get_time_zone_offset(). We can safely assume that the TZDB has no useful information that far |
375 | | // into the past and future anyway, so clamp it to the i64 range. |
376 | 0 | if (value < min_bigint) |
377 | 0 | return NumericLimits<i64>::min(); |
378 | 0 | if (value > max_bigint) |
379 | 0 | return NumericLimits<i64>::max(); |
380 | | |
381 | | // FIXME: Can we do this without string conversion? |
382 | 0 | return value.to_base_deprecated(10).to_number<i64>().value(); |
383 | 0 | } |
384 | | |
385 | | // 21.4.1.20 GetNamedTimeZoneEpochNanoseconds ( timeZoneIdentifier, year, month, day, hour, minute, second, millisecond, microsecond, nanosecond ), https://tc39.es/ecma262/#sec-getnamedtimezoneepochnanoseconds |
386 | | Vector<Crypto::SignedBigInteger> get_named_time_zone_epoch_nanoseconds(StringView time_zone_identifier, i32 year, u8 month, u8 day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond) |
387 | 0 | { |
388 | 0 | auto local_nanoseconds = get_utc_epoch_nanoseconds(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond); |
389 | 0 | auto local_time = UnixDateTime::from_nanoseconds_since_epoch(clip_bigint_to_sane_time(local_nanoseconds)); |
390 | | |
391 | | // FIXME: LibTimeZone does not behave exactly as the spec expects. It does not consider repeated or skipped time points. |
392 | 0 | auto offset = TimeZone::get_time_zone_offset(time_zone_identifier, local_time); |
393 | | |
394 | | // Can only fail if the time zone identifier is invalid, which cannot be the case here. |
395 | 0 | VERIFY(offset.has_value()); |
396 | | |
397 | 0 | return { local_nanoseconds.minus(Crypto::SignedBigInteger { offset->seconds }.multiplied_by(s_one_billion_bigint)) }; |
398 | 0 | } |
399 | | |
400 | | // 21.4.1.21 GetNamedTimeZoneOffsetNanoseconds ( timeZoneIdentifier, epochNanoseconds ), https://tc39.es/ecma262/#sec-getnamedtimezoneoffsetnanoseconds |
401 | | i64 get_named_time_zone_offset_nanoseconds(StringView time_zone_identifier, Crypto::SignedBigInteger const& epoch_nanoseconds) |
402 | 0 | { |
403 | | // Only called with validated time zone identifier as argument. |
404 | 0 | auto time_zone = TimeZone::time_zone_from_string(time_zone_identifier); |
405 | 0 | VERIFY(time_zone.has_value()); |
406 | | |
407 | | // Since UnixDateTime::from_seconds_since_epoch() and UnixDateTime::from_nanoseconds_since_epoch() both take an i64, converting to |
408 | | // seconds first gives us a greater range. The TZDB doesn't have sub-second offsets. |
409 | 0 | auto seconds = epoch_nanoseconds.divided_by(s_one_billion_bigint).quotient; |
410 | 0 | auto time = UnixDateTime::from_seconds_since_epoch(clip_bigint_to_sane_time(seconds)); |
411 | |
|
412 | 0 | auto offset = TimeZone::get_time_zone_offset(*time_zone, time); |
413 | 0 | VERIFY(offset.has_value()); |
414 | | |
415 | 0 | return offset->seconds * 1'000'000'000; |
416 | 0 | } |
417 | | |
418 | | // 21.4.1.23 AvailableNamedTimeZoneIdentifiers ( ), https://tc39.es/ecma262/#sec-time-zone-identifier-record |
419 | | Vector<TimeZoneIdentifier> available_named_time_zone_identifiers() |
420 | 0 | { |
421 | | // 1. If the implementation does not include local political rules for any time zones, then |
422 | | // a. Return « the Time Zone Identifier Record { [[Identifier]]: "UTC", [[PrimaryIdentifier]]: "UTC" } ». |
423 | | // NOTE: This step is not applicable as LibTimeZone will always return at least UTC, even if the TZDB is disabled. |
424 | | |
425 | | // 2. Let identifiers be the List of unique available named time zone identifiers. |
426 | 0 | auto identifiers = TimeZone::all_time_zones(); |
427 | | |
428 | | // 3. Sort identifiers into the same order as if an Array of the same values had been sorted using %Array.prototype.sort% with undefined as comparefn. |
429 | | // NOTE: LibTimeZone provides the identifiers already sorted. |
430 | | |
431 | | // 4. Let result be a new empty List. |
432 | 0 | Vector<TimeZoneIdentifier> result; |
433 | 0 | result.ensure_capacity(identifiers.size()); |
434 | |
|
435 | 0 | bool found_utc = false; |
436 | | |
437 | | // 5. For each element identifier of identifiers, do |
438 | 0 | for (auto identifier : identifiers) { |
439 | | // a. Let primary be identifier. |
440 | 0 | auto primary = identifier.name; |
441 | | |
442 | | // b. If identifier is a non-primary time zone identifier in this implementation and identifier is not "UTC", then |
443 | 0 | if (identifier.is_link == TimeZone::IsLink::Yes && identifier.name != "UTC"sv) { |
444 | | // i. Set primary to the primary time zone identifier associated with identifier. |
445 | | // ii. NOTE: An implementation may need to resolve identifier iteratively to obtain the primary time zone identifier. |
446 | 0 | primary = TimeZone::canonicalize_time_zone(identifier.name).value(); |
447 | 0 | } |
448 | | |
449 | | // c. Let record be the Time Zone Identifier Record { [[Identifier]]: identifier, [[PrimaryIdentifier]]: primary }. |
450 | 0 | TimeZoneIdentifier record { .identifier = identifier.name, .primary_identifier = primary }; |
451 | | |
452 | | // d. Append record to result. |
453 | 0 | result.unchecked_append(record); |
454 | |
|
455 | 0 | if (!found_utc && identifier.name == "UTC"sv && primary == "UTC"sv) |
456 | 0 | found_utc = true; |
457 | 0 | } |
458 | | |
459 | | // 6. Assert: result contains a Time Zone Identifier Record r such that r.[[Identifier]] is "UTC" and r.[[PrimaryIdentifier]] is "UTC". |
460 | 0 | VERIFY(found_utc); |
461 | | |
462 | | // 7. Return result. |
463 | 0 | return result; |
464 | 0 | } |
465 | | |
466 | | // 21.4.1.24 SystemTimeZoneIdentifier ( ), https://tc39.es/ecma262/#sec-systemtimezoneidentifier |
467 | | StringView system_time_zone_identifier() |
468 | 0 | { |
469 | 0 | return TimeZone::current_time_zone(); |
470 | 0 | } |
471 | | |
472 | | // 21.4.1.25 LocalTime ( t ), https://tc39.es/ecma262/#sec-localtime |
473 | | double local_time(double time) |
474 | 0 | { |
475 | | // 1. Let systemTimeZoneIdentifier be SystemTimeZoneIdentifier(). |
476 | 0 | auto system_time_zone_identifier = JS::system_time_zone_identifier(); |
477 | |
|
478 | 0 | double offset_nanoseconds { 0 }; |
479 | | |
480 | | // 2. If IsTimeZoneOffsetString(systemTimeZoneIdentifier) is true, then |
481 | 0 | if (is_time_zone_offset_string(system_time_zone_identifier)) { |
482 | | // a. Let offsetNs be ParseTimeZoneOffsetString(systemTimeZoneIdentifier). |
483 | 0 | offset_nanoseconds = parse_time_zone_offset_string(system_time_zone_identifier); |
484 | 0 | } |
485 | | // 3. Else, |
486 | 0 | else { |
487 | | // a. Let offsetNs be GetNamedTimeZoneOffsetNanoseconds(systemTimeZoneIdentifier, ℤ(ℝ(t) × 10^6)). |
488 | 0 | auto time_bigint = Crypto::SignedBigInteger { time }.multiplied_by(s_one_million_bigint); |
489 | 0 | offset_nanoseconds = get_named_time_zone_offset_nanoseconds(system_time_zone_identifier, time_bigint); |
490 | 0 | } |
491 | | |
492 | | // 4. Let offsetMs be truncate(offsetNs / 10^6). |
493 | 0 | auto offset_milliseconds = trunc(offset_nanoseconds / 1e6); |
494 | | |
495 | | // 5. Return t + 𝔽(offsetMs). |
496 | 0 | return time + offset_milliseconds; |
497 | 0 | } |
498 | | |
499 | | // 21.4.1.26 UTC ( t ), https://tc39.es/ecma262/#sec-utc-t |
500 | | double utc_time(double time) |
501 | 0 | { |
502 | | // 1. Let systemTimeZoneIdentifier be SystemTimeZoneIdentifier(). |
503 | 0 | auto system_time_zone_identifier = JS::system_time_zone_identifier(); |
504 | |
|
505 | 0 | double offset_nanoseconds { 0 }; |
506 | | |
507 | | // 2. If IsTimeZoneOffsetString(systemTimeZoneIdentifier) is true, then |
508 | 0 | if (is_time_zone_offset_string(system_time_zone_identifier)) { |
509 | | // a. Let offsetNs be ParseTimeZoneOffsetString(systemTimeZoneIdentifier). |
510 | 0 | offset_nanoseconds = parse_time_zone_offset_string(system_time_zone_identifier); |
511 | 0 | } |
512 | | // 3. Else, |
513 | 0 | else { |
514 | | // a. Let possibleInstants be GetNamedTimeZoneEpochNanoseconds(systemTimeZoneIdentifier, ℝ(YearFromTime(t)), ℝ(MonthFromTime(t)) + 1, ℝ(DateFromTime(t)), ℝ(HourFromTime(t)), ℝ(MinFromTime(t)), ℝ(SecFromTime(t)), ℝ(msFromTime(t)), 0, 0). |
515 | 0 | auto possible_instants = get_named_time_zone_epoch_nanoseconds(system_time_zone_identifier, year_from_time(time), month_from_time(time) + 1, date_from_time(time), hour_from_time(time), min_from_time(time), sec_from_time(time), ms_from_time(time), 0, 0); |
516 | | |
517 | | // b. NOTE: The following steps ensure that when t represents local time repeating multiple times at a negative time zone transition (e.g. when the daylight saving time ends or the time zone offset is decreased due to a time zone rule change) or skipped local time at a positive time zone transition (e.g. when the daylight saving time starts or the time zone offset is increased due to a time zone rule change), t is interpreted using the time zone offset before the transition. |
518 | 0 | Crypto::SignedBigInteger disambiguated_instant; |
519 | | |
520 | | // c. If possibleInstants is not empty, then |
521 | 0 | if (!possible_instants.is_empty()) { |
522 | | // i. Let disambiguatedInstant be possibleInstants[0]. |
523 | 0 | disambiguated_instant = move(possible_instants.first()); |
524 | 0 | } |
525 | | // d. Else, |
526 | 0 | else { |
527 | | // i. NOTE: t represents a local time skipped at a positive time zone transition (e.g. due to daylight saving time starting or a time zone rule change increasing the UTC offset). |
528 | | // ii. Let possibleInstantsBefore be GetNamedTimeZoneEpochNanoseconds(systemTimeZoneIdentifier, ℝ(YearFromTime(tBefore)), ℝ(MonthFromTime(tBefore)) + 1, ℝ(DateFromTime(tBefore)), ℝ(HourFromTime(tBefore)), ℝ(MinFromTime(tBefore)), ℝ(SecFromTime(tBefore)), ℝ(msFromTime(tBefore)), 0, 0), where tBefore is the largest integral Number < t for which possibleInstantsBefore is not empty (i.e., tBefore represents the last local time before the transition). |
529 | | // iii. Let disambiguatedInstant be the last element of possibleInstantsBefore. |
530 | | |
531 | | // FIXME: This branch currently cannot be reached with our implementation, because LibTimeZone does not handle skipped time points. |
532 | | // When GetNamedTimeZoneEpochNanoseconds is updated to use a LibTimeZone API which does handle them, implement these steps. |
533 | 0 | VERIFY_NOT_REACHED(); |
534 | 0 | } |
535 | | |
536 | | // e. Let offsetNs be GetNamedTimeZoneOffsetNanoseconds(systemTimeZoneIdentifier, disambiguatedInstant). |
537 | 0 | offset_nanoseconds = get_named_time_zone_offset_nanoseconds(system_time_zone_identifier, disambiguated_instant); |
538 | 0 | } |
539 | | |
540 | | // 4. Let offsetMs be truncate(offsetNs / 10^6). |
541 | 0 | auto offset_milliseconds = trunc(offset_nanoseconds / 1e6); |
542 | | |
543 | | // 5. Return t - 𝔽(offsetMs). |
544 | 0 | return time - offset_milliseconds; |
545 | 0 | } |
546 | | |
547 | | // 21.4.1.27 MakeTime ( hour, min, sec, ms ), https://tc39.es/ecma262/#sec-maketime |
548 | | double make_time(double hour, double min, double sec, double ms) |
549 | 0 | { |
550 | | // 1. If hour is not finite or min is not finite or sec is not finite or ms is not finite, return NaN. |
551 | 0 | if (!isfinite(hour) || !isfinite(min) || !isfinite(sec) || !isfinite(ms)) |
552 | 0 | return NAN; |
553 | | |
554 | | // 2. Let h be 𝔽(! ToIntegerOrInfinity(hour)). |
555 | 0 | auto h = to_integer_or_infinity(hour); |
556 | | // 3. Let m be 𝔽(! ToIntegerOrInfinity(min)). |
557 | 0 | auto m = to_integer_or_infinity(min); |
558 | | // 4. Let s be 𝔽(! ToIntegerOrInfinity(sec)). |
559 | 0 | auto s = to_integer_or_infinity(sec); |
560 | | // 5. Let milli be 𝔽(! ToIntegerOrInfinity(ms)). |
561 | 0 | auto milli = to_integer_or_infinity(ms); |
562 | | // 6. Let t be ((h * msPerHour + m * msPerMinute) + s * msPerSecond) + milli, performing the arithmetic according to IEEE 754-2019 rules (that is, as if using the ECMAScript operators * and +). |
563 | | // NOTE: C++ arithmetic abides by IEEE 754 rules |
564 | 0 | auto t = ((h * ms_per_hour + m * ms_per_minute) + s * ms_per_second) + milli; |
565 | | // 7. Return t. |
566 | 0 | return t; |
567 | 0 | } |
568 | | |
569 | | // 21.4.1.28 MakeDay ( year, month, date ), https://tc39.es/ecma262/#sec-makeday |
570 | | double make_day(double year, double month, double date) |
571 | 0 | { |
572 | | // 1. If year is not finite or month is not finite or date is not finite, return NaN. |
573 | 0 | if (!isfinite(year) || !isfinite(month) || !isfinite(date)) |
574 | 0 | return NAN; |
575 | | |
576 | | // 2. Let y be 𝔽(! ToIntegerOrInfinity(year)). |
577 | 0 | auto y = to_integer_or_infinity(year); |
578 | | // 3. Let m be 𝔽(! ToIntegerOrInfinity(month)). |
579 | 0 | auto m = to_integer_or_infinity(month); |
580 | | // 4. Let dt be 𝔽(! ToIntegerOrInfinity(date)). |
581 | 0 | auto dt = to_integer_or_infinity(date); |
582 | | // 5. Let ym be y + 𝔽(floor(ℝ(m) / 12)). |
583 | 0 | auto ym = y + floor(m / 12); |
584 | | // 6. If ym is not finite, return NaN. |
585 | 0 | if (!isfinite(ym)) |
586 | 0 | return NAN; |
587 | | // 7. Let mn be 𝔽(ℝ(m) modulo 12). |
588 | 0 | auto mn = modulo(m, 12); |
589 | | |
590 | | // 8. Find a finite time value t such that YearFromTime(t) is ym and MonthFromTime(t) is mn and DateFromTime(t) is 1𝔽; but if this is not possible (because some argument is out of range), return NaN. |
591 | 0 | if (!AK::is_within_range<int>(ym) || !AK::is_within_range<int>(mn + 1)) |
592 | 0 | return NAN; |
593 | 0 | auto t = days_since_epoch(static_cast<int>(ym), static_cast<int>(mn) + 1, 1) * ms_per_day; |
594 | | |
595 | | // 9. Return Day(t) + dt - 1𝔽. |
596 | 0 | return day(static_cast<double>(t)) + dt - 1; |
597 | 0 | } |
598 | | |
599 | | // 21.4.1.29 MakeDate ( day, time ), https://tc39.es/ecma262/#sec-makedate |
600 | | double make_date(double day, double time) |
601 | 0 | { |
602 | | // 1. If day is not finite or time is not finite, return NaN. |
603 | 0 | if (!isfinite(day) || !isfinite(time)) |
604 | 0 | return NAN; |
605 | | |
606 | | // 2. Let tv be day × msPerDay + time. |
607 | 0 | auto tv = day * ms_per_day + time; |
608 | | |
609 | | // 3. If tv is not finite, return NaN. |
610 | 0 | if (!isfinite(tv)) |
611 | 0 | return NAN; |
612 | | |
613 | | // 4. Return tv. |
614 | 0 | return tv; |
615 | 0 | } |
616 | | |
617 | | // 21.4.1.31 TimeClip ( time ), https://tc39.es/ecma262/#sec-timeclip |
618 | | double time_clip(double time) |
619 | 0 | { |
620 | | // 1. If time is not finite, return NaN. |
621 | 0 | if (!isfinite(time)) |
622 | 0 | return NAN; |
623 | | |
624 | | // 2. If abs(ℝ(time)) > 8.64 × 10^15, return NaN. |
625 | 0 | if (fabs(time) > 8.64E15) |
626 | 0 | return NAN; |
627 | | |
628 | | // 3. Return 𝔽(! ToIntegerOrInfinity(time)). |
629 | 0 | return to_integer_or_infinity(time); |
630 | 0 | } |
631 | | |
632 | | // 21.4.1.33.1 IsTimeZoneOffsetString ( offsetString ), https://tc39.es/ecma262/#sec-istimezoneoffsetstring |
633 | | bool is_time_zone_offset_string(StringView offset_string) |
634 | 0 | { |
635 | | // 1. Let parseResult be ParseText(StringToCodePoints(offsetString), UTCOffset). |
636 | 0 | auto parse_result = Temporal::parse_iso8601(Temporal::Production::TimeZoneNumericUTCOffset, offset_string); |
637 | | |
638 | | // 2. If parseResult is a List of errors, return false. |
639 | | // 3. Return true. |
640 | 0 | return parse_result.has_value(); |
641 | 0 | } |
642 | | |
643 | | // 21.4.1.33.2 ParseTimeZoneOffsetString ( offsetString ), https://tc39.es/ecma262/#sec-parsetimezoneoffsetstring |
644 | | double parse_time_zone_offset_string(StringView offset_string) |
645 | 0 | { |
646 | | // 1. Let parseResult be ParseText(StringToCodePoints(offsetString), UTCOffset). |
647 | 0 | auto parse_result = Temporal::parse_iso8601(Temporal::Production::TimeZoneNumericUTCOffset, offset_string); |
648 | | |
649 | | // 2. Assert: parseResult is not a List of errors. |
650 | 0 | VERIFY(parse_result.has_value()); |
651 | | |
652 | | // 3. Assert: parseResult contains a TemporalSign Parse Node. |
653 | 0 | VERIFY(parse_result->time_zone_utc_offset_sign.has_value()); |
654 | | |
655 | | // 4. Let parsedSign be the source text matched by the TemporalSign Parse Node contained within parseResult. |
656 | 0 | auto parsed_sign = *parse_result->time_zone_utc_offset_sign; |
657 | 0 | i8 sign { 0 }; |
658 | | |
659 | | // 5. If parsedSign is the single code point U+002D (HYPHEN-MINUS) or U+2212 (MINUS SIGN), then |
660 | 0 | if (parsed_sign.is_one_of("-"sv, "\xE2\x88\x92"sv)) { |
661 | | // a. Let sign be -1. |
662 | 0 | sign = -1; |
663 | 0 | } |
664 | | // 6. Else, |
665 | 0 | else { |
666 | | // a. Let sign be 1. |
667 | 0 | sign = 1; |
668 | 0 | } |
669 | | |
670 | | // 7. NOTE: Applications of StringToNumber below do not lose precision, since each of the parsed values is guaranteed to be a sufficiently short string of decimal digits. |
671 | | |
672 | | // 8. Assert: parseResult contains an Hour Parse Node. |
673 | 0 | VERIFY(parse_result->time_zone_utc_offset_hour.has_value()); |
674 | | |
675 | | // 9. Let parsedHours be the source text matched by the Hour Parse Node contained within parseResult. |
676 | 0 | auto parsed_hours = *parse_result->time_zone_utc_offset_hour; |
677 | | |
678 | | // 10. Let hours be ℝ(StringToNumber(CodePointsToString(parsedHours))). |
679 | 0 | auto hours = string_to_number(parsed_hours); |
680 | |
|
681 | 0 | double minutes { 0 }; |
682 | 0 | double seconds { 0 }; |
683 | 0 | double nanoseconds { 0 }; |
684 | | |
685 | | // 11. If parseResult does not contain a MinuteSecond Parse Node, then |
686 | 0 | if (!parse_result->time_zone_utc_offset_minute.has_value()) { |
687 | | // a. Let minutes be 0. |
688 | 0 | minutes = 0; |
689 | 0 | } |
690 | | // 12. Else, |
691 | 0 | else { |
692 | | // a. Let parsedMinutes be the source text matched by the first MinuteSecond Parse Node contained within parseResult. |
693 | 0 | auto parsed_minutes = *parse_result->time_zone_utc_offset_minute; |
694 | | |
695 | | // b. Let minutes be ℝ(StringToNumber(CodePointsToString(parsedMinutes))). |
696 | 0 | minutes = string_to_number(parsed_minutes); |
697 | 0 | } |
698 | | |
699 | | // 13. If parseResult does not contain two MinuteSecond Parse Nodes, then |
700 | 0 | if (!parse_result->time_zone_utc_offset_second.has_value()) { |
701 | | // a. Let seconds be 0. |
702 | 0 | seconds = 0; |
703 | 0 | } |
704 | | // 14. Else, |
705 | 0 | else { |
706 | | // a. Let parsedSeconds be the source text matched by the second secondSecond Parse Node contained within parseResult. |
707 | 0 | auto parsed_seconds = *parse_result->time_zone_utc_offset_second; |
708 | | |
709 | | // b. Let seconds be ℝ(StringToNumber(CodePointsToString(parsedSeconds))). |
710 | 0 | seconds = string_to_number(parsed_seconds); |
711 | 0 | } |
712 | | |
713 | | // 15. If parseResult does not contain a TemporalDecimalFraction Parse Node, then |
714 | 0 | if (!parse_result->time_zone_utc_offset_fraction.has_value()) { |
715 | | // a. Let nanoseconds be 0. |
716 | 0 | nanoseconds = 0; |
717 | 0 | } |
718 | | // 16. Else, |
719 | 0 | else { |
720 | | // a. Let parsedFraction be the source text matched by the TemporalDecimalFraction Parse Node contained within parseResult. |
721 | 0 | auto parsed_fraction = *parse_result->time_zone_utc_offset_fraction; |
722 | | |
723 | | // b. Let fraction be the string-concatenation of CodePointsToString(parsedFraction) and "000000000". |
724 | 0 | auto fraction = ByteString::formatted("{}000000000", parsed_fraction); |
725 | | |
726 | | // c. Let nanosecondsString be the substring of fraction from 1 to 10. |
727 | 0 | auto nanoseconds_string = fraction.substring_view(1, 9); |
728 | | |
729 | | // d. Let nanoseconds be ℝ(StringToNumber(nanosecondsString)). |
730 | 0 | nanoseconds = string_to_number(nanoseconds_string); |
731 | 0 | } |
732 | | |
733 | | // 17. Return sign × (((hours × 60 + minutes) × 60 + seconds) × 10^9 + nanoseconds). |
734 | | // NOTE: Using scientific notation (1e9) ensures the result of this expression is a double, |
735 | | // which is important - otherwise it's all integers and the result overflows! |
736 | 0 | return sign * (((hours * 60 + minutes) * 60 + seconds) * 1e9 + nanoseconds); |
737 | 0 | } |
738 | | |
739 | | } |