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 "config.h"
32#include "DateComponents.h"
33
34#include <limits.h>
35#include <wtf/ASCIICType.h>
36#include <wtf/DateMath.h>
37#include <wtf/MathExtras.h>
38#include <wtf/text/StringConcatenateNumbers.h>
39
40namespace WebCore {
41
42// HTML5 specification defines minimum week of year is one.
43const int DateComponents::minimumWeekNumber = 1;
44
45// HTML5 specification defines maximum week of year is 53.
46const int DateComponents::maximumWeekNumber = 53;
47
48static const int maximumMonthInMaximumYear = 8; // This is September, since months are 0 based.
49static const int maximumDayInMaximumMonth = 13;
50static const int maximumWeekInMaximumYear = 37; // The week of 275760-09-13
51
52static const int daysInMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
53
54static bool isLeapYear(int year)
55{
56 if (year % 4)
57 return false;
58 if (!(year % 400))
59 return true;
60 if (!(year % 100))
61 return false;
62 return true;
63}
64
65// 'month' is 0-based.
66static int maxDayOfMonth(int year, int month)
67{
68 if (month != 1) // February?
69 return daysInMonth[month];
70 return isLeapYear(year) ? 29 : 28;
71}
72
73// 'month' is 0-based.
74static int dayOfWeek(int year, int month, int day)
75{
76 int shiftedMonth = month + 2;
77 // 2:January, 3:Feburuary, 4:March, ...
78
79 // Zeller's congruence
80 if (shiftedMonth <= 3) {
81 shiftedMonth += 12;
82 year--;
83 }
84 // 4:March, ..., 14:January, 15:February
85
86 int highYear = year / 100;
87 int lowYear = year % 100;
88 // We add 6 to make the result Sunday-origin.
89 int result = (day + 13 * shiftedMonth / 5 + lowYear + lowYear / 4 + highYear / 4 + 5 * highYear + 6) % 7;
90 return result;
91}
92
93int DateComponents::maxWeekNumberInYear() const
94{
95 int day = dayOfWeek(m_year, 0, 1); // January 1.
96 return day == Thursday || (day == Wednesday && isLeapYear(m_year)) ? maximumWeekNumber : maximumWeekNumber - 1;
97}
98
99static unsigned countDigits(const UChar* src, unsigned length, unsigned start)
100{
101 unsigned index = start;
102 for (; index < length; ++index) {
103 if (!isASCIIDigit(src[index]))
104 break;
105 }
106 return index - start;
107}
108
109// Very strict integer parser. Do not allow leading or trailing whitespace unlike charactersToIntStrict().
110static bool toInt(const UChar* src, unsigned length, unsigned parseStart, unsigned parseLength, int& out)
111{
112 if (parseStart + parseLength > length || parseLength <= 0)
113 return false;
114 int value = 0;
115 const UChar* current = src + parseStart;
116 const UChar* end = current + parseLength;
117
118 // We don't need to handle negative numbers for ISO 8601.
119 for (; current < end; ++current) {
120 if (!isASCIIDigit(*current))
121 return false;
122 int digit = *current - '0';
123 if (value > (INT_MAX - digit) / 10) // Check for overflow.
124 return false;
125 value = value * 10 + digit;
126 }
127 out = value;
128 return true;
129}
130
131bool DateComponents::parseYear(const UChar* src, unsigned length, unsigned start, unsigned& end)
132{
133 unsigned digitsLength = countDigits(src, length, start);
134 // Needs at least 4 digits according to the standard.
135 if (digitsLength < 4)
136 return false;
137 int year;
138 if (!toInt(src, length, start, digitsLength, year))
139 return false;
140 if (year < minimumYear() || year > maximumYear())
141 return false;
142 m_year = year;
143 end = start + digitsLength;
144 return true;
145}
146
147static bool withinHTMLDateLimits(int year, int month)
148{
149 if (year < DateComponents::minimumYear())
150 return false;
151 if (year < DateComponents::maximumYear())
152 return true;
153 return month <= maximumMonthInMaximumYear;
154}
155
156static bool withinHTMLDateLimits(int year, int month, int monthDay)
157{
158 if (year < DateComponents::minimumYear())
159 return false;
160 if (year < DateComponents::maximumYear())
161 return true;
162 if (month < maximumMonthInMaximumYear)
163 return true;
164 return monthDay <= maximumDayInMaximumMonth;
165}
166
167static bool withinHTMLDateLimits(int year, int month, int monthDay, int hour, int minute, int second, int millisecond)
168{
169 if (year < DateComponents::minimumYear())
170 return false;
171 if (year < DateComponents::maximumYear())
172 return true;
173 if (month < maximumMonthInMaximumYear)
174 return true;
175 if (monthDay < maximumDayInMaximumMonth)
176 return true;
177 if (monthDay > maximumDayInMaximumMonth)
178 return false;
179 // (year, month, monthDay) = (maximumYear, maximumMonthInMaximumYear, maximumDayInMaximumMonth)
180 return !hour && !minute && !second && !millisecond;
181}
182
183bool DateComponents::addDay(int dayDiff)
184{
185 ASSERT(m_monthDay);
186
187 int day = m_monthDay + dayDiff;
188 if (day > maxDayOfMonth(m_year, m_month)) {
189 day = m_monthDay;
190 int year = m_year;
191 int month = m_month;
192 int maxDay = maxDayOfMonth(year, month);
193 for (; dayDiff > 0; --dayDiff) {
194 ++day;
195 if (day > maxDay) {
196 day = 1;
197 ++month;
198 if (month >= 12) { // month is 0-origin.
199 month = 0;
200 ++year;
201 }
202 maxDay = maxDayOfMonth(year, month);
203 }
204 }
205 if (!withinHTMLDateLimits(year, month, day))
206 return false;
207 m_year = year;
208 m_month = month;
209 } else if (day < 1) {
210 int month = m_month;
211 int year = m_year;
212 day = m_monthDay;
213 for (; dayDiff < 0; ++dayDiff) {
214 --day;
215 if (day < 1) {
216 --month;
217 if (month < 0) {
218 month = 11;
219 --year;
220 }
221 day = maxDayOfMonth(year, month);
222 }
223 }
224 if (!withinHTMLDateLimits(year, month, day))
225 return false;
226 m_year = year;
227 m_month = month;
228 } else {
229 if (!withinHTMLDateLimits(m_year, m_month, day))
230 return false;
231 }
232 m_monthDay = day;
233 return true;
234}
235
236bool DateComponents::addMinute(int minute)
237{
238 // This function is used to adjust timezone offset. So m_year, m_month,
239 // m_monthDay have values between the lower and higher limits.
240 ASSERT(withinHTMLDateLimits(m_year, m_month, m_monthDay));
241
242 int carry;
243 // minute can be negative or greater than 59.
244 minute += m_minute;
245 if (minute > 59) {
246 carry = minute / 60;
247 minute = minute % 60;
248 } else if (minute < 0) {
249 carry = (59 - minute) / 60;
250 minute += carry * 60;
251 carry = -carry;
252 ASSERT(minute >= 0 && minute <= 59);
253 } else {
254 if (!withinHTMLDateLimits(m_year, m_month, m_monthDay, m_hour, minute, m_second, m_millisecond))
255 return false;
256 m_minute = minute;
257 return true;
258 }
259
260 int hour = m_hour + carry;
261 if (hour > 23) {
262 carry = hour / 24;
263 hour = hour % 24;
264 } else if (hour < 0) {
265 carry = (23 - hour) / 24;
266 hour += carry * 24;
267 carry = -carry;
268 ASSERT(hour >= 0 && hour <= 23);
269 } else {
270 if (!withinHTMLDateLimits(m_year, m_month, m_monthDay, hour, minute, m_second, m_millisecond))
271 return false;
272 m_minute = minute;
273 m_hour = hour;
274 return true;
275 }
276 if (!addDay(carry))
277 return false;
278 if (!withinHTMLDateLimits(m_year, m_month, m_monthDay, hour, minute, m_second, m_millisecond))
279 return false;
280 m_minute = minute;
281 m_hour = hour;
282 return true;
283}
284
285// Parses a timezone part, and adjust year, month, monthDay, hour, minute, second, millisecond.
286bool DateComponents::parseTimeZone(const UChar* src, unsigned length, unsigned start, unsigned& end)
287{
288 if (start >= length)
289 return false;
290 unsigned index = start;
291 if (src[index] == 'Z') {
292 end = index + 1;
293 return true;
294 }
295
296 bool minus;
297 if (src[index] == '+')
298 minus = false;
299 else if (src[index] == '-')
300 minus = true;
301 else
302 return false;
303 ++index;
304
305 int hour;
306 int minute;
307 if (!toInt(src, length, index, 2, hour) || hour < 0 || hour > 23)
308 return false;
309 index += 2;
310
311 if (index >= length || src[index] != ':')
312 return false;
313 ++index;
314
315 if (!toInt(src, length, index, 2, minute) || minute < 0 || minute > 59)
316 return false;
317 index += 2;
318
319 if (minus) {
320 hour = -hour;
321 minute = -minute;
322 }
323
324 // Subtract the timezone offset.
325 if (!addMinute(-(hour * 60 + minute)))
326 return false;
327 end = index;
328 return true;
329}
330
331bool DateComponents::parseMonth(const UChar* src, unsigned length, unsigned start, unsigned& end)
332{
333 ASSERT(src);
334 unsigned index;
335 if (!parseYear(src, length, start, index))
336 return false;
337 if (index >= length || src[index] != '-')
338 return false;
339 ++index;
340
341 int month;
342 if (!toInt(src, length, index, 2, month) || month < 1 || month > 12)
343 return false;
344 --month;
345 if (!withinHTMLDateLimits(m_year, month))
346 return false;
347 m_month = month;
348 end = index + 2;
349 m_type = Month;
350 return true;
351}
352
353bool DateComponents::parseDate(const UChar* src, unsigned length, unsigned start, unsigned& end)
354{
355 ASSERT(src);
356 unsigned index;
357 if (!parseMonth(src, length, start, index))
358 return false;
359 // '-' and 2-digits are needed.
360 if (index + 2 >= length)
361 return false;
362 if (src[index] != '-')
363 return false;
364 ++index;
365
366 int day;
367 if (!toInt(src, length, index, 2, day) || day < 1 || day > maxDayOfMonth(m_year, m_month))
368 return false;
369 if (!withinHTMLDateLimits(m_year, m_month, day))
370 return false;
371 m_monthDay = day;
372 end = index + 2;
373 m_type = Date;
374 return true;
375}
376
377bool DateComponents::parseWeek(const UChar* src, unsigned length, unsigned start, unsigned& end)
378{
379 ASSERT(src);
380 unsigned index;
381 if (!parseYear(src, length, start, index))
382 return false;
383
384 // 4 characters ('-' 'W' digit digit) are needed.
385 if (index + 3 >= length)
386 return false;
387 if (src[index] != '-')
388 return false;
389 ++index;
390 if (src[index] != 'W')
391 return false;
392 ++index;
393
394 int week;
395 if (!toInt(src, length, index, 2, week) || week < minimumWeekNumber || week > maxWeekNumberInYear())
396 return false;
397 if (m_year == maximumYear() && week > maximumWeekInMaximumYear)
398 return false;
399 m_week = week;
400 end = index + 2;
401 m_type = Week;
402 return true;
403}
404
405bool DateComponents::parseTime(const UChar* src, unsigned length, unsigned start, unsigned& end)
406{
407 ASSERT(src);
408 int hour;
409 if (!toInt(src, length, start, 2, hour) || hour < 0 || hour > 23)
410 return false;
411 unsigned index = start + 2;
412 if (index >= length)
413 return false;
414 if (src[index] != ':')
415 return false;
416 ++index;
417
418 int minute;
419 if (!toInt(src, length, index, 2, minute) || minute < 0 || minute > 59)
420 return false;
421 index += 2;
422
423 int second = 0;
424 int millisecond = 0;
425 // Optional second part.
426 // Do not return with false because the part is optional.
427 if (index + 2 < length && src[index] == ':') {
428 if (toInt(src, length, index + 1, 2, second) && second >= 0 && second <= 59) {
429 index += 3;
430
431 // Optional fractional second part.
432 if (index < length && src[index] == '.') {
433 unsigned digitsLength = countDigits(src, length, index + 1);
434 if (digitsLength > 0) {
435 ++index;
436 bool ok;
437 if (digitsLength == 1) {
438 ok = toInt(src, length, index, 1, millisecond);
439 millisecond *= 100;
440 } else if (digitsLength == 2) {
441 ok = toInt(src, length, index, 2, millisecond);
442 millisecond *= 10;
443 } else // digitsLength >= 3
444 ok = toInt(src, length, index, 3, millisecond);
445 ASSERT_UNUSED(ok, ok);
446 index += digitsLength;
447 }
448 }
449 }
450 }
451 m_hour = hour;
452 m_minute = minute;
453 m_second = second;
454 m_millisecond = millisecond;
455 end = index;
456 m_type = Time;
457 return true;
458}
459
460bool DateComponents::parseDateTimeLocal(const UChar* src, unsigned length, unsigned start, unsigned& end)
461{
462 ASSERT(src);
463 unsigned index;
464 if (!parseDate(src, length, start, index))
465 return false;
466 if (index >= length)
467 return false;
468 if (src[index] != 'T')
469 return false;
470 ++index;
471 if (!parseTime(src, length, index, end))
472 return false;
473 if (!withinHTMLDateLimits(m_year, m_month, m_monthDay, m_hour, m_minute, m_second, m_millisecond))
474 return false;
475 m_type = DateTimeLocal;
476 return true;
477}
478
479bool DateComponents::parseDateTime(const UChar* src, unsigned length, unsigned start, unsigned& end)
480{
481 ASSERT(src);
482 unsigned index;
483 if (!parseDate(src, length, start, index))
484 return false;
485 if (index >= length)
486 return false;
487 if (src[index] != 'T')
488 return false;
489 ++index;
490 if (!parseTime(src, length, index, index))
491 return false;
492 if (!parseTimeZone(src, length, index, end))
493 return false;
494 if (!withinHTMLDateLimits(m_year, m_month, m_monthDay, m_hour, m_minute, m_second, m_millisecond))
495 return false;
496 m_type = DateTime;
497 return true;
498}
499
500static inline double positiveFmod(double value, double divider)
501{
502 double remainder = fmod(value, divider);
503 return remainder < 0 ? remainder + divider : remainder;
504}
505
506void DateComponents::setMillisecondsSinceMidnightInternal(double msInDay)
507{
508 ASSERT(msInDay >= 0 && msInDay < msPerDay);
509 m_millisecond = static_cast<int>(fmod(msInDay, msPerSecond));
510 double value = floor(msInDay / msPerSecond);
511 m_second = static_cast<int>(fmod(value, secondsPerMinute));
512 value = floor(value / secondsPerMinute);
513 m_minute = static_cast<int>(fmod(value, minutesPerHour));
514 m_hour = static_cast<int>(value / minutesPerHour);
515}
516
517bool DateComponents::setMillisecondsSinceEpochForDateInternal(double ms)
518{
519 m_year = msToYear(ms);
520 int yearDay = dayInYear(ms, m_year);
521 m_month = monthFromDayInYear(yearDay, isLeapYear(m_year));
522 m_monthDay = dayInMonthFromDayInYear(yearDay, isLeapYear(m_year));
523 return true;
524}
525
526bool DateComponents::setMillisecondsSinceEpochForDate(double ms)
527{
528 m_type = Invalid;
529 if (!std::isfinite(ms))
530 return false;
531 if (!setMillisecondsSinceEpochForDateInternal(round(ms)))
532 return false;
533 if (!withinHTMLDateLimits(m_year, m_month, m_monthDay))
534 return false;
535 m_type = Date;
536 return true;
537}
538
539bool DateComponents::setMillisecondsSinceEpochForDateTime(double ms)
540{
541 m_type = Invalid;
542 if (!std::isfinite(ms))
543 return false;
544 ms = round(ms);
545 setMillisecondsSinceMidnightInternal(positiveFmod(ms, msPerDay));
546 if (!setMillisecondsSinceEpochForDateInternal(ms))
547 return false;
548 if (!withinHTMLDateLimits(m_year, m_month, m_monthDay, m_hour, m_minute, m_second, m_millisecond))
549 return false;
550 m_type = DateTime;
551 return true;
552}
553
554bool DateComponents::setMillisecondsSinceEpochForDateTimeLocal(double ms)
555{
556 // Internal representation of DateTimeLocal is the same as DateTime except m_type.
557 if (!setMillisecondsSinceEpochForDateTime(ms))
558 return false;
559 m_type = DateTimeLocal;
560 return true;
561}
562
563bool DateComponents::setMillisecondsSinceEpochForMonth(double ms)
564{
565 m_type = Invalid;
566 if (!std::isfinite(ms))
567 return false;
568 if (!setMillisecondsSinceEpochForDateInternal(round(ms)))
569 return false;
570 if (!withinHTMLDateLimits(m_year, m_month))
571 return false;
572 m_type = Month;
573 return true;
574}
575
576bool DateComponents::setMillisecondsSinceMidnight(double ms)
577{
578 m_type = Invalid;
579 if (!std::isfinite(ms))
580 return false;
581 setMillisecondsSinceMidnightInternal(positiveFmod(round(ms), msPerDay));
582 m_type = Time;
583 return true;
584}
585
586bool DateComponents::setMonthsSinceEpoch(double months)
587{
588 if (!std::isfinite(months))
589 return false;
590 months = round(months);
591 double doubleMonth = positiveFmod(months, 12);
592 double doubleYear = 1970 + (months - doubleMonth) / 12;
593 if (doubleYear < minimumYear() || maximumYear() < doubleYear)
594 return false;
595 int year = static_cast<int>(doubleYear);
596 int month = static_cast<int>(doubleMonth);
597 if (!withinHTMLDateLimits(year, month))
598 return false;
599 m_year = year;
600 m_month = month;
601 m_type = Month;
602 return true;
603}
604
605// Offset from January 1st to Monday of the ISO 8601's first week.
606// ex. If January 1st is Friday, such Monday is 3 days later. Returns 3.
607static int offsetTo1stWeekStart(int year)
608{
609 int offsetTo1stWeekStart = 1 - dayOfWeek(year, 0, 1);
610 if (offsetTo1stWeekStart <= -4)
611 offsetTo1stWeekStart += 7;
612 return offsetTo1stWeekStart;
613}
614
615bool DateComponents::setMillisecondsSinceEpochForWeek(double ms)
616{
617 m_type = Invalid;
618 if (!std::isfinite(ms))
619 return false;
620 ms = round(ms);
621
622 m_year = msToYear(ms);
623 if (m_year < minimumYear() || m_year > maximumYear())
624 return false;
625
626 int yearDay = dayInYear(ms, m_year);
627 int offset = offsetTo1stWeekStart(m_year);
628 if (yearDay < offset) {
629 // The day belongs to the last week of the previous year.
630 m_year--;
631 if (m_year <= minimumYear())
632 return false;
633 m_week = maxWeekNumberInYear();
634 } else {
635 m_week = ((yearDay - offset) / 7) + 1;
636 if (m_week > maxWeekNumberInYear()) {
637 m_year++;
638 m_week = 1;
639 }
640 if (m_year > maximumYear() || (m_year == maximumYear() && m_week > maximumWeekInMaximumYear))
641 return false;
642 }
643 m_type = Week;
644 return true;
645}
646
647double DateComponents::millisecondsSinceEpochForTime() const
648{
649 ASSERT(m_type == Time || m_type == DateTime || m_type == DateTimeLocal);
650 return ((m_hour * minutesPerHour + m_minute) * secondsPerMinute + m_second) * msPerSecond + m_millisecond;
651}
652
653double DateComponents::millisecondsSinceEpoch() const
654{
655 switch (m_type) {
656 case Date:
657 return dateToDaysFrom1970(m_year, m_month, m_monthDay) * msPerDay;
658 case DateTime:
659 case DateTimeLocal:
660 return dateToDaysFrom1970(m_year, m_month, m_monthDay) * msPerDay + millisecondsSinceEpochForTime();
661 case Month:
662 return dateToDaysFrom1970(m_year, m_month, 1) * msPerDay;
663 case Time:
664 return millisecondsSinceEpochForTime();
665 case Week:
666 return (dateToDaysFrom1970(m_year, 0, 1) + offsetTo1stWeekStart(m_year) + (m_week - 1) * 7) * msPerDay;
667 case Invalid:
668 break;
669 }
670 ASSERT_NOT_REACHED();
671 return invalidMilliseconds();
672}
673
674double DateComponents::monthsSinceEpoch() const
675{
676 ASSERT(m_type == Month);
677 return (m_year - 1970) * 12 + m_month;
678}
679
680String DateComponents::toStringForTime(SecondFormat format) const
681{
682 ASSERT(m_type == DateTime || m_type == DateTimeLocal || m_type == Time);
683 SecondFormat effectiveFormat = format;
684 if (m_millisecond)
685 effectiveFormat = Millisecond;
686 else if (format == None && m_second)
687 effectiveFormat = Second;
688
689 switch (effectiveFormat) {
690 default:
691 ASSERT_NOT_REACHED();
692#if ASSERT_DISABLED
693 FALLTHROUGH; // To None.
694#endif
695 case None:
696 return makeString(pad('0', 2, m_hour), ':', pad('0', 2, m_minute));
697 case Second:
698 return makeString(pad('0', 2, m_hour), ':', pad('0', 2, m_minute), ':', pad('0', 2, m_second));
699 case Millisecond:
700 return makeString(pad('0', 2, m_hour), ':', pad('0', 2, m_minute), ':', pad('0', 2, m_second), '.', pad('0', 3, m_millisecond));
701 }
702}
703
704String DateComponents::toString(SecondFormat format) const
705{
706 switch (m_type) {
707 case Date:
708 return makeString(pad('0', 4, m_year), '-', pad('0', 2, m_month + 1), '-', pad('0', 2, m_monthDay));
709 case DateTime:
710 return makeString(pad('0', 4, m_year), '-', pad('0', 2, m_month + 1), '-', pad('0', 2, m_monthDay), 'T', toStringForTime(format), 'Z');
711 case DateTimeLocal:
712 return makeString(pad('0', 4, m_year), '-', pad('0', 2, m_month + 1), '-', pad('0', 2, m_monthDay), 'T', toStringForTime(format));
713 case Month:
714 return makeString(pad('0', 4, m_year), '-', pad('0', 2, m_month + 1));
715 case Time:
716 return toStringForTime(format);
717 case Week:
718 return makeString(pad('0', 4, m_year), "-W", pad('0', 2, m_week));
719 case Invalid:
720 break;
721 }
722 ASSERT_NOT_REACHED();
723 return "(Invalid DateComponents)"_str;
724}
725
726} // namespace WebCore
727