2 * Copyright (C) 2009 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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
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.
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.
32 #include "platform/DateComponents.h"
35 #include "wtf/ASCIICType.h"
36 #include "wtf/DateMath.h"
37 #include "wtf/MathExtras.h"
38 #include "wtf/text/WTFString.h"
42 // HTML5 specification defines minimum week of year is one.
43 const int DateComponents::minimumWeekNumber
= 1;
45 // HTML5 specification defines maximum week of year is 53.
46 const int DateComponents::maximumWeekNumber
= 53;
48 static const int maximumMonthInMaximumYear
= 8; // This is September, since months are 0 based.
49 static const int maximumDayInMaximumMonth
= 13;
50 static const int maximumWeekInMaximumYear
= 37; // The week of 275760-09-13
52 static const int daysInMonth
[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
54 // 'month' is 0-based.
55 static int maxDayOfMonth(int year
, int month
)
57 if (month
!= 1) // February?
58 return daysInMonth
[month
];
59 return isLeapYear(year
) ? 29 : 28;
62 // 'month' is 0-based.
63 static int dayOfWeek(int year
, int month
, int day
)
65 int shiftedMonth
= month
+ 2;
66 // 2:January, 3:Feburuary, 4:March, ...
68 // Zeller's congruence
69 if (shiftedMonth
<= 3) {
73 // 4:March, ..., 14:January, 15:February
75 int highYear
= year
/ 100;
76 int lowYear
= year
% 100;
77 // We add 6 to make the result Sunday-origin.
78 int result
= (day
+ 13 * shiftedMonth
/ 5 + lowYear
+ lowYear
/ 4 + highYear
/ 4 + 5 * highYear
+ 6) % 7;
82 int DateComponents::weekDay() const
84 return dayOfWeek(m_year
, m_month
, m_monthDay
);
87 int DateComponents::maxWeekNumberInYear() const
89 int day
= dayOfWeek(m_year
, 0, 1); // January 1.
90 return day
== Thursday
|| (day
== Wednesday
&& isLeapYear(m_year
)) ? maximumWeekNumber
: maximumWeekNumber
- 1;
93 static unsigned countDigits(const String
& src
, unsigned start
)
95 unsigned index
= start
;
96 for (; index
< src
.length(); ++index
) {
97 if (!isASCIIDigit(src
[index
]))
100 return index
- start
;
103 // Very strict integer parser. Do not allow leading or trailing whitespace unlike charactersToIntStrict().
104 static bool toInt(const String
& src
, unsigned parseStart
, unsigned parseLength
, int& out
)
106 if (parseStart
+ parseLength
> src
.length() || !parseLength
)
109 unsigned current
= parseStart
;
110 unsigned end
= current
+ parseLength
;
112 // We don't need to handle negative numbers for ISO 8601.
113 for (; current
< end
; ++current
) {
114 if (!isASCIIDigit(src
[current
]))
116 int digit
= src
[current
] - '0';
117 if (value
> (INT_MAX
- digit
) / 10) // Check for overflow.
119 value
= value
* 10 + digit
;
125 bool DateComponents::parseYear(const String
& src
, unsigned start
, unsigned& end
)
127 unsigned digitsLength
= countDigits(src
, start
);
128 // Needs at least 4 digits according to the standard.
129 if (digitsLength
< 4)
132 if (!toInt(src
, start
, digitsLength
, year
))
134 if (year
< minimumYear() || year
> maximumYear())
137 end
= start
+ digitsLength
;
141 static bool withinHTMLDateLimits(int year
, int month
)
143 if (year
< DateComponents::minimumYear())
145 if (year
< DateComponents::maximumYear())
147 return month
<= maximumMonthInMaximumYear
;
150 static bool withinHTMLDateLimits(int year
, int month
, int monthDay
)
152 if (year
< DateComponents::minimumYear())
154 if (year
< DateComponents::maximumYear())
156 if (month
< maximumMonthInMaximumYear
)
158 return monthDay
<= maximumDayInMaximumMonth
;
161 static bool withinHTMLDateLimits(int year
, int month
, int monthDay
, int hour
, int minute
, int second
, int millisecond
)
163 if (year
< DateComponents::minimumYear())
165 if (year
< DateComponents::maximumYear())
167 if (month
< maximumMonthInMaximumYear
)
169 if (monthDay
< maximumDayInMaximumMonth
)
171 if (monthDay
> maximumDayInMaximumMonth
)
173 // (year, month, monthDay) = (maximumYear, maximumMonthInMaximumYear, maximumDayInMaximumMonth)
174 return !hour
&& !minute
&& !second
&& !millisecond
;
177 bool DateComponents::addDay(int dayDiff
)
181 int day
= m_monthDay
+ dayDiff
;
182 if (day
> maxDayOfMonth(m_year
, m_month
)) {
186 int maxDay
= maxDayOfMonth(year
, month
);
187 for (; dayDiff
> 0; --dayDiff
) {
192 if (month
>= 12) { // month is 0-origin.
196 maxDay
= maxDayOfMonth(year
, month
);
199 if (!withinHTMLDateLimits(year
, month
, day
))
203 } else if (day
< 1) {
207 for (; dayDiff
< 0; ++dayDiff
) {
215 day
= maxDayOfMonth(year
, month
);
218 if (!withinHTMLDateLimits(year
, month
, day
))
223 if (!withinHTMLDateLimits(m_year
, m_month
, day
))
230 bool DateComponents::addMinute(int minute
)
232 // This function is used to adjust timezone offset. So m_year, m_month,
233 // m_monthDay have values between the lower and higher limits.
234 ASSERT(withinHTMLDateLimits(m_year
, m_month
, m_monthDay
));
237 // minute can be negative or greater than 59.
241 minute
= minute
% 60;
242 } else if (minute
< 0) {
243 carry
= (59 - minute
) / 60;
244 minute
+= carry
* 60;
246 ASSERT(minute
>= 0 && minute
<= 59);
248 if (!withinHTMLDateLimits(m_year
, m_month
, m_monthDay
, m_hour
, minute
, m_second
, m_millisecond
))
254 int hour
= m_hour
+ carry
;
258 } else if (hour
< 0) {
259 carry
= (23 - hour
) / 24;
262 ASSERT(hour
>= 0 && hour
<= 23);
264 if (!withinHTMLDateLimits(m_year
, m_month
, m_monthDay
, hour
, minute
, m_second
, m_millisecond
))
272 if (!withinHTMLDateLimits(m_year
, m_month
, m_monthDay
, hour
, minute
, m_second
, m_millisecond
))
279 // Parses a timezone part, and adjust year, month, monthDay, hour, minute, second, millisecond.
280 bool DateComponents::parseTimeZone(const String
& src
, unsigned start
, unsigned& end
)
282 if (start
>= src
.length())
284 unsigned index
= start
;
285 if (src
[index
] == 'Z') {
291 if (src
[index
] == '+')
293 else if (src
[index
] == '-')
301 if (!toInt(src
, index
, 2, hour
) || hour
< 0 || hour
> 23)
305 if (index
>= src
.length() || src
[index
] != ':')
309 if (!toInt(src
, index
, 2, minute
) || minute
< 0 || minute
> 59)
318 // Subtract the timezone offset.
319 if (!addMinute(-(hour
* 60 + minute
)))
325 bool DateComponents::parseMonth(const String
& src
, unsigned start
, unsigned& end
)
328 if (!parseYear(src
, start
, index
))
330 if (index
>= src
.length() || src
[index
] != '-')
335 if (!toInt(src
, index
, 2, month
) || month
< 1 || month
> 12)
338 if (!withinHTMLDateLimits(m_year
, month
))
346 bool DateComponents::parseDate(const String
& src
, unsigned start
, unsigned& end
)
349 if (!parseMonth(src
, start
, index
))
351 // '-' and 2-digits are needed.
352 if (index
+ 2 >= src
.length())
354 if (src
[index
] != '-')
359 if (!toInt(src
, index
, 2, day
) || day
< 1 || day
> maxDayOfMonth(m_year
, m_month
))
361 if (!withinHTMLDateLimits(m_year
, m_month
, day
))
369 bool DateComponents::parseWeek(const String
& src
, unsigned start
, unsigned& end
)
372 if (!parseYear(src
, start
, index
))
375 // 4 characters ('-' 'W' digit digit) are needed.
376 if (index
+ 3 >= src
.length())
378 if (src
[index
] != '-')
381 if (src
[index
] != 'W')
386 if (!toInt(src
, index
, 2, week
) || week
< minimumWeekNumber
|| week
> maxWeekNumberInYear())
388 if (m_year
== maximumYear() && week
> maximumWeekInMaximumYear
)
396 bool DateComponents::parseTime(const String
& src
, unsigned start
, unsigned& end
)
399 if (!toInt(src
, start
, 2, hour
) || hour
< 0 || hour
> 23)
401 unsigned index
= start
+ 2;
402 if (index
>= src
.length())
404 if (src
[index
] != ':')
409 if (!toInt(src
, index
, 2, minute
) || minute
< 0 || minute
> 59)
415 // Optional second part.
416 // Do not return with false because the part is optional.
417 if (index
+ 2 < src
.length() && src
[index
] == ':') {
418 if (toInt(src
, index
+ 1, 2, second
) && second
>= 0 && second
<= 59) {
421 // Optional fractional second part.
422 if (index
< src
.length() && src
[index
] == '.') {
423 unsigned digitsLength
= countDigits(src
, index
+ 1);
424 if (digitsLength
> 0) {
427 if (digitsLength
== 1) {
428 ok
= toInt(src
, index
, 1, millisecond
);
430 } else if (digitsLength
== 2) {
431 ok
= toInt(src
, index
, 2, millisecond
);
433 } else { // digitsLength >= 3
434 ok
= toInt(src
, index
, 3, millisecond
);
436 ASSERT_UNUSED(ok
, ok
);
437 index
+= digitsLength
;
445 m_millisecond
= millisecond
;
451 bool DateComponents::parseDateTimeLocal(const String
& src
, unsigned start
, unsigned& end
)
454 if (!parseDate(src
, start
, index
))
456 if (index
>= src
.length())
458 if (src
[index
] != 'T')
461 if (!parseTime(src
, index
, end
))
463 if (!withinHTMLDateLimits(m_year
, m_month
, m_monthDay
, m_hour
, m_minute
, m_second
, m_millisecond
))
465 m_type
= DateTimeLocal
;
469 static inline double positiveFmod(double value
, double divider
)
471 double remainder
= fmod(value
, divider
);
472 return remainder
< 0 ? remainder
+ divider
: remainder
;
475 void DateComponents::setMillisecondsSinceMidnightInternal(double msInDay
)
477 ASSERT(msInDay
>= 0 && msInDay
< msPerDay
);
478 m_millisecond
= static_cast<int>(fmod(msInDay
, msPerSecond
));
479 double value
= std::floor(msInDay
/ msPerSecond
);
480 m_second
= static_cast<int>(fmod(value
, secondsPerMinute
));
481 value
= std::floor(value
/ secondsPerMinute
);
482 m_minute
= static_cast<int>(fmod(value
, minutesPerHour
));
483 m_hour
= static_cast<int>(value
/ minutesPerHour
);
486 bool DateComponents::setMillisecondsSinceEpochForDateInternal(double ms
)
488 m_year
= msToYear(ms
);
489 int yearDay
= dayInYear(ms
, m_year
);
490 m_month
= monthFromDayInYear(yearDay
, isLeapYear(m_year
));
491 m_monthDay
= dayInMonthFromDayInYear(yearDay
, isLeapYear(m_year
));
495 bool DateComponents::setMillisecondsSinceEpochForDate(double ms
)
498 if (!std::isfinite(ms
))
500 if (!setMillisecondsSinceEpochForDateInternal(round(ms
)))
502 if (!withinHTMLDateLimits(m_year
, m_month
, m_monthDay
))
508 bool DateComponents::setMillisecondsSinceEpochForDateTime(double ms
)
511 if (!std::isfinite(ms
))
514 setMillisecondsSinceMidnightInternal(positiveFmod(ms
, msPerDay
));
515 if (!setMillisecondsSinceEpochForDateInternal(ms
))
517 if (!withinHTMLDateLimits(m_year
, m_month
, m_monthDay
, m_hour
, m_minute
, m_second
, m_millisecond
))
523 bool DateComponents::setMillisecondsSinceEpochForDateTimeLocal(double ms
)
525 // Internal representation of DateTimeLocal is the same as DateTime except m_type.
526 if (!setMillisecondsSinceEpochForDateTime(ms
))
528 m_type
= DateTimeLocal
;
532 bool DateComponents::setMillisecondsSinceEpochForMonth(double ms
)
535 if (!std::isfinite(ms
))
537 if (!setMillisecondsSinceEpochForDateInternal(round(ms
)))
539 if (!withinHTMLDateLimits(m_year
, m_month
))
545 bool DateComponents::setMillisecondsSinceMidnight(double ms
)
548 if (!std::isfinite(ms
))
550 setMillisecondsSinceMidnightInternal(positiveFmod(round(ms
), msPerDay
));
555 bool DateComponents::setMonthsSinceEpoch(double months
)
557 if (!std::isfinite(months
))
559 months
= round(months
);
560 double doubleMonth
= positiveFmod(months
, 12);
561 double doubleYear
= 1970 + (months
- doubleMonth
) / 12;
562 if (doubleYear
< minimumYear() || maximumYear() < doubleYear
)
564 int year
= static_cast<int>(doubleYear
);
565 int month
= static_cast<int>(doubleMonth
);
566 if (!withinHTMLDateLimits(year
, month
))
574 // Offset from January 1st to Monday of the ISO 8601's first week.
575 // ex. If January 1st is Friday, such Monday is 3 days later. Returns 3.
576 static int offsetTo1stWeekStart(int year
)
578 int offsetTo1stWeekStart
= 1 - dayOfWeek(year
, 0, 1);
579 if (offsetTo1stWeekStart
<= -4)
580 offsetTo1stWeekStart
+= 7;
581 return offsetTo1stWeekStart
;
584 bool DateComponents::setMillisecondsSinceEpochForWeek(double ms
)
587 if (!std::isfinite(ms
))
591 m_year
= msToYear(ms
);
592 if (m_year
< minimumYear() || m_year
> maximumYear())
595 int yearDay
= dayInYear(ms
, m_year
);
596 int offset
= offsetTo1stWeekStart(m_year
);
597 if (yearDay
< offset
) {
598 // The day belongs to the last week of the previous year.
600 if (m_year
<= minimumYear())
602 m_week
= maxWeekNumberInYear();
604 m_week
= ((yearDay
- offset
) / 7) + 1;
605 if (m_week
> maxWeekNumberInYear()) {
609 if (m_year
> maximumYear() || (m_year
== maximumYear() && m_week
> maximumWeekInMaximumYear
))
616 bool DateComponents::setWeek(int year
, int weekNumber
)
619 if (year
< minimumYear() || year
> maximumYear())
622 if (weekNumber
< 1 || weekNumber
> maxWeekNumberInYear())
629 double DateComponents::millisecondsSinceEpochForTime() const
631 ASSERT(m_type
== Time
|| m_type
== DateTime
|| m_type
== DateTimeLocal
);
632 return ((m_hour
* minutesPerHour
+ m_minute
) * secondsPerMinute
+ m_second
) * msPerSecond
+ m_millisecond
;
635 double DateComponents::millisecondsSinceEpoch() const
639 return dateToDaysFrom1970(m_year
, m_month
, m_monthDay
) * msPerDay
;
642 return dateToDaysFrom1970(m_year
, m_month
, m_monthDay
) * msPerDay
+ millisecondsSinceEpochForTime();
644 return dateToDaysFrom1970(m_year
, m_month
, 1) * msPerDay
;
646 return millisecondsSinceEpochForTime();
648 return (dateToDaysFrom1970(m_year
, 0, 1) + offsetTo1stWeekStart(m_year
) + (m_week
- 1) * 7) * msPerDay
;
652 ASSERT_NOT_REACHED();
653 return invalidMilliseconds();
656 double DateComponents::monthsSinceEpoch() const
658 ASSERT(m_type
== Month
);
659 return (m_year
- 1970) * 12 + m_month
;
662 String
DateComponents::toStringForTime(SecondFormat format
) const
664 ASSERT(m_type
== DateTime
|| m_type
== DateTimeLocal
|| m_type
== Time
);
665 SecondFormat effectiveFormat
= format
;
667 effectiveFormat
= Millisecond
;
668 else if (format
== None
&& m_second
)
669 effectiveFormat
= Second
;
671 switch (effectiveFormat
) {
673 ASSERT_NOT_REACHED();
676 return String::format("%02d:%02d", m_hour
, m_minute
);
678 return String::format("%02d:%02d:%02d", m_hour
, m_minute
, m_second
);
680 return String::format("%02d:%02d:%02d.%03d", m_hour
, m_minute
, m_second
, m_millisecond
);
684 String
DateComponents::toString(SecondFormat format
) const
688 return String::format("%04d-%02d-%02d", m_year
, m_month
+ 1, m_monthDay
);
690 return String::format("%04d-%02d-%02dT", m_year
, m_month
+ 1, m_monthDay
)
691 + toStringForTime(format
) + String("Z");
693 return String::format("%04d-%02d-%02dT", m_year
, m_month
+ 1, m_monthDay
)
694 + toStringForTime(format
);
696 return String::format("%04d-%02d", m_year
, m_month
+ 1);
698 return toStringForTime(format
);
700 return String::format("%04d-W%02d", m_year
, m_week
);
704 ASSERT_NOT_REACHED();
705 return String("(Invalid DateComponents)");