1 /*
2 * Copyright (C) 2009 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 #include "third_party/blink/renderer/platform/text/date_components.h"
32
33 #include <limits.h>
34 #include "base/notreached.h"
35 #include "third_party/blink/renderer/platform/wtf/date_math.h"
36 #include "third_party/blink/renderer/platform/wtf/math_extras.h"
37 #include "third_party/blink/renderer/platform/wtf/text/ascii_ctype.h"
38 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
39
40 namespace blink {
41
42 // HTML5 specification defines minimum week of year is one.
43 const int DateComponents::kMinimumWeekNumber = 1;
44
45 // HTML5 specification defines maximum week of year is 53.
46 const int DateComponents::kMaximumWeekNumber = 53;
47
48 // This is September, since months are 0 based.
49 static const int kMaximumMonthInMaximumYear = 8;
50 static const int kMaximumDayInMaximumMonth = 13;
51 static const int kMaximumWeekInMaximumYear = 37; // The week of 275760-09-13
52
53 static const int kDaysInMonth[12] = {31, 28, 31, 30, 31, 30,
54 31, 31, 30, 31, 30, 31};
55
56 // 'month' is 0-based.
MaxDayOfMonth(int year,int month)57 static int MaxDayOfMonth(int year, int month) {
58 if (month != 1) // February?
59 return kDaysInMonth[month];
60 return IsLeapYear(year) ? 29 : 28;
61 }
62
63 // 'month' is 0-based.
DayOfWeek(int year,int month,int day)64 static int DayOfWeek(int year, int month, int day) {
65 int shifted_month = month + 2;
66 // 2:January, 3:Feburuary, 4:March, ...
67
68 // Zeller's congruence
69 if (shifted_month <= 3) {
70 shifted_month += 12;
71 year--;
72 }
73 // 4:March, ..., 14:January, 15:February
74
75 int high_year = year / 100;
76 int low_year = year % 100;
77 // We add 6 to make the result Sunday-origin.
78 int result = (day + 13 * shifted_month / 5 + low_year + low_year / 4 +
79 high_year / 4 + 5 * high_year + 6) %
80 7;
81 return result;
82 }
83
WeekDay() const84 int DateComponents::WeekDay() const {
85 return DayOfWeek(year_, month_, month_day_);
86 }
87
MaxWeekNumberInYear() const88 int DateComponents::MaxWeekNumberInYear() const {
89 int day = DayOfWeek(year_, 0, 1); // January 1.
90 return day == kThursday || (day == kWednesday && IsLeapYear(year_))
91 ? kMaximumWeekNumber
92 : kMaximumWeekNumber - 1;
93 }
94
CountDigits(const String & src,unsigned start)95 static unsigned CountDigits(const String& src, unsigned start) {
96 unsigned index = start;
97 for (; index < src.length(); ++index) {
98 if (!IsASCIIDigit(src[index]))
99 break;
100 }
101 return index - start;
102 }
103
104 // Very strict integer parser. Do not allow leading or trailing whitespace
105 // unlike charactersToIntStrict().
ToInt(const String & src,unsigned parse_start,unsigned parse_length,int & out)106 static bool ToInt(const String& src,
107 unsigned parse_start,
108 unsigned parse_length,
109 int& out) {
110 if (parse_start + parse_length > src.length() || !parse_length)
111 return false;
112 int value = 0;
113 unsigned current = parse_start;
114 unsigned end = current + parse_length;
115
116 // We don't need to handle negative numbers for ISO 8601.
117 for (; current < end; ++current) {
118 if (!IsASCIIDigit(src[current]))
119 return false;
120 int digit = src[current] - '0';
121 if (value > (INT_MAX - digit) / 10) // Check for overflow.
122 return false;
123 value = value * 10 + digit;
124 }
125 out = value;
126 return true;
127 }
128
ParseYear(const String & src,unsigned start,unsigned & end)129 bool DateComponents::ParseYear(const String& src,
130 unsigned start,
131 unsigned& end) {
132 unsigned digits_length = CountDigits(src, start);
133 // Needs at least 4 digits according to the standard.
134 if (digits_length < 4)
135 return false;
136 int year;
137 if (!ToInt(src, start, digits_length, year))
138 return false;
139 if (year < MinimumYear() || year > MaximumYear())
140 return false;
141 year_ = year;
142 end = start + digits_length;
143 return true;
144 }
145
WithinHTMLDateLimits(int year,int month)146 static bool WithinHTMLDateLimits(int year, int month) {
147 if (year < DateComponents::MinimumYear())
148 return false;
149 if (year < DateComponents::MaximumYear())
150 return true;
151 return month <= kMaximumMonthInMaximumYear;
152 }
153
WithinHTMLDateLimits(int year,int month,int month_day)154 static bool WithinHTMLDateLimits(int year, int month, int month_day) {
155 if (year < DateComponents::MinimumYear())
156 return false;
157 if (year < DateComponents::MaximumYear())
158 return true;
159 if (month < kMaximumMonthInMaximumYear)
160 return true;
161 return month_day <= kMaximumDayInMaximumMonth;
162 }
163
WithinHTMLDateLimits(int year,int month,int month_day,int hour,int minute,int second,int millisecond)164 static bool WithinHTMLDateLimits(int year,
165 int month,
166 int month_day,
167 int hour,
168 int minute,
169 int second,
170 int millisecond) {
171 if (year < DateComponents::MinimumYear())
172 return false;
173 if (year < DateComponents::MaximumYear())
174 return true;
175 if (month < kMaximumMonthInMaximumYear)
176 return true;
177 if (month_day < kMaximumDayInMaximumMonth)
178 return true;
179 if (month_day > kMaximumDayInMaximumMonth)
180 return false;
181 // (year, month, monthDay) =
182 // (MaximumYear, kMaximumMonthInMaximumYear, kMaximumDayInMaximumMonth)
183 return !hour && !minute && !second && !millisecond;
184 }
185
ParseMonth(const String & src,unsigned start,unsigned & end)186 bool DateComponents::ParseMonth(const String& src,
187 unsigned start,
188 unsigned& end) {
189 unsigned index;
190 if (!ParseYear(src, start, index))
191 return false;
192 if (index >= src.length() || src[index] != '-')
193 return false;
194 ++index;
195
196 int month;
197 if (!ToInt(src, index, 2, month) || month < 1 || month > 12)
198 return false;
199 --month;
200 if (!WithinHTMLDateLimits(year_, month))
201 return false;
202 month_ = month;
203 end = index + 2;
204 type_ = kMonth;
205 return true;
206 }
207
ParseDate(const String & src,unsigned start,unsigned & end)208 bool DateComponents::ParseDate(const String& src,
209 unsigned start,
210 unsigned& end) {
211 unsigned index;
212 if (!ParseMonth(src, start, index))
213 return false;
214 // '-' and 2-digits are needed.
215 if (index + 2 >= src.length())
216 return false;
217 if (src[index] != '-')
218 return false;
219 ++index;
220
221 int day;
222 if (!ToInt(src, index, 2, day) || day < 1 ||
223 day > MaxDayOfMonth(year_, month_))
224 return false;
225 if (!WithinHTMLDateLimits(year_, month_, day))
226 return false;
227 month_day_ = day;
228 end = index + 2;
229 type_ = kDate;
230 return true;
231 }
232
ParseWeek(const String & src,unsigned start,unsigned & end)233 bool DateComponents::ParseWeek(const String& src,
234 unsigned start,
235 unsigned& end) {
236 unsigned index;
237 if (!ParseYear(src, start, index))
238 return false;
239
240 // 4 characters ('-' 'W' digit digit) are needed.
241 if (index + 3 >= src.length())
242 return false;
243 if (src[index] != '-')
244 return false;
245 ++index;
246 if (src[index] != 'W')
247 return false;
248 ++index;
249
250 int week;
251 if (!ToInt(src, index, 2, week) || week < kMinimumWeekNumber ||
252 week > MaxWeekNumberInYear())
253 return false;
254 if (year_ == MaximumYear() && week > kMaximumWeekInMaximumYear)
255 return false;
256 week_ = week;
257 end = index + 2;
258 type_ = kWeek;
259 return true;
260 }
261
ParseTime(const String & src,unsigned start,unsigned & end)262 bool DateComponents::ParseTime(const String& src,
263 unsigned start,
264 unsigned& end) {
265 int hour;
266 if (!ToInt(src, start, 2, hour) || hour < 0 || hour > 23)
267 return false;
268 unsigned index = start + 2;
269 if (index >= src.length())
270 return false;
271 if (src[index] != ':')
272 return false;
273 ++index;
274
275 int minute;
276 if (!ToInt(src, index, 2, minute) || minute < 0 || minute > 59)
277 return false;
278 index += 2;
279
280 int second = 0;
281 int millisecond = 0;
282 // Optional second part.
283 // Do not return with false because the part is optional.
284 if (index + 2 < src.length() && src[index] == ':') {
285 if (ToInt(src, index + 1, 2, second) && second >= 0 && second <= 59) {
286 index += 3;
287
288 // Optional fractional second part.
289 if (index < src.length() && src[index] == '.') {
290 unsigned digits_length = CountDigits(src, index + 1);
291 if (digits_length > 0) {
292 ++index;
293 bool ok;
294 if (digits_length == 1) {
295 ok = ToInt(src, index, 1, millisecond);
296 millisecond *= 100;
297 } else if (digits_length == 2) {
298 ok = ToInt(src, index, 2, millisecond);
299 millisecond *= 10;
300 } else if (digits_length == 3) {
301 ok = ToInt(src, index, 3, millisecond);
302 } else { // digits_length >= 4
303 return false;
304 }
305 DCHECK(ok);
306 index += digits_length;
307 }
308 }
309 }
310 }
311 hour_ = hour;
312 minute_ = minute;
313 second_ = second;
314 millisecond_ = millisecond;
315 end = index;
316 type_ = kTime;
317 return true;
318 }
319
ParseDateTimeLocal(const String & src,unsigned start,unsigned & end)320 bool DateComponents::ParseDateTimeLocal(const String& src,
321 unsigned start,
322 unsigned& end) {
323 unsigned index;
324 if (!ParseDate(src, start, index))
325 return false;
326 if (index >= src.length())
327 return false;
328 if (src[index] != 'T')
329 return false;
330 ++index;
331 if (!ParseTime(src, index, end))
332 return false;
333 if (!WithinHTMLDateLimits(year_, month_, month_day_, hour_, minute_, second_,
334 millisecond_))
335 return false;
336 type_ = kDateTimeLocal;
337 return true;
338 }
339
PositiveFmod(double value,double divider)340 static inline double PositiveFmod(double value, double divider) {
341 double remainder = fmod(value, divider);
342 return remainder < 0 ? remainder + divider : remainder;
343 }
344
SetMillisecondsSinceMidnightInternal(double ms_in_day)345 void DateComponents::SetMillisecondsSinceMidnightInternal(double ms_in_day) {
346 DCHECK_GE(ms_in_day, 0);
347 DCHECK_LT(ms_in_day, kMsPerDay);
348 millisecond_ = static_cast<int>(fmod(ms_in_day, kMsPerSecond));
349 double value = std::floor(ms_in_day / kMsPerSecond);
350 second_ = static_cast<int>(fmod(value, kSecondsPerMinute));
351 value = std::floor(value / kSecondsPerMinute);
352 minute_ = static_cast<int>(fmod(value, kMinutesPerHour));
353 hour_ = static_cast<int>(value / kMinutesPerHour);
354 }
355
SetMillisecondsSinceEpochForDateInternal(double ms)356 bool DateComponents::SetMillisecondsSinceEpochForDateInternal(double ms) {
357 year_ = MsToYear(ms);
358 int year_day = DayInYear(ms, year_);
359 month_ = MonthFromDayInYear(year_day, IsLeapYear(year_));
360 month_day_ = DayInMonthFromDayInYear(year_day, IsLeapYear(year_));
361 return true;
362 }
363
SetMillisecondsSinceEpochForDate(double ms)364 bool DateComponents::SetMillisecondsSinceEpochForDate(double ms) {
365 type_ = kInvalid;
366 if (!std::isfinite(ms))
367 return false;
368 if (!SetMillisecondsSinceEpochForDateInternal(round(ms)))
369 return false;
370 if (!WithinHTMLDateLimits(year_, month_, month_day_))
371 return false;
372 type_ = kDate;
373 return true;
374 }
375
SetMillisecondsSinceEpochForDateTimeLocal(double ms)376 bool DateComponents::SetMillisecondsSinceEpochForDateTimeLocal(double ms) {
377 type_ = kInvalid;
378 if (!std::isfinite(ms))
379 return false;
380 ms = round(ms);
381 SetMillisecondsSinceMidnightInternal(PositiveFmod(ms, kMsPerDay));
382 if (!SetMillisecondsSinceEpochForDateInternal(ms))
383 return false;
384 if (!WithinHTMLDateLimits(year_, month_, month_day_, hour_, minute_, second_,
385 millisecond_))
386 return false;
387 type_ = kDateTimeLocal;
388 return true;
389 }
390
SetMillisecondsSinceEpochForMonth(double ms)391 bool DateComponents::SetMillisecondsSinceEpochForMonth(double ms) {
392 type_ = kInvalid;
393 if (!std::isfinite(ms))
394 return false;
395 if (!SetMillisecondsSinceEpochForDateInternal(round(ms)))
396 return false;
397 if (!WithinHTMLDateLimits(year_, month_))
398 return false;
399 type_ = kMonth;
400 return true;
401 }
402
SetMillisecondsSinceMidnight(double ms)403 bool DateComponents::SetMillisecondsSinceMidnight(double ms) {
404 type_ = kInvalid;
405 if (!std::isfinite(ms))
406 return false;
407 SetMillisecondsSinceMidnightInternal(PositiveFmod(round(ms), kMsPerDay));
408 type_ = kTime;
409 return true;
410 }
411
SetMonthsSinceEpoch(double months)412 bool DateComponents::SetMonthsSinceEpoch(double months) {
413 if (!std::isfinite(months))
414 return false;
415 months = round(months);
416 double double_month = PositiveFmod(months, 12);
417 double double_year = 1970 + (months - double_month) / 12;
418 if (double_year < MinimumYear() || MaximumYear() < double_year)
419 return false;
420 int year = static_cast<int>(double_year);
421 int month = static_cast<int>(double_month);
422 if (!WithinHTMLDateLimits(year, month))
423 return false;
424 year_ = year;
425 month_ = month;
426 type_ = kMonth;
427 return true;
428 }
429
430 // Offset from January 1st to Monday of the ISO 8601's first week.
431 // ex. If January 1st is Friday, such Monday is 3 days later. Returns 3.
OffsetTo1stWeekStart(int year)432 static int OffsetTo1stWeekStart(int year) {
433 int offset_to1st_week_start = 1 - DayOfWeek(year, 0, 1);
434 if (offset_to1st_week_start <= -4)
435 offset_to1st_week_start += 7;
436 return offset_to1st_week_start;
437 }
438
SetMillisecondsSinceEpochForWeek(double ms)439 bool DateComponents::SetMillisecondsSinceEpochForWeek(double ms) {
440 type_ = kInvalid;
441 if (!std::isfinite(ms))
442 return false;
443 ms = round(ms);
444
445 year_ = MsToYear(ms);
446 if (year_ < MinimumYear() || year_ > MaximumYear())
447 return false;
448
449 int year_day = DayInYear(ms, year_);
450 int offset = OffsetTo1stWeekStart(year_);
451 if (year_day < offset) {
452 // The day belongs to the last week of the previous year.
453 year_--;
454 if (year_ <= MinimumYear())
455 return false;
456 week_ = MaxWeekNumberInYear();
457 } else {
458 week_ = ((year_day - offset) / 7) + 1;
459 if (week_ > MaxWeekNumberInYear()) {
460 year_++;
461 week_ = 1;
462 }
463 if (year_ > MaximumYear() ||
464 (year_ == MaximumYear() && week_ > kMaximumWeekInMaximumYear))
465 return false;
466 }
467 type_ = kWeek;
468 return true;
469 }
470
SetWeek(int year,int week_number)471 bool DateComponents::SetWeek(int year, int week_number) {
472 type_ = kInvalid;
473 if (year < MinimumYear() || year > MaximumYear())
474 return false;
475 year_ = year;
476 if (week_number < 1 || week_number > MaxWeekNumberInYear())
477 return false;
478 week_ = week_number;
479 type_ = kWeek;
480 return true;
481 }
482
MillisecondsSinceEpochForTime() const483 double DateComponents::MillisecondsSinceEpochForTime() const {
484 DCHECK(type_ == kTime || type_ == kDateTimeLocal);
485 return ((hour_ * kMinutesPerHour + minute_) * kSecondsPerMinute + second_) *
486 kMsPerSecond +
487 millisecond_;
488 }
489
MillisecondsSinceEpoch() const490 double DateComponents::MillisecondsSinceEpoch() const {
491 switch (type_) {
492 case kDate:
493 return DateToDaysFrom1970(year_, month_, month_day_) * kMsPerDay;
494 case kDateTimeLocal:
495 return DateToDaysFrom1970(year_, month_, month_day_) * kMsPerDay +
496 MillisecondsSinceEpochForTime();
497 case kMonth:
498 return DateToDaysFrom1970(year_, month_, 1) * kMsPerDay;
499 case kTime:
500 return MillisecondsSinceEpochForTime();
501 case kWeek:
502 return (DateToDaysFrom1970(year_, 0, 1) + OffsetTo1stWeekStart(year_) +
503 (week_ - 1) * 7) *
504 kMsPerDay;
505 case kInvalid:
506 break;
507 }
508 NOTREACHED();
509 return InvalidMilliseconds();
510 }
511
MonthsSinceEpoch() const512 double DateComponents::MonthsSinceEpoch() const {
513 DCHECK_EQ(type_, kMonth);
514 return (year_ - 1970) * 12 + month_;
515 }
516
ToStringForTime(SecondFormat format) const517 String DateComponents::ToStringForTime(SecondFormat format) const {
518 DCHECK(type_ == kDateTimeLocal || type_ == kTime);
519 SecondFormat effective_format = format;
520 if (millisecond_)
521 effective_format = kMillisecond;
522 else if (format == kNone && second_)
523 effective_format = kSecond;
524
525 switch (effective_format) {
526 default:
527 NOTREACHED();
528 FALLTHROUGH;
529 case kNone:
530 return String::Format("%02d:%02d", hour_, minute_);
531 case kSecond:
532 return String::Format("%02d:%02d:%02d", hour_, minute_, second_);
533 case kMillisecond:
534 return String::Format("%02d:%02d:%02d.%03d", hour_, minute_, second_,
535 millisecond_);
536 }
537 }
538
ToString(SecondFormat format) const539 String DateComponents::ToString(SecondFormat format) const {
540 switch (type_) {
541 case kDate:
542 return String::Format("%04d-%02d-%02d", year_, month_ + 1, month_day_);
543 case kDateTimeLocal:
544 return String::Format("%04d-%02d-%02dT", year_, month_ + 1, month_day_) +
545 ToStringForTime(format);
546 case kMonth:
547 return String::Format("%04d-%02d", year_, month_ + 1);
548 case kTime:
549 return ToStringForTime(format);
550 case kWeek:
551 return String::Format("%04d-W%02d", year_, week_);
552 case kInvalid:
553 break;
554 }
555 NOTREACHED();
556 return String("(Invalid DateComponents)");
557 }
558
559 } // namespace blink
560