blob: 87f4972d3af5290cc7b0827eb54df31986a62d08 [file] [log] [blame]
/*
* Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
* Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
* Copyright (C) 2009 Google Inc. All rights reserved.
* Copyright (C) 2007-2009 Torch Mobile, Inc.
* Copyright (C) 2010 &yet, LLC. (nate@andyet.net)
*
* The Original Code is Mozilla Communicator client code, released
* March 31, 1998.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998
* the Initial Developer. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Alternatively, the contents of this file may be used under the terms
* of either the Mozilla Public License Version 1.1, found at
* http://www.mozilla.org/MPL/ (the "MPL") or the GNU General Public
* License Version 2.0, found at http://www.fsf.org/copyleft/gpl.html
* (the "GPL"), in which case the provisions of the MPL or the GPL are
* applicable instead of those above. If you wish to allow use of your
* version of this file only under the terms of one of those two
* licenses (the MPL or the GPL) and not to allow others to use your
* version of this file under the LGPL, indicate your decision by
* deletingthe provisions above and replace them with the notice and
* other provisions required by the MPL or the GPL, as the case may be.
* If you do not delete the provisions above, a recipient may use your
* version of this file under any of the LGPL, the MPL or the GPL.
* Copyright 2006-2008 the V8 project authors. 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 "third_party/blink/renderer/platform/wtf/date_math.h"
#include <limits.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>
#include <algorithm>
#include <limits>
#include <memory>
#include "base/stl_util.h"
#include "build/build_config.h"
#include "third_party/blink/renderer/platform/wtf/ascii_ctype.h"
#include "third_party/blink/renderer/platform/wtf/assertions.h"
#include "third_party/blink/renderer/platform/wtf/math_extras.h"
#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
#include "third_party/blink/renderer/platform/wtf/string_extras.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
#include "third_party/blink/renderer/platform/wtf/time.h"
#include <unicode/basictz.h>
#include <unicode/timezone.h>
#if defined(OS_WIN)
#include <windows.h>
#else
#include <sys/time.h>
#endif
namespace WTF {
/* Constants */
static const double kHoursPerDay = 24.0;
static const double kMinimumECMADateInMs = -8640000000000000.0;
static const double kMaximumECMADateInMs = 8640000000000000.0;
// Day of year for the first day of each month, where index 0 is January, and
// day 0 is January 1. First for non-leap years, then for leap years.
static const int kFirstDayOfMonth[2][12] = {
{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334},
{0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}};
bool IsLeapYear(int year) {
if (year % 4 != 0)
return false;
if (year % 400 == 0)
return true;
if (year % 100 == 0)
return false;
return true;
}
static inline int DaysInYear(int year) {
return 365 + IsLeapYear(year);
}
static inline double DaysFrom1970ToYear(int year) {
// The Gregorian Calendar rules for leap years:
// Every fourth year is a leap year. 2004, 2008, and 2012 are leap years.
// However, every hundredth year is not a leap year. 1900 and 2100 are not
// leap years.
// Every four hundred years, there's a leap year after all. 2000 and 2400 are
// leap years.
static const int kLeapDaysBefore1971By4Rule = 1970 / 4;
static const int kExcludedLeapDaysBefore1971By100Rule = 1970 / 100;
static const int kLeapDaysBefore1971By400Rule = 1970 / 400;
const double year_minus_one = year - 1;
const double years_to_add_by4_rule =
floor(year_minus_one / 4.0) - kLeapDaysBefore1971By4Rule;
const double years_to_exclude_by100_rule =
floor(year_minus_one / 100.0) - kExcludedLeapDaysBefore1971By100Rule;
const double years_to_add_by400_rule =
floor(year_minus_one / 400.0) - kLeapDaysBefore1971By400Rule;
return 365.0 * (year - 1970) + years_to_add_by4_rule -
years_to_exclude_by100_rule + years_to_add_by400_rule;
}
static double MsToDays(double ms) {
return floor(ms / kMsPerDay);
}
static void AppendTwoDigitNumber(StringBuilder& builder, int number) {
DCHECK_GE(number, 0);
DCHECK_LT(number, 100);
if (number <= 9)
builder.Append('0');
builder.AppendNumber(number);
}
int MsToYear(double ms) {
DCHECK(std::isfinite(ms));
DCHECK_GE(ms, kMinimumECMADateInMs);
DCHECK_LE(ms, kMaximumECMADateInMs);
int approx_year = static_cast<int>(floor(ms / (kMsPerDay * 365.2425)) + 1970);
double ms_from_approx_year_to1970 =
kMsPerDay * DaysFrom1970ToYear(approx_year);
if (ms_from_approx_year_to1970 > ms)
return approx_year - 1;
if (ms_from_approx_year_to1970 + kMsPerDay * DaysInYear(approx_year) <= ms)
return approx_year + 1;
return approx_year;
}
int DayInYear(double ms, int year) {
return static_cast<int>(MsToDays(ms) - DaysFrom1970ToYear(year));
}
int MonthFromDayInYear(int day_in_year, bool leap_year) {
const int d = day_in_year;
int step;
if (d < (step = 31))
return 0;
step += (leap_year ? 29 : 28);
if (d < step)
return 1;
if (d < (step += 31))
return 2;
if (d < (step += 30))
return 3;
if (d < (step += 31))
return 4;
if (d < (step += 30))
return 5;
if (d < (step += 31))
return 6;
if (d < (step += 31))
return 7;
if (d < (step += 30))
return 8;
if (d < (step += 31))
return 9;
if (d < (step += 30))
return 10;
return 11;
}
static inline bool CheckMonth(int day_in_year,
int& start_day_of_this_month,
int& start_day_of_next_month,
int days_in_this_month) {
start_day_of_this_month = start_day_of_next_month;
start_day_of_next_month += days_in_this_month;
return (day_in_year <= start_day_of_next_month);
}
int DayInMonthFromDayInYear(int day_in_year, bool leap_year) {
const int d = day_in_year;
int step;
int next = 30;
if (d <= next)
return d + 1;
const int days_in_feb = (leap_year ? 29 : 28);
if (CheckMonth(d, step, next, days_in_feb))
return d - step;
if (CheckMonth(d, step, next, 31))
return d - step;
if (CheckMonth(d, step, next, 30))
return d - step;
if (CheckMonth(d, step, next, 31))
return d - step;
if (CheckMonth(d, step, next, 30))
return d - step;
if (CheckMonth(d, step, next, 31))
return d - step;
if (CheckMonth(d, step, next, 31))
return d - step;
if (CheckMonth(d, step, next, 30))
return d - step;
if (CheckMonth(d, step, next, 31))
return d - step;
if (CheckMonth(d, step, next, 30))
return d - step;
step = next;
return d - step;
}
int DayInYear(int year, int month, int day) {
return kFirstDayOfMonth[IsLeapYear(year)][month] + day - 1;
}
double DateToDaysFrom1970(int year, int month, int day) {
year += month / 12;
month %= 12;
if (month < 0) {
month += 12;
--year;
}
double yearday = floor(DaysFrom1970ToYear(year));
DCHECK((year >= 1970 && yearday >= 0) || (year < 1970 && yearday < 0));
return yearday + DayInYear(year, month, day);
}
static inline double YmdhmsToSeconds(int year,
long mon,
long day,
long hour,
long minute,
double second) {
double days =
(day - 32075) + floor(1461 * (year + 4800.0 + (mon - 14) / 12) / 4) +
367 * (mon - 2 - (mon - 14) / 12 * 12) / 12 -
floor(3 * ((year + 4900.0 + (mon - 14) / 12) / 100) / 4) - 2440588;
return ((days * kHoursPerDay + hour) * kMinutesPerHour + minute) *
kSecondsPerMinute +
second;
}
// We follow the recommendation of RFC 2822 to consider all
// obsolete time zones not listed here equivalent to "-0000".
static const struct KnownZone {
#if !defined(OS_WIN)
const
#endif
char tz_name[4];
int tz_offset;
} known_zones[] = {{"UT", 0}, {"GMT", 0}, {"EST", -300}, {"EDT", -240},
{"CST", -360}, {"CDT", -300}, {"MST", -420}, {"MDT", -360},
{"PST", -480}, {"PDT", -420}};
inline static void SkipSpacesAndComments(const char*& s) {
int nesting = 0;
char ch;
while ((ch = *s)) {
if (!IsASCIISpace(ch)) {
if (ch == '(')
nesting++;
else if (ch == ')' && nesting > 0)
nesting--;
else if (nesting == 0)
break;
}
s++;
}
}
// returns 0-11 (Jan-Dec); -1 on failure
static int FindMonth(const char* month_str) {
DCHECK(month_str);
char needle[4];
for (int i = 0; i < 3; ++i) {
if (!*month_str)
return -1;
needle[i] = static_cast<char>(ToASCIILower(*month_str++));
}
needle[3] = '\0';
const char* haystack = "janfebmaraprmayjunjulaugsepoctnovdec";
const char* str = strstr(haystack, needle);
if (str) {
int position = static_cast<int>(str - haystack);
if (position % 3 == 0)
return position / 3;
}
return -1;
}
static bool ParseInt(const char* string,
char** stop_position,
int base,
int* result) {
long long_result = strtol(string, stop_position, base);
// Avoid the use of errno as it is not available on Windows CE
if (string == *stop_position ||
long_result <= std::numeric_limits<int>::min() ||
long_result >= std::numeric_limits<int>::max())
return false;
*result = static_cast<int>(long_result);
return true;
}
static bool ParseLong(const char* string,
char** stop_position,
int base,
long* result) {
*result = strtol(string, stop_position, base);
// Avoid the use of errno as it is not available on Windows CE
if (string == *stop_position || *result == std::numeric_limits<long>::min() ||
*result == std::numeric_limits<long>::max())
return false;
return true;
}
// Odd case where 'exec' is allowed to be 0, to accommodate a caller in WebCore.
static double ParseDateFromNullTerminatedCharacters(const char* date_string,
bool& have_tz,
int& offset) {
have_tz = false;
offset = 0;
// This parses a date in the form:
// Tuesday, 09-Nov-99 23:12:40 GMT
// or
// Sat, 01-Jan-2000 08:00:00 GMT
// or
// Sat, 01 Jan 2000 08:00:00 GMT
// or
// 01 Jan 99 22:00 +0100 (exceptions in rfc822/rfc2822)
// ### non RFC formats, added for Javascript:
// [Wednesday] January 09 1999 23:12:40 GMT
// [Wednesday] January 09 23:12:40 GMT 1999
//
// We ignore the weekday.
// Skip leading space
SkipSpacesAndComments(date_string);
long month = -1;
const char* word_start = date_string;
// Check contents of first words if not number
while (*date_string && !IsASCIIDigit(*date_string)) {
if (IsASCIISpace(*date_string) || *date_string == '(') {
if (date_string - word_start >= 3)
month = FindMonth(word_start);
SkipSpacesAndComments(date_string);
word_start = date_string;
} else {
date_string++;
}
}
// Missing delimiter between month and day (like "January29")?
if (month == -1 && word_start != date_string)
month = FindMonth(word_start);
SkipSpacesAndComments(date_string);
if (!*date_string)
return std::numeric_limits<double>::quiet_NaN();
// ' 09-Nov-99 23:12:40 GMT'
char* new_pos_str;
long day;
if (!ParseLong(date_string, &new_pos_str, 10, &day))
return std::numeric_limits<double>::quiet_NaN();
date_string = new_pos_str;
if (!*date_string)
return std::numeric_limits<double>::quiet_NaN();
if (day < 0)
return std::numeric_limits<double>::quiet_NaN();
int year = 0;
if (day > 31) {
// ### where is the boundary and what happens below?
if (*date_string != '/')
return std::numeric_limits<double>::quiet_NaN();
// looks like a YYYY/MM/DD date
if (!*++date_string)
return std::numeric_limits<double>::quiet_NaN();
if (day <= std::numeric_limits<int>::min() ||
day >= std::numeric_limits<int>::max())
return std::numeric_limits<double>::quiet_NaN();
year = static_cast<int>(day);
if (!ParseLong(date_string, &new_pos_str, 10, &month))
return std::numeric_limits<double>::quiet_NaN();
month -= 1;
date_string = new_pos_str;
if (*date_string++ != '/' || !*date_string)
return std::numeric_limits<double>::quiet_NaN();
if (!ParseLong(date_string, &new_pos_str, 10, &day))
return std::numeric_limits<double>::quiet_NaN();
date_string = new_pos_str;
} else if (*date_string == '/' && month == -1) {
date_string++;
// This looks like a MM/DD/YYYY date, not an RFC date.
month = day - 1; // 0-based
if (!ParseLong(date_string, &new_pos_str, 10, &day))
return std::numeric_limits<double>::quiet_NaN();
if (day < 1 || day > 31)
return std::numeric_limits<double>::quiet_NaN();
date_string = new_pos_str;
if (*date_string == '/')
date_string++;
if (!*date_string)
return std::numeric_limits<double>::quiet_NaN();
} else {
if (*date_string == '-')
date_string++;
SkipSpacesAndComments(date_string);
if (*date_string == ',')
date_string++;
if (month == -1) { // not found yet
month = FindMonth(date_string);
if (month == -1)
return std::numeric_limits<double>::quiet_NaN();
while (*date_string && *date_string != '-' && *date_string != ',' &&
!IsASCIISpace(*date_string))
date_string++;
if (!*date_string)
return std::numeric_limits<double>::quiet_NaN();
// '-99 23:12:40 GMT'
if (*date_string != '-' && *date_string != '/' && *date_string != ',' &&
!IsASCIISpace(*date_string))
return std::numeric_limits<double>::quiet_NaN();
date_string++;
}
}
if (month < 0 || month > 11)
return std::numeric_limits<double>::quiet_NaN();
// '99 23:12:40 GMT'
if (year <= 0 && *date_string) {
if (!ParseInt(date_string, &new_pos_str, 10, &year))
return std::numeric_limits<double>::quiet_NaN();
}
// Don't fail if the time is missing.
long hour = 0;
long minute = 0;
long second = 0;
if (!*new_pos_str) {
date_string = new_pos_str;
} else {
// ' 23:12:40 GMT'
if (!(IsASCIISpace(*new_pos_str) || *new_pos_str == ',')) {
if (*new_pos_str != ':')
return std::numeric_limits<double>::quiet_NaN();
// There was no year; the number was the hour.
year = -1;
} else {
// in the normal case (we parsed the year), advance to the next number
date_string = ++new_pos_str;
SkipSpacesAndComments(date_string);
}
ParseLong(date_string, &new_pos_str, 10, &hour);
// Do not check for errno here since we want to continue
// even if errno was set because we are still looking
// for the timezone!
// Read a number? If not, this might be a timezone name.
if (new_pos_str != date_string) {
date_string = new_pos_str;
if (hour < 0 || hour > 23)
return std::numeric_limits<double>::quiet_NaN();
if (!*date_string)
return std::numeric_limits<double>::quiet_NaN();
// ':12:40 GMT'
if (*date_string++ != ':')
return std::numeric_limits<double>::quiet_NaN();
if (!ParseLong(date_string, &new_pos_str, 10, &minute))
return std::numeric_limits<double>::quiet_NaN();
date_string = new_pos_str;
if (minute < 0 || minute > 59)
return std::numeric_limits<double>::quiet_NaN();
// ':40 GMT'
if (*date_string && *date_string != ':' && !IsASCIISpace(*date_string))
return std::numeric_limits<double>::quiet_NaN();
// seconds are optional in rfc822 + rfc2822
if (*date_string == ':') {
date_string++;
if (!ParseLong(date_string, &new_pos_str, 10, &second))
return std::numeric_limits<double>::quiet_NaN();
date_string = new_pos_str;
if (second < 0 || second > 59)
return std::numeric_limits<double>::quiet_NaN();
}
SkipSpacesAndComments(date_string);
if (strncasecmp(date_string, "AM", 2) == 0) {
if (hour > 12)
return std::numeric_limits<double>::quiet_NaN();
if (hour == 12)
hour = 0;
date_string += 2;
SkipSpacesAndComments(date_string);
} else if (strncasecmp(date_string, "PM", 2) == 0) {
if (hour > 12)
return std::numeric_limits<double>::quiet_NaN();
if (hour != 12)
hour += 12;
date_string += 2;
SkipSpacesAndComments(date_string);
}
}
}
// The year may be after the time but before the time zone.
if (IsASCIIDigit(*date_string) && year == -1) {
if (!ParseInt(date_string, &new_pos_str, 10, &year))
return std::numeric_limits<double>::quiet_NaN();
date_string = new_pos_str;
SkipSpacesAndComments(date_string);
}
// Don't fail if the time zone is missing.
// Some websites omit the time zone (4275206).
if (*date_string) {
if (strncasecmp(date_string, "GMT", 3) == 0 ||
strncasecmp(date_string, "UTC", 3) == 0) {
date_string += 3;
have_tz = true;
}
if (*date_string == '+' || *date_string == '-') {
int o;
if (!ParseInt(date_string, &new_pos_str, 10, &o))
return std::numeric_limits<double>::quiet_NaN();
date_string = new_pos_str;
if (o < -9959 || o > 9959)
return std::numeric_limits<double>::quiet_NaN();
int sgn = (o < 0) ? -1 : 1;
o = abs(o);
if (*date_string != ':') {
if (o >= 24)
offset = ((o / 100) * 60 + (o % 100)) * sgn;
else
offset = o * 60 * sgn;
} else { // GMT+05:00
++date_string; // skip the ':'
int o2;
if (!ParseInt(date_string, &new_pos_str, 10, &o2))
return std::numeric_limits<double>::quiet_NaN();
date_string = new_pos_str;
offset = (o * 60 + o2) * sgn;
}
have_tz = true;
} else {
for (size_t i = 0; i < base::size(known_zones); ++i) {
if (0 == strncasecmp(date_string, known_zones[i].tz_name,
strlen(known_zones[i].tz_name))) {
offset = known_zones[i].tz_offset;
date_string += strlen(known_zones[i].tz_name);
have_tz = true;
break;
}
}
}
}
SkipSpacesAndComments(date_string);
if (*date_string && year == -1) {
if (!ParseInt(date_string, &new_pos_str, 10, &year))
return std::numeric_limits<double>::quiet_NaN();
date_string = new_pos_str;
SkipSpacesAndComments(date_string);
}
// Trailing garbage
if (*date_string)
return std::numeric_limits<double>::quiet_NaN();
// Y2K: Handle 2 digit years.
if (year >= 0 && year < 100) {
if (year < 50)
year += 2000;
else
year += 1900;
}
return YmdhmsToSeconds(year, month + 1, day, hour, minute, second) *
kMsPerSecond;
}
double ParseDateFromNullTerminatedCharacters(const char* date_string) {
bool have_tz;
int offset;
double ms =
ParseDateFromNullTerminatedCharacters(date_string, have_tz, offset);
if (std::isnan(ms))
return std::numeric_limits<double>::quiet_NaN();
// fall back to local timezone
if (!have_tz) {
std::unique_ptr<icu::TimeZone> timezone(icu::TimeZone::createDefault());
int32_t raw_offset, dst_offset;
UErrorCode status = U_ZERO_ERROR;
// Handle the conversion of localtime to UTC the same way as the
// latest ECMA 262 spec for Javascript (v8 does that, too).
// TODO(jshin): Once http://bugs.icu-project.org/trac/ticket/13705
// is fixed, no casting would be necessary.
static_cast<const icu::BasicTimeZone*>(timezone.get())
->getOffsetFromLocal(ms, icu::BasicTimeZone::kFormer,
icu::BasicTimeZone::kFormer, raw_offset,
dst_offset, status);
DCHECK(U_SUCCESS(status));
offset = static_cast<int>((raw_offset + dst_offset) / kMsPerMinute);
}
return ms - (offset * kMsPerMinute);
}
// See http://tools.ietf.org/html/rfc2822#section-3.3 for more information.
String MakeRFC2822DateString(const Time date, int utc_offset) {
Time::Exploded time_exploded;
date.UTCExplode(&time_exploded);
StringBuilder string_builder;
string_builder.Append(kWeekdayName[time_exploded.day_of_week]);
string_builder.Append(", ");
string_builder.AppendNumber(time_exploded.day_of_month);
string_builder.Append(' ');
// |month| is 1-based in Exploded
string_builder.Append(kMonthName[time_exploded.month - 1]);
string_builder.Append(' ');
string_builder.AppendNumber(time_exploded.year);
string_builder.Append(' ');
AppendTwoDigitNumber(string_builder, time_exploded.hour);
string_builder.Append(':');
AppendTwoDigitNumber(string_builder, time_exploded.minute);
string_builder.Append(':');
AppendTwoDigitNumber(string_builder, time_exploded.second);
string_builder.Append(' ');
string_builder.Append(utc_offset > 0 ? '+' : '-');
int absolute_utc_offset = abs(utc_offset);
AppendTwoDigitNumber(string_builder, absolute_utc_offset / 60);
AppendTwoDigitNumber(string_builder, absolute_utc_offset % 60);
return string_builder.ToString();
}
double ConvertToLocalTime(double ms) {
std::unique_ptr<icu::TimeZone> timezone(icu::TimeZone::createDefault());
int32_t raw_offset, dst_offset;
UErrorCode status = U_ZERO_ERROR;
timezone->getOffset(ms, false, raw_offset, dst_offset, status);
DCHECK(U_SUCCESS(status));
return (ms + static_cast<double>(raw_offset + dst_offset));
}
} // namespace WTF