You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
552 lines
14 KiB
552 lines
14 KiB
// 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 <time.h>
|
|
|
|
#include <cmath>
|
|
|
|
#include "build/build_config.h"
|
|
#include "core/fxcrt/fx_extension.h"
|
|
#include "core/fxcrt/fx_system.h"
|
|
#include "fpdfsdk/cpdfsdk_helpers.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 < FX_ArraySize(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() && std::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 std::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 (!std::isfinite(day) || !std::isfinite(time))
|
|
return std::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 < FX_ArraySize(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 < FX_ArraySize(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 (std::isnan(dt))
|
|
return ConversionStatus::kBadDate;
|
|
|
|
*result = dt;
|
|
return ConversionStatus::kSuccess;
|
|
}
|
|
|
|
} // namespace fxjs
|