text: /*
   * Copyright (C) 2009 Google Inc. All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are
   * met:
   *
   *     * Redistributions of source code must retain the above copyright
   * notice, this list of conditions and the following disclaimer.
   *     * Redistributions in binary form must reproduce the above
   * copyright notice, this list of conditions and the following disclaimer
   * in the documentation and/or other materials provided with the
   * distribution.
   *     * Neither the name of Google Inc. nor the names of its
   * contributors may be used to endorse or promote products derived from
   * this software without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
   * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
   * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */
  
  #include "platform/DateComponents.h"
  
  #include <limits.h>
  #include "platform/wtf/ASCIICType.h"
  #include "platform/wtf/DateMath.h"
  #include "platform/wtf/MathExtras.h"
  #include "platform/wtf/text/WTFString.h"
  
  namespace blink {
  
  // HTML5 specification defines minimum week of year is one.
  const int DateComponents::kMinimumWeekNumber = 1;
  
  // HTML5 specification defines maximum week of year is 53.
  const int DateComponents::kMaximumWeekNumber = 53;
  
  static const int kMaximumMonthInMaximumYear =
      8;  // This is September, since months are 0 based.
  static const int kMaximumDayInMaximumMonth = 13;
  static const int kMaximumWeekInMaximumYear = 37;  // The week of 275760-09-13
  
  static const int kDaysInMonth[12] = {31, 28, 31, 30, 31, 30,
                                       31, 31, 30, 31, 30, 31};
  
  // 'month' is 0-based.
  static int MaxDayOfMonth(int year, int month) {
    if (month != 1)  // February?
      return kDaysInMonth[month];
    return IsLeapYear(year) ? 29 : 28;
  }
  
  // 'month' is 0-based.
  static int DayOfWeek(int year, int month, int day) {
    int shifted_month = month + 2;
    // 2:January, 3:Feburuary, 4:March, ...
  
    // Zeller's congruence
    if (shifted_month <= 3) {
      shifted_month += 12;
      year--;
    }
    // 4:March, ..., 14:January, 15:February
  
    int high_year = year / 100;
    int low_year = year % 100;
    // We add 6 to make the result Sunday-origin.
    int result = (day + 13 * shifted_month / 5 + low_year + low_year / 4 +
                  high_year / 4 + 5 * high_year + 6) %
                 7;
    return result;
  }
  
  int DateComponents::WeekDay() const {
    return DayOfWeek(year_, month_, month_day_);
  }
  
  int DateComponents::MaxWeekNumberInYear() const {
    int day = DayOfWeek(year_, 0, 1);  // January 1.
    return day == kThursday || (day == kWednesday && IsLeapYear(year_))
               ? kMaximumWeekNumber
               : kMaximumWeekNumber - 1;
  }
  
  static unsigned CountDigits(const String& src, unsigned start) {
    unsigned index = start;
    for (; index < src.length(); ++index) {
      if (!IsASCIIDigit(src[index]))
        break;
    }
    return index - start;
  }
  
  // Very strict integer parser. Do not allow leading or trailing whitespace
  // unlike charactersToIntStrict().
  static bool ToInt(const String& src,
                    unsigned parse_start,
                    unsigned parse_length,
                    int& out) {
    if (parse_start + parse_length > src.length() || !parse_length)
      return false;
    int value = 0;
    unsigned current = parse_start;
    unsigned end = current + parse_length;
  
    // We don't need to handle negative numbers for ISO 8601.
    for (; current < end; ++current) {
      if (!IsASCIIDigit(src[current]))
        return false;
      int digit = src[current] - '0';
      if (value > (INT_MAX - digit) / 10)  // Check for overflow.
        return false;
      value = value * 10 + digit;
    }
    out = value;
    return true;
  }
  
  bool DateComponents::ParseYear(const String& src,
                                 unsigned start,
                                 unsigned& end) {
    unsigned digits_length = CountDigits(src, start);
    // Needs at least 4 digits according to the standard.
    if (digits_length < 4)
      return false;
    int year;
    if (!ToInt(src, start, digits_length, year))
      return false;
    if (year < MinimumYear() || year > MaximumYear())
      return false;
    year_ = year;
    end = start + digits_length;
    return true;
  }
  
  static bool WithinHTMLDateLimits(int year, int month) {
    if (year < DateComponents::MinimumYear())
      return false;
    if (year < DateComponents::MaximumYear())
      return true;
    return month <= kMaximumMonthInMaximumYear;
  }
  
  static bool WithinHTMLDateLimits(int year, int month, int month_day) {
    if (year < DateComponents::MinimumYear())
      return false;
    if (year < DateComponents::MaximumYear())
      return true;
    if (month < kMaximumMonthInMaximumYear)
      return true;
    return month_day <= kMaximumDayInMaximumMonth;
  }
  
  static bool WithinHTMLDateLimits(int year,
                                   int month,
                                   int month_day,
                                   int hour,
                                   int minute,
                                   int second,
                                   int millisecond) {
    if (year < DateComponents::MinimumYear())
      return false;
    if (year < DateComponents::MaximumYear())
      return true;
    if (month < kMaximumMonthInMaximumYear)
      return true;
    if (month_day < kMaximumDayInMaximumMonth)
      return true;
    if (month_day > kMaximumDayInMaximumMonth)
      return false;
    // (year, month, monthDay) =
    // (maximumYear, maximumMonthInMaximumYear, maximumDayInMaximumMonth)
    return !hour && !minute && !second && !millisecond;
  }
  
  bool DateComponents::AddDay(int day_diff) {
    DCHECK(month_day_);
  
    int day = month_day_ + day_diff;
    if (day > MaxDayOfMonth(year_, month_)) {
      day = month_day_;
      int year = year_;
      int month = month_;
      int max_day = MaxDayOfMonth(year, month);
      for (; day_diff > 0; --day_diff) {
        ++day;
        if (day > max_day) {
          day = 1;
          ++month;
          if (month >= 12) {  // month is 0-origin.
            month = 0;
            ++year;
          }
          max_day = MaxDayOfMonth(year, month);
        }
      }
      if (!WithinHTMLDateLimits(year, month, day))
        return false;
      year_ = year;
      month_ = month;
    } else if (day < 1) {
      int month = month_;
      int year = year_;
      day = month_day_;
      for (; day_diff < 0; ++day_diff) {
        --day;
        if (day < 1) {
          --month;
          if (month < 0) {
            month = 11;
            --year;
          }
          day = MaxDayOfMonth(year, month);
        }
      }
      if (!WithinHTMLDateLimits(year, month, day))
        return false;
      year_ = year;
      month_ = month;
    } else {
      if (!WithinHTMLDateLimits(year_, month_, day))
        return false;
    }
    month_day_ = day;
    return true;
  }
  
  bool DateComponents::AddMinute(int minute) {
    // This function is used to adjust timezone offset. So m_year, m_month,
    // m_monthDay have values between the lower and higher limits.
    DCHECK(WithinHTMLDateLimits(year_, month_, month_day_));
  
    int carry;
    // minute can be negative or greater than 59.
    minute += minute_;
    if (minute > 59) {
      carry = minute / 60;
      minute = minute % 60;
    } else if (minute < 0) {
      carry = (59 - minute) / 60;
      minute += carry * 60;
      carry = -carry;
      DCHECK_GE(minute, 0);
      DCHECK_LE(minute, 59);
    } else {
      if (!WithinHTMLDateLimits(year_, month_, month_day_, hour_, minute, second_,
                                millisecond_))
        return false;
      minute_ = minute;
      return true;
    }
  
    int hour = hour_ + carry;
    if (hour > 23) {
      carry = hour / 24;
      hour = hour % 24;
    } else if (hour < 0) {
      carry = (23 - hour) / 24;
      hour += carry * 24;
      carry = -carry;
      DCHECK_GE(hour, 0);
      DCHECK_LE(hour, 23);
    } else {
      if (!WithinHTMLDateLimits(year_, month_, month_day_, hour, minute, second_,
                                millisecond_))
        return false;
      minute_ = minute;
      hour_ = hour;
      return true;
    }
    if (!AddDay(carry))
      return false;
    if (!WithinHTMLDateLimits(year_, month_, month_day_, hour, minute, second_,
                              millisecond_))
      return false;
    minute_ = minute;
    hour_ = hour;
    return true;
  }
  
  // Parses a timezone part, and adjust year, month, monthDay, hour, minute,
  // second, millisecond.
  bool DateComponents::ParseTimeZone(const String& src,
                                     unsigned start,
                                     unsigned& end) {
    if (start >= src.length())
      return false;
    unsigned index = start;
    if (src[index] == 'Z') {
      end = index + 1;
      return true;
    }
  
    bool minus;
    if (src[index] == '+')
      minus = false;
    else if (src[index] == '-')
      minus = true;
    else
      return false;
    ++index;
  
    int hour;
    int minute;
    if (!ToInt(src, index, 2, hour) || hour < 0 || hour > 23)
      return false;
    index += 2;
  
    if (index >= src.length() || src[index] != ':')
      return false;
    ++index;
  
    if (!ToInt(src, index, 2, minute) || minute < 0 || minute > 59)
      return false;
    index += 2;
  
    if (minus) {
      hour = -hour;
      minute = -minute;
    }
  
    // Subtract the timezone offset.
    if (!AddMinute(-(hour * 60 + minute)))
      return false;
    end = index;
    return true;
  }
  
  bool DateComponents::ParseMonth(const String& src,
                                  unsigned start,
                                  unsigned& end) {
    unsigned index;
    if (!ParseYear(src, start, index))
      return false;
    if (index >= src.length() || src[index] != '-')
      return false;
    ++index;
  
    int month;
    if (!ToInt(src, index, 2, month) || month < 1 || month > 12)
      return false;
    --month;
    if (!WithinHTMLDateLimits(year_, month))
      return false;
    month_ = month;
    end = index + 2;
    type_ = kMonth;
    return true;
  }
  
  bool DateComponents::ParseDate(const String& src,
                                 unsigned start,
                                 unsigned& end) {
    unsigned index;
    if (!ParseMonth(src, start, index))
      return false;
    // '-' and 2-digits are needed.
    if (index + 2 >= src.length())
      return false;
    if (src[index] != '-')
      return false;
    ++index;
  
    int day;
    if (!ToInt(src, index, 2, day) || day < 1 ||
        day > MaxDayOfMonth(year_, month_))
      return false;
    if (!WithinHTMLDateLimits(year_, month_, day))
      return false;
    month_day_ = day;
    end = index + 2;
    type_ = kDate;
    return true;
  }
  
  bool DateComponents::ParseWeek(const String& src,
                                 unsigned start,
                                 unsigned& end) {
    unsigned index;
    if (!ParseYear(src, start, index))
      return false;
  
    // 4 characters ('-' 'W' digit digit) are needed.
    if (index + 3 >= src.length())
      return false;
    if (src[index] != '-')
      return false;
    ++index;
    if (src[index] != 'W')
      return false;
    ++index;
  
    int week;
    if (!ToInt(src, index, 2, week) || week < kMinimumWeekNumber ||
        week > MaxWeekNumberInYear())
      return false;
    if (year_ == MaximumYear() && week > kMaximumWeekInMaximumYear)
      return false;
    week_ = week;
    end = index + 2;
    type_ = kWeek;
    return true;
  }
  
  bool DateComponents::ParseTime(const String& src,
                                 unsigned start,
                                 unsigned& end) {
    int hour;
    if (!ToInt(src, start, 2, hour) || hour < 0 || hour > 23)
      return false;
    unsigned index = start + 2;
    if (index >= src.length())
      return false;
    if (src[index] != ':')
      return false;
    ++index;
  
    int minute;
    if (!ToInt(src, index, 2, minute) || minute < 0 || minute > 59)
      return false;
    index += 2;
  
    int second = 0;
    int millisecond = 0;
    // Optional second part.
    // Do not return with false because the part is optional.
    if (index + 2 < src.length() && src[index] == ':') {
      if (ToInt(src, index + 1, 2, second) && second >= 0 && second <= 59) {
        index += 3;
  
        // Optional fractional second part.
        if (index < src.length() && src[index] == '.') {
          unsigned digits_length = CountDigits(src, index + 1);
          if (digits_length > 0) {
            ++index;
            bool ok;
            if (digits_length == 1) {
              ok = ToInt(src, index, 1, millisecond);
              millisecond *= 100;
            } else if (digits_length == 2) {
              ok = ToInt(src, index, 2, millisecond);
              millisecond *= 10;
            } else if (digits_length == 3) {
              ok = ToInt(src, index, 3, millisecond);
            } else {  // digitsLength >= 4
              return false;
            }
            DCHECK(ok);
            index += digits_length;
          }
        }
      }
    }
    hour_ = hour;
    minute_ = minute;
    second_ = second;
    millisecond_ = millisecond;
    end = index;
    type_ = kTime;
    return true;
  }
  
  bool DateComponents::ParseDateTimeLocal(const String& src,
                                          unsigned start,
                                          unsigned& end) {
    unsigned index;
    if (!ParseDate(src, start, index))
      return false;
    if (index >= src.length())
      return false;
    if (src[index] != 'T')
      return false;
    ++index;
    if (!ParseTime(src, index, end))
      return false;
    if (!WithinHTMLDateLimits(year_, month_, month_day_, hour_, minute_, second_,
                              millisecond_))
      return false;
    type_ = kDateTimeLocal;
    return true;
  }
  
  static inline double PositiveFmod(double value, double divider) {
    double remainder = fmod(value, divider);
    return remainder < 0 ? remainder + divider : remainder;
  }
  
  void DateComponents::SetMillisecondsSinceMidnightInternal(double ms_in_day) {
    DCHECK_GE(ms_in_day, 0);
    DCHECK_LT(ms_in_day, kMsPerDay);
    millisecond_ = static_cast<int>(fmod(ms_in_day, kMsPerSecond));
    double value = std::floor(ms_in_day / kMsPerSecond);
    second_ = static_cast<int>(fmod(value, kSecondsPerMinute));
    value = std::floor(value / kSecondsPerMinute);
    minute_ = static_cast<int>(fmod(value, kMinutesPerHour));
    hour_ = static_cast<int>(value / kMinutesPerHour);
  }
  
  bool DateComponents::SetMillisecondsSinceEpochForDateInternal(double ms) {
    year_ = MsToYear(ms);
    int year_day = DayInYear(ms, year_);
    month_ = MonthFromDayInYear(year_day, IsLeapYear(year_));
    month_day_ = DayInMonthFromDayInYear(year_day, IsLeapYear(year_));
    return true;
  }
  
  bool DateComponents::SetMillisecondsSinceEpochForDate(double ms) {
    type_ = kInvalid;
    if (!std::isfinite(ms))
      return false;
    if (!SetMillisecondsSinceEpochForDateInternal(round(ms)))
      return false;
    if (!WithinHTMLDateLimits(year_, month_, month_day_))
      return false;
    type_ = kDate;
    return true;
  }
  
  bool DateComponents::SetMillisecondsSinceEpochForDateTime(double ms) {
    type_ = kInvalid;
    if (!std::isfinite(ms))
      return false;
    ms = round(ms);
    SetMillisecondsSinceMidnightInternal(PositiveFmod(ms, kMsPerDay));
    if (!SetMillisecondsSinceEpochForDateInternal(ms))
      return false;
    if (!WithinHTMLDateLimits(year_, month_, month_day_, hour_, minute_, second_,
                              millisecond_))
      return false;
    type_ = kDateTime;
    return true;
  }
  
  bool DateComponents::SetMillisecondsSinceEpochForDateTimeLocal(double ms) {
    // Internal representation of DateTimeLocal is the same as DateTime except
    // m_type.
    if (!SetMillisecondsSinceEpochForDateTime(ms))
      return false;
    type_ = kDateTimeLocal;
    return true;
  }
  
  bool DateComponents::SetMillisecondsSinceEpochForMonth(double ms) {
    type_ = kInvalid;
    if (!std::isfinite(ms))
      return false;
    if (!SetMillisecondsSinceEpochForDateInternal(round(ms)))
      return false;
    if (!WithinHTMLDateLimits(year_, month_))
      return false;
    type_ = kMonth;
    return true;
  }
  
  bool DateComponents::SetMillisecondsSinceMidnight(double ms) {
    type_ = kInvalid;
    if (!std::isfinite(ms))
      return false;
    SetMillisecondsSinceMidnightInternal(PositiveFmod(round(ms), kMsPerDay));
    type_ = kTime;
    return true;
  }
  
  bool DateComponents::SetMonthsSinceEpoch(double months) {
    if (!std::isfinite(months))
      return false;
    months = round(months);
    double double_month = PositiveFmod(months, 12);
    double double_year = 1970 + (months - double_month) / 12;
    if (double_year < MinimumYear() || MaximumYear() < double_year)
      return false;
    int year = static_cast<int>(double_year);
    int month = static_cast<int>(double_month);
    if (!WithinHTMLDateLimits(year, month))
      return false;
    year_ = year;
    month_ = month;
    type_ = kMonth;
    return true;
  }
  
  // Offset from January 1st to Monday of the ISO 8601's first week.
  //   ex. If January 1st is Friday, such Monday is 3 days later. Returns 3.
  static int OffsetTo1stWeekStart(int year) {
    int offset_to1st_week_start = 1 - DayOfWeek(year, 0, 1);
    if (offset_to1st_week_start <= -4)
      offset_to1st_week_start += 7;
    return offset_to1st_week_start;
  }
  
  bool DateComponents::SetMillisecondsSinceEpochForWeek(double ms) {
    type_ = kInvalid;
    if (!std::isfinite(ms))
      return false;
    ms = round(ms);
  
    year_ = MsToYear(ms);
    if (year_ < MinimumYear() || year_ > MaximumYear())
      return false;
  
    int year_day = DayInYear(ms, year_);
    int offset = OffsetTo1stWeekStart(year_);
    if (year_day < offset) {
      // The day belongs to the last week of the previous year.
      year_--;
      if (year_ <= MinimumYear())
        return false;
      week_ = MaxWeekNumberInYear();
    } else {
      week_ = ((year_day - offset) / 7) + 1;
      if (week_ > MaxWeekNumberInYear()) {
        year_++;
        week_ = 1;
      }
      if (year_ > MaximumYear() ||
          (year_ == MaximumYear() && week_ > kMaximumWeekInMaximumYear))
        return false;
    }
    type_ = kWeek;
    return true;
  }
  
  bool DateComponents::SetWeek(int year, int week_number) {
    type_ = kInvalid;
    if (year < MinimumYear() || year > MaximumYear())
      return false;
    year_ = year;
    if (week_number < 1 || week_number > MaxWeekNumberInYear())
      return false;
    week_ = week_number;
    type_ = kWeek;
    return true;
  }
  
  double DateComponents::MillisecondsSinceEpochForTime() const {
    DCHECK(type_ == kTime || type_ == kDateTime || type_ == kDateTimeLocal);
    return ((hour_ * kMinutesPerHour + minute_) * kSecondsPerMinute + second_) *
               kMsPerSecond +
           millisecond_;
  }
  
  double DateComponents::MillisecondsSinceEpoch() const {
    switch (type_) {
      case kDate:
        return DateToDaysFrom1970(year_, month_, month_day_) * kMsPerDay;
      case kDateTime:
      case kDateTimeLocal:
        return DateToDaysFrom1970(year_, month_, month_day_) * kMsPerDay +
               MillisecondsSinceEpochForTime();
      case kMonth:
        return DateToDaysFrom1970(year_, month_, 1) * kMsPerDay;
      case kTime:
        return MillisecondsSinceEpochForTime();
      case kWeek:
        return (DateToDaysFrom1970(year_, 0, 1) + OffsetTo1stWeekStart(year_) +
                (week_ - 1) * 7) *
               kMsPerDay;
      case kInvalid:
        break;
    }
    NOTREACHED();
    return InvalidMilliseconds();
  }
  
  double DateComponents::MonthsSinceEpoch() const {
    DCHECK_EQ(type_, kMonth);
    return (year_ - 1970) * 12 + month_;
  }
  
  String DateComponents::ToStringForTime(SecondFormat format) const {
    DCHECK(type_ == kDateTime || type_ == kDateTimeLocal || type_ == kTime);
    SecondFormat effective_format = format;
    if (millisecond_)
      effective_format = kMillisecond;
    else if (format == kNone && second_)
      effective_format = kSecond;
  
    switch (effective_format) {
      default:
        NOTREACHED();
      // Fallback to None.
      case kNone:
        return String::Format("%02d:%02d", hour_, minute_);
      case kSecond:
        return String::Format("%02d:%02d:%02d", hour_, minute_, second_);
      case kMillisecond:
        return String::Format("%02d:%02d:%02d.%03d", hour_, minute_, second_,
                              millisecond_);
    }
  }
  
  String DateComponents::ToString(SecondFormat format) const {
    switch (type_) {
      case kDate:
        return String::Format("%04d-%02d-%02d", year_, month_ + 1, month_day_);
      case kDateTime:
        return String::Format("%04d-%02d-%02dT", year_, month_ + 1, month_day_) +
               ToStringForTime(format) + String("Z");
      case kDateTimeLocal:
        return String::Format("%04d-%02d-%02dT", year_, month_ + 1, month_day_) +
               ToStringForTime(format);
      case kMonth:
        return String::Format("%04d-%02d", year_, month_ + 1);
      case kTime:
        return ToStringForTime(format);
      case kWeek:
        return String::Format("%04d-W%02d", year_, week_);
      case kInvalid:
        break;
    }
    NOTREACHED();
    return String("(Invalid DateComponents)");
  }
  
  }  // namespace blink
  