mux2.4/game/data/
mux2.4/src/tools/
// 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 &lta)
{
    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 &lta,
    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(&lt);
    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 &lta,
    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, &ltdOffset, 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, &ltdDontCare, &bDontCare, &iEntry))
    {
        QueryLocalOffsetAt_Internal(ltaProbe, &bDontCare, iEntry);
    }

    ltaProbe = lta + ltdIntervalMinimum;
    if (!QueryOffsetTable(ltaProbe, &ltdDontCare, &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 &lta, 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 &lt,
    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;
}