forked from dart-lang/sdk
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdate_patch.dart
428 lines (369 loc) · 15 KB
/
date_patch.dart
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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// part of "core_patch.dart";
// VM implementation of DateTime.
@patch
class DateTime {
// Natives.
// The natives have been moved up here to work around Issue 10401.
static int _getCurrentMicros() native "DateTime_currentTimeMicros";
static String _timeZoneNameForClampedSeconds(int secondsSinceEpoch)
native "DateTime_timeZoneName";
static int _timeZoneOffsetInSecondsForClampedSeconds(int secondsSinceEpoch)
native "DateTime_timeZoneOffsetInSeconds";
static int _localTimeZoneAdjustmentInSeconds()
native "DateTime_localTimeZoneAdjustmentInSeconds";
static const _MICROSECOND_INDEX = 0;
static const _MILLISECOND_INDEX = 1;
static const _SECOND_INDEX = 2;
static const _MINUTE_INDEX = 3;
static const _HOUR_INDEX = 4;
static const _DAY_INDEX = 5;
static const _WEEKDAY_INDEX = 6;
static const _MONTH_INDEX = 7;
static const _YEAR_INDEX = 8;
List __parts;
@patch
DateTime.fromMillisecondsSinceEpoch(int millisecondsSinceEpoch,
{bool isUtc: false})
: this._withValue(
millisecondsSinceEpoch * Duration.microsecondsPerMillisecond,
isUtc: isUtc);
@patch
DateTime.fromMicrosecondsSinceEpoch(int microsecondsSinceEpoch,
{bool isUtc: false})
: this._withValue(microsecondsSinceEpoch, isUtc: isUtc);
@patch
DateTime._internal(int year, int month, int day, int hour, int minute,
int second, int millisecond, int microsecond, bool isUtc)
: this.isUtc = isUtc,
this._value = _brokenDownDateToValue(year, month, day, hour, minute,
second, millisecond, microsecond, isUtc) {
if (_value == null) throw new ArgumentError();
if (isUtc == null) throw new ArgumentError();
}
@patch
DateTime._now()
: isUtc = false,
_value = _getCurrentMicros() {}
@patch
String get timeZoneName {
if (isUtc) return "UTC";
return _timeZoneName(microsecondsSinceEpoch);
}
@patch
Duration get timeZoneOffset {
if (isUtc) return new Duration();
int offsetInSeconds = _timeZoneOffsetInSeconds(microsecondsSinceEpoch);
return new Duration(seconds: offsetInSeconds);
}
/** The first list contains the days until each month in non-leap years. The
* second list contains the days in leap years. */
static const List<List<int>> _DAYS_UNTIL_MONTH = const [
const [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334],
const [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335]
];
static List _computeUpperPart(int localMicros) {
const int DAYS_IN_4_YEARS = 4 * 365 + 1;
const int DAYS_IN_100_YEARS = 25 * DAYS_IN_4_YEARS - 1;
const int DAYS_IN_400_YEARS = 4 * DAYS_IN_100_YEARS + 1;
const int DAYS_1970_TO_2000 = 30 * 365 + 7;
const int DAYS_OFFSET =
1000 * DAYS_IN_400_YEARS + 5 * DAYS_IN_400_YEARS - DAYS_1970_TO_2000;
const int YEARS_OFFSET = 400000;
int resultYear = 0;
int resultMonth = 0;
int resultDay = 0;
// Always round down.
final int daysSince1970 =
_flooredDivision(localMicros, Duration.microsecondsPerDay);
int days = daysSince1970;
days += DAYS_OFFSET;
resultYear = 400 * (days ~/ DAYS_IN_400_YEARS) - YEARS_OFFSET;
days = days.remainder(DAYS_IN_400_YEARS);
days--;
int yd1 = days ~/ DAYS_IN_100_YEARS;
days = days.remainder(DAYS_IN_100_YEARS);
resultYear += 100 * yd1;
days++;
int yd2 = days ~/ DAYS_IN_4_YEARS;
days = days.remainder(DAYS_IN_4_YEARS);
resultYear += 4 * yd2;
days--;
int yd3 = days ~/ 365;
days = days.remainder(365);
resultYear += yd3;
bool isLeap = (yd1 == 0 || yd2 != 0) && yd3 == 0;
if (isLeap) days++;
List<int> daysUntilMonth = _DAYS_UNTIL_MONTH[isLeap ? 1 : 0];
for (resultMonth = 12;
daysUntilMonth[resultMonth - 1] > days;
resultMonth--) {
// Do nothing.
}
resultDay = days - daysUntilMonth[resultMonth - 1] + 1;
int resultMicrosecond = localMicros % Duration.microsecondsPerMillisecond;
int resultMillisecond =
_flooredDivision(localMicros, Duration.microsecondsPerMillisecond) %
Duration.millisecondsPerSecond;
int resultSecond =
_flooredDivision(localMicros, Duration.microsecondsPerSecond) %
Duration.secondsPerMinute;
int resultMinute =
_flooredDivision(localMicros, Duration.microsecondsPerMinute);
resultMinute %= Duration.minutesPerHour;
int resultHour =
_flooredDivision(localMicros, Duration.microsecondsPerHour);
resultHour %= Duration.hoursPerDay;
// In accordance with ISO 8601 a week
// starts with Monday. Monday has the value 1 up to Sunday with 7.
// 1970-1-1 was a Thursday.
int resultWeekday = ((daysSince1970 + DateTime.thursday - DateTime.monday) %
DateTime.daysPerWeek) +
DateTime.monday;
List list = new List(_YEAR_INDEX + 1);
list[_MICROSECOND_INDEX] = resultMicrosecond;
list[_MILLISECOND_INDEX] = resultMillisecond;
list[_SECOND_INDEX] = resultSecond;
list[_MINUTE_INDEX] = resultMinute;
list[_HOUR_INDEX] = resultHour;
list[_DAY_INDEX] = resultDay;
list[_WEEKDAY_INDEX] = resultWeekday;
list[_MONTH_INDEX] = resultMonth;
list[_YEAR_INDEX] = resultYear;
return list;
}
get _parts {
if (__parts == null) {
__parts = _computeUpperPart(_localDateInUtcMicros);
}
return __parts;
}
@patch
DateTime add(Duration duration) {
return new DateTime._withValue(_value + duration.inMicroseconds,
isUtc: isUtc);
}
@patch
DateTime subtract(Duration duration) {
return new DateTime._withValue(_value - duration.inMicroseconds,
isUtc: isUtc);
}
@patch
Duration difference(DateTime other) {
return new Duration(microseconds: _value - other._value);
}
@patch
int get millisecondsSinceEpoch =>
_value ~/ Duration.microsecondsPerMillisecond;
@patch
int get microsecondsSinceEpoch => _value;
@patch
int get microsecond => _parts[_MICROSECOND_INDEX];
@patch
int get millisecond => _parts[_MILLISECOND_INDEX];
@patch
int get second => _parts[_SECOND_INDEX];
@patch
int get minute => _parts[_MINUTE_INDEX];
@patch
int get hour => _parts[_HOUR_INDEX];
@patch
int get day => _parts[_DAY_INDEX];
@patch
int get weekday => _parts[_WEEKDAY_INDEX];
@patch
int get month => _parts[_MONTH_INDEX];
@patch
int get year => _parts[_YEAR_INDEX];
/**
* Returns the amount of microseconds in UTC that represent the same values
* as [this].
*
* Say `t` is the result of this function, then
* * `this.year == new DateTime.fromMicrosecondsSinceEpoch(t, true).year`,
* * `this.month == new DateTime.fromMicrosecondsSinceEpoch(t, true).month`,
* * `this.day == new DateTime.fromMicrosecondsSinceEpoch(t, true).day`,
* * `this.hour == new DateTime.fromMicrosecondsSinceEpoch(t, true).hour`,
* * ...
*
* Daylight savings is computed as if the date was computed in [1970..2037].
* If [this] lies outside this range then it is a year with similar
* properties (leap year, weekdays) is used instead.
*/
int get _localDateInUtcMicros {
int micros = _value;
if (isUtc) return micros;
int offset =
_timeZoneOffsetInSeconds(micros) * Duration.microsecondsPerSecond;
return micros + offset;
}
static int _flooredDivision(int a, int b) {
return (a - (a < 0 ? b - 1 : 0)) ~/ b;
}
// Returns the days since 1970 for the start of the given [year].
// [year] may be before epoch.
static int _dayFromYear(int year) {
return 365 * (year - 1970) +
_flooredDivision(year - 1969, 4) -
_flooredDivision(year - 1901, 100) +
_flooredDivision(year - 1601, 400);
}
static bool _isLeapYear(y) {
// (y % 16 == 0) matches multiples of 400, and is faster than % 400.
return (y % 4 == 0) && ((y % 16 == 0) || (y % 100 != 0));
}
/// Converts the given broken down date to microseconds.
@patch
static int _brokenDownDateToValue(int year, int month, int day, int hour,
int minute, int second, int millisecond, int microsecond, bool isUtc) {
// Simplify calculations by working with zero-based month.
--month;
// Deal with under and overflow.
if (month >= 12) {
year += month ~/ 12;
month = month % 12;
} else if (month < 0) {
int realMonth = month % 12;
year += (month - realMonth) ~/ 12;
month = realMonth;
}
// First compute the seconds in UTC, independent of the [isUtc] flag. If
// necessary we will add the time-zone offset later on.
int days = day - 1;
days += _DAYS_UNTIL_MONTH[_isLeapYear(year) ? 1 : 0][month];
days += _dayFromYear(year);
int microsecondsSinceEpoch = days * Duration.microsecondsPerDay +
hour * Duration.microsecondsPerHour +
minute * Duration.microsecondsPerMinute +
second * Duration.microsecondsPerSecond +
millisecond * Duration.microsecondsPerMillisecond +
microsecond;
// Since [_timeZoneOffsetInSeconds] will crash if the input is far out of
// the valid range we do a preliminary test that weeds out values that can
// not become valid even with timezone adjustments.
// The timezone adjustment is always less than a day, so adding a security
// margin of one day should be enough.
if (microsecondsSinceEpoch.abs() >
_maxMillisecondsSinceEpoch * 1000 + Duration.microsecondsPerDay) {
return null;
}
if (!isUtc) {
// Note that we can't literally follow the ECMAScript spec (which this
// code is based on), because it leads to incorrect computations at
// the DST transition points.
//
// See V8's comment here:
// https://github.com/v8/v8/blob/089dd7d2447d6eaf57c8ba6d8f37957f3a269777/src/date.h#L118
// We need to remove the local timezone adjustment before asking for the
// correct zone offset.
int adjustment =
_localTimeZoneAdjustmentInSeconds() * Duration.microsecondsPerSecond;
// The adjustment is independent of the actual date and of the daylight
// saving time. It is positive east of the Prime Meridian and negative
// west of it, e.g. -28800 sec for America/Los_Angeles timezone.
// We remove one hour to ensure that we have the correct offset at
// DST transitioning points. This is a temporary solution and only
// correct in timezones that shift for exactly one hour.
adjustment += Duration.microsecondsPerHour;
int zoneOffset =
_timeZoneOffsetInSeconds(microsecondsSinceEpoch - adjustment);
// The zoneOffset depends on the actual date and reflects any daylight
// saving time and/or historical deviation relative to UTC time.
// It is positive east of the Prime Meridian and negative west of it,
// e.g. -25200 sec for America/Los_Angeles timezone during DST.
microsecondsSinceEpoch -= zoneOffset * Duration.microsecondsPerSecond;
// The resulting microsecondsSinceEpoch value is therefore the calculated
// UTC value decreased by a (positive if east of GMT) timezone adjustment
// and decreased by typically one hour if DST is in effect.
}
if (microsecondsSinceEpoch.abs() >
_maxMillisecondsSinceEpoch * Duration.microsecondsPerMillisecond) {
return null;
}
return microsecondsSinceEpoch;
}
static int _weekDay(y) {
// 1/1/1970 was a Thursday.
return (_dayFromYear(y) + 4) % 7;
}
/**
* Returns a year in the range 2008-2035 matching
* * leap year, and
* * week day of first day.
*
* Leap seconds are ignored.
* Adapted from V8's date implementation. See ECMA 262 - 15.9.1.9.
*/
static int _equivalentYear(int year) {
// Returns year y so that _weekDay(y) == _weekDay(year).
// _weekDay returns the week day (in range 0 - 6).
// 1/1/1956 was a Sunday (i.e. weekday 0). 1956 was a leap-year.
// 1/1/1967 was a Sunday (i.e. weekday 0).
// Without leap years a subsequent year has a week day + 1 (for example
// 1/1/1968 was a Monday). With leap-years it jumps over one week day
// (e.g. 1/1/1957 was a Tuesday).
// After 12 years the weekdays have advanced by 12 days + 3 leap days =
// 15 days. 15 % 7 = 1. So after 12 years the week day has always
// (now independently of leap-years) advanced by one.
// weekDay * 12 gives thus a year starting with the wanted weekDay.
int recentYear = (_isLeapYear(year) ? 1956 : 1967) + (_weekDay(year) * 12);
// Close to the year 2008 the calendar cycles every 4 * 7 years (4 for the
// leap years, 7 for the weekdays).
// Find the year in the range 2008..2037 that is equivalent mod 28.
return 2008 + (recentYear - 2008) % 28;
}
/**
* Returns the UTC year for the corresponding [secondsSinceEpoch].
* It is relatively fast for values in the range 0 to year 2098.
*
* Code is adapted from V8.
*/
static int _yearsFromSecondsSinceEpoch(int secondsSinceEpoch) {
const int DAYS_IN_4_YEARS = 4 * 365 + 1;
const int DAYS_IN_100_YEARS = 25 * DAYS_IN_4_YEARS - 1;
const int DAYS_YEAR_2098 = DAYS_IN_100_YEARS + 6 * DAYS_IN_4_YEARS;
int days = secondsSinceEpoch ~/ Duration.secondsPerDay;
if (days > 0 && days < DAYS_YEAR_2098) {
// According to V8 this fast case works for dates from 1970 to 2099.
return 1970 + (4 * days + 2) ~/ DAYS_IN_4_YEARS;
}
int micros = secondsSinceEpoch * Duration.microsecondsPerSecond;
return _computeUpperPart(micros)[_YEAR_INDEX];
}
/**
* Returns a date in seconds that is equivalent to the given
* date in microseconds [microsecondsSinceEpoch]. An equivalent
* date has the same fields (`month`, `day`, etc.) as the given
* date, but the `year` is in the range [1901..2038].
*
* * The time since the beginning of the year is the same.
* * If the given date is in a leap year then the returned
* seconds are in a leap year, too.
* * The week day of given date is the same as the one for the
* returned date.
*/
static int _equivalentSeconds(int microsecondsSinceEpoch) {
const int CUT_OFF_SECONDS = 0x7FFFFFFF;
int secondsSinceEpoch = _flooredDivision(
microsecondsSinceEpoch, Duration.microsecondsPerSecond);
if (secondsSinceEpoch.abs() > CUT_OFF_SECONDS) {
int year = _yearsFromSecondsSinceEpoch(secondsSinceEpoch);
int days = _dayFromYear(year);
int equivalentYear = _equivalentYear(year);
int equivalentDays = _dayFromYear(equivalentYear);
int diffDays = equivalentDays - days;
secondsSinceEpoch += diffDays * Duration.secondsPerDay;
}
return secondsSinceEpoch;
}
static int _timeZoneOffsetInSeconds(int microsecondsSinceEpoch) {
int equivalentSeconds = _equivalentSeconds(microsecondsSinceEpoch);
return _timeZoneOffsetInSecondsForClampedSeconds(equivalentSeconds);
}
static String _timeZoneName(int microsecondsSinceEpoch) {
int equivalentSeconds = _equivalentSeconds(microsecondsSinceEpoch);
return _timeZoneNameForClampedSeconds(equivalentSeconds);
}
}