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