blob: c0d8998a59e01d52f0cad3da7886df90087586ff [file] [log] [blame]
// Copyright 2014 PDFium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
#include "fxjs/fx_date_helpers.h"
#include <math.h>
#include <time.h>
#include <wctype.h>
#include "build/build_config.h"
#include "core/fxcrt/fx_extension.h"
#include "core/fxcrt/fx_system.h"
#include "fpdfsdk/cpdfsdk_helpers.h"
#include "third_party/base/cxx17_backports.h"
namespace fxjs {
namespace {
constexpr uint16_t daysMonth[12] = {0, 31, 59, 90, 120, 151,
181, 212, 243, 273, 304, 334};
constexpr uint16_t leapDaysMonth[12] = {0, 31, 60, 91, 121, 152,
182, 213, 244, 274, 305, 335};
double Mod(double x, double y) {
double r = fmod(x, y);
if (r < 0)
r += y;
return r;
}
double GetLocalTZA() {
if (!IsPDFSandboxPolicyEnabled(FPDF_POLICY_MACHINETIME_ACCESS))
return 0;
time_t t = 0;
FXSYS_time(&t);
FXSYS_localtime(&t);
#if defined(OS_WIN)
// In gcc 'timezone' is a global variable declared in time.h. In VC++, that
// variable was removed in VC++ 2015, with _get_timezone replacing it.
long timezone = 0;
_get_timezone(&timezone);
#endif
return (double)(-(timezone * 1000));
}
int GetDaylightSavingTA(double d) {
if (!IsPDFSandboxPolicyEnabled(FPDF_POLICY_MACHINETIME_ACCESS))
return 0;
time_t t = (time_t)(d / 1000);
struct tm* tmp = FXSYS_localtime(&t);
if (!tmp)
return 0;
if (tmp->tm_isdst > 0)
// One hour.
return (int)60 * 60 * 1000;
return 0;
}
bool IsLeapYear(int year) {
return (year % 4 == 0) && ((year % 100 != 0) || (year % 400 != 0));
}
int DayFromYear(int y) {
return (int)(365 * (y - 1970.0) + floor((y - 1969.0) / 4) -
floor((y - 1901.0) / 100) + floor((y - 1601.0) / 400));
}
double TimeFromYear(int y) {
return 86400000.0 * DayFromYear(y);
}
double TimeFromYearMonth(int y, int m) {
const uint16_t* pMonth = IsLeapYear(y) ? leapDaysMonth : daysMonth;
return TimeFromYear(y) + ((double)pMonth[m]) * 86400000;
}
int Day(double t) {
return static_cast<int>(floor(t / 86400000.0));
}
int YearFromTime(double t) {
// estimate the time.
int y = 1970 + static_cast<int>(t / (365.2425 * 86400000.0));
if (TimeFromYear(y) <= t) {
while (TimeFromYear(y + 1) <= t)
y++;
} else {
while (TimeFromYear(y) > t)
y--;
}
return y;
}
int DayWithinYear(double t) {
int year = YearFromTime(t);
int day = Day(t);
return day - DayFromYear(year);
}
int MonthFromTime(double t) {
// Check for negative |day| values and check for January.
int day = DayWithinYear(t);
if (day < 0)
return -1;
if (day < 31)
return 0;
if (IsLeapYear(YearFromTime(t)))
--day;
// Check for February onwards.
static constexpr int kCumulativeDaysInMonths[] = {
59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};
for (size_t i = 0; i < pdfium::size(kCumulativeDaysInMonths); ++i) {
if (day < kCumulativeDaysInMonths[i])
return i + 1;
}
return -1;
}
int DateFromTime(double t) {
int day = DayWithinYear(t);
int year = YearFromTime(t);
int leap = IsLeapYear(year);
int month = MonthFromTime(t);
switch (month) {
case 0:
return day + 1;
case 1:
return day - 30;
case 2:
return day - 58 - leap;
case 3:
return day - 89 - leap;
case 4:
return day - 119 - leap;
case 5:
return day - 150 - leap;
case 6:
return day - 180 - leap;
case 7:
return day - 211 - leap;
case 8:
return day - 242 - leap;
case 9:
return day - 272 - leap;
case 10:
return day - 303 - leap;
case 11:
return day - 333 - leap;
default:
return 0;
}
}
size_t FindSubWordLength(const WideString& str, size_t nStart) {
pdfium::span<const wchar_t> data = str.span();
size_t i = nStart;
while (i < data.size() && iswalnum(data[i]))
++i;
return i - nStart;
}
} // namespace
const wchar_t* const kMonths[12] = {L"Jan", L"Feb", L"Mar", L"Apr",
L"May", L"Jun", L"Jul", L"Aug",
L"Sep", L"Oct", L"Nov", L"Dec"};
const wchar_t* const kFullMonths[12] = {L"January", L"February", L"March",
L"April", L"May", L"June",
L"July", L"August", L"September",
L"October", L"November", L"December"};
static constexpr size_t KMonthAbbreviationLength = 3; // Anything in |kMonths|.
static constexpr size_t kLongestFullMonthLength = 9; // September
double FX_GetDateTime() {
if (!IsPDFSandboxPolicyEnabled(FPDF_POLICY_MACHINETIME_ACCESS))
return 0;
time_t t = FXSYS_time(nullptr);
struct tm* pTm = FXSYS_localtime(&t);
double t1 = TimeFromYear(pTm->tm_year + 1900);
return t1 + pTm->tm_yday * 86400000.0 + pTm->tm_hour * 3600000.0 +
pTm->tm_min * 60000.0 + pTm->tm_sec * 1000.0;
}
int FX_GetYearFromTime(double dt) {
return YearFromTime(dt);
}
int FX_GetMonthFromTime(double dt) {
return MonthFromTime(dt);
}
int FX_GetDayFromTime(double dt) {
return DateFromTime(dt);
}
int FX_GetHourFromTime(double dt) {
return (int)Mod(floor(dt / (60 * 60 * 1000)), 24);
}
int FX_GetMinFromTime(double dt) {
return (int)Mod(floor(dt / (60 * 1000)), 60);
}
int FX_GetSecFromTime(double dt) {
return (int)Mod(floor(dt / 1000), 60);
}
bool FX_IsValidMonth(int m) {
return m >= 1 && m <= 12;
}
// TODO(thestig): Should this take the month into consideration?
bool FX_IsValidDay(int d) {
return d >= 1 && d <= 31;
}
// TODO(thestig): Should 24 be allowed? Similarly, 60 for minutes and seconds.
bool FX_IsValid24Hour(int h) {
return h >= 0 && h <= 24;
}
bool FX_IsValidMinute(int m) {
return m >= 0 && m <= 60;
}
bool FX_IsValidSecond(int s) {
return s >= 0 && s <= 60;
}
double FX_LocalTime(double d) {
return d + GetLocalTZA() + GetDaylightSavingTA(d);
}
double FX_MakeDay(int nYear, int nMonth, int nDate) {
double y = static_cast<double>(nYear);
double m = static_cast<double>(nMonth);
double dt = static_cast<double>(nDate);
double ym = y + floor(m / 12);
double mn = Mod(m, 12);
double t = TimeFromYearMonth(static_cast<int>(ym), static_cast<int>(mn));
if (YearFromTime(t) != ym || MonthFromTime(t) != mn || DateFromTime(t) != 1)
return nan("");
return Day(t) + dt - 1;
}
double FX_MakeTime(int nHour, int nMin, int nSec, int nMs) {
double h = static_cast<double>(nHour);
double m = static_cast<double>(nMin);
double s = static_cast<double>(nSec);
double milli = static_cast<double>(nMs);
return h * 3600000 + m * 60000 + s * 1000 + milli;
}
double FX_MakeDate(double day, double time) {
if (!isfinite(day) || !isfinite(time))
return nan("");
return day * 86400000 + time;
}
int FX_ParseStringInteger(const WideString& str,
size_t nStart,
size_t* pSkip,
size_t nMaxStep) {
int nRet = 0;
size_t nSkip = 0;
for (size_t i = nStart; i < str.GetLength(); ++i) {
if (i - nStart > 10)
break;
wchar_t c = str[i];
if (!FXSYS_IsDecimalDigit(c))
break;
nRet = nRet * 10 + FXSYS_DecimalCharToInt(c);
++nSkip;
if (nSkip >= nMaxStep)
break;
}
*pSkip = nSkip;
return nRet;
}
ConversionStatus FX_ParseDateUsingFormat(const WideString& value,
const WideString& format,
double* result) {
double dt = FX_GetDateTime();
if (format.IsEmpty() || value.IsEmpty()) {
*result = dt;
return ConversionStatus::kSuccess;
}
int nYear = FX_GetYearFromTime(dt);
int nMonth = FX_GetMonthFromTime(dt) + 1;
int nDay = FX_GetDayFromTime(dt);
int nHour = FX_GetHourFromTime(dt);
int nMin = FX_GetMinFromTime(dt);
int nSec = FX_GetSecFromTime(dt);
int nYearSub = 99; // nYear - 2000;
bool bPm = false;
bool bExit = false;
bool bBadFormat = false;
size_t i = 0;
size_t j = 0;
while (i < format.GetLength()) {
if (bExit)
break;
wchar_t c = format[i];
switch (c) {
case ':':
case '.':
case '-':
case '\\':
case '/':
i++;
j++;
break;
case 'y':
case 'm':
case 'd':
case 'H':
case 'h':
case 'M':
case 's':
case 't': {
size_t oldj = j;
size_t nSkip = 0;
size_t remaining = format.GetLength() - i - 1;
if (remaining == 0 || format[i + 1] != c) {
switch (c) {
case 'y':
i++;
j++;
break;
case 'm':
nMonth = FX_ParseStringInteger(value, j, &nSkip, 2);
i++;
j += nSkip;
break;
case 'd':
nDay = FX_ParseStringInteger(value, j, &nSkip, 2);
i++;
j += nSkip;
break;
case 'H':
nHour = FX_ParseStringInteger(value, j, &nSkip, 2);
i++;
j += nSkip;
break;
case 'h':
nHour = FX_ParseStringInteger(value, j, &nSkip, 2);
i++;
j += nSkip;
break;
case 'M':
nMin = FX_ParseStringInteger(value, j, &nSkip, 2);
i++;
j += nSkip;
break;
case 's':
nSec = FX_ParseStringInteger(value, j, &nSkip, 2);
i++;
j += nSkip;
break;
case 't':
bPm = (j < value.GetLength() && value[j] == 'p');
i++;
j++;
break;
}
} else if (remaining == 1 || format[i + 2] != c) {
switch (c) {
case 'y':
nYear = FX_ParseStringInteger(value, j, &nSkip, 2);
i += 2;
j += nSkip;
break;
case 'm':
nMonth = FX_ParseStringInteger(value, j, &nSkip, 2);
i += 2;
j += nSkip;
break;
case 'd':
nDay = FX_ParseStringInteger(value, j, &nSkip, 2);
i += 2;
j += nSkip;
break;
case 'H':
nHour = FX_ParseStringInteger(value, j, &nSkip, 2);
i += 2;
j += nSkip;
break;
case 'h':
nHour = FX_ParseStringInteger(value, j, &nSkip, 2);
i += 2;
j += nSkip;
break;
case 'M':
nMin = FX_ParseStringInteger(value, j, &nSkip, 2);
i += 2;
j += nSkip;
break;
case 's':
nSec = FX_ParseStringInteger(value, j, &nSkip, 2);
i += 2;
j += nSkip;
break;
case 't':
bPm = (j + 1 < value.GetLength() && value[j] == 'p' &&
value[j + 1] == 'm');
i += 2;
j += 2;
break;
}
} else if (remaining == 2 || format[i + 3] != c) {
switch (c) {
case 'm': {
bool bFind = false;
nSkip = FindSubWordLength(value, j);
if (nSkip == KMonthAbbreviationLength) {
WideString sMonth = value.Substr(j, KMonthAbbreviationLength);
for (size_t m = 0; m < pdfium::size(kMonths); ++m) {
if (sMonth.CompareNoCase(kMonths[m]) == 0) {
nMonth = m + 1;
i += 3;
j += nSkip;
bFind = true;
break;
}
}
}
if (!bFind) {
nMonth = FX_ParseStringInteger(value, j, &nSkip, 3);
i += 3;
j += nSkip;
}
} break;
case 'y':
break;
default:
i += 3;
j += 3;
break;
}
} else if (remaining == 3 || format[i + 4] != c) {
switch (c) {
case 'y':
nYear = FX_ParseStringInteger(value, j, &nSkip, 4);
j += nSkip;
i += 4;
break;
case 'm': {
bool bFind = false;
nSkip = FindSubWordLength(value, j);
if (nSkip <= kLongestFullMonthLength) {
WideString sMonth = value.Substr(j, nSkip);
sMonth.MakeLower();
for (size_t m = 0; m < pdfium::size(kFullMonths); ++m) {
WideString sFullMonths = WideString(kFullMonths[m]);
sFullMonths.MakeLower();
if (sFullMonths.Contains(sMonth.c_str())) {
nMonth = m + 1;
i += 4;
j += nSkip;
bFind = true;
break;
}
}
}
if (!bFind) {
nMonth = FX_ParseStringInteger(value, j, &nSkip, 4);
i += 4;
j += nSkip;
}
} break;
default:
i += 4;
j += 4;
break;
}
} else {
if (j >= value.GetLength() || format[i] != value[j]) {
bBadFormat = true;
bExit = true;
}
i++;
j++;
}
if (oldj == j) {
bBadFormat = true;
bExit = true;
}
break;
}
default:
if (value.GetLength() <= j) {
bExit = true;
} else if (format[i] != value[j]) {
bBadFormat = true;
bExit = true;
}
i++;
j++;
break;
}
}
if (bBadFormat)
return ConversionStatus::kBadFormat;
if (bPm)
nHour += 12;
if (nYear >= 0 && nYear <= nYearSub)
nYear += 2000;
if (!FX_IsValidMonth(nMonth) || !FX_IsValidDay(nDay) ||
!FX_IsValid24Hour(nHour) || !FX_IsValidMinute(nMin) ||
!FX_IsValidSecond(nSec)) {
return ConversionStatus::kBadDate;
}
dt = FX_MakeDate(FX_MakeDay(nYear, nMonth - 1, nDay),
FX_MakeTime(nHour, nMin, nSec, 0));
if (isnan(dt))
return ConversionStatus::kBadDate;
*result = dt;
return ConversionStatus::kSuccess;
}
} // namespace fxjs