// 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 #include #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(floor(t / 86400000.0)); } int YearFromTime(double t) { // estimate the time. int y = 1970 + static_cast(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 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(nYear); double m = static_cast(nMonth); double dt = static_cast(nDate); double ym = y + floor(m / 12); double mn = Mod(m, 12); double t = TimeFromYearMonth(static_cast(ym), static_cast(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(nHour); double m = static_cast(nMin); double s = static_cast(nSec); double milli = static_cast(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