mirror of
https://github.com/brazilofmux/tinymux
synced 2026-08-13 00:23:11 -04:00
Both found by UBSan during a sanitizer smoke run (#1440). Neither produces a wrong answer today; both are the kind of UB that stays invisible until a compiler version or optimisation level changes. timeutil.cpp:884 -- left shift into the sign bit left shift of 1095782985 by 8 places cannot be represented in type 'int' ParseThreeLetters accumulates a token a byte at a time into an int, and the loop runs over the WHOLE token before the three-letter check below it. A four-letter alpha token therefore overflows: 1095782985 is four ASCII bytes. Accumulate in uint32_t instead. No accepted input changes value. A three-letter token occupies 24 bits, and anything longer is rejected by `q - p != 3` before iHash is ever stored -- the overflowed value could never reach the MonthTabHash comparison. dbt_emit_a64.h:156 -- shift by the full width shift exponent 64 is too large for 64-bit type 'uint64_t' The logical-immediate encoder rotates an element with ((elem >> rot) | (elem << (size - rot))) & mask and size reaches 64, so rot == 0 is elem << 64. AArch64 masks the shift count to six bits, so it silently becomes elem << 0 and the correct answer falls out by accident -- on the very architecture this emits code for. rot == 0 is the identity rotation, so spelling that case out removes the UB. For size < 64 the shift was already defined and the mask discarded it, so the special case is the same value at every size. Verified the encoder is unchanged rather than assuming it: enumerated the (size, elem, rot) space and compared the old form -- evaluated the way AArch64 actually behaves, with the shift count masked to six bits -- against the new one. checked=34867400 differences=0 make test-dbt all green, including 960 hand-assembled and both ELF legs smoke 1497 passed, 0 failed, 315/315 UBSan timeutil and dbt_emit_a64 reports gone With #1459 and #1462 this takes the tree to zero standing UBSan reports, which is what makes a future run signal rather than noise. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1135 lines
24 KiB
C++
1135 lines
24 KiB
C++
/*! \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<unsigned short>(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<int>(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<short>(gd.iYear);
|
|
ft->iMonth = static_cast<unsigned short>(gd.iMonth);
|
|
ft->iDayOfYear = static_cast<unsigned short>(gd.iDayOfYear);
|
|
ft->iDayOfMonth = static_cast<unsigned short>(gd.iDayOfMonth);
|
|
ft->iDayOfWeek = static_cast<unsigned short>(gd.iDayOfWeek);
|
|
|
|
ft->iHour = static_cast<unsigned short>(ns100 / FACTOR_100NS_PER_HOUR);
|
|
ns100 = ns100 % FACTOR_100NS_PER_HOUR;
|
|
ft->iMinute = static_cast<unsigned short>(ns100 / FACTOR_100NS_PER_MINUTE);
|
|
ns100 = ns100 % FACTOR_100NS_PER_MINUTE;
|
|
ft->iSecond = static_cast<unsigned short>(ns100 / FACTOR_100NS_PER_SECOND);
|
|
ns100 = ns100 % FACTOR_100NS_PER_SECOND;
|
|
|
|
ft->iMillisecond = static_cast<unsigned short>(ns100 / FACTOR_100NS_PER_MILLISECOND);
|
|
ns100 = ns100 % FACTOR_100NS_PER_MILLISECOND;
|
|
ft->iMicrosecond = static_cast<unsigned short>(ns100 / FACTOR_100NS_PER_MICROSECOND);
|
|
ns100 = ns100 % FACTOR_100NS_PER_MICROSECOND;
|
|
ft->iNanosecond = static_cast<unsigned short>(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<uint32_t>(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<int>(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<unsigned short>(ns % 1000);
|
|
ns /= 1000;
|
|
*iMicro = static_cast<unsigned short>(ns % 1000);
|
|
*iMilli = static_cast<unsigned short>(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<unsigned short>(i + 1);
|
|
|
|
// Day of month.
|
|
//
|
|
ft->iDayOfMonth = static_cast<unsigned short>(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<unsigned short>(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<unsigned short>(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<unsigned short>(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<const UTF8 *>(strchr(reinterpret_cast<const char *>(p), ' '));
|
|
if (q)
|
|
{
|
|
n = q - p;
|
|
}
|
|
else
|
|
{
|
|
n = strlen(reinterpret_cast<const char *>(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<short>(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<struct _FILETIME *>(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<int64_t>(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<int64_t>(t) * FACTOR_100NS_PER_SECOND)
|
|
+ EPOCH_OFFSET;
|
|
#endif
|
|
}
|
|
|
|
#endif // UNIX_TIME
|