// timeutil.cpp -- CLinearTimeAbsolute and CLinearTimeDelta modules.
//
// $Id: timeutil.cpp,v 1.47 2005/05/26 06:02:59 sdennis Exp $
//
// MUX 2.4
// Copyright (C) 1998 through 2004 Solid Vertical Domains, Ltd. All
// rights not explicitly given are reserved.
//
// Date/Time code based on algorithms presented in "Calendrical Calculations",
// Cambridge Press, 1998.
//
#include "copyright.h"
#include "autoconf.h"
#include "config.h"
#include "externs.h"
// for tzset() and localtime()
//
#include <time.h>
#include "timeutil.h"
#include "stringutil.h"
CMuxAlarm MuxAlarm;
#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)
{
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 i64Mod(INT64 x, INT64 y)
{
if (y < 0)
{
if (x <= 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.
//
DCL_INLINE int iRemainder(int x, int y)
{
return x % y;
}
// Provide SGEQ division on a SGEQ platform.
//
DCL_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 i64FloorDivision(INT64 x, INT64 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;
}
}
}
int iFloorDivisionMod(int x, int y, int *piMod)
{
if (y < 0)
{
if (x <= 0)
{
*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 i64FloorDivisionMod(INT64 x, INT64 y, INT64 *piMod)
{
if (y < 0)
{
if (x <= 0)
{
*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;
}
}
}
#if 0
int iCeilingDivision(int x, int y)
{
if (x < 0)
{
return x / y;
}
else
{
return (x + y - 1) / y;
}
}
INT64 i64CeilingDivision(INT64 x, INT64 y)
{
if (x < 0)
{
return x / y;
}
else
{
return (x + y - 1) / y;
}
}
#endif // 0
#else // LARGEST_INT_LTE_NEG_QUOTIENT
// Provide LLEQ modulus on a LLEQ platform.
//
int DCL_INLINE iMod(int x, int y)
{
return x % y;
}
// Provide a SGEQ modulus on a LLEQ platform.
//
int iRemainder(int x, int y)
{
if (y < 0)
{
if (x <= 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 i64Remainder(INT64 x, INT64 y)
{
if (y < 0)
{
if (x <= 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 i64Division(INT64 x, INT64 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;
}
}
}
// Provide a LLEQ division on a LLEQ platform.
//
int DCL_INLINE iFloorDivision(int x, int y)
{
return x / y;
}
INT64 DCL_INLINE i64FloorDivisionMod(INT64 x, INT64 y, INT64 *piMod)
{
*piMod = x % y;
return x / y;
}
#endif // LARGEST_INT_LTE_NEG_QUOTIENT
int iModAdjusted(int x, int y)
{
return iMod(x - 1, y) + 1;
}
#if 0
INT64 i64ModAdjusted(INT64 x, INT64 y)
{
return i64Mod(x - 1, y) + 1;
}
#endif // 0
#ifdef WIN32
const INT64 FACTOR_100NS_PER_SECOND = 10000000i64;
#else // WIN32
const INT64 FACTOR_100NS_PER_SECOND = 10000000ull;
#endif // WIN32
const INT64 FACTOR_100NS_PER_MINUTE = FACTOR_100NS_PER_SECOND*60;
const INT64 FACTOR_100NS_PER_HOUR = FACTOR_100NS_PER_MINUTE*60;
const INT64 FACTOR_100NS_PER_DAY = FACTOR_100NS_PER_HOUR*24;
const INT64 FACTOR_100NS_PER_WEEK = FACTOR_100NS_PER_DAY*7;
int CLinearTimeAbsolute::m_nCount = 0;
char CLinearTimeAbsolute::m_Buffer[204];
char CLinearTimeDelta::m_Buffer[204];
void GetUTCLinearTime(INT64 *plt);
void GetLocalFieldedTime(FIELDEDTIME *ft);
bool operator<(const CLinearTimeAbsolute& lta, const CLinearTimeAbsolute& ltb)
{
return lta.m_tAbsolute < ltb.m_tAbsolute;
}
bool operator>(const CLinearTimeAbsolute& lta, const CLinearTimeAbsolute& ltb)
{
return lta.m_tAbsolute > ltb.m_tAbsolute;
}
bool operator==(const CLinearTimeAbsolute& lta, const CLinearTimeAbsolute& ltb)
{
return lta.m_tAbsolute == ltb.m_tAbsolute;
}
bool operator==(const CLinearTimeDelta& lta, const CLinearTimeDelta& ltb)
{
return lta.m_tDelta == ltb.m_tDelta;
}
bool operator!=(const CLinearTimeDelta& lta, const CLinearTimeDelta& ltb)
{
return lta.m_tDelta != ltb.m_tDelta;
}
bool operator<=(const CLinearTimeDelta& lta, const CLinearTimeDelta& ltb)
{
return lta.m_tDelta <= ltb.m_tDelta;
}
bool operator<=(const CLinearTimeAbsolute& lta, const CLinearTimeAbsolute& ltb)
{
return lta.m_tAbsolute <= ltb.m_tAbsolute;
}
CLinearTimeAbsolute operator-(const CLinearTimeAbsolute& lta, const CLinearTimeDelta& ltd)
{
CLinearTimeAbsolute t;
t.m_tAbsolute = lta.m_tAbsolute - ltd.m_tDelta;
return t;
}
CLinearTimeAbsolute::CLinearTimeAbsolute(const CLinearTimeAbsolute& ltaOrigin, const CLinearTimeDelta& ltdOffset)
{
m_tAbsolute = ltaOrigin.m_tAbsolute + ltdOffset.m_tDelta;
}
bool ParseFractionalSecondsString(INT64 &i64, char *str)
{
bool bMinus = false;
i64 = 0;
bool bGotOne;
// Leading spaces.
//
while (mux_isspace(*str))
{
str++;
}
// Leading minus
//
if (*str == '-')
{
bMinus = true;
str++;
// But not if just a minus
//
if (!*str)
{
return false;
}
}
// Need at least one digit.
//
bGotOne = false;
char *pIntegerStart = str;
if (mux_isdigit(*str))
{
bGotOne = true;
str++;
}
// The number (int)
//
while (mux_isdigit(*str))
{
str++;
}
char *pIntegerEnd = str;
// Decimal point.
//
if (*str == '.')
{
str++;
}
// Need at least one digit
//
char *pFractionalStart = str;
if (mux_isdigit(*str))
{
bGotOne = true;
str++;
}
// The number (fract)
//
while (mux_isdigit(*str))
{
str++;
}
char *pFractionalEnd = str;
// Trailing spaces.
//
while (mux_isspace(*str))
{
str++;
}
if (*str || !bGotOne)
{
return false;
}
#define PFSS_PRECISION 7
char aBuffer[64];
size_t nBufferAvailable = sizeof(aBuffer) - PFSS_PRECISION - 1;
char *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_VALUE;
}
else
{
i64 = INT64_MAX_VALUE;
}
}
else
{
i64 = mux_atoi64(aBuffer);
}
return true;
}
void ConvertToSecondsString(char *buffer, INT64 n64, int nFracDigits)
{
INT64 Leftover;
INT64 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)
{
char *p = buffer + n;
*p++ = '.';
char *q = p;
char 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';
}
}
char *CLinearTimeDelta::ReturnSecondsString(int nFracDigits)
{
ConvertToSecondsString(m_Buffer, m_tDelta, nFracDigits);
return m_Buffer;
}
char *CLinearTimeAbsolute::ReturnSecondsString(int nFracDigits)
{
ConvertToSecondsString(m_Buffer, m_tAbsolute - EPOCH_OFFSET, nFracDigits);
return m_Buffer;
}
CLinearTimeAbsolute::CLinearTimeAbsolute(const CLinearTimeAbsolute <a)
{
m_tAbsolute = lta.m_tAbsolute;
}
CLinearTimeAbsolute::CLinearTimeAbsolute(void)
{
m_tAbsolute = 0;
}
CLinearTimeDelta::CLinearTimeDelta(void)
{
m_tDelta = 0;
}
CLinearTimeDelta::CLinearTimeDelta(INT64 arg_t100ns)
{
m_tDelta = arg_t100ns;
}
void CLinearTimeDelta::ReturnTimeValueStruct(struct timeval *tv)
{
INT64 Leftover;
tv->tv_sec = (long)i64FloorDivisionMod(m_tDelta, FACTOR_100NS_PER_SECOND, &Leftover);
tv->tv_usec = (long)i64FloorDivision(Leftover, FACTOR_100NS_PER_MICROSECOND);
}
#ifdef HAVE_NANOSLEEP
void CLinearTimeDelta::ReturnTimeSpecStruct(struct timespec *ts)
{
INT64 Leftover;
ts->tv_sec = (long)i64FloorDivisionMod(m_tDelta, FACTOR_100NS_PER_SECOND, &Leftover);
ts->tv_nsec = (long)Leftover*FACTOR_NANOSECONDS_PER_100NS;
}
#endif // HAVE_NANOSLEEP
void CLinearTimeDelta::SetTimeValueStruct(struct timeval *tv)
{
m_tDelta = FACTOR_100NS_PER_SECOND * tv->tv_sec
+ FACTOR_100NS_PER_MICROSECOND * tv->tv_usec;
}
// Time string format is usually 24 characters long, in format
// Ddd Mmm DD HH:MM:SS YYYY
//
// However, the year may be larger than 4 characters.
//
char *DayOfWeekString[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
const char daystab[] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
const char *monthtab[] =
{
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
void CLinearTimeDelta::SetMilliseconds(unsigned long arg_dwMilliseconds)
{
m_tDelta = arg_dwMilliseconds * FACTOR_100NS_PER_MILLISECOND;
}
long CLinearTimeDelta::ReturnMilliseconds(void)
{
return (long)(m_tDelta/FACTOR_100NS_PER_MILLISECOND);
}
INT64 CLinearTimeDelta::ReturnMicroseconds(void)
{
return m_tDelta/FACTOR_100NS_PER_MICROSECOND;
}
void CLinearTimeDelta::SetSecondsString(char *arg_szSeconds)
{
ParseFractionalSecondsString(m_tDelta, arg_szSeconds);
}
void CLinearTimeDelta::SetSeconds(INT64 arg_tSeconds)
{
m_tDelta = arg_tSeconds * FACTOR_100NS_PER_SECOND;
}
void CLinearTimeDelta::Set100ns(INT64 arg_t100ns)
{
m_tDelta = arg_t100ns;
}
INT64 CLinearTimeDelta::Return100ns(void)
{
return m_tDelta;
}
void CLinearTimeAbsolute::Set100ns(INT64 arg_t100ns)
{
m_tAbsolute = arg_t100ns;
}
INT64 CLinearTimeAbsolute::Return100ns(void)
{
return m_tAbsolute;
}
void CLinearTimeAbsolute::SetSeconds(INT64 arg_tSeconds)
{
m_tAbsolute = arg_tSeconds;
m_tAbsolute *= FACTOR_100NS_PER_SECOND;
// Epoch difference between (00:00:00 UTC, January 1, 1970) and
// (00:00:00 UTC, January 1, 1601).
//
m_tAbsolute += EPOCH_OFFSET;
}
INT64 CLinearTimeAbsolute::ReturnSeconds(void)
{
// INT64 is in hundreds of nanoseconds.
// And the Epoch is 0:00 1/1/1601 instead of 0:00 1/1/1970
//
return i64FloorDivision(m_tAbsolute - EPOCH_OFFSET, FACTOR_100NS_PER_SECOND);
}
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;
}
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;
}
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;
}
// Epoch of iFixedDay should be 1 R.D.
//
void GregorianFromFixed(int iFixedDay, int &iYear, int &iMonth, int &iDayOfYear, int &iDayOfMonth, int &iDayOfWeek)
{
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;
iYear = 400*n400 + 100*n100 + 4*n4 + n1;
if (n100 != 4 && n1 != 4)
{
iYear = iYear + 1;
}
static int cache_iYear = 99999;
static int cache_iJan1st = 0;
static int cache_iMar1st = 0;
int iFixedDayOfJanuary1st;
int iFixedDayOfMarch1st;
if (iYear == cache_iYear)
{
iFixedDayOfJanuary1st = cache_iJan1st;
iFixedDayOfMarch1st = cache_iMar1st;
}
else
{
cache_iYear = iYear;
cache_iJan1st = iFixedDayOfJanuary1st = FixedFromGregorian(iYear, 1, 1);
cache_iMar1st = iFixedDayOfMarch1st = FixedFromGregorian(iYear, 3, 1);
}
int iPriorDays = iFixedDay - iFixedDayOfJanuary1st;
int iCorrection;
if (iFixedDay < iFixedDayOfMarch1st)
{
iCorrection = 0;
}
else if (isLeapYear(iYear))
{
iCorrection = 1;
}
else
{
iCorrection = 2;
}
iMonth = (12*(iPriorDays+iCorrection)+373)/367;
iDayOfMonth = iFixedDay - FixedFromGregorian(iYear, iMonth, 1) + 1;
iDayOfYear = iPriorDays + 1;
// Calculate the Day of week using the linear progression of days.
//
iDayOfWeek = iMod(iFixedDay, 7);
}
void GregorianFromFixed_Adjusted(int iFixedDay, int &iYear, int &iMonth, int &iDayOfYear, int &iDayOfMonth, int &iDayOfWeek)
{
// We need to convert the Epoch to 1 R.D. from
// (00:00:00 UTC, January 1, 1601)
//
GregorianFromFixed(iFixedDay + 584389, iYear, iMonth, iDayOfYear, iDayOfMonth, iDayOfWeek);
}
// 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.
//
int MonthTabHash[12] =
{
0x004a414e, 0x00464542, 0x004d4152, 0x00415052,
0x004d4159, 0x004a554e, 0x004a554c, 0x00415547,
0x00534550, 0x004f4354, 0x004e4f56, 0x00444543
};
bool ParseThreeLetters(const char **pp, int *piHash)
{
*piHash = 0;
// Skip Initial spaces
//
const char *p = *pp;
while (*p == ' ')
{
p++;
}
// Parse space-separate token.
//
const char *q = p;
int iHash = 0;
while (*q && *q != ' ')
{
if (!mux_isalpha(*q))
{
return false;
}
iHash = (iHash << 8) | mux_toupper(*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 = iHash;
return true;
}
void ParseDecimalSeconds(size_t n, const char *p, unsigned short *iMilli,
unsigned short *iMicro, unsigned short *iNano)
{
char 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';
int ns = mux_atol(aBuffer);
*iNano = ns % 1000;
ns /= 1000;
*iMicro = ns % 1000;
*iMilli = ns / 1000;
}
bool do_convtime(const char *str, FIELDEDTIME *ft)
{
memset(ft, 0, sizeof(FIELDEDTIME));
if (!str || !ft)
{
return false;
}
// Day-of-week OR month.
//
const char *p = str;
int i, iHash;
if (!ParseThreeLetters(&p, &iHash))
{
return false;
}
for (i = 0; (i < 12) && iHash != MonthTabHash[i]; i++) ;
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++) ;
if (i == 12)
{
return false;
}
}
ft->iMonth = i + 1; // January = 1, February = 2, etc.
// Day of month.
//
ft->iDayOfMonth = (unsigned short)mux_atol(p);
if (ft->iDayOfMonth < 1 || daystab[i] < ft->iDayOfMonth)
{
return false;
}
while (*p && *p != ' ') p++;
while (*p == ' ') p++;
// Hours
//
ft->iHour = (unsigned short)mux_atol(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 = (unsigned short)mux_atol(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 = (unsigned short)mux_atol(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 char *q = strchr(p, ' ');
if (q)
{
n = q - p;
}
else
{
n = strlen(p);
}
ParseDecimalSeconds(n, p, &ft->iMillisecond, &ft->iMicrosecond,
&ft->iNanosecond);
}
while (*p && *p != ' ') p++;
while (*p == ' ') p++;
// Year
//
ft->iYear = (short)mux_atol(p);
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);
}
CLinearTimeDelta::CLinearTimeDelta(CLinearTimeAbsolute t0, CLinearTimeAbsolute t1)
{
m_tDelta = t1.m_tAbsolute - t0.m_tAbsolute;
}
long CLinearTimeDelta::ReturnDays(void)
{
return (long)(m_tDelta/FACTOR_100NS_PER_DAY);
}
long CLinearTimeDelta::ReturnSeconds(void)
{
return (long)(m_tDelta/FACTOR_100NS_PER_SECOND);
}
bool CLinearTimeAbsolute::ReturnFields(FIELDEDTIME *arg_tStruct)
{
return LinearTimeToFieldedTime(m_tAbsolute, arg_tStruct);
}
bool CLinearTimeAbsolute::SetString(const char *arg_tBuffer)
{
FIELDEDTIME ft;
if (do_convtime(arg_tBuffer, &ft))
{
if (FieldedTimeToLinearTime(&ft, &m_tAbsolute))
{
return true;
}
}
m_tAbsolute = 0;
return false;
}
void CLinearTimeDelta::operator+=(const CLinearTimeDelta& ltd)
{
m_tDelta += ltd.m_tDelta;
}
CLinearTimeDelta operator-(const CLinearTimeAbsolute& ltaA, const CLinearTimeAbsolute& ltaB)
{
CLinearTimeDelta ltd;
ltd.m_tDelta = ltaA.m_tAbsolute - ltaB.m_tAbsolute;
return ltd;
}
CLinearTimeDelta operator-(const CLinearTimeDelta& lta, const CLinearTimeDelta& ltb)
{
CLinearTimeDelta ltd;
ltd.m_tDelta = lta.m_tDelta - ltb.m_tDelta;
return ltd;
}
CLinearTimeAbsolute operator+(const CLinearTimeAbsolute& ltaA, const CLinearTimeDelta& ltdB)
{
CLinearTimeAbsolute lta;
lta.m_tAbsolute = ltaA.m_tAbsolute + ltdB.m_tDelta;
return lta;
}
void CLinearTimeAbsolute::operator=(const CLinearTimeAbsolute& lta)
{
m_tAbsolute = lta.m_tAbsolute;
}
CLinearTimeDelta operator*(const CLinearTimeDelta& ltd, int Scale)
{
CLinearTimeDelta ltdResult;
ltdResult.m_tDelta = ltd.m_tDelta * Scale;
return ltdResult;
}
int operator/(const CLinearTimeDelta& ltdA, const CLinearTimeDelta& ltdB)
{
int iResult = (int)(ltdA.m_tDelta / ltdB.m_tDelta);
return iResult;
}
bool operator<(const CLinearTimeDelta& ltdA, const CLinearTimeDelta& ltdB)
{
return ltdA.m_tDelta < ltdB.m_tDelta;
}
bool operator>(const CLinearTimeDelta& ltdA, const CLinearTimeDelta& ltdB)
{
return ltdA.m_tDelta > ltdB.m_tDelta;
}
void CLinearTimeAbsolute::operator-=(const CLinearTimeDelta& ltd)
{
m_tAbsolute -= ltd.m_tDelta;
}
void CLinearTimeAbsolute::operator+=(const CLinearTimeDelta& ltd)
{
m_tAbsolute += ltd.m_tDelta;
}
bool CLinearTimeAbsolute::SetFields(FIELDEDTIME *arg_tStruct)
{
m_tAbsolute = 0;
return FieldedTimeToLinearTime(arg_tStruct, &m_tAbsolute);
}
void SetStructTm(FIELDEDTIME *ft, struct tm *ptm)
{
ft->iYear = ptm->tm_year + 1900;
ft->iMonth = ptm->tm_mon + 1;
ft->iDayOfMonth = ptm->tm_mday;
ft->iDayOfWeek = ptm->tm_wday;
ft->iDayOfYear = 0;
ft->iHour = ptm->tm_hour;
ft->iMinute = ptm->tm_min;
ft->iSecond = ptm->tm_sec;
}
void CLinearTimeAbsolute::ReturnUniqueString(char *buffer)
{
FIELDEDTIME ft;
if (LinearTimeToFieldedTime(m_tAbsolute, &ft))
{
sprintf(buffer, "%04d%02d%02d-%02d%02d%02d", ft.iYear, ft.iMonth,
ft.iDayOfMonth, ft.iHour, ft.iMinute, ft.iSecond);
}
else
{
sprintf(buffer, "%03d", m_nCount++);
}
}
char *CLinearTimeAbsolute::ReturnDateString(int nFracDigits)
{
FIELDEDTIME ft;
if (LinearTimeToFieldedTime(m_tAbsolute, &ft))
{
// Sanitize Precision Request.
//
const int maxFracDigits = 7;
const int minFracDigits = 0;
if (nFracDigits < minFracDigits)
{
nFracDigits = minFracDigits;
}
else if (maxFracDigits < nFracDigits)
{
nFracDigits = maxFracDigits;
}
char buffer[11];
buffer[0] = '\0';
if ( 0 < nFracDigits
&& ( ft.iMillisecond != 0
|| ft.iMicrosecond != 0
|| ft.iNanosecond != 0))
{
sprintf(buffer, ".%03d%03d%03d", ft.iMillisecond, ft.iMicrosecond,
ft.iNanosecond);
// Remove trailing zeros.
//
char *p = (buffer + 1) + (nFracDigits - 1);
while (*p == '0')
{
p--;
}
p++;
*p = '\0';
}
sprintf(m_Buffer, "%s %s %02d %02d:%02d:%02d%s %04d",
DayOfWeekString[ft.iDayOfWeek], monthtab[ft.iMonth-1],
ft.iDayOfMonth, ft.iHour, ft.iMinute, ft.iSecond, buffer,
ft.iYear);
}
else
{
m_Buffer[0] = '\0';
}
return m_Buffer;
}
void CLinearTimeAbsolute::GetUTC(void)
{
GetUTCLinearTime(&m_tAbsolute);
}
void CLinearTimeAbsolute::GetLocal(void)
{
GetUTCLinearTime(&m_tAbsolute);
UTC2Local();
}
bool FieldedTimeToLinearTime(FIELDEDTIME *ft, INT64 *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 = iMod(iFixedDay+1, 7);
INT64 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 lt, FIELDEDTIME *ft)
{
INT64 ns100;
int iYear, iMonth, iDayOfYear, iDayOfMonth, iDayOfWeek;
memset(ft, 0, sizeof(FIELDEDTIME));
int d0 = (int)i64FloorDivisionMod(lt, FACTOR_100NS_PER_DAY, &ns100);
GregorianFromFixed_Adjusted(d0, iYear, iMonth, iDayOfYear, iDayOfMonth, iDayOfWeek);
if (!isValidDate(iYear, iMonth, iDayOfMonth))
{
return false;
}
ft->iYear = iYear;
ft->iMonth = iMonth;
ft->iDayOfYear = iDayOfYear;
ft->iDayOfMonth = iDayOfMonth;
ft->iDayOfWeek = iDayOfWeek;
ft->iHour = (int)(ns100 / FACTOR_100NS_PER_HOUR);
ns100 = ns100 % FACTOR_100NS_PER_HOUR;
ft->iMinute = (int)(ns100 / FACTOR_100NS_PER_MINUTE);
ns100 = ns100 % FACTOR_100NS_PER_MINUTE;
ft->iSecond = (int)(ns100 / FACTOR_100NS_PER_SECOND);
ns100 = ns100 % FACTOR_100NS_PER_SECOND;
ft->iMillisecond = (int)(ns100 / FACTOR_100NS_PER_MILLISECOND);
ns100 = ns100 % FACTOR_100NS_PER_MILLISECOND;
ft->iMicrosecond = (int)(ns100 / FACTOR_100NS_PER_MICROSECOND);
ns100 = ns100 % FACTOR_100NS_PER_MICROSECOND;
ft->iNanosecond = (int)(ns100 * FACTOR_NANOSECONDS_PER_100NS);
return true;
}
void CLinearTimeAbsolute::SetSecondsString(char *arg_szSeconds)
{
ParseFractionalSecondsString(m_tAbsolute, arg_szSeconds);
m_tAbsolute += EPOCH_OFFSET;
}
// OS Dependent Routines:
//
#ifdef WIN32
// This calculates (FACTOR_100NS_PER_SECOND*x)/y accurately without
// overflow.
//
class CxyDiv
{
public:
void SetDenominator(const INT64 y_arg);
INT64 Convert(const INT64 x_arg);
private:
INT64 A, B, C, D;
};
void CxyDiv::SetDenominator(const INT64 y_arg)
{
A = FACTOR_100NS_PER_SECOND / y_arg;
B = FACTOR_100NS_PER_SECOND % y_arg;
C = y_arg/2;
D = y_arg;
}
INT64 CxyDiv::Convert(const INT64 x_arg)
{
return A*x_arg + (B*x_arg + C)/D;
}
CxyDiv Ticks2Seconds;
INT64 xIntercept = 0;
INT64 liInit;
INT64 tInit;
INT64 tError;
bool bQueryPerformanceAvailable = false;
bool bUseQueryPerformance = false;
const INT64 TargetError = 5*FACTOR_100NS_PER_MILLISECOND;
BOOL CalibrateQueryPerformance(void)
{
if (!bQueryPerformanceAvailable)
{
return false;
}
INT64 li;
INT64 t;
MuxAlarm.SurrenderSlice();
if (QueryPerformanceCounter((LARGE_INTEGER *)&li))
{
GetSystemTimeAsFileTime((struct _FILETIME *)&t);
// Estimate Error.
//
// x = y/m + b;
//
tError = Ticks2Seconds.Convert(li) + xIntercept - t;
if ( -TargetError < tError
&& tError < TargetError)
{
bUseQueryPerformance = true;
}
// x = y/m + b
// m = dy/dx = (y1 - y0)/(x1 - x0)
//
// y is ticks and x is seconds.
//
INT64 dli = li - liInit;
INT64 dt = t - tInit;
CxyDiv Ticks2Freq;
Ticks2Freq.SetDenominator(dt);
INT64 liFreq = Ticks2Freq.Convert(dli);
Ticks2Seconds.SetDenominator(liFreq);
// Therefore, b = x - y/m
//
xIntercept = t - Ticks2Seconds.Convert(li);
return true;
}
else
{
bQueryPerformanceAvailable = false;
bUseQueryPerformance = false;
return false;
}
}
void InitializeQueryPerformance(void)
{
// The frequency returned is the number of ticks per second.
//
INT64 liFreq;
if (QueryPerformanceFrequency((LARGE_INTEGER *)&liFreq))
{
Ticks2Seconds.SetDenominator(liFreq);
MuxAlarm.SurrenderSlice();
if (QueryPerformanceCounter((LARGE_INTEGER *)&liInit))
{
GetSystemTimeAsFileTime((struct _FILETIME *)&tInit);
xIntercept = tInit - Ticks2Seconds.Convert(liInit);
bQueryPerformanceAvailable = true;
}
}
}
void GetUTCLinearTime(INT64 *plt)
{
if (bUseQueryPerformance)
{
INT64 li;
if (QueryPerformanceCounter((LARGE_INTEGER *)&li))
{
*plt = Ticks2Seconds.Convert(li) + xIntercept;
return;
}
bQueryPerformanceAvailable = false;
bUseQueryPerformance = false;
}
GetSystemTimeAsFileTime((struct _FILETIME *)plt);
}
DWORD WINAPI AlarmProc(LPVOID lpParameter)
{
CMuxAlarm *pthis = (CMuxAlarm *)lpParameter;
DWORD dwWait = pthis->dwWait;
for (;;)
{
HANDLE hSemAlarm = pthis->hSemAlarm;
if (hSemAlarm == INVALID_HANDLE_VALUE)
{
break;
}
DWORD dwReason = WaitForSingleObject(hSemAlarm, dwWait);
if (dwReason == WAIT_TIMEOUT)
{
pthis->bAlarmed = true;
dwWait = INFINITE;
}
else
{
dwWait = pthis->dwWait;
}
}
return 1;
}
CMuxAlarm::CMuxAlarm(void)
{
hSemAlarm = CreateSemaphore(NULL, 0, 1, NULL);
Clear();
hThread = CreateThread(NULL, 0, AlarmProc, (LPVOID)this, 0, NULL);
}
CMuxAlarm::~CMuxAlarm()
{
HANDLE hSave = hSemAlarm;
hSemAlarm = INVALID_HANDLE_VALUE;
ReleaseSemaphore(hSave, 1, NULL);
WaitForSingleObject(hThread, 15*FACTOR_100NS_PER_SECOND);
CloseHandle(hSave);
}
void CMuxAlarm::Sleep(CLinearTimeDelta ltd)
{
::Sleep(ltd.ReturnMilliseconds());
}
void CMuxAlarm::SurrenderSlice(void)
{
::Sleep(0);
}
void CMuxAlarm::Set(CLinearTimeDelta ltd)
{
dwWait = ltd.ReturnMilliseconds();
ReleaseSemaphore(hSemAlarm, 1, NULL);
bAlarmed = false;
bAlarmSet = true;
}
void CMuxAlarm::Clear(void)
{
dwWait = INFINITE;
ReleaseSemaphore(hSemAlarm, 1, NULL);
bAlarmed = false;
bAlarmSet = false;
}
#else // !WIN32
void GetUTCLinearTime(INT64 *plt)
{
#ifdef HAVE_GETTIMEOFDAY
struct timeval tv;
struct timezone tz;
tz.tz_minuteswest = 0;
tz.tz_dsttime = 0;
gettimeofday(&tv, &tz);
*plt = (((INT64)tv.tv_sec) * FACTOR_100NS_PER_SECOND)
+ (tv.tv_usec * FACTOR_100NS_PER_MICROSECOND)
+ EPOCH_OFFSET;
#else
time_t t;
time(&t);
*plt = t*FACTOR_100NS_PER_SECOND;
#endif
}
CMuxAlarm::CMuxAlarm(void)
{
bAlarmed = false;
bAlarmSet = false;
}
void CMuxAlarm::Sleep(CLinearTimeDelta ltd)
{
#if defined(HAVE_NANOSLEEP)
struct timespec req;
ltd.ReturnTimeSpecStruct(&req);
while (!mudstate.shutdown_flag)
{
struct timespec rem;
if ( nanosleep(&req, &rem) == -1
&& errno == EINTR)
{
req = rem;
}
else
{
break;
}
}
#else
#ifdef HAVE_SETITIMER
struct itimerval oit;
bool bSaved = false;
if (bAlarmSet)
{
// Save existing timer and disable.
//
struct itimerval it;
it.it_value.tv_sec = 0;
it.it_value.tv_usec = 0;
it.it_interval.tv_sec = 0;
it.it_interval.tv_usec = 0;
setitimer(ITIMER_PROF, &it, &oit);
bSaved = true;
bAlarmSet = false;
}
#endif
#if defined(HAVE_USLEEP)
#define TIME_1S 1000000
unsigned long usec;
INT64 usecTotal = ltd.ReturnMicroseconds();
while ( usecTotal
&& mudstate.shutdown_flag)
{
usec = usecTotal;
if (usecTotal < TIME_1S)
{
usec = usecTotal;
}
else
{
usec = TIME_1S-1;
}
usleep(usec);
usecTotal -= usec;
}
#else
::sleep(ltd.ReturnSeconds());
#endif
#ifdef HAVE_SETITIMER
if (bSaved)
{
// Restore and re-enabled timer.
//
setitimer(ITIMER_PROF, &oit, NULL);
bAlarmSet = true;
}
#endif
#endif
}
void CMuxAlarm::SurrenderSlice(void)
{
::sleep(0);
}
void CMuxAlarm::Set(CLinearTimeDelta ltd)
{
#ifdef HAVE_SETITIMER
struct itimerval it;
ltd.ReturnTimeValueStruct(&it.it_value);
it.it_interval.tv_sec = 0;
it.it_interval.tv_usec = 0;
setitimer(ITIMER_PROF, &it, NULL);
bAlarmSet = true;
bAlarmed = false;
#endif
}
void CMuxAlarm::Clear(void)
{
#ifdef HAVE_SETITIMER
// Turn off the timer.
//
struct itimerval it;
it.it_value.tv_sec = 0;
it.it_value.tv_usec = 0;
it.it_interval.tv_sec = 0;
it.it_interval.tv_usec = 0;
setitimer(ITIMER_PROF, &it, NULL);
bAlarmSet = false;
bAlarmed = false;
#endif
}
void CMuxAlarm::Signal(void)
{
bAlarmSet = false;
bAlarmed = true;
}
#endif // !WIN32
static int YearType(int iYear)
{
FIELDEDTIME ft;
memset(&ft, 0, sizeof(FIELDEDTIME));
ft.iYear = iYear;
ft.iMonth = 1;
ft.iDayOfMonth = 1;
CLinearTimeAbsolute ltaLocal;
ltaLocal.SetFields(&ft);
if (isLeapYear(iYear))
{
return ft.iDayOfWeek + 8;
}
else
{
return ft.iDayOfWeek + 1;
}
}
static CLinearTimeAbsolute ltaLowerBound;
static CLinearTimeAbsolute ltaUpperBound;
static CLinearTimeDelta ltdTimeZoneStandard;
// Because of signed-ness and -LONG_MAX overflowing, we need to be
// particularly careful with finding the mid-point.
//
time_t time_t_midpoint(time_t tLower, time_t tUpper)
{
time_t tDiff = (tUpper-2) - tLower;
return tLower+tDiff/2+1;
}
time_t time_t_largest(void)
{
// Assuming 2's complement.
//
time_t tOne = 1;
int nBits = sizeof(time_t)*8;
time_t t = ~(tOne << (nBits-1));
#ifdef WIN32
#if (_MSC_VER >= 1400)
// Not only can Windows not handle negative time_t values, but it also
// cannot handle positive 64-bit values which are 'too large'. Even
// though the interface to localtime() provides for a NULL return value
// for any unsupported arguments, with VS 2005, Microsoft has decided that
// an assert is more useful.
//
// The logic of their assert is based on private #defines which are not
// available to applications. Also, the values have changed from VS 2003
// (0x100000000000i64) to VS 2005 (32535215999). The latter corresponds to
// December 31, 2999, 23:59:59 UTC.
//
// The message here is that they really don't think anyone should be using
// localtime(), but if you do use it, they get to decide unilaterally and
// without hints whether your application is making reasonable calls.
//
const INT64 WIN_MAX__TIME64_T = 32535215999i64;
if ( 4 < sizeof(time_t)
&& WIN_MAX__TIME64_T < t)
{
t = WIN_MAX__TIME64_T;
}
#endif
#endif
return t;
}
time_t time_t_smallest(void)
{
#ifdef WIN32
time_t t = 0;
#else
// Assuming 2's complement.
//
time_t tOne = 1;
int nBits = sizeof(time_t)*8;
time_t t = tOne << (nBits-1);
#endif
return t;
}
// This determines the valid range of localtime() and finds a 'standard'
// time zone near the earliest supported time_t.
//
void test_time_t(void)
{
// Search for the highest supported value of time_t.
//
time_t tUpper = time_t_largest();
time_t tLower = 0;
time_t tMid;
while (tLower < tUpper)
{
tMid = time_t_midpoint(tLower+1, tUpper);
if (localtime(&tMid))
{
tLower = tMid;
}
else
{
tUpper = tMid-1;
}
}
ltaUpperBound.SetSeconds(tLower);
// Search for the lowest supported value of time_t.
//
tUpper = 0;
tLower = time_t_smallest();
while (tLower < tUpper)
{
tMid = time_t_midpoint(tLower, tUpper-1);
if (localtime(&tMid))
{
tUpper = tMid;
}
else
{
tLower = tMid+1;
}
}
ltaLowerBound.SetSeconds(tUpper);
// Find a time near tLower for which DST is not in affect.
//
for (;;)
{
struct tm *ptm = localtime(&tLower);
if (ptm->tm_isdst <= 0)
{
// Daylight savings time is either not in effect or
// we have no way of knowing whether it is in effect
// or not.
//
FIELDEDTIME ft;
SetStructTm(&ft, ptm);
ft.iMillisecond = 0;
ft.iMicrosecond = 0;
ft.iNanosecond = 0;
CLinearTimeAbsolute ltaLocal;
CLinearTimeAbsolute ltaUTC;
ltaLocal.SetFields(&ft);
ltaUTC.SetSeconds(tLower);
ltdTimeZoneStandard = ltaLocal - ltaUTC;
break;
}
// Advance the time by 1 month (expressed as seconds).
//
tLower += 30*24*60*60;
}
}
int NearestYearOfType[15];
static CLinearTimeDelta ltdIntervalMinimum;
static bool bTimeInitialized = false;
void TIME_Initialize(void)
{
if (bTimeInitialized)
{
return;
}
bTimeInitialized = true;
tzset();
test_time_t();
ltdIntervalMinimum = time_1w;
int i;
for (i = 0; i < 15; i++)
{
NearestYearOfType[i] = -1;
}
int cnt = 14;
FIELDEDTIME ft;
ltaUpperBound.ReturnFields(&ft);
for (i = ft.iYear-1; cnt; i--)
{
int iYearType = YearType(i);
if (NearestYearOfType[iYearType] < 0)
{
NearestYearOfType[iYearType] = i;
cnt--;
}
}
#ifdef WIN32
InitializeQueryPerformance();
#endif
}
// Explanation of the table.
//
// The table contains intervals of time for which (ltdOffset, isDST)
// tuples are known.
//
// Two intervals may be combined when they share the same tuple
// value and the time between them is less than ltdIntervalMinimum.
//
// Intervals are thrown away in a least-recently-used (LRU) fashion.
//
typedef struct
{
CLinearTimeAbsolute ltaStart;
CLinearTimeAbsolute ltaEnd;
CLinearTimeDelta ltdOffset;
int nTouched;
bool isDST;
} OffsetEntry;
#define MAX_OFFSETS 50
int nOffsetTable = 0;
int nTouched0 = 0;
OffsetEntry OffsetTable[MAX_OFFSETS];
// This function finds the entry in the table (0...nOffsetTable-1)
// whose ltaStart is less than or equal to the search key.
// If no entry satisfies this search, -1 is returned.
//
static int FindOffsetEntry(const CLinearTimeAbsolute& lta)
{
int lo = 0;
int hi = nOffsetTable - 1;
int mid = 0;
while (lo <= hi)
{
mid = ((hi - lo) >> 1) + lo;
if (OffsetTable[mid].ltaStart <= lta)
{
lo = mid + 1;
}
else
{
hi = mid - 1;
}
}
return lo-1;
}
static bool QueryOffsetTable
(
CLinearTimeAbsolute lta,
CLinearTimeDelta *pltdOffset,
bool *pisDST,
int *piEntry
)
{
nTouched0++;
int i = FindOffsetEntry(lta);
*piEntry = i;
// Is the interval defined?
//
if ( 0 <= i
&& lta <= OffsetTable[i].ltaEnd)
{
*pltdOffset = OffsetTable[i].ltdOffset;
*pisDST = OffsetTable[i].isDST;
OffsetTable[i].nTouched = nTouched0;
return true;
}
return false;
}
static void UpdateOffsetTable
(
CLinearTimeAbsolute <a,
CLinearTimeDelta ltdOffset,
bool isDST,
int i
)
{
Again:
nTouched0++;
// Is the interval defined?
//
if ( 0 <= i
&& lta <= OffsetTable[i].ltaEnd)
{
OffsetTable[i].nTouched = nTouched0;
return;
}
bool bTryMerge = false;
// Coalesce new data point into this interval if:
//
// 1. Tuple for this interval matches the new tuple value.
// 2. It's close enough that we can assume all intervening
// values are the same.
//
if ( 0 <= i
&& OffsetTable[i].ltdOffset == ltdOffset
&& OffsetTable[i].isDST == isDST
&& lta <= OffsetTable[i].ltaEnd + ltdIntervalMinimum)
{
// Cool. We can just extend this interval to include our new
// data point.
//
OffsetTable[i].ltaEnd = lta;
OffsetTable[i].nTouched = nTouched0;
// Since we have changed this interval, we may be able to
// coalesce it with the next interval.
//
bTryMerge = true;
}
// Coalesce new data point into next interval if:
//
// 1. Next interval exists.
// 2. Tuple in next interval matches the new tuple value.
// 3. It's close enough that we can assume all intervening
// values are the same.
//
int iNext = i+1;
if ( 0 <= iNext
&& iNext < nOffsetTable
&& OffsetTable[iNext].ltdOffset == ltdOffset
&& OffsetTable[iNext].isDST == isDST
&& OffsetTable[iNext].ltaStart - ltdIntervalMinimum <= lta)
{
// Cool. We can just extend the next interval to include our
// new data point.
//
OffsetTable[iNext].ltaStart = lta;
OffsetTable[iNext].nTouched = nTouched0;
// Since we have changed the next interval, we may be able
// to coalesce it with the previous interval.
//
bTryMerge = true;
}
if (bTryMerge)
{
// We should combine the current and next intervals if we can.
//
if ( 0 <= i
&& iNext < nOffsetTable
&& OffsetTable[i].ltdOffset == OffsetTable[iNext].ltdOffset
&& OffsetTable[i].isDST == OffsetTable[iNext].isDST
&& OffsetTable[iNext].ltaStart - ltdIntervalMinimum
<= OffsetTable[i].ltaEnd)
{
if (0 <= i && 0 <= iNext)
{
OffsetTable[i].ltaEnd = OffsetTable[iNext].ltaEnd;
}
int nSize = sizeof(OffsetEntry)*(nOffsetTable-i-2);
memmove(OffsetTable+i+1, OffsetTable+i+2, nSize);
nOffsetTable--;
}
}
else
{
// We'll have'ta create a new interval.
//
if (nOffsetTable < MAX_OFFSETS)
{
size_t nSize = sizeof(OffsetEntry)*(nOffsetTable-i-1);
memmove(OffsetTable+i+2, OffsetTable+i+1, nSize);
nOffsetTable++;
i++;
OffsetTable[i].isDST = isDST;
OffsetTable[i].ltdOffset = ltdOffset;
OffsetTable[i].ltaStart= lta;
OffsetTable[i].ltaEnd= lta;
OffsetTable[i].nTouched = nTouched0;
}
else
{
// We ran out of room. Throw away the least used
// interval and try again.
//
int nMinTouched = OffsetTable[0].nTouched;
int iMinTouched = 0;
for (int j = 1; j < nOffsetTable; j++)
{
if (OffsetTable[j].nTouched - nMinTouched < 0)
{
nMinTouched = OffsetTable[j].nTouched;
iMinTouched = j;
}
}
int nSize = sizeof(OffsetEntry)*(nOffsetTable-iMinTouched-1);
memmove(OffsetTable+iMinTouched, OffsetTable+iMinTouched+1, nSize);
nOffsetTable--;
if (iMinTouched <= i)
{
i--;
}
goto Again;
}
}
}
static CLinearTimeDelta QueryLocalOffsetAt_Internal
(
CLinearTimeAbsolute lta,
bool *pisDST,
int iEntry
)
{
if (!bTimeInitialized)
{
TIME_Initialize();
}
// At this point, we must use localtime() to discover what the
// UTC to local time offset is for the requested UTC time.
//
// However, localtime() does not support times beyond around
// the 2038 year on machines with 32-bit integers, so to
// compensant for this, and knowing that we are already dealing
// with fictionalized adjustments, we associate a future year
// that is outside the supported range with one that is inside
// the support range of the same type (there are 14 different
// year types depending on leap-year-ness and which day of the
// week that January 1st falls on.
//
// Note: Laws beyond the current year have not been written yet
// and are subject to change at any time. For example, Israel
// doesn't have regular rules for DST but makes a directive each
// year...sometimes to avoid conflicts with Jewish holidays.
//
if (lta > ltaUpperBound)
{
// Map the specified year to the closest year with the same
// pattern of weeks.
//
FIELDEDTIME ft;
lta.ReturnFields(&ft);
ft.iYear = NearestYearOfType[YearType(ft.iYear)];
lta.SetFields(&ft);
}
// Rely on localtime() to take a UTC count of seconds and convert
// to a fielded local time complete with known timezone and DST
// adjustments.
//
time_t lt = (time_t)lta.ReturnSeconds();
struct tm *ptm = localtime(<);
if (!ptm)
{
// This should never happen as we have already taken pains
// to restrict the range of UTC seconds gives to localtime().
//
return ltdTimeZoneStandard;
}
// With the fielded (or broken down) time from localtime(), we
// can convert to a linear time in the same time zone.
//
FIELDEDTIME ft;
SetStructTm(&ft, ptm);
ft.iMillisecond = 0;
ft.iMicrosecond = 0;
ft.iNanosecond = 0;
CLinearTimeAbsolute ltaLocal;
CLinearTimeDelta ltdOffset;
ltaLocal.SetFields(&ft);
lta.SetSeconds(lt);
ltdOffset = ltaLocal - lta;
*pisDST = (ptm->tm_isdst > 0);
// We now have a mapping from UTC lta to a (ltdOffset, *pisDST)
// tuple which will will use to update the cache.
//
UpdateOffsetTable(lta, ltdOffset, *pisDST, iEntry);
return ltdOffset;
}
static CLinearTimeDelta QueryLocalOffsetAtUTC
(
const CLinearTimeAbsolute <a,
bool *pisDST
)
{
*pisDST = false;
// DST started in Britain in May 1916 and in the US in 1918.
// Germany used it a little before May 1916, but I'm not sure
// of exactly when.
//
// So, there is locale specific information about DST adjustments
// that could reasonable be made between 1916 and 1970.
// Because Unix supports negative time_t values while Win32 does
// not, it can also support that 1916 to 1970 interval with
// timezone information.
//
// Win32 only supports one timezone rule at a time, or rather
// it doesn't have any historical timezone information, but you
// can/must provide it yourself. So, in the Win32 case, unless we
// are willing to provide historical information (from a tzfile
// perhaps), it will only give us the current timezone rule
// (the one selected by the control panel or by a TZ environment
// variable). It projects this rule forwards and backwards.
//
// Feel free to fill that gap in yourself with a tzfile file
// reader for Win32.
//
if (lta < ltaLowerBound)
{
return ltdTimeZoneStandard;
}
// Next, we check our table for whether this time falls into a
// previously discovered interval. You could view this as a
// cache, or you could also view it as a way of reading in the
// tzfile without becoming system-dependent enough to actually
// read the tzfile.
//
CLinearTimeDelta ltdOffset;
int iEntry;
if (QueryOffsetTable(lta, <dOffset, pisDST, &iEntry))
{
return ltdOffset;
}
ltdOffset = QueryLocalOffsetAt_Internal(lta, pisDST, iEntry);
// Since the cache failed, let's make sure we have a useful
// interval surrounding this last request so that future queries
// nearby will be serviced by the cache.
//
CLinearTimeAbsolute ltaProbe;
CLinearTimeDelta ltdDontCare;
bool bDontCare;
ltaProbe = lta - ltdIntervalMinimum;
if (!QueryOffsetTable(ltaProbe, <dDontCare, &bDontCare, &iEntry))
{
QueryLocalOffsetAt_Internal(ltaProbe, &bDontCare, iEntry);
}
ltaProbe = lta + ltdIntervalMinimum;
if (!QueryOffsetTable(ltaProbe, <dDontCare, &bDontCare, &iEntry))
{
QueryLocalOffsetAt_Internal(ltaProbe, &bDontCare, iEntry);
}
return ltdOffset;
}
void CLinearTimeAbsolute::UTC2Local(void)
{
bool bDST;
CLinearTimeDelta ltd = QueryLocalOffsetAtUTC(*this, &bDST);
m_tAbsolute += ltd.m_tDelta;
}
void CLinearTimeAbsolute::Local2UTC(void)
{
bool bDST1, bDST2;
CLinearTimeDelta ltdOffset1 = QueryLocalOffsetAtUTC(*this, &bDST1);
CLinearTimeAbsolute ltaUTC2 = *this - ltdOffset1;
CLinearTimeDelta ltdOffset2 = QueryLocalOffsetAtUTC(ltaUTC2, &bDST2);
CLinearTimeAbsolute ltaLocalGuess = ltaUTC2 + ltdOffset2;
if (ltaLocalGuess == *this)
{
// We found an offset, UTC, local time combination that
// works.
//
m_tAbsolute = ltaUTC2.m_tAbsolute;
}
else
{
CLinearTimeAbsolute ltaUTC1 = *this - ltdOffset2;
m_tAbsolute = ltaUTC1.m_tAbsolute;
}
}
// AUTOMAGIC DATE PARSING.
// We must deal with several levels at once. That is, a single
// character is overlapped by layers and layers of meaning from 'digit'
// to 'the second digit of the hours field of the timezone'.
//
typedef struct tag_pd_node
{
// The following is a bitfield which contains a '1' bit for every
// possible meaning associated with this token. This bitfield is
// initially determined by looking at the token, and then we use
// the following logic to refine this set further:
//
// 1. Suffix and Prefix hints. e.g., '1st', '2nd', etc. ':' with
// time fields, 'am'/'pm', timezone field must follow time
// field, 'Wn' is a week-of-year indicator, 'nTn' is an ISO
// date/time seperator, '<timefield>Z' shows that 'Z' is a
// military timezone letter, '-n' indicates that the field is
// either a year or a numeric timezone, '+n' indicates that the
// field can only be a timezone.
//
// 2. Single Field Exclusiveness. We only allow -one- timezone
// indicator in the field. Likewise, there can't be two months,
// two day-of-month fields, two years, etc.
//
// 3. Semantic exclusions. day-of-year, month/day-of-month, and
// and week-of-year/day-of-year(numeric) are mutually exclusive.
//
// If successful, this bitfield will ultimately only contain a single
// '1' bit which tells us what it is.
//
unsigned uCouldBe;
// These fields deal with token things and we avoid mixing
// them up in higher meanings. This is the lowest level.
//
// Further Notes:
//
// PDTT_SYMBOL is always a -single- (nToken==1) character.
//
// uTokenType must be one of the PDTT_* values.
//
// PDTT_NUMERIC_* and PDTT_TEXT types may have an
// iToken value associated with them.
//
#define PDTT_SYMBOL 1 // e.g., :/.-+
#define PDTT_NUMERIC_UNSIGNED 2 // [0-9]+
#define PDTT_SPACES 3 // One or more space/tab characters
#define PDTT_TEXT 4 // 'January' 'Jan' 'T' 'W'
#define PDTT_NUMERIC_SIGNED 5 // [+-][0-9]+
unsigned uTokenType;
char *pToken;
size_t nToken;
int iToken;
// Link to previous and next node.
//
struct tag_pd_node *pNextNode;
struct tag_pd_node *pPrevNode;
} PD_Node;
#define PDCB_NOTHING 0x00000000
#define PDCB_TIME_FIELD_SEPARATOR 0x00000001
#define PDCB_DATE_FIELD_SEPARATOR 0x00000002
#define PDCB_WHITESPACE 0x00000004
#define PDCB_DAY_OF_MONTH_SUFFIX 0x00000008
#define PDCB_SIGN 0x00000010
#define PDCB_SECONDS_DECIMAL 0x00000020
#define PDCB_REMOVEABLE 0x00000040
#define PDCB_YEAR 0x00000080
#define PDCB_MONTH 0x00000100
#define PDCB_DAY_OF_MONTH 0x00000200
#define PDCB_DAY_OF_WEEK 0x00000400
#define PDCB_WEEK_OF_YEAR 0x00000800
#define PDCB_DAY_OF_YEAR 0x00001000
#define PDCB_YD 0x00002000
#define PDCB_YMD 0x00004000
#define PDCB_MDY 0x00008000
#define PDCB_DMY 0x00010000
#define PDCB_DATE_TIME_SEPARATOR 0x00020000
#define PDCB_TIMEZONE 0x00040000
#define PDCB_WEEK_OF_YEAR_PREFIX 0x00080000
#define PDCB_MERIDIAN 0x00100000
#define PDCB_MINUTE 0x00200000
#define PDCB_SECOND 0x00400000
#define PDCB_SUBSECOND 0x00800000
#define PDCB_HOUR_TIME 0x01000000
#define PDCB_HMS_TIME 0x02000000
#define PDCB_HOUR_TIMEZONE 0x04000000
#define PDCB_HMS_TIMEZONE 0x08000000
extern PD_Node *PD_NewNode(void);
extern void PD_AppendNode(PD_Node *pNode);
extern void PD_InsertAfter(PD_Node *pWhere, PD_Node *pNode);
typedef void BREAK_DOWN_FUNC(PD_Node *pNode);
typedef struct tag_pd_breakdown
{
unsigned int mask;
BREAK_DOWN_FUNC *fpBreakDown;
} PD_BREAKDOWN;
extern const PD_BREAKDOWN BreakDownTable[];
#define NOT_PRESENT -9999999
typedef struct tag_AllFields
{
int iYear;
int iDayOfYear;
int iMonthOfYear;
int iDayOfMonth;
int iWeekOfYear;
int iDayOfWeek;
int iHourTime;
int iMinuteTime;
int iSecondTime;
int iMillisecondTime;
int iMicrosecondTime;
int iNanosecondTime;
int iMinuteTimeZone;
} ALLFIELDS;
// isValidYear assumes numeric string.
//
bool isValidYear(size_t nStr, char *pStr, int iValue)
{
// Year may be Y, YY, YYY, YYYY, or YYYYY.
// Negative and zero years are permitted in general, but we aren't
// give the leading sign.
//
if (1 <= nStr && nStr <= 5)
{
return true;
}
return false;
}
bool isValidMonth(size_t nStr, char *pStr, int iValue)
{
// Month may be 1 through 9, 01 through 09, 10, 11, or 12.
//
if ( 1 <= nStr && nStr <= 2
&& 1 <= iValue && iValue <= 12)
{
return true;
}
return false;
}
bool isValidDayOfMonth(size_t nStr, char *pStr, int iValue)
{
// Day Of Month may be 1 through 9, 01 through 09, 10 through 19,
// 20 through 29, 30, and 31.
//
if ( 1 <= nStr && nStr <= 2
&& 1 <= iValue && iValue <= 31)
{
return true;
}
return false;
}
bool isValidDayOfWeek(size_t nStr, char *pStr, int iValue)
{
// Day Of Week may be 1 through 7.
//
if ( 1 == nStr
&& 1 <= iValue && iValue <= 7)
{
return true;
}
return false;
}
bool isValidDayOfYear(size_t nStr, char *pStr, int iValue)
{
// Day Of Year 001 through 366
//
if ( 3 == nStr
&& 1 <= iValue && iValue <= 366)
{
return true;
}
return false;
}
bool isValidWeekOfYear(size_t nStr, char *pStr, int iValue)
{
// Week Of Year may be 01 through 53.
//
if ( 2 == nStr
&& 1 <= iValue && iValue <= 53)
{
return true;
}
return false;
}
bool isValidHour(size_t nStr, char *pStr, int iValue)
{
// Hour may be 0 through 9, 00 through 09, 10 through 19, 20 through 24.
//
if ( 1 <= nStr && nStr <= 2
&& 0 <= iValue && iValue <= 24)
{
return true;
}
return false;
}
bool isValidMinute(size_t nStr, char *pStr, int iValue)
{
// Minute may be 00 through 59.
//
if ( 2 == nStr
&& 0 <= iValue && iValue <= 59)
{
return true;
}
return false;
}
bool isValidSecond(size_t nStr, char *pStr, int iValue)
{
// Second may be 00 through 59. Leap seconds represented
// by '60' are not dealt with.
//
if ( 2 == nStr
&& 0 <= iValue && iValue <= 59)
{
return true;
}
return false;
}
bool isValidSubSecond(size_t nStr, char *pStr, int iValue)
{
// Sub seconds can really be anything, but we limit
// it's precision to 100 ns.
//
if (nStr <= 7)
{
return true;
}
return false;
}
// This function handles H, HH, HMM, HHMM, HMMSS, HHMMSS
//
bool isValidHMS(size_t nStr, char *pStr, int iValue)
{
int iHour, iMinutes, iSeconds;
switch (nStr)
{
case 1:
case 2:
return isValidHour(nStr, pStr, iValue);
break;
case 3:
case 4:
iHour = iValue/100; iValue -= iHour*100;
iMinutes = iValue;
if ( isValidHour(nStr-2, pStr, iHour)
&& isValidMinute(2, pStr+nStr-2, iMinutes))
{
return true;
}
break;
case 5:
case 6:
iHour = iValue/10000; iValue -= iHour*10000;
iMinutes = iValue/100; iValue -= iMinutes*100;
iSeconds = iValue;
if ( isValidHour(nStr-4, pStr, iHour)
&& isValidMinute(2, pStr+nStr-4, iMinutes)
&& isValidSecond(2, pStr+nStr-2, iSeconds))
{
return true;
}
break;
}
return false;
}
void SplitLastTwoDigits(PD_Node *pNode, unsigned mask)
{
PD_Node *p = PD_NewNode();
if (p)
{
*p = *pNode;
p->uCouldBe = mask;
p->nToken = 2;
p->pToken += pNode->nToken - 2;
p->iToken = pNode->iToken % 100;
pNode->nToken -= 2;
pNode->iToken /= 100;
PD_InsertAfter(pNode, p);
}
}
void SplitLastThreeDigits(PD_Node *pNode, unsigned mask)
{
PD_Node *p = PD_NewNode();
if (p)
{
*p = *pNode;
p->uCouldBe = mask;
p->nToken = 3;
p->pToken += pNode->nToken - 3;
p->iToken = pNode->iToken % 1000;
pNode->nToken -= 3;
pNode->iToken /= 1000;
PD_InsertAfter(pNode, p);
}
}
// This function breaks down H, HH, HMM, HHMM, HMMSS, HHMMSS
//
void BreakDownHMS(PD_Node *pNode)
{
if (pNode->uCouldBe & PDCB_HMS_TIME)
{
pNode->uCouldBe = PDCB_HOUR_TIME;
}
else
{
pNode->uCouldBe = PDCB_HOUR_TIMEZONE;
}
switch (pNode->nToken)
{
case 5:
case 6:
SplitLastTwoDigits(pNode, PDCB_SECOND);
case 3:
case 4:
SplitLastTwoDigits(pNode, PDCB_MINUTE);
}
}
// This function handles YYMMDD, YYYMMDD, YYYYMMDD, YYYYYMMDD
//
bool isValidYMD(size_t nStr, char *pStr, int iValue)
{
int iYear = iValue / 10000;
iValue -= 10000 * iYear;
int iMonth = iValue / 100;
iValue -= 100 * iMonth;
int iDay = iValue;
if ( isValidMonth(2, pStr+nStr-4, iMonth)
&& isValidDayOfMonth(2, pStr+nStr-2, iDay)
&& isValidYear(nStr-4, pStr, iYear))
{
return true;
}
return false;
}
// This function breaks down YYMMDD, YYYMMDD, YYYYMMDD, YYYYYMMDD
//
void BreakDownYMD(PD_Node *pNode)
{
pNode->uCouldBe = PDCB_YEAR;
SplitLastTwoDigits(pNode, PDCB_DAY_OF_MONTH);
SplitLastTwoDigits(pNode, PDCB_MONTH);
}
// This function handles MMDDYY
//
bool isValidMDY(size_t nStr, char *pStr, int iValue)
{
int iMonth = iValue / 10000;
iValue -= 10000 * iMonth;
int iDay = iValue / 100;
iValue -= 100 * iDay;
int iYear = iValue;
if ( 6 == nStr
&& isValidMonth(2, pStr, iMonth)
&& isValidDayOfMonth(2, pStr+2, iDay)
&& isValidYear(2, pStr+4, iYear))
{
return true;
}
return false;
}
// This function breaks down MMDDYY
//
void BreakDownMDY(PD_Node *pNode)
{
pNode->uCouldBe = PDCB_MONTH;
SplitLastTwoDigits(pNode, PDCB_YEAR);
SplitLastTwoDigits(pNode, PDCB_DAY_OF_MONTH);
}
// This function handles DDMMYY
//
bool isValidDMY(size_t nStr, char *pStr, int iValue)
{
int iDay = iValue / 10000;
iValue -= 10000 * iDay;
int iMonth = iValue / 100;
iValue -= 100 * iMonth;
int iYear = iValue;
if ( 6 == nStr
&& isValidMonth(2, pStr+2, iMonth)
&& isValidDayOfMonth(2, pStr, iDay)
&& isValidYear(2, pStr+4, iYear))
{
return true;
}
return false;
}
// This function breaks down DDMMYY
//
void BreakDownDMY(PD_Node *pNode)
{
pNode->uCouldBe = PDCB_DAY_OF_MONTH;
SplitLastTwoDigits(pNode, PDCB_YEAR);
SplitLastTwoDigits(pNode, PDCB_MONTH);
}
// This function handles YDDD, YYDDD, YYYDDD, YYYYDDD, YYYYYDDD
//
bool isValidYD(size_t nStr, char *pStr, int iValue)
{
int iYear = iValue / 1000;
iValue -= 1000*iYear;
int iDay = iValue;
if ( 4 <= nStr && nStr <= 8
&& isValidDayOfYear(3, pStr+nStr-3, iDay)
&& isValidYear(nStr-3, pStr, iYear))
{
return true;
}
return false;
}
// This function breaks down YDDD, YYDDD, YYYDDD, YYYYDDD, YYYYYDDD
//
void BreakDownYD(PD_Node *pNode)
{
pNode->uCouldBe = PDCB_YEAR;
SplitLastThreeDigits(pNode, PDCB_DAY_OF_YEAR);
}
const int InitialCouldBe[9] =
{
PDCB_YEAR|PDCB_MONTH|PDCB_DAY_OF_MONTH|PDCB_DAY_OF_WEEK|PDCB_HMS_TIME|PDCB_HMS_TIMEZONE|PDCB_HOUR_TIME|PDCB_HOUR_TIMEZONE|PDCB_SUBSECOND, // 1
PDCB_YEAR|PDCB_MONTH|PDCB_DAY_OF_MONTH|PDCB_WEEK_OF_YEAR|PDCB_HMS_TIME|PDCB_HMS_TIMEZONE|PDCB_HOUR_TIME|PDCB_HOUR_TIMEZONE|PDCB_MINUTE|PDCB_SECOND|PDCB_SUBSECOND, // 2
PDCB_YEAR|PDCB_HMS_TIME|PDCB_HMS_TIMEZONE|PDCB_DAY_OF_YEAR|PDCB_SUBSECOND, // 3
PDCB_YEAR|PDCB_HMS_TIME|PDCB_HMS_TIMEZONE|PDCB_YD|PDCB_SUBSECOND, // 4
PDCB_YEAR|PDCB_HMS_TIME|PDCB_HMS_TIMEZONE|PDCB_YD|PDCB_SUBSECOND, // 5
PDCB_HMS_TIME|PDCB_HMS_TIMEZONE|PDCB_YMD|PDCB_MDY|PDCB_DMY|PDCB_YD|PDCB_SUBSECOND, // 6
PDCB_YMD|PDCB_YD|PDCB_SUBSECOND, // 7
PDCB_YMD|PDCB_YD, // 8
PDCB_YMD // 9
};
typedef bool PVALIDFUNC(size_t nStr, char *pStr, int iValue);
typedef struct tag_pd_numeric_valid
{
unsigned mask;
PVALIDFUNC *fnValid;
} NUMERIC_VALID_RECORD;
const NUMERIC_VALID_RECORD NumericSet[] =
{
{ PDCB_YEAR, isValidYear },
{ PDCB_MONTH, isValidMonth },
{ PDCB_DAY_OF_MONTH, isValidDayOfMonth },
{ PDCB_DAY_OF_YEAR, isValidDayOfYear },
{ PDCB_WEEK_OF_YEAR, isValidWeekOfYear },
{ PDCB_DAY_OF_WEEK, isValidDayOfWeek },
{ PDCB_HMS_TIME|PDCB_HMS_TIMEZONE, isValidHMS },
{ PDCB_YMD, isValidYMD },
{ PDCB_MDY, isValidMDY },
{ PDCB_DMY, isValidDMY },
{ PDCB_YD, isValidYD },
{ PDCB_HOUR_TIME|PDCB_HOUR_TIMEZONE, isValidHour },
{ PDCB_MINUTE, isValidMinute },
{ PDCB_SECOND, isValidSecond },
{ PDCB_SUBSECOND, isValidSubSecond },
{ 0, 0},
};
// This function looks at the numeric token and assigns the initial set
// of possibilities.
//
void ClassifyNumericToken(PD_Node *pNode)
{
size_t nToken = pNode->nToken;
char *pToken = pNode->pToken;
int iToken = pNode->iToken;
unsigned int uCouldBe = InitialCouldBe[nToken-1];
int i = 0;
int mask = 0;
while ((mask = NumericSet[i].mask) != 0)
{
if ( (mask & uCouldBe)
&& !(NumericSet[i].fnValid(nToken, pToken, iToken)))
{
uCouldBe &= ~mask;
}
i++;
}
pNode->uCouldBe = uCouldBe;
}
typedef struct
{
char *szText;
unsigned int uCouldBe;
int iValue;
} PD_TEXT_ENTRY;
const PD_TEXT_ENTRY PD_TextTable[] =
{
{"sun", PDCB_DAY_OF_WEEK, 7 },
{"mon", PDCB_DAY_OF_WEEK, 1 },
{"tue", PDCB_DAY_OF_WEEK, 2 },
{"wed", PDCB_DAY_OF_WEEK, 3 },
{"thu", PDCB_DAY_OF_WEEK, 4 },
{"fri", PDCB_DAY_OF_WEEK, 5 },
{"sat", PDCB_DAY_OF_WEEK, 6 },
{"jan", PDCB_MONTH, 1 },
{"feb", PDCB_MONTH, 2 },
{"mar", PDCB_MONTH, 3 },
{"apr", PDCB_MONTH, 4 },
{"may", PDCB_MONTH, 5 },
{"jun", PDCB_MONTH, 6 },
{"jul", PDCB_MONTH, 7 },
{"aug", PDCB_MONTH, 8 },
{"sep", PDCB_MONTH, 9 },
{"oct", PDCB_MONTH, 10 },
{"nov", PDCB_MONTH, 11 },
{"dec", PDCB_MONTH, 12 },
{"january", PDCB_MONTH, 1 },
{"february", PDCB_MONTH, 2 },
{"march", PDCB_MONTH, 3 },
{"april", PDCB_MONTH, 4 },
{"may", PDCB_MONTH, 5 },
{"june", PDCB_MONTH, 6 },
{"july", PDCB_MONTH, 7 },
{"august", PDCB_MONTH, 8 },
{"september", PDCB_MONTH, 9 },
{"october", PDCB_MONTH, 10 },
{"november", PDCB_MONTH, 11 },
{"december", PDCB_MONTH, 12 },
{"sunday", PDCB_DAY_OF_WEEK, 7 },
{"monday", PDCB_DAY_OF_WEEK, 1 },
{"tuesday", PDCB_DAY_OF_WEEK, 2 },
{"wednesday", PDCB_DAY_OF_WEEK, 3 },
{"thursday", PDCB_DAY_OF_WEEK, 4 },
{"friday", PDCB_DAY_OF_WEEK, 5 },
{"saturday", PDCB_DAY_OF_WEEK, 6 },
{"a", PDCB_TIMEZONE, 100 },
{"b", PDCB_TIMEZONE, 200 },
{"c", PDCB_TIMEZONE, 300 },
{"d", PDCB_TIMEZONE, 400 },
{"e", PDCB_TIMEZONE, 500 },
{"f", PDCB_TIMEZONE, 600 },
{"g", PDCB_TIMEZONE, 700 },
{"h", PDCB_TIMEZONE, 800 },
{"i", PDCB_TIMEZONE, 900 },
{"k", PDCB_TIMEZONE, 1000 },
{"l", PDCB_TIMEZONE, 1100 },
{"m", PDCB_TIMEZONE, 1200 },
{"n", PDCB_TIMEZONE, -100 },
{"o", PDCB_TIMEZONE, -200 },
{"p", PDCB_TIMEZONE, -300 },
{"q", PDCB_TIMEZONE, -400 },
{"r", PDCB_TIMEZONE, -500 },
{"s", PDCB_TIMEZONE, -600 },
{"t", PDCB_DATE_TIME_SEPARATOR|PDCB_TIMEZONE, -700},
{"u", PDCB_TIMEZONE, -800 },
{"v", PDCB_TIMEZONE, -900 },
{"w", PDCB_WEEK_OF_YEAR_PREFIX|PDCB_TIMEZONE, -1000 },
{"x", PDCB_TIMEZONE, -1100 },
{"y", PDCB_TIMEZONE, -1200 },
{"z", PDCB_TIMEZONE, 0 },
{"hst", PDCB_TIMEZONE, -1000 },
{"akst", PDCB_TIMEZONE, -900 },
{"pst", PDCB_TIMEZONE, -800 },
{"mst", PDCB_TIMEZONE, -700 },
{"cst", PDCB_TIMEZONE, -600 },
{"est", PDCB_TIMEZONE, -500 },
{"ast", PDCB_TIMEZONE, -400 },
{"akdt", PDCB_TIMEZONE, -800 },
{"pdt", PDCB_TIMEZONE, -700 },
{"mdt", PDCB_TIMEZONE, -600 },
{"cdt", PDCB_TIMEZONE, -500 },
{"edt", PDCB_TIMEZONE, -400 },
{"adt", PDCB_TIMEZONE, -300 },
{"bst", PDCB_TIMEZONE, 100 },
{"ist", PDCB_TIMEZONE, 100 },
{"cet", PDCB_TIMEZONE, 100 },
{"cest", PDCB_TIMEZONE, 200 },
{"eet", PDCB_TIMEZONE, 200 },
{"eest", PDCB_TIMEZONE, 300 },
{"aest", PDCB_TIMEZONE, 1000 },
{"gmt", PDCB_TIMEZONE, 0 },
{"ut", PDCB_TIMEZONE, 0 },
{"utc", PDCB_TIMEZONE, 0 },
{"st", PDCB_DAY_OF_MONTH_SUFFIX, 0 },
{"nd", PDCB_DAY_OF_MONTH_SUFFIX, 0 },
{"rd", PDCB_DAY_OF_MONTH_SUFFIX, 0 },
{"th", PDCB_DAY_OF_MONTH_SUFFIX, 0 },
{"am", PDCB_MERIDIAN, 0 },
{"pm", PDCB_MERIDIAN, 12 },
{ 0, 0, 0}
};
#define PD_LEX_INVALID 0
#define PD_LEX_SYMBOL 1
#define PD_LEX_DIGIT 2
#define PD_LEX_SPACE 3
#define PD_LEX_ALPHA 4
#define PD_LEX_EOS 5
const char LexTable[256] =
{
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
//
5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1
3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, // 2
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0, // 3
0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 4
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, // 5
0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 6
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, // 7
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // F
};
int nNodes = 0;
#define MAX_NODES 200
PD_Node Nodes[MAX_NODES];
PD_Node *PD_Head = 0;
PD_Node *PD_Tail = 0;
void PD_Reset(void)
{
nNodes = 0;
PD_Head = 0;
PD_Tail = 0;
}
PD_Node *PD_NewNode(void)
{
if (nNodes < MAX_NODES)
{
return Nodes+(nNodes++);
}
return NULL;
}
PD_Node *PD_FirstNode(void)
{
return PD_Head;
}
PD_Node *PD_LastNode(void)
{
return PD_Tail;
}
PD_Node *PD_NextNode(PD_Node *pNode)
{
return pNode->pNextNode;
}
PD_Node *PD_PrevNode(PD_Node *pNode)
{
return pNode->pPrevNode;
}
// PD_AppendNode - Appends a node onto the end of the list. It's used during
// the first pass over the date string; these nodes are therefore always
// elemental nodes. It might be possible to append a group node. However,
// usually group nodes a created at a later from recognizing a deeper semantic
// meaning of an elemental node and this promotion event could happen anywhere
// in the sequence. The order of promotion isn't clear during the first pass
// over the date string.
//
void PD_AppendNode(PD_Node *pNode)
{
if (!PD_Head)
{
PD_Head = PD_Tail = pNode;
return;
}
pNode->pNextNode = 0;
PD_Tail->pNextNode = pNode;
pNode->pPrevNode = PD_Tail;
PD_Tail = pNode;
}
void PD_InsertAfter(PD_Node *pWhere, PD_Node *pNode)
{
pNode->pPrevNode = pWhere;
pNode->pNextNode = pWhere->pNextNode;
pWhere->pNextNode = pNode;
if (pNode->pNextNode)
{
pNode->pNextNode->pPrevNode = pNode;
}
else
{
PD_Tail = pNode;
}
}
void PD_RemoveNode(PD_Node *pNode)
{
if (pNode == PD_Head)
{
if (pNode == PD_Tail)
{
PD_Head = PD_Tail = 0;
}
else
{
PD_Head = pNode->pNextNode;
PD_Head->pPrevNode = 0;
pNode->pNextNode = 0;
}
}
else if (pNode == PD_Tail)
{
PD_Tail = pNode->pPrevNode;
PD_Tail->pNextNode = 0;
pNode->pPrevNode = 0;
}
else
{
pNode->pNextNode->pPrevNode = pNode->pPrevNode;
pNode->pPrevNode->pNextNode = pNode->pNextNode;
pNode->pNextNode = 0;
pNode->pPrevNode = 0;
}
}
PD_Node *PD_ScanNextToken(char **ppString)
{
char *p = *ppString;
int ch = *p;
if (ch == 0)
{
return NULL;
}
PD_Node *pNode;
int iType = LexTable[ch];
if (iType == PD_LEX_EOS || iType == PD_LEX_INVALID)
{
return NULL;
}
else if (iType == PD_LEX_SYMBOL)
{
pNode = PD_NewNode();
if (!pNode)
{
return NULL;
}
pNode->pNextNode = 0;
pNode->pPrevNode = 0;
pNode->iToken = 0;
pNode->nToken = 1;
pNode->pToken = p;
pNode->uTokenType = PDTT_SYMBOL;
if (ch == ':')
{
pNode->uCouldBe = PDCB_TIME_FIELD_SEPARATOR;
}
else if (ch == '-')
{
pNode->uCouldBe = PDCB_DATE_FIELD_SEPARATOR|PDCB_SIGN;
}
else if (ch == '+')
{
pNode->uCouldBe = PDCB_SIGN;
}
else if (ch == '/')
{
pNode->uCouldBe = PDCB_DATE_FIELD_SEPARATOR;
}
else if (ch == '.')
{
pNode->uCouldBe = PDCB_DATE_FIELD_SEPARATOR|PDCB_SECONDS_DECIMAL;
}
else if (ch == ',')
{
pNode->uCouldBe = PDCB_REMOVEABLE|PDCB_SECONDS_DECIMAL|PDCB_DAY_OF_MONTH_SUFFIX;
}
p++;
}
else
{
char *pSave = p;
do
{
p++;
ch = *p;
} while (iType == LexTable[ch]);
pNode = PD_NewNode();
if (!pNode)
{
return NULL;
}
pNode->pNextNode = 0;
pNode->pPrevNode = 0;
size_t nLen = p - pSave;
pNode->nToken = nLen;
pNode->pToken = pSave;
pNode->iToken = 0;
pNode->uCouldBe = PDCB_NOTHING;
if (iType == PD_LEX_DIGIT)
{
pNode->uTokenType = PDTT_NUMERIC_UNSIGNED;
if (1 <= nLen && nLen <= 9)
{
char buff[10];
memcpy(buff, pSave, nLen);
buff[nLen] = '\0';
pNode->iToken = mux_atol(buff);
ClassifyNumericToken(pNode);
}
}
else if (iType == PD_LEX_SPACE)
{
pNode->uTokenType = PDTT_SPACES;
pNode->uCouldBe = PDCB_WHITESPACE;
}
else if (iType == PD_LEX_ALPHA)
{
pNode->uTokenType = PDTT_TEXT;
// Match Text.
//
int j = 0;
bool bFound = false;
while (PD_TextTable[j].szText)
{
if ( strlen(PD_TextTable[j].szText) == nLen
&& mux_memicmp(PD_TextTable[j].szText, pSave, nLen) == 0)
{
pNode->uCouldBe = PD_TextTable[j].uCouldBe;
pNode->iToken = PD_TextTable[j].iValue;
bFound = true;
break;
}
j++;
}
if (!bFound)
{
return NULL;
}
}
}
*ppString = p;
return pNode;
}
const PD_BREAKDOWN BreakDownTable[] =
{
{ PDCB_HMS_TIME, BreakDownHMS },
{ PDCB_HMS_TIMEZONE, BreakDownHMS },
{ PDCB_YD, BreakDownYD },
{ PDCB_YMD, BreakDownYMD },
{ PDCB_MDY, BreakDownMDY },
{ PDCB_DMY, BreakDownDMY },
{ 0, 0 }
};
void PD_Pass2(void)
{
PD_Node *pNode = PD_FirstNode();
while (pNode)
{
PD_Node *pPrev = PD_PrevNode(pNode);
PD_Node *pNext = PD_NextNode(pNode);
// Absorb information from PDCB_TIME_FIELD_SEPARATOR.
//
if (pNode->uCouldBe & PDCB_TIME_FIELD_SEPARATOR)
{
if (pPrev && pNext)
{
if ( (pPrev->uCouldBe & (PDCB_HOUR_TIME|PDCB_HOUR_TIMEZONE))
&& (pNext->uCouldBe & PDCB_MINUTE))
{
pPrev->uCouldBe &= (PDCB_HOUR_TIME|PDCB_HOUR_TIMEZONE);
pNext->uCouldBe = PDCB_MINUTE;
}
else if ( (pPrev->uCouldBe & PDCB_MINUTE)
&& (pNext->uCouldBe & PDCB_SECOND))
{
pPrev->uCouldBe = PDCB_MINUTE;
pNext->uCouldBe = PDCB_SECOND;
}
}
pNode->uCouldBe = PDCB_REMOVEABLE;
}
// Absorb information from PDCB_SECONDS_DECIMAL.
//
if (pNode->uCouldBe & PDCB_SECONDS_DECIMAL)
{
if ( pPrev
&& pNext
&& pPrev->uCouldBe == PDCB_SECOND
&& (pNext->uCouldBe & PDCB_SUBSECOND))
{
pNode->uCouldBe = PDCB_SECONDS_DECIMAL;
pNext->uCouldBe = PDCB_SUBSECOND;
}
else
{
pNode->uCouldBe &= ~PDCB_SECONDS_DECIMAL;
}
pNode->uCouldBe = PDCB_REMOVEABLE;
}
// Absorb information from PDCB_SUBSECOND
//
if (pNode->uCouldBe != PDCB_SUBSECOND)
{
pNode->uCouldBe &= ~PDCB_SUBSECOND;
}
// Absorb information from PDCB_DATE_FIELD_SEPARATOR.
//
if (pNode->uCouldBe & PDCB_DATE_FIELD_SEPARATOR)
{
pNode->uCouldBe &= ~PDCB_DATE_FIELD_SEPARATOR;
#define PDCB_SEPS (PDCB_YEAR|PDCB_MONTH|PDCB_DAY_OF_MONTH|PDCB_DAY_OF_YEAR|PDCB_WEEK_OF_YEAR|PDCB_DAY_OF_WEEK)
if ( pPrev
&& pNext
&& (pPrev->uCouldBe & PDCB_SEPS)
&& (pNext->uCouldBe & PDCB_SEPS))
{
pPrev->uCouldBe &= PDCB_SEPS;
pNext->uCouldBe &= PDCB_SEPS;
pNode->uCouldBe = PDCB_REMOVEABLE;
}
}
// Process PDCB_DAY_OF_MONTH_SUFFIX
//
if (pNode->uCouldBe & PDCB_DAY_OF_MONTH_SUFFIX)
{
pNode->uCouldBe = PDCB_REMOVEABLE;
if ( pPrev
&& (pPrev->uCouldBe & PDCB_DAY_OF_MONTH))
{
pPrev->uCouldBe = PDCB_DAY_OF_MONTH;
}
}
// Absorb semantic meaning of PDCB_SIGN.
//
if (pNode->uCouldBe == PDCB_SIGN)
{
#define PDCB_SIGNABLES_POS (PDCB_HMS_TIME|PDCB_HMS_TIMEZONE)
#define PDCB_SIGNABLES_NEG (PDCB_YEAR|PDCB_YD|PDCB_SIGNABLES_POS|PDCB_YMD)
unsigned Signable;
if (pNode->pToken[0] == '-')
{
Signable = PDCB_SIGNABLES_NEG;
}
else
{
Signable = PDCB_SIGNABLES_POS;
}
if ( pNext
&& (pNext->uCouldBe & Signable))
{
pNext->uCouldBe &= Signable;
}
else
{
pNode->uCouldBe = PDCB_REMOVEABLE;
}
}
// A timezone HOUR or HMS requires a leading sign.
//
if (pNode->uCouldBe & (PDCB_HMS_TIMEZONE|PDCB_HOUR_TIMEZONE))
{
if ( !pPrev
|| pPrev->uCouldBe != PDCB_SIGN)
{
pNode->uCouldBe &= ~(PDCB_HMS_TIMEZONE|PDCB_HOUR_TIMEZONE);
}
}
// Likewise, a PDCB_HOUR_TIME or PDCB_HMS_TIME cannot have a
// leading sign.
//
if (pNode->uCouldBe & (PDCB_HMS_TIME|PDCB_HOUR_TIME))
{
if ( pPrev
&& pPrev->uCouldBe == PDCB_SIGN)
{
pNode->uCouldBe &= ~(PDCB_HMS_TIME|PDCB_HOUR_TIME);
}
}
// Remove PDCB_WHITESPACE.
//
if (pNode->uCouldBe & (PDCB_WHITESPACE|PDCB_REMOVEABLE))
{
PD_RemoveNode(pNode);
}
pNode = pNext;
}
}
typedef struct tag_pd_cantbe
{
unsigned int mask;
unsigned int cantbe;
} PD_CANTBE;
const PD_CANTBE CantBeTable[] =
{
{ PDCB_YEAR, PDCB_YEAR|PDCB_YD|PDCB_YMD|PDCB_MDY|PDCB_DMY },
{ PDCB_MONTH, PDCB_MONTH|PDCB_WEEK_OF_YEAR|PDCB_DAY_OF_YEAR|PDCB_YD|PDCB_YMD|PDCB_MDY|PDCB_DMY|PDCB_WEEK_OF_YEAR_PREFIX },
{ PDCB_DAY_OF_MONTH, PDCB_DAY_OF_MONTH|PDCB_WEEK_OF_YEAR|PDCB_DAY_OF_YEAR|PDCB_YD|PDCB_YMD|PDCB_MDY|PDCB_DMY|PDCB_WEEK_OF_YEAR_PREFIX },
{ PDCB_DAY_OF_WEEK, PDCB_DAY_OF_WEEK },
{ PDCB_WEEK_OF_YEAR, PDCB_WEEK_OF_YEAR|PDCB_MONTH|PDCB_DAY_OF_MONTH|PDCB_YMD|PDCB_MDY|PDCB_DMY },
{ PDCB_DAY_OF_YEAR, PDCB_DAY_OF_YEAR|PDCB_MONTH|PDCB_DAY_OF_MONTH|PDCB_WEEK_OF_YEAR|PDCB_YMD|PDCB_MDY|PDCB_DMY|PDCB_WEEK_OF_YEAR_PREFIX },
{ PDCB_YD, PDCB_YEAR|PDCB_YD|PDCB_MONTH|PDCB_DAY_OF_MONTH|PDCB_WEEK_OF_YEAR|PDCB_YMD|PDCB_MDY|PDCB_DMY|PDCB_WEEK_OF_YEAR_PREFIX },
{ PDCB_YMD, PDCB_YEAR|PDCB_YMD|PDCB_MDY|PDCB_DMY|PDCB_MONTH|PDCB_DAY_OF_MONTH|PDCB_WEEK_OF_YEAR_PREFIX|PDCB_WEEK_OF_YEAR|PDCB_YD|PDCB_DAY_OF_YEAR },
{ PDCB_MDY, PDCB_YEAR|PDCB_YMD|PDCB_MDY|PDCB_DMY|PDCB_MONTH|PDCB_DAY_OF_MONTH|PDCB_WEEK_OF_YEAR_PREFIX|PDCB_WEEK_OF_YEAR|PDCB_YD|PDCB_DAY_OF_YEAR },
{ PDCB_DMY, PDCB_YEAR|PDCB_YMD|PDCB_MDY|PDCB_DMY|PDCB_MONTH|PDCB_DAY_OF_MONTH|PDCB_WEEK_OF_YEAR_PREFIX|PDCB_WEEK_OF_YEAR|PDCB_YD|PDCB_DAY_OF_YEAR },
{ PDCB_YMD|PDCB_MDY, PDCB_YEAR|PDCB_YMD|PDCB_MDY|PDCB_DMY|PDCB_MONTH|PDCB_DAY_OF_MONTH|PDCB_WEEK_OF_YEAR_PREFIX|PDCB_WEEK_OF_YEAR|PDCB_YD|PDCB_DAY_OF_YEAR },
{ PDCB_MDY|PDCB_DMY, PDCB_YEAR|PDCB_YMD|PDCB_MDY|PDCB_DMY|PDCB_MONTH|PDCB_DAY_OF_MONTH|PDCB_WEEK_OF_YEAR_PREFIX|PDCB_WEEK_OF_YEAR|PDCB_YD|PDCB_DAY_OF_YEAR },
{ PDCB_YMD|PDCB_DMY, PDCB_YEAR|PDCB_YMD|PDCB_MDY|PDCB_DMY|PDCB_MONTH|PDCB_DAY_OF_MONTH|PDCB_WEEK_OF_YEAR_PREFIX|PDCB_WEEK_OF_YEAR|PDCB_YD|PDCB_DAY_OF_YEAR },
{ PDCB_YMD|PDCB_DMY|PDCB_MDY, PDCB_YEAR|PDCB_YMD|PDCB_MDY|PDCB_DMY|PDCB_MONTH|PDCB_DAY_OF_MONTH|PDCB_WEEK_OF_YEAR_PREFIX|PDCB_WEEK_OF_YEAR|PDCB_YD|PDCB_DAY_OF_YEAR },
{ PDCB_TIMEZONE, PDCB_TIMEZONE|PDCB_HMS_TIMEZONE|PDCB_HOUR_TIMEZONE },
{ PDCB_HOUR_TIME, PDCB_HMS_TIME|PDCB_HOUR_TIME },
{ PDCB_HOUR_TIMEZONE, PDCB_TIMEZONE|PDCB_HMS_TIMEZONE|PDCB_HOUR_TIMEZONE },
{ PDCB_HMS_TIME, PDCB_HMS_TIME|PDCB_HOUR_TIME },
{ PDCB_HMS_TIMEZONE, PDCB_TIMEZONE|PDCB_HMS_TIMEZONE|PDCB_HOUR_TIMEZONE },
{ 0, 0 }
};
void PD_Deduction(void)
{
PD_Node *pNode = PD_FirstNode();
while (pNode)
{
int j =0;
while (CantBeTable[j].mask)
{
if (pNode->uCouldBe == CantBeTable[j].mask)
{
PD_Node *pNodeInner = PD_FirstNode();
while (pNodeInner)
{
pNodeInner->uCouldBe &= ~CantBeTable[j].cantbe;
pNodeInner = PD_NextNode(pNodeInner);
}
pNode->uCouldBe = CantBeTable[j].mask;
break;
}
j++;
}
pNode = PD_NextNode(pNode);
}
}
void PD_BreakItDown(void)
{
PD_Node *pNode = PD_FirstNode();
while (pNode)
{
int j =0;
while (BreakDownTable[j].mask)
{
if (pNode->uCouldBe == BreakDownTable[j].mask)
{
BreakDownTable[j].fpBreakDown(pNode);
break;
}
j++;
}
pNode = PD_NextNode(pNode);
}
}
void PD_Pass5(void)
{
bool bHaveSeenTimeHour = false;
bool bMightHaveSeenTimeHour = false;
PD_Node *pNode = PD_FirstNode();
while (pNode)
{
PD_Node *pPrev = PD_PrevNode(pNode);
PD_Node *pNext = PD_NextNode(pNode);
// If all that is left is PDCB_HMS_TIME and PDCB_HOUR_TIME, then
// it's PDCB_HOUR_TIME.
//
if (pNode->uCouldBe == (PDCB_HMS_TIME|PDCB_HOUR_TIME))
{
pNode->uCouldBe = PDCB_HOUR_TIME;
}
if (pNode->uCouldBe == (PDCB_HMS_TIMEZONE|PDCB_HOUR_TIMEZONE))
{
pNode->uCouldBe = PDCB_HOUR_TIMEZONE;
}
// PDCB_MINUTE must follow an PDCB_HOUR_TIME or PDCB_HOUR_TIMEZONE.
//
if (pNode->uCouldBe & PDCB_MINUTE)
{
if ( !pPrev
|| !(pPrev->uCouldBe & (PDCB_HOUR_TIME|PDCB_HOUR_TIMEZONE)))
{
pNode->uCouldBe &= ~PDCB_MINUTE;
}
}
// PDCB_SECOND must follow an PDCB_MINUTE.
//
if (pNode->uCouldBe & PDCB_SECOND)
{
if ( !pPrev
|| !(pPrev->uCouldBe & PDCB_MINUTE))
{
pNode->uCouldBe &= ~PDCB_SECOND;
}
}
// YMD MDY DMY
//
// PDCB_DAY_OF_MONTH cannot follow PDCB_YEAR.
//
if ( (pNode->uCouldBe & PDCB_DAY_OF_MONTH)
&& pPrev
&& pPrev->uCouldBe == PDCB_YEAR)
{
pNode->uCouldBe &= ~PDCB_DAY_OF_MONTH;
}
// Timezone cannot occur before the time.
//
if ( (pNode->uCouldBe & PDCB_TIMEZONE)
&& !bMightHaveSeenTimeHour)
{
pNode->uCouldBe &= ~PDCB_TIMEZONE;
}
// TimeDateSeparator cannot occur after the time.
//
if ( (pNode->uCouldBe & PDCB_DATE_TIME_SEPARATOR)
&& bHaveSeenTimeHour)
{
pNode->uCouldBe &= ~PDCB_DATE_TIME_SEPARATOR;
}
if (pNode->uCouldBe == PDCB_DATE_TIME_SEPARATOR)
{
PD_Node *pNodeInner = PD_FirstNode();
while (pNodeInner && pNodeInner != pNode)
{
pNodeInner->uCouldBe &= ~(PDCB_TIMEZONE|PDCB_HOUR_TIME|PDCB_HOUR_TIMEZONE|PDCB_MINUTE|PDCB_SECOND|PDCB_SUBSECOND|PDCB_MERIDIAN|PDCB_HMS_TIME|PDCB_HMS_TIMEZONE);
pNodeInner = PD_NextNode(pNodeInner);
}
pNodeInner = pNext;
while (pNodeInner)
{
pNodeInner->uCouldBe &= ~(PDCB_WEEK_OF_YEAR_PREFIX|PDCB_YD|PDCB_YMD|PDCB_MDY|PDCB_DMY|PDCB_YEAR|PDCB_MONTH|PDCB_DAY_OF_MONTH|PDCB_DAY_OF_WEEK|PDCB_WEEK_OF_YEAR|PDCB_DAY_OF_YEAR);
pNodeInner = PD_NextNode(pNodeInner);
}
pNode->uCouldBe = PDCB_REMOVEABLE;
}
if (pNode->uCouldBe & PDCB_WEEK_OF_YEAR_PREFIX)
{
if ( pNext
&& (pNext->uCouldBe & PDCB_WEEK_OF_YEAR))
{
pNext->uCouldBe = PDCB_WEEK_OF_YEAR;
pNode->uCouldBe = PDCB_REMOVEABLE;
}
else if (pNode->uCouldBe == PDCB_WEEK_OF_YEAR_PREFIX)
{
pNode->uCouldBe = PDCB_REMOVEABLE;
}
}
if (pNode->uCouldBe & (PDCB_HOUR_TIME|PDCB_HMS_TIME))
{
if ((pNode->uCouldBe & ~(PDCB_HOUR_TIME|PDCB_HMS_TIME)) == 0)
{
bHaveSeenTimeHour = true;
}
bMightHaveSeenTimeHour = true;
}
// Remove PDCB_REMOVEABLE.
//
if (pNode->uCouldBe & PDCB_REMOVEABLE)
{
PD_RemoveNode(pNode);
}
pNode = pNext;
}
}
void PD_Pass6(void)
{
int cYear = 0;
int cMonth = 0;
int cDayOfMonth = 0;
int cWeekOfYear = 0;
int cDayOfYear = 0;
int cDayOfWeek = 0;
int cTime = 0;
PD_Node *pNode = PD_FirstNode();
while (pNode)
{
if (pNode->uCouldBe & (PDCB_HMS_TIME|PDCB_HOUR_TIME))
{
cTime++;
}
if (pNode->uCouldBe & PDCB_WEEK_OF_YEAR)
{
cWeekOfYear++;
}
if (pNode->uCouldBe & (PDCB_YEAR|PDCB_YD|PDCB_YMD|PDCB_MDY|PDCB_DMY))
{
cYear++;
}
if (pNode->uCouldBe & (PDCB_MONTH|PDCB_YMD|PDCB_MDY|PDCB_DMY))
{
cMonth++;
}
if (pNode->uCouldBe & (PDCB_DAY_OF_MONTH|PDCB_YMD|PDCB_MDY|PDCB_DMY))
{
cDayOfMonth++;
}
if (pNode->uCouldBe & PDCB_DAY_OF_WEEK)
{
cDayOfWeek++;
}
if (pNode->uCouldBe & (PDCB_DAY_OF_YEAR|PDCB_YD))
{
cDayOfYear++;
}
pNode = PD_NextNode(pNode);
}
unsigned OnlyOneMask = 0;
unsigned CantBeMask = 0;
if (cYear == 1)
{
OnlyOneMask |= PDCB_YEAR|PDCB_YD|PDCB_YMD|PDCB_MDY|PDCB_DMY;
}
if (cTime == 1)
{
OnlyOneMask |= PDCB_HMS_TIME|PDCB_HOUR_TIME;
}
if (cMonth == 0 || cDayOfMonth == 0)
{
CantBeMask |= PDCB_MONTH|PDCB_DAY_OF_MONTH;
}
if (cDayOfWeek == 0)
{
CantBeMask |= PDCB_WEEK_OF_YEAR;
}
if ( cMonth == 1 && cDayOfMonth == 1
&& (cWeekOfYear != 1 || cDayOfWeek != 1)
&& cDayOfYear != 1)
{
OnlyOneMask |= PDCB_MONTH|PDCB_YMD|PDCB_MDY|PDCB_DMY;
OnlyOneMask |= PDCB_DAY_OF_MONTH;
CantBeMask |= PDCB_WEEK_OF_YEAR|PDCB_YD;
}
else if (cDayOfYear == 1 && (cWeekOfYear != 1 || cDayOfWeek != 1))
{
OnlyOneMask |= PDCB_DAY_OF_YEAR|PDCB_YD;
CantBeMask |= PDCB_WEEK_OF_YEAR|PDCB_MONTH|PDCB_YMD|PDCB_MDY|PDCB_DMY;
CantBeMask |= PDCB_DAY_OF_MONTH;
}
else if (cWeekOfYear == 1 && cDayOfWeek == 1)
{
OnlyOneMask |= PDCB_WEEK_OF_YEAR;
OnlyOneMask |= PDCB_DAY_OF_WEEK;
CantBeMask |= PDCB_YD|PDCB_MONTH|PDCB_YMD|PDCB_MDY|PDCB_DMY;
CantBeMask |= PDCB_DAY_OF_MONTH;
}
// Also, if we match OnlyOneMask, then force only something in
// OnlyOneMask.
//
pNode = PD_FirstNode();
while (pNode)
{
if (pNode->uCouldBe & OnlyOneMask)
{
pNode->uCouldBe &= OnlyOneMask;
}
if (pNode->uCouldBe & ~CantBeMask)
{
pNode->uCouldBe &= ~CantBeMask;
}
pNode = PD_NextNode(pNode);
}
}
bool PD_GetFields(ALLFIELDS *paf)
{
paf->iYear = NOT_PRESENT;
paf->iDayOfYear = NOT_PRESENT;
paf->iMonthOfYear = NOT_PRESENT;
paf->iDayOfMonth = NOT_PRESENT;
paf->iWeekOfYear = NOT_PRESENT;
paf->iDayOfWeek = NOT_PRESENT;
paf->iHourTime = NOT_PRESENT;
paf->iMinuteTime = NOT_PRESENT;
paf->iSecondTime = NOT_PRESENT;
paf->iMillisecondTime = NOT_PRESENT;
paf->iMicrosecondTime = NOT_PRESENT;
paf->iNanosecondTime = NOT_PRESENT;
paf->iMinuteTimeZone = NOT_PRESENT;
PD_Node *pNode = PD_FirstNode();
while (pNode)
{
if (pNode->uCouldBe == PDCB_YEAR)
{
paf->iYear = pNode->iToken;
PD_Node *pPrev = PD_PrevNode(pNode);
if ( pPrev
&& pPrev->uCouldBe == PDCB_SIGN
&& pPrev->pToken[0] == '-')
{
paf->iYear = -paf->iYear;
}
}
else if (pNode->uCouldBe == PDCB_DAY_OF_YEAR)
{
paf->iDayOfYear = pNode->iToken;
}
else if (pNode->uCouldBe == PDCB_MONTH)
{
paf->iMonthOfYear = pNode->iToken;
}
else if (pNode->uCouldBe == PDCB_DAY_OF_MONTH)
{
paf->iDayOfMonth = pNode->iToken;
}
else if (pNode->uCouldBe == PDCB_WEEK_OF_YEAR)
{
paf->iWeekOfYear = pNode->iToken;
}
else if (pNode->uCouldBe == PDCB_DAY_OF_WEEK)
{
paf->iDayOfWeek = pNode->iToken;
}
else if (pNode->uCouldBe == PDCB_HOUR_TIME)
{
paf->iHourTime = pNode->iToken;
pNode = PD_NextNode(pNode);
if ( pNode
&& pNode->uCouldBe == PDCB_MINUTE)
{
paf->iMinuteTime = pNode->iToken;
pNode = PD_NextNode(pNode);
if ( pNode
&& pNode->uCouldBe == PDCB_SECOND)
{
paf->iSecondTime = pNode->iToken;
pNode = PD_NextNode(pNode);
if ( pNode
&& pNode->uCouldBe == PDCB_SUBSECOND)
{
unsigned short ms, us, ns;
ParseDecimalSeconds(pNode->nToken, pNode->pToken, &ms,
&us, &ns);
paf->iMillisecondTime = ms;
paf->iMicrosecondTime = us;
paf->iNanosecondTime = ns;
pNode = PD_NextNode(pNode);
}
}
}
if ( pNode
&& pNode->uCouldBe == PDCB_MERIDIAN)
{
if (paf->iHourTime == 12)
{
paf->iHourTime = 0;
}
paf->iHourTime += pNode->iToken;
pNode = PD_NextNode(pNode);
}
continue;
}
else if (pNode->uCouldBe == PDCB_HOUR_TIMEZONE)
{
paf->iMinuteTimeZone = pNode->iToken * 60;
PD_Node *pPrev = PD_PrevNode(pNode);
if ( pPrev
&& pPrev->uCouldBe == PDCB_SIGN
&& pPrev->pToken[0] == '-')
{
paf->iMinuteTimeZone = -paf->iMinuteTimeZone;
}
pNode = PD_NextNode(pNode);
if ( pNode
&& pNode->uCouldBe == PDCB_MINUTE)
{
if (paf->iMinuteTimeZone < 0)
{
paf->iMinuteTimeZone -= pNode->iToken;
}
else
{
paf->iMinuteTimeZone += pNode->iToken;
}
pNode = PD_NextNode(pNode);
}
continue;
}
else if (pNode->uCouldBe == PDCB_TIMEZONE)
{
if (pNode->iToken < 0)
{
paf->iMinuteTimeZone = (pNode->iToken / 100) * 60
- ((-pNode->iToken) % 100);
}
else
{
paf->iMinuteTimeZone = (pNode->iToken / 100) * 60
+ pNode->iToken % 100;
}
}
else if (pNode->uCouldBe & (PDCB_SIGN|PDCB_DATE_TIME_SEPARATOR))
{
; // Nothing
}
else
{
return false;
}
pNode = PD_NextNode(pNode);
}
return true;
}
bool ConvertAllFieldsToLinearTime(CLinearTimeAbsolute <a, ALLFIELDS *paf)
{
FIELDEDTIME ft;
memset(&ft, 0, sizeof(ft));
int iExtraDays = 0;
if (paf->iYear == NOT_PRESENT)
{
return false;
}
ft.iYear = paf->iYear;
if (paf->iMonthOfYear != NOT_PRESENT && paf->iDayOfMonth != NOT_PRESENT)
{
ft.iMonth = paf->iMonthOfYear;
ft.iDayOfMonth = paf->iDayOfMonth;
}
else if (paf->iDayOfYear != NOT_PRESENT)
{
iExtraDays = paf->iDayOfYear - 1;
ft.iMonth = 1;
ft.iDayOfMonth = 1;
}
else if (paf->iWeekOfYear != NOT_PRESENT && paf->iDayOfWeek != NOT_PRESENT)
{
// Remember that iYear in this case represents an ISO year, which
// is not exactly the same thing as a Gregorian year.
//
FIELDEDTIME ftWD;
memset(&ftWD, 0, sizeof(ftWD));
ftWD.iYear = paf->iYear - 1;
ftWD.iMonth = 12;
ftWD.iDayOfMonth = 27;
if (!lta.SetFields(&ftWD))
{
return false;
}
INT64 i64 = lta.Return100ns();
INT64 j64;
i64FloorDivisionMod(i64+FACTOR_100NS_PER_DAY, FACTOR_100NS_PER_WEEK, &j64);
i64 -= j64;
// i64 now corresponds to the Sunday that strickly preceeds before
// December 28th, and the 28th is guaranteed to be in the previous
// year so that the ISO and Gregorian Years are the same thing.
//
i64 += FACTOR_100NS_PER_WEEK*paf->iWeekOfYear;
i64 += FACTOR_100NS_PER_DAY*paf->iDayOfWeek;
lta.Set100ns(i64);
lta.ReturnFields(&ft);
// Validate that this week actually has a week 53.
//
if (paf->iWeekOfYear == 53)
{
int iDOW_ISO = (ft.iDayOfWeek == 0) ? 7 : ft.iDayOfWeek;
int j = ft.iDayOfMonth - iDOW_ISO;
if (ft.iMonth == 12)
{
if (28 <= j)
{
return false;
}
}
else // if (ft.iMonth == 1)
{
if (-3 <= j)
{
return false;
}
}
}
}
else
{
// Under-specified.
//
return false;
}
if (paf->iHourTime != NOT_PRESENT)
{
ft.iHour = paf->iHourTime;
if (paf->iMinuteTime != NOT_PRESENT)
{
ft.iMinute = paf->iMinuteTime;
if (paf->iSecondTime != NOT_PRESENT)
{
ft.iSecond = paf->iSecondTime;
if (paf->iMillisecondTime != NOT_PRESENT)
{
ft.iMillisecond = paf->iMillisecondTime;
ft.iMicrosecond = paf->iMicrosecondTime;
ft.iNanosecond = paf->iNanosecondTime;
}
}
}
}
if (lta.SetFields(&ft))
{
CLinearTimeDelta ltd;
if (paf->iMinuteTimeZone != NOT_PRESENT)
{
ltd.SetSeconds(60 * paf->iMinuteTimeZone);
lta -= ltd;
}
if (iExtraDays)
{
ltd.Set100ns(FACTOR_100NS_PER_DAY);
lta += ltd * iExtraDays;
}
return true;
}
return false;
}
bool ParseDate
(
CLinearTimeAbsolute <,
char *pDateString,
bool *pbZoneSpecified
)
{
PD_Reset();
char *p = pDateString;
PD_Node *pNode;
while ((pNode = PD_ScanNextToken(&p)))
{
PD_AppendNode(pNode);
}
PD_Pass2();
PD_Deduction();
PD_BreakItDown();
PD_Pass5();
PD_Pass6();
PD_Deduction();
PD_BreakItDown();
PD_Pass5();
PD_Pass6();
ALLFIELDS af;
if ( PD_GetFields(&af)
&& ConvertAllFieldsToLinearTime(lt, &af))
{
*pbZoneSpecified = (af.iMinuteTimeZone != NOT_PRESENT);
return true;
}
return false;
}