/*! \file timeutil.cpp * \brief Time-related helper functions. * * Date/Time code based on algorithms presented in "Calendrical Calculations", * Cambridge Press, 1998. * * This contains conversions between linear and fielded time as well helper * functions to gloss over time-related platform differences. */ #include "copyright.h" #include "autoconf.h" #include "config.h" #include "core.h" const int64_t FACTOR_MS_PER_SECOND = INT64_C(1000); const int64_t FACTOR_US_PER_SECOND = INT64_C(1000000); const int64_t FACTOR_100NS_PER_SECOND = INT64_C(10000000); const int64_t FACTOR_100NS_PER_MINUTE = FACTOR_100NS_PER_SECOND*60; const int64_t FACTOR_100NS_PER_HOUR = FACTOR_100NS_PER_MINUTE*60; const int64_t FACTOR_100NS_PER_DAY = FACTOR_100NS_PER_HOUR*24; const int64_t FACTOR_100NS_PER_WEEK = FACTOR_100NS_PER_DAY*7; const UTF8 *DayOfWeekString[7] = { T("Sun"), T("Mon"), T("Tue"), T("Wed"), T("Thu"), T("Fri"), T("Sat") }; const char daystab[12] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; const UTF8 *monthtab[12] = { T("Jan"), T("Feb"), T("Mar"), T("Apr"), T("May"), T("Jun"), T("Jul"), T("Aug"), T("Sep"), T("Oct"), T("Nov"), T("Dec") }; #ifdef SMALLEST_INT_GTE_NEG_QUOTIENT // The following functions provide a consistent division/modulus function // regardless of how the platform chooses to provide this function. // // Confused yet? Here's an example: // // SMALLEST_INT_GTE_NEG_QUOTIENT indicates that this platform computes // division and modulus like so: // // -9/5 ==> -1 and -9%5 ==> -4 // and (-9/5)*5 + (-9%5) ==> -1*5 + -4 ==> -5 + -4 ==> -9 // // The iMod() function uses this to provide LARGEST_INT_LTE_NEG_QUOTIENT // behavior (required by much math). This behavior computes division and // modulus like so: // // -9/5 ==> -2 and -9%5 ==> 1 // and (-9/5)*5 + (-9%5) ==> -2*5 + 1 ==> -10 + 1 ==> -9 // // Provide LLEQ modulus on a SGEQ platform. // int iMod(int x, int y) { if (y < 0) { if (x <= 0) { if (INT_MIN == x && -1 == y) { return 0; } return x % y; } else { return ((x-1) % y) + y + 1; } } else { if (x < 0) { return ((x+1) % y) + y - 1; } else { return x % y; } } } int64_t i64Mod(int64_t x, int64_t y) { if (y < 0) { if (x <= 0) { if (INT64_MIN == x && -1 == y) { return 0; } return x % y; } else { return ((x-1) % y) + y + 1; } } else { if (x < 0) { return ((x+1) % y) + y - 1; } else { return x % y; } } } // Provide SGEQ modulus on a SGEQ platform. // inline int iRemainder(int x, int y) { if (INT_MIN == x && -1 == y) { return 0; } return x % y; } // Provide SGEQ division on a SGEQ platform. // inline int iDivision(int x, int y) { return x / y; } // Provide LLEQ division on a SGEQ platform. // int iFloorDivision(int x, int y) { if (y < 0) { if (x <= 0) { return x / y; } else { return (x - y - 1) / y; } } else { if (x < 0) { return (x - y + 1) / y; } else { return x / y; } } } int64_t i64FloorDivision(int64_t x, int64_t y) { // INT64_MIN / -1 overflows and traps (#DE/SIGFPE) on x86 — guard it (#805). if (INT64_MIN == x && -1 == y) { return INT64_MIN; } if (y < 0) { if (x <= 0) { return x / y; } else { return (x - y - 1) / y; } } else { if (x < 0) { return (x - y + 1) / y; } else { return x / y; } } } int iFloorDivisionMod(int x, int y, int *piMod) { if (y < 0) { if (x <= 0) { if (INT_MIN == x && -1 == y) { *piMod = 0; } else { *piMod = x % y; } return x / y; } else { *piMod = ((x-1) % y) + y + 1; return (x - y - 1) / y; } } else { if (x < 0) { *piMod = ((x+1) % y) + y - 1; return (x - y + 1) / y; } else { *piMod = x % y; return x / y; } } } int64_t i64FloorDivisionMod(int64_t x, int64_t y, int64_t *piMod) { if (y < 0) { if (x <= 0) { if (INT64_MIN == x && -1 == y) { *piMod = 0; } else { *piMod = x % y; } return x / y; } else { *piMod = ((x-1) % y) + y + 1; return (x - y - 1) / y; } } else { if (x < 0) { *piMod = ((x+1) % y) + y - 1; return (x - y + 1) / y; } else { *piMod = x % y; return x / y; } } } #else // LARGEST_INT_LTE_NEG_QUOTIENT // Provide LLEQ modulus on a LLEQ platform. // inline int iMod(int x, int y) { if (INT_MIN == x && -1 == y) { return 0; } return x % y; } // Provide a SGEQ modulus on a LLEQ platform. // int iRemainder(int x, int y) { if (y < 0) { if (x <= 0) { if (INT_MIN == x && -1 == y) { return 0; } return x % y; } else { return ((x+1) % y) - y - 1; } } else { if (x < 0) { return ((x-1) % y) - y + 1; } else { return x % y; } } } int64_t i64Remainder(int64_t x, int64_t y) { if (y < 0) { if (x <= 0) { if (INT64_MIN == x && -1 == y) { return 0; } return x % y; } else { return ((x+1) % y) - y - 1; } } else { if (x < 0) { return ((x-1) % y) - y + 1; } else { return x % y; } } } // Provide SGEQ division on a LLEQ platform. // int iDivision(int x, int y) { if (y < 0) { if (x <= 0) { return x / y; } else { return (x + y + 1) / y; } } else { if (x < 0) { return (x + y - 1) / y; } else { return x / y; } } } int64_t i64Division(int64_t x, int64_t y) { // INT64_MIN / -1 overflows and traps (#DE/SIGFPE) on x86 — guard it (#805). if (INT64_MIN == x && -1 == y) { return INT64_MIN; } if (y < 0) { if (x <= 0) { return x / y; } else { return (x + y + 1) / y; } } else { if (x < 0) { return (x + y - 1) / y; } else { return x / y; } } } // Provide a LLEQ division on a LLEQ platform. // inline int iFloorDivision(int x, int y) { return x / y; } inline int64_t i64FloorDivisionMod(int64_t x, int64_t y, int64_t*piMod) { if (INT64_MIN == x && -1 == y) { *piMod = 0; } else { *piMod = x % y; } return x / y; } #endif // LARGEST_INT_LTE_NEG_QUOTIENT bool ParseFractionalSecondsString(int64_t& i64, const UTF8* str) { bool bMinus = false; i64 = 0; bool bGotOne; const UTF8* cursor = str; // Leading spaces while (mux_isspace(*cursor)) { cursor++; } // Leading minus if (*cursor == '-') { bMinus = true; cursor++; // But not if just a minus if (!*cursor) { return false; } } // Need at least one digit bGotOne = false; const UTF8* pIntegerStart = cursor; if (mux_isdigit(*cursor)) { bGotOne = true; cursor++; } // The number (int) while (mux_isdigit(*cursor)) { cursor++; } const UTF8* pIntegerEnd = cursor; // Decimal point if (*cursor == '.') { cursor++; } // Need at least one digit const UTF8* pFractionalStart = cursor; if (mux_isdigit(*cursor)) { bGotOne = true; cursor++; } // The number (fract) while (mux_isdigit(*cursor)) { cursor++; } const UTF8* pFractionalEnd = cursor; // Trailing spaces while (mux_isspace(*cursor)) { cursor++; } if (*cursor || !bGotOne) { return false; } #define PFSS_PRECISION 7 UTF8 aBuffer[64]; size_t nBufferAvailable = sizeof(aBuffer) - PFSS_PRECISION - 1; UTF8* p = aBuffer; // Sign if (bMinus) { *p++ = '-'; nBufferAvailable--; } // Integer part bool bOverUnderflow = false; size_t n = pIntegerEnd - pIntegerStart; if (n > 0) { if (n > nBufferAvailable) { bOverUnderflow = true; n = nBufferAvailable; } memcpy(p, pIntegerStart, n); p += n; nBufferAvailable -= n; } // Fractional part n = pFractionalEnd - pFractionalStart; if (n > 0) { if (n > PFSS_PRECISION) { n = PFSS_PRECISION; } memcpy(p, pFractionalStart, n); p += n; nBufferAvailable -= n; } // Handle trailing zeroes n = PFSS_PRECISION - n; if (n > 0) { memset(p, '0', n); p += n; } *p++ = '\0'; if (bOverUnderflow) { if (bMinus) { i64 = INT64_MIN; } else { i64 = INT64_MAX; } } else { i64 = mux_atoi64(aBuffer); } return true; } void ConvertToSecondsString(UTF8 *buffer, int64_t n64, int nFracDigits) { int64_t Leftover; auto lt = i64FloorDivisionMod(n64, FACTOR_100NS_PER_SECOND, &Leftover); size_t n = mux_i64toa(lt, buffer); if (Leftover == 0) { return; } // Sanitize Precision Request. // const int maxFracDigits = 7; const int minFracDigits = 0; if (nFracDigits < minFracDigits) { nFracDigits = minFracDigits; } else if (maxFracDigits < nFracDigits) { nFracDigits = maxFracDigits; } if (0 < nFracDigits) { UTF8 *p = buffer + n; *p++ = '.'; UTF8 *q = p; UTF8 buf[maxFracDigits+1]; size_t m = mux_i64toa(Leftover, buf); memset(p, '0', maxFracDigits - m); p += maxFracDigits - m; memcpy(p, buf, m); p = q + nFracDigits - 1; while (*p == '0') { p--; } p++; *p = '\0'; } } bool isLeapYear(long iYear) { if (iMod(iYear, 4) != 0) { // Not a leap year. // return false; } unsigned long wMod = iMod(iYear, 400); if ((wMod == 100) || (wMod == 200) || (wMod == 300)) { // Not a leap year. // return false; } return true; } bool isValidDate(int iYear, int iMonth, int iDay) { if (iYear < -27256 || 30826 < iYear) { return false; } if (iMonth < 1 || 12 < iMonth) { return false; } if (iDay < 1 || daystab[iMonth-1] < iDay) { return false; } if (iMonth == 2 && iDay == 29 && !isLeapYear(iYear)) { return false; } return true; } static int FixedFromGregorian(int iYear, int iMonth, int iDay) { iYear = iYear - 1; int iFixedDay = 365 * iYear; iFixedDay += iFloorDivision(iYear, 4); iFixedDay -= iFloorDivision(iYear, 100); iFixedDay += iFloorDivision(iYear, 400); iFixedDay += iFloorDivision(367 * iMonth - 362, 12); iFixedDay += iDay; if (iMonth > 2) { if (isLeapYear(iYear+1)) { iFixedDay -= 1; } else { iFixedDay -= 2; } } // At this point, iFixedDay has an epoch of 1 R.D. // return iFixedDay; } static int FixedFromGregorian_Adjusted(int iYear, int iMonth, int iDay) { int iFixedDay = FixedFromGregorian(iYear, iMonth, iDay); // At this point, iFixedDay has an epoch of 1 R.D. // We need an Epoch of (00:00:00 UTC, January 1, 1601) // return iFixedDay - 584389; } struct GregorianDate { int iYear; int iMonth; int iDayOfYear; int iDayOfMonth; int iDayOfWeek; }; // Epoch of iFixedDay should be 1 R.D. // static GregorianDate GregorianFromFixed(int iFixedDay) { GregorianDate result; int d0 = iFixedDay - 1; int d1, n400 = iFloorDivisionMod(d0, 146097, &d1); int d2, n100 = iFloorDivisionMod(d1, 36524, &d2); int d3, n4 = iFloorDivisionMod(d2, 1461, &d3); int d4, n1 = iFloorDivisionMod(d3, 365, &d4); d4 = d4 + 1; result.iYear = 400*n400 + 100*n100 + 4*n4 + n1; if (n100 != 4 && n1 != 4) { result.iYear = result.iYear + 1; } static int cache_iYear = 99999; static int cache_iJan1st = 0; static int cache_iMar1st = 0; int iFixedDayOfJanuary1st; int iFixedDayOfMarch1st; if (result.iYear == cache_iYear) { iFixedDayOfJanuary1st = cache_iJan1st; iFixedDayOfMarch1st = cache_iMar1st; } else { cache_iYear = result.iYear; cache_iJan1st = iFixedDayOfJanuary1st = FixedFromGregorian(result.iYear, 1, 1); cache_iMar1st = iFixedDayOfMarch1st = FixedFromGregorian(result.iYear, 3, 1); } int iPriorDays = iFixedDay - iFixedDayOfJanuary1st; int iCorrection; if (iFixedDay < iFixedDayOfMarch1st) { iCorrection = 0; } else if (isLeapYear(result.iYear)) { iCorrection = 1; } else { iCorrection = 2; } result.iMonth = (12*(iPriorDays+iCorrection)+373)/367; result.iDayOfMonth = iFixedDay - FixedFromGregorian(result.iYear, result.iMonth, 1) + 1; result.iDayOfYear = iPriorDays + 1; // Calculate the Day of week using the linear progression of days. // result.iDayOfWeek = iMod(iFixedDay, 7); return result; } static GregorianDate GregorianFromFixed_Adjusted(int iFixedDay) { // We need to convert the Epoch to 1 R.D. from // (00:00:00 UTC, January 1, 1601) // return GregorianFromFixed(iFixedDay + 584389); } bool FieldedTimeToLinearTime(FIELDEDTIME *ft, int64_t *plt) { if (!isValidDate(ft->iYear, ft->iMonth, ft->iDayOfMonth)) { *plt = 0; return false; } int iFixedDay = FixedFromGregorian_Adjusted(ft->iYear, ft->iMonth, ft->iDayOfMonth); ft->iDayOfWeek = static_cast(iMod(iFixedDay+1, 7)); int64_t lt; lt = iFixedDay * FACTOR_100NS_PER_DAY; lt += ft->iHour * FACTOR_100NS_PER_HOUR; lt += ft->iMinute * FACTOR_100NS_PER_MINUTE; lt += ft->iSecond * FACTOR_100NS_PER_SECOND; lt += ft->iMicrosecond * FACTOR_100NS_PER_MICROSECOND; lt += ft->iMillisecond * FACTOR_100NS_PER_MILLISECOND; lt += ft->iNanosecond / FACTOR_NANOSECONDS_PER_100NS; *plt = lt; return true; } bool LinearTimeToFieldedTime(int64_t lt, FIELDEDTIME *ft) { int64_t ns100; memset(ft, 0, sizeof(FIELDEDTIME)); int d0 = static_cast(i64FloorDivisionMod(lt, FACTOR_100NS_PER_DAY, &ns100)); GregorianDate gd = GregorianFromFixed_Adjusted(d0); if (!isValidDate(gd.iYear, gd.iMonth, gd.iDayOfMonth)) { return false; } ft->iYear = static_cast(gd.iYear); ft->iMonth = static_cast(gd.iMonth); ft->iDayOfYear = static_cast(gd.iDayOfYear); ft->iDayOfMonth = static_cast(gd.iDayOfMonth); ft->iDayOfWeek = static_cast(gd.iDayOfWeek); ft->iHour = static_cast(ns100 / FACTOR_100NS_PER_HOUR); ns100 = ns100 % FACTOR_100NS_PER_HOUR; ft->iMinute = static_cast(ns100 / FACTOR_100NS_PER_MINUTE); ns100 = ns100 % FACTOR_100NS_PER_MINUTE; ft->iSecond = static_cast(ns100 / FACTOR_100NS_PER_SECOND); ns100 = ns100 % FACTOR_100NS_PER_SECOND; ft->iMillisecond = static_cast(ns100 / FACTOR_100NS_PER_MILLISECOND); ns100 = ns100 % FACTOR_100NS_PER_MILLISECOND; ft->iMicrosecond = static_cast(ns100 / FACTOR_100NS_PER_MICROSECOND); ns100 = ns100 % FACTOR_100NS_PER_MICROSECOND; ft->iNanosecond = static_cast(ns100 * FACTOR_NANOSECONDS_PER_100NS); return true; } // do_convtime() // // converts time string to time structure (fielded time). Returns 1 on // success, 0 on fail. Time string format is: // // [Ddd] Mmm DD HH:MM:SS YYYY // // The initial Day-of-week token is optional. // static int MonthTabHash[12] = { 0x004a414e, 0x00464542, 0x004d4152, 0x00415052, 0x004d4159, 0x004a554e, 0x004a554c, 0x00415547, 0x00534550, 0x004f4354, 0x004e4f56, 0x00444543 }; static bool ParseThreeLetters(const UTF8 **pp, int *piHash) { *piHash = 0; // Skip Initial spaces // const UTF8 *p = *pp; while (*p == ' ') { p++; } // Parse space-separate token. // const UTF8 *q = p; // Accumulate unsigned. A signed left shift into the sign bit is // undefined, and this loop runs over the whole token before the // three-letter check below -- so a longer alpha token overflowed an int // here (#1456). UBSan caught it on a four-letter one: // // left shift of 1095782985 by 8 places cannot be represented in 'int' // // No accepted input changes value: a three-letter token occupies 24 // bits, and anything longer is rejected before iHash is stored. // uint32_t iHash = 0; while (*q && *q != ' ') { if (!mux_isalpha(*q)) { return false; } // ASCII-only: month abbreviations are always ASCII. // iHash = (iHash << 8) | static_cast(mux_toupper_ascii(*q)); q++; } // Must be exactly 3 letters long. // if (q - p != 3) { return false; } p = q; // Skip final spaces // while (*p == ' ') { p++; } *pp = p; *piHash = static_cast(iHash); return true; } void ParseDecimalSeconds(size_t n, const UTF8 *p, unsigned short *iMilli, unsigned short *iMicro, unsigned short *iNano) { UTF8 aBuffer[10]; if (n > sizeof(aBuffer) - 1) { n = sizeof(aBuffer) - 1; } memcpy(aBuffer, p, n); memset(aBuffer + n, '0', sizeof(aBuffer) - n - 1); aBuffer[sizeof(aBuffer) - 1] = '\0'; // int64_t, not long: long is 32-bit on LLP64 (#1402). The digit run // is at most 9 chars here, so either type holds it; keep the wider one. // int64_t ns = mux_atoi64(aBuffer); *iNano = static_cast(ns % 1000); ns /= 1000; *iMicro = static_cast(ns % 1000); *iMilli = static_cast(ns / 1000); } bool do_convtime(const UTF8 *str, FIELDEDTIME *ft) { memset(ft, 0, sizeof(FIELDEDTIME)); if (!str || !ft) { return false; } // Day-of-week OR month. // const UTF8 *p = str; int i, iHash; if (!ParseThreeLetters(&p, &iHash)) { return false; } for (i = 0; (i < 12) && iHash != MonthTabHash[i]; i++) { ; // Nothing. } if (i == 12) { // The above three letters were probably the Day-Of-Week, the // next three letters are required to be the month name. // if (!ParseThreeLetters(&p, &iHash)) { return false; } for (i = 0; (i < 12) && iHash != MonthTabHash[i]; i++) { ; // Nothing. } if (i == 12) { return false; } } // January = 1, February = 2, etc. // ft->iMonth = static_cast(i + 1); // Day of month. // ft->iDayOfMonth = static_cast(mux_atoi64(p)); if (ft->iDayOfMonth < 1 || daystab[i] < ft->iDayOfMonth) { return false; } while (*p && *p != ' ') p++; while (*p == ' ') p++; // Hours // ft->iHour = static_cast(mux_atoi64(p)); if (ft->iHour > 23 || (ft->iHour == 0 && *p != '0')) { return false; } while (*p && *p != ':') p++; if (*p == ':') p++; while (*p == ' ') p++; // Minutes // ft->iMinute = static_cast(mux_atoi64(p)); if (ft->iMinute > 59 || (ft->iMinute == 0 && *p != '0')) { return false; } while (*p && *p != ':') p++; if (*p == ':') p++; while (*p == ' ') p++; // Seconds // ft->iSecond = static_cast(mux_atoi64(p)); if (ft->iSecond > 59 || (ft->iSecond == 0 && *p != '0')) { return false; } while (mux_isdigit(*p)) { p++; } // Milliseconds, Microseconds, and Nanoseconds // if (*p == '.') { p++; size_t n; const UTF8 *q = reinterpret_cast(strchr(reinterpret_cast(p), ' ')); if (q) { n = q - p; } else { n = strlen(reinterpret_cast(p)); } ParseDecimalSeconds(n, p, &ft->iMillisecond, &ft->iMicrosecond, &ft->iNanosecond); } while (*p && *p != ' ') p++; while (*p == ' ') p++; // Year // // Reject years outside the range timeutil accepts (see isValidDate) // *before* narrowing to short. Otherwise an out-of-range year wraps // silently into a plausible one -- e.g. 9999999999 -> -7169 -- and is // accepted as the wrong date. This mirrors the same guard in // ParseDate() (#708); do_convtime() is the conversion branch it missed. // // Cap the digit count before parsing: mux_atoi64() accumulates without // overflow detection, so a long-enough year string can wrap and land // back inside the valid range. Valid years have at most 5 digits. { size_t nYearDigits = 0; while (mux_isdigit(p[nYearDigits])) { nYearDigits++; } if (5 < nYearDigits) { return false; } } // int64_t, not long: long is 32-bit on LLP64 (#1402). // int64_t iYearLong = mux_atoi64(p); if (iYearLong < -27256 || 30826 < iYearLong) { return false; } ft->iYear = static_cast(iYearLong); while (mux_isdigit(*p)) { p++; } while (*p == ' ') p++; if (*p != '\0') { return false; } // DayOfYear and DayOfWeek // ft->iDayOfYear = 0; ft->iDayOfWeek = 0; return isValidDate(ft->iYear, ft->iMonth, ft->iDayOfMonth); } // OS Dependent Routines: // #if defined(WINDOWS_TIME) void GetUTCLinearTime(int64_t *plt) { GetSystemTimeAsFileTime(reinterpret_cast(plt)); } #elif defined(UNIX_TIME) void GetUTCLinearTime(int64_t *plt) { #ifdef HAVE_GETTIMEOFDAY struct timeval tv; struct timezone tz; tz.tz_minuteswest = 0; tz.tz_dsttime = 0; gettimeofday(&tv, &tz); *plt = ((static_cast(tv.tv_sec)) * FACTOR_100NS_PER_SECOND) + (tv.tv_usec * FACTOR_100NS_PER_MICROSECOND) + EPOCH_OFFSET; #else time_t t; time(&t); *plt = (static_cast(t) * FACTOR_100NS_PER_SECOND) + EPOCH_OFFSET; #endif } #endif // UNIX_TIME