/*++

Copyright (c) 1994-1998,  Microsoft Corporation  All rights reserved.

Module Name:

    date.c

Abstract:

    This module implements the textual representations of
    Day Month Year : Hours Min Seconds for the Date/Time applet.

Revision History:

--*/



//
//  Include Files.
//

#include "timedate.h"
#include <commctrl.h>
#include <prsht.h>
#include <regstr.h>
#include <imm.h>
#include "clock.h"
#include "mapctl.h"
#include "inettime.h"
#include "rc.h"
#include <help.h>
#include <shlwapi.h>
#include <windows.h>
#include <windowsx.h>




//
//  Constant Declarations.
//

#define TZNAME_SIZE          128
#define TZDISPLAYZ           128

#define BIAS_ONE_HOUR        (-60L)

#define ZONE_IMAGE_SCALE     (356)
#define ZONE_BIAS_SCALE      (-1440)
#define ZONE_IMAGE_LEFT      (120)
#define ZONE_IMAGE_WIDTH     (80)

#define BIAS_PLUS_12         (12L * BIAS_ONE_HOUR)
#define BIAS_MINUS_12        (- BIAS_PLUS_12)

#define TZ_HIT_NONE          (0)
#define TZ_HIT_BASE          (1)
#define TZ_HIT_PARTIAL       (2)
#define TZ_HIT_EXACT         (3)




//
//  Global Variables.
//

TCHAR const szIntl[] = TEXT("intl");

//
//  Default used if none could be found.
//
INTLSTRUCT IntlDef =
{
    TEXT("Other Country"),
    1, 0, 0, 0, 0, 2, 0, 1, 2, 1,
    TEXT("AM"),
    TEXT("PM"),
    TEXT("$"),
    TEXT(","),
    TEXT("."),
    TEXT("/"),
    TEXT(":"),
    TEXT(","),
    TEXT("dddd, MMMM dd, yyyy"),
    TEXT("M/d/yyyy"),
    TEXT("USA"),
    1, 0, 1, 0, 0x0409,
    TEXT("hh:mm:ss tt"),
    0, 1,
    TEXT(","),
    TEXT(".")
};

BOOL g_bFirstBoot = FALSE;   // for first boot during setup

int g_Time[3];               // time the user currently has set
int g_LastTime[3];           // last displayed time - to stop flicker

short wDateTime[7];                 // values for first 7 date/time items
short wPrevDateTime[7];             // only repaint fields if necessary
BOOL  fDateDirty;

//
//  Formatting strings for AM and PM
//
TCHAR sz1159[12];
TCHAR sz2359[12];

//
//  Are we in 24 hour time.  If not, is it AM or PM.
//
BOOL g_b24HR;
BOOL g_bPM;

//
//  This flag indicates if the user has tried to change the time.
//  If so, then we stop providing the system time and use the
//  time that we store internally. We send the clock control our
//  TimeProvider function.
//
WORD g_Modified = 0;
WORD g_WasModified = 0;

//
//  Which of the HMS MDY have leading zeros.
//
BOOL g_bLZero[6] = {FALSE, TRUE, TRUE, FALSE, FALSE, FALSE};

//
//  Ranges of HMS MDY
//
struct
{
    int nMax;
    int nMin;
} g_sDateInfo[] =
{
    23, 0,
    59, 0,
    59, 0,
    12, 1,
    31, 1,
    2099, 1980,
};

//
//  Time Zone info globals.
//
int g_nTimeZones = 0;
TIME_ZONE_INFORMATION g_tziCurrent, *g_ptziCurrent = NULL;

//
//  Registry location for Time Zone information.
//
#ifdef WINNT
  TCHAR c_szTimeZones[] = TEXT("Software\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones");
#else
  TCHAR c_szTimeZones[] = TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Time Zones");
#endif

//
//  Time Zone data value keys.
//
TCHAR c_szTZDisplayName[]  = TEXT("Display");
TCHAR c_szTZStandardName[] = TEXT("Std");
TCHAR c_szTZDaylightName[] = TEXT("Dlt");
TCHAR c_szTZI[]            = TEXT("TZI");
TCHAR c_szTZMapInfo[]      = TEXT("MapID");

//
//  IME globals.
//
HIMC g_PrevIMCForDateField;




//
//  Context Help Ids.
//

const DWORD aDateTimeHelpIds[] =
{
    IDD_GROUPBOX1,      IDH_DATETIME_DATE_GROUP,
    IDD_GROUPBOX2,      IDH_DATETIME_TIME,
    DATETIME_CURTZ,     IDH_DATETIME_CURRENT_TIME_ZONE,
    DATETIME_CALENDAR,  IDH_DATETIME_DATE,
    DATETIME_CLOCK,     IDH_DATETIME_TIME,
    DATETIME_TBORDER,   IDH_DATETIME_TIME,
    DATETIME_HOUR,      IDH_DATETIME_TIME,
    DATETIME_TSEP1,     IDH_DATETIME_TIME,
    DATETIME_MINUTE,    IDH_DATETIME_TIME,
    DATETIME_TSEP2,     IDH_DATETIME_TIME,
    DATETIME_SECOND,    IDH_DATETIME_TIME,
    DATETIME_AMPM,      IDH_DATETIME_TIME,
    DATETIME_TARROW,    IDH_DATETIME_TIME,
    DATETIME_MONTHNAME, IDH_DATETIME_MONTH,
    DATETIME_YEAR,      IDH_DATETIME_YEAR,
    DATETIME_YARROW,    IDH_DATETIME_YEAR,
    IDD_TIMEZONES,      IDH_DATETIME_TIMEZONE,
//  IDD_TIMEMAP,        IDH_DATETIME_BITMAP,
    IDD_TIMEMAP,        NO_HELP,
    IDD_AUTOMAGIC,      IDH_DATETIME_DAYLIGHT_SAVE,

    0, 0
};




//
//  Typedef Declarations.
//

//
//  Registry info goes in this structure.
//
typedef struct tagTZINFO
{
    struct tagTZINFO *next;
    TCHAR            szDisplayName[TZDISPLAYZ];
    TCHAR            szStandardName[TZNAME_SIZE];
    TCHAR            szDaylightName[TZNAME_SIZE];
    int              ComboIndex;
    int              SeaIndex;
    int              LandIndex;
    int              MapLeft;
    int              MapWidth;
    LONG             Bias;
    LONG             StandardBias;
    LONG             DaylightBias;
    SYSTEMTIME       StandardDate;
    SYSTEMTIME       DaylightDate;

} TZINFO, *PTZINFO;

//
//  State info for the time zone page.
//
typedef struct
{
    PTZINFO zone;
    BOOL initializing;
    PTZINFO lookup[MAPCTL_MAX_INDICES];

} TZPAGE_STATE;



DWORD GetTextExtent(
    HDC hdc,
    LPCTSTR lpsz,
    int cb);


#ifndef UNICODE

////////////////////////////////////////////////////////////////////////////
//
//  WideStrToStr
//
////////////////////////////////////////////////////////////////////////////

int WINAPI WideStrToStr(
    LPSTR psz,
    LPWSTR pwsz)
{
    return WideCharToMultiByte(CP_ACP, 0, pwsz, -1, psz, MAX_PATH, NULL, NULL);
}


////////////////////////////////////////////////////////////////////////////
//
//  StrToWideStr
//
////////////////////////////////////////////////////////////////////////////

int WINAPI StrToWideStr(
    LPWSTR pwsz,
    LPCSTR psz)
{
    return MultiByteToWideChar(CP_ACP, 0, psz, -1, pwsz, MAX_PATH);
}


////////////////////////////////////////////////////////////////////////////
//
//  AnsiWideStrCmpI
//
////////////////////////////////////////////////////////////////////////////

int AnsiWideStrCmpI(
    LPCSTR pszAnsi,
    WCHAR *pwszWide)
{
    char szAnsi[64];

    WideStrToStr(szAnsi, pwszWide);
    return (lstrcmpi(pszAnsi, szAnsi));
}

#endif



////////////////////////////////////////////////////////////////////////////
//
//  ParseDateElement
//
//  Assumes that the character pointed to by pszElement is a
//  'M', 'd', or 'y', and checks if the string indicates a leading zero
//  or century.  The return value is a pointer to the next character,
//  which should be a separator or NULL.  A return value of NULL indicates
//  an error.
//
////////////////////////////////////////////////////////////////////////////

LPTSTR ParseDateElement(
    LPTSTR pszElement,
    BOOL *pbLZero)
{
    //
    //  Check for valid character.
    //
    switch (*pszElement)
    {
        case ( TEXT('y') ) :
        case ( TEXT('M') ) :
        case ( TEXT('d') ) :
        {
            break;
        }
        default:
        {
            return (NULL);
        }
    }

    ++pszElement;

    if (*pszElement != *(pszElement - 1))
    {
        *pbLZero = 0;
    }
    else
    {
        *pbLZero = 1;

        if (*pszElement++ == TEXT('y'))
        {
            if (!(*pszElement == TEXT('y')))
            {
                *pbLZero = 0;
            }
            else
            {
                if (!(*++pszElement == TEXT('y')))
                {
                    //
                    //  Found 3 y's, invalid format.
                    //
                    return (NULL);
                }
                else
                {
                    ++pszElement;
                }
            }
        }
    }

    return (pszElement);
}


int rgMoveTimeControls [] = 
{
    DATETIME_HOUR,
    DATETIME_MINUTE,
    DATETIME_SECOND,
    DATETIME_TSEP1,
    DATETIME_TSEP2
};


////////////////////////////////////////////////////////////////////////////
//
//  AdjustAMPMPosition
//
////////////////////////////////////////////////////////////////////////////
void AdjustAMPMPosition(HWND hwnd)
{
   TCHAR    szTimePrefix[5];
   static BOOL fMoved = FALSE;

   GetLocaleInfo(LOCALE_USER_DEFAULT, 
                 LOCALE_ITIMEMARKPOSN,
                 szTimePrefix,
                 ARRAYSIZE(szTimePrefix));

   if (!fMoved && szTimePrefix[0] == TEXT('1'))
   {
        RECT rLeftCtl, rAMPMCtl, rCurrCtl;
        HWND hwndAMPM, hwndCurr;
        int i, width;
        POINT pt;

        fMoved = TRUE;
        
        //Get rect of left most control (hours)
        GetWindowRect(GetDlgItem(hwnd, DATETIME_HOUR), &rLeftCtl);
        
        //Get rect of AM PM control
        hwndAMPM = GetDlgItem(hwnd, DATETIME_AMPM);
        GetWindowRect(hwndAMPM, &rAMPMCtl);
        width = rAMPMCtl.right - rAMPMCtl.left;
        
        //Shift all controls right by the AM PM control width
        for (i = 0; i < ARRAYSIZE(rgMoveTimeControls); i++)
        {
            hwndCurr = GetDlgItem(hwnd, rgMoveTimeControls[i]);
            GetWindowRect(hwndCurr, &rCurrCtl);
            pt.x = rCurrCtl.left;
            pt.y = rCurrCtl.top;
            ScreenToClient(hwnd, &pt);

            MoveWindow(hwndCurr, pt.x + width, 
                        pt.y,
                        rCurrCtl.right - rCurrCtl.left,
                        rCurrCtl.bottom - rCurrCtl.top,
                        TRUE);
        }
        
        //Move AM PM control left to where the hours were.
        pt.x = rLeftCtl.left;
        pt.y = rAMPMCtl.top;
        ScreenToClient(hwnd, &pt);
        MoveWindow(hwndAMPM, pt.x, 
                    pt.y,
                    rAMPMCtl.right - rAMPMCtl.left,
                    rAMPMCtl.bottom - rAMPMCtl.top,
                    TRUE);
        
   }
}


////////////////////////////////////////////////////////////////////////////
//
//  MonthUpperBound
//
////////////////////////////////////////////////////////////////////////////

int _fastcall MonthUpperBound(
    int nMonth,
    int nYear)
{
    switch (nMonth)
    {
        case ( 2 ) :
        {
            //
            //  A year is a leap year if it is divisible by 4 and is not
            //  a century year (multiple of 100) or if it is divisible by
            //  400.
            //
            return ( ((nYear % 4 == 0) &&
                      ((nYear % 100 != 0) || (nYear % 400 == 0))) ? 29 : 28 );
        }
        case ( 4 ) :
        case ( 6 ) :
        case ( 9 ) :
        case ( 11 ) :
        {
            return (30);
        }
    }

    return (31);
}


////////////////////////////////////////////////////////////////////////////
//
//  IsAMPM
//
//  True if PM.
//
////////////////////////////////////////////////////////////////////////////

BOOL IsAMPM(
    int iHour)
{
    return ((iHour >= 12) ? 1 : 0);
}


////////////////////////////////////////////////////////////////////////////
//
//  GetDateTime
//
////////////////////////////////////////////////////////////////////////////

void GetDateTime()
{
    SYSTEMTIME SystemTime;

    GetLocalTime(&SystemTime);

    wDateTime[HOUR]    = SystemTime.wHour;
    wDateTime[MINUTE]  = SystemTime.wMinute;
    wDateTime[SECOND]  = SystemTime.wSecond;
    wDateTime[MONTH]   = SystemTime.wMonth;
    wDateTime[DAY]     = SystemTime.wDay;
    wDateTime[YEAR]    = SystemTime.wYear;
    wDateTime[WEEKDAY] = SystemTime.wDayOfWeek;
}


////////////////////////////////////////////////////////////////////////////
//
//  GetTime
//
////////////////////////////////////////////////////////////////////////////

void GetTime()
{
    SYSTEMTIME SystemTime;

    GetLocalTime(&SystemTime);

    wDateTime[HOUR]   = SystemTime.wHour;
    wDateTime[MINUTE] = SystemTime.wMinute;
    wDateTime[SECOND] = SystemTime.wSecond;
}


////////////////////////////////////////////////////////////////////////////
//
//  GetDate
//
////////////////////////////////////////////////////////////////////////////

void GetDate()
{
    SYSTEMTIME SystemTime;

    GetLocalTime(&SystemTime);

    wDateTime[MONTH]   = SystemTime.wMonth;
    wDateTime[DAY]     = SystemTime.wDay;
    wDateTime[YEAR]    = SystemTime.wYear;
    wDateTime[WEEKDAY] = SystemTime.wDayOfWeek;
    fDateDirty = FALSE;
}


////////////////////////////////////////////////////////////////////////////
//
//  SetTime
//
////////////////////////////////////////////////////////////////////////////

void SetTime()
{
    SYSTEMTIME SystemTime;

    SystemTime.wHour   = wDateTime[HOUR];
    SystemTime.wMinute = wDateTime[MINUTE];
    SystemTime.wSecond = wDateTime[SECOND];

    SystemTime.wMilliseconds = 0;

    SystemTime.wMonth  = wDateTime[MONTH];
    SystemTime.wDay    = wDateTime[DAY];
    SystemTime.wYear   = wDateTime[YEAR];

    SetLocalTime(&SystemTime);
    SetLocalTime(&SystemTime);
    fDateDirty = FALSE;
}


////////////////////////////////////////////////////////////////////////////
//
//  SetDate
//
////////////////////////////////////////////////////////////////////////////

void SetDate()
{
    SYSTEMTIME SystemTime;

    SystemTime.wHour   = wDateTime[HOUR];
    SystemTime.wMinute = wDateTime[MINUTE];
    SystemTime.wSecond = wDateTime[SECOND];

    SystemTime.wMilliseconds = 0;

    SystemTime.wMonth  = wDateTime[MONTH];
    SystemTime.wDay    = wDateTime[DAY];
    SystemTime.wYear   = wDateTime[YEAR];

    SetLocalTime(&SystemTime);
    SetLocalTime(&SystemTime);
    fDateDirty = FALSE;
}


////////////////////////////////////////////////////////////////////////////
//
//  AdjustDelta
//
//  Alters the variables in wDeltaDateTime, allowing a CANCEL button
//  to perform its job by resetting the time as if it had never been
//  touched.  GetTime() & GetDate() should already have been called.
//
////////////////////////////////////////////////////////////////////////////

void AdjustDelta(
    HWND hDlg,
    int nIndex)
{
    int nDelta;

    //
    //  We dont do time this way any more.
    //
    if (nIndex <= SECOND && nIndex >= HOUR)
    {
        return;
    }

    //
    //  Get position of the buddy from either the date or the time.
    //
    nDelta = (int)SendDlgItemMessage( hDlg,
                                      nIndex <= SECOND
                                        ? DATETIME_TARROW
                                        : DATETIME_YARROW,
                                      UDM_GETPOS,
                                      0,
                                      0L );

    if ((nIndex == YEAR) && !g_bLZero[YEAR])
    {
        //
        //  Years before 80 are 2080.
        //  Range is 1980...2079.
        //
        if (nDelta < 80)
        {
            nDelta += 2000;
        }
        else
        {
            nDelta += 1900;
        }
    }

    //
    //  If our current recording of the time/date is not what we have
    //  now, do deltas.
    //
    if (wDateTime[nIndex] != nDelta)
    {
        //
        //  Previous value is current user's settings.
        //
        wPrevDateTime[nIndex] = wDateTime[nIndex] = (WORD)nDelta;
        fDateDirty = TRUE;
        
        //
        // If we are changing HMS, update the time.
        //
        if (nIndex <= SECOND)
        {
            nIndex = 0;
        }
    }
}


////////////////////////////////////////////////////////////////////////////
//
//  AdjustDeltaMonth
//
//  Change the month part of wDateTime
//
////////////////////////////////////////////////////////////////////////////

extern int GetDaysOfTheMonth(int iMonth);

void AdjustDeltaMonth(
    int iMonth)
{
    GetTime();

    if (wDateTime[MONTH] != iMonth)
    {
        //
        //  Make sure the current day is valid in the new month.
        //
        if (wDateTime[DAY] > (WORD)GetDaysOfTheMonth(iMonth))
        {
            wDateTime[DAY] = (WORD)GetDaysOfTheMonth(iMonth);
        }

        wPrevDateTime[MONTH] = wDateTime[MONTH] = (WORD)iMonth;
        fDateDirty = TRUE;

        g_sDateInfo[DAY].nMax = MonthUpperBound( wDateTime[MONTH],
                                                 wDateTime[YEAR] );
    }
}


////////////////////////////////////////////////////////////////////////////
//
//  ReadShortDate
//
//  Verify that pszDate is one of MDY, DMY, or YMD.
//
////////////////////////////////////////////////////////////////////////////

int ReadShortDate(
    LPTSTR pszDate,
    BOOL *pbMonth,
    BOOL *pbDay,
    BOOL *pbYear)
{
    int i, nOrder;
    BOOL *pbOrder[3];
    TCHAR cHope[3];

    //
    //  nOrder :  0 = MDY
    //            1 = DMY
    //            2 = YMD
    //
    switch (cHope[0] = *pszDate)
    {
        case ( TEXT('M') ) :
        {
            nOrder = 0;
            pbOrder[0] = pbMonth;
            break;
        }
        case ( TEXT('d') ) :
        {
            nOrder = 1;
            pbOrder[0] = pbDay;
            break;
        }
        case ( TEXT('y') ) :
        {
            nOrder = 2;
            pbOrder[0] = pbYear;
            break;
        }
        default :
        {
            return (FALSE);
        }
    }

    //
    //  Sets element 1.
    //
    if (nOrder)         // 1 2
    {
        cHope[1] = TEXT('M');
        pbOrder[1] = pbMonth;
    }
    else                // 0
    {
        cHope[1] = TEXT('d');
        pbOrder[1] = pbDay;
    }

    //
    //  Sets element 2.
    //
    if (nOrder == 2)    // 2
    {
        cHope[2] = TEXT('d');
        pbOrder[2] = pbDay;
    }
    else                // 0 1
    {
        cHope[2] = TEXT('y');
        pbOrder[2] = pbYear;
    }

    //
    //  Verifies that pszDate is of the form MDY DMY YMD.
    //
    for (i = 0; i < 3; i++, pszDate++)
    {
        if (*pszDate != cHope[i])
        {
            return (-1 - nOrder);
        }

        if (!(pszDate = ParseDateElement(pszDate, pbOrder[i])))
        {
            return (-1 - nOrder);
        }
    }

    //
    //  Success.  Return MDY, DMY or YMD index.
    //
    return (nOrder);
}


////////////////////////////////////////////////////////////////////////////
//
//  GetMaxCharWidth
//
//  Determine the widest digit (safety against variable pitch fonts).
//
////////////////////////////////////////////////////////////////////////////

int GetMaxCharWidth(
    HDC hDC)
{
    UINT nNumWidth[10];
    UINT i, nMaxNumWidth;

    GetCharWidth32(hDC, TEXT('0'), TEXT('9'), nNumWidth);

    for (nMaxNumWidth = 0, i = 0; i < 10; i++)
    {
        if (nNumWidth[i] > nMaxNumWidth)
        {
            nMaxNumWidth = nNumWidth[i];
        }
    }

    return (nMaxNumWidth);
}
////////////////////////////////////////////////////////////////////////////
//
//  GetMaxSubstitutedCharWidth
//
//  Determine the widest digit (safety against variable pitch fonts), but
//  do it using strings so that if number substitution is on, we will get
//  the width of the number based on what will actually be displayed
//
////////////////////////////////////////////////////////////////////////////

int GetMaxSubstitutedCharWidth(
    HDC hDC)
{
    char sz[2] = "0";
    TCHAR szAMPM[12];
    LONG i, nMaxNumWidth;
    DWORD dwWidth;
    SIZE size;
    
    for (nMaxNumWidth = 0, i = 0; i < 10; (*sz)++, i++)
    {
        if (GetTextExtentPoint32A(hDC, sz, 1, &size) && size.cx > nMaxNumWidth)
            nMaxNumWidth = size.cx;
        
    }

    if (nMaxNumWidth <= 8)
    {
        GetProfileString(szIntl, TEXT("s1159"), IntlDef.s1159, szAMPM, ARRAYSIZE(szAMPM));
        dwWidth = LOWORD(GetTextExtent(hDC, szAMPM, lstrlen(szAMPM)));
        if (dwWidth > 22)
            nMaxNumWidth = 10;
        GetProfileString(szIntl, TEXT("s2359"), IntlDef.s2359, szAMPM, ARRAYSIZE(szAMPM));
        dwWidth = LOWORD(GetTextExtent(hDC, szAMPM, lstrlen(szAMPM)));
        if (dwWidth > 22)
            nMaxNumWidth = 10;
    }
    return (nMaxNumWidth);
}


////////////////////////////////////////////////////////////////////////////
//
//  ReflectAMPM
//
//  Sets the global g_bPM and updates the control to display AM or PM.
//
////////////////////////////////////////////////////////////////////////////

void ReflectAMPM(
    HWND hDlg,
    int nNum)
{
    HWND hCtl = GetDlgItem(hDlg, DATETIME_AMPM);

    ListBox_SetTopIndex(hCtl, g_bPM);
    ListBox_SetCurSel(hCtl, (GetFocus() == hCtl) ? g_bPM : -1);
}


////////////////////////////////////////////////////////////////////////////
//
//  GetTextExtent
//
////////////////////////////////////////////////////////////////////////////

#ifdef WIN32
DWORD GetTextExtent(
    HDC hdc,
    LPCTSTR lpsz,
    int cb)
{
    SIZE sz;
    BOOL bSuccess = GetTextExtentPoint(hdc, lpsz, cb, &sz);
    
    if ( !bSuccess )
    {
        ZeroMemory( &sz, sizeof(sz) );
    }

    return ( MAKELONG((WORD)sz.cx, (WORD)sz.cy) );
}
#endif


////////////////////////////////////////////////////////////////////////////
//
//  DateTimeInit
//
//  Determine the widest digit (safety against variable pitch fonts).
//
////////////////////////////////////////////////////////////////////////////

void DateTimeInit(
    HWND hDlg,
    WORD nBaseID,
    WORD nSepID,
    LPTSTR pszSep,
    int nMaxDigitWidth,
    BOOL bDate)
{
    HWND hAMPMList;
    HWND hDay, hMonth, hYear;     // also used as hHour, hMinute, & hSecond
    HWND hOrder[5];
    HDC hDC;
    int nWidth, nHeight, X;
    DWORD dwSepExt;
    RECT Rect;
    int i;
    int nAMPMlength;

    hMonth    = GetDlgItem(hDlg, nBaseID);
    hDay      = GetDlgItem(hDlg, nBaseID + 1);
    hYear     = GetDlgItem(hDlg, nBaseID + 2);
    hOrder[1] = GetDlgItem(hDlg, nSepID);
    hOrder[3] = GetDlgItem(hDlg, nSepID + 1);

    if (bDate)
    {
        i = GetProfileInt(szIntl, TEXT("iDate"), 0);
    }
    else
    {
        if (g_b24HR = (BOOL)GetProfileInt(szIntl, TEXT("iTime"), 0))
        {
            g_sDateInfo[HOUR].nMin = 0;
            g_sDateInfo[HOUR].nMax = 23;
        }
        else
        {
            g_sDateInfo[HOUR].nMin = 1;
            g_sDateInfo[HOUR].nMax = 12;

            GetProfileString(szIntl, TEXT("s1159"), IntlDef.s1159, sz1159, ARRAYSIZE(sz1159));
            GetProfileString(szIntl, TEXT("s2359"), IntlDef.s2359, sz2359, ARRAYSIZE(sz2359));
        }
        i = 0;
    }

    switch (i)
    {
        case ( 1 ) :
        {
            hOrder[0] = hDay;
            hOrder[2] = hMonth;
            hOrder[4] = hYear;
            break;
        }
        case ( 2 ) :
        {
            hOrder[0] = hYear;
            hOrder[2] = hMonth;
            hOrder[4] = hDay;
            break;
        }
        case ( 0 ) :
        default :
        {
            hOrder[0] = hMonth;
            hOrder[2] = hDay;
            hOrder[4] = hYear;
            break;
        }
    }

    hDC = GetDC(hDlg);

    if (!bDate)
    {
        dwSepExt = GetTextExtent(hDC, sz1159, lstrlen(sz1159));
        nAMPMlength = LOWORD(GetTextExtent(hDC, sz2359, lstrlen(sz2359)));
        if (nAMPMlength < (int)LOWORD(dwSepExt))
        {
            nAMPMlength = (int)LOWORD(dwSepExt);
        }
    }

    dwSepExt = GetTextExtent(hDC, pszSep, lstrlen(pszSep));
    ReleaseDC(hDlg, hDC);

    GetWindowRect(hYear, (LPRECT)&Rect);
    ScreenToClient(hDlg, (LPPOINT)&Rect.left);
    ScreenToClient(hDlg, (LPPOINT)&Rect.right);

    nHeight = Rect.bottom - Rect.top;
    nWidth = Rect.top;

    GetWindowRect( GetDlgItem( hDlg,
                               bDate ? DATETIME_CALENDAR : DATETIME_CLOCK ),
                   (LPRECT)&Rect );
    ScreenToClient(hDlg, (LPPOINT)&Rect.left);
    ScreenToClient(hDlg, (LPPOINT)&Rect.right);

    Rect.top = nWidth;
    X = (Rect.left + Rect.right - (6 * nMaxDigitWidth) - (2 * LOWORD(dwSepExt))) / 2;

    if (bDate)
    {
        if (g_bLZero[YEAR])
        {
            X -= nMaxDigitWidth;
        }
    }
    else if (!g_b24HR)
    {
        X -= nAMPMlength / 2;
    }

    for (i = 0; i < 5; i++)
    {
        nWidth = (i % 2) ? LOWORD(dwSepExt) : 2 * nMaxDigitWidth;

        if ((hOrder[i] == hYear) && bDate && g_bLZero[YEAR])
        {
            nWidth *= 2;
        }

        //
        //  Allow for centering in edit control.
        //
        nWidth += 2;

    //  MoveWindow(hOrder[i], X, Rect.top, nWidth, nHeight, FALSE);
        X += nWidth;
    }

    hAMPMList = GetDlgItem(hDlg, DATETIME_AMPM);
    ListBox_ResetContent(hAMPMList);

    if (!bDate && !g_b24HR)
    {
        ListBox_InsertString(hAMPMList, 0, sz1159);
        ListBox_InsertString(hAMPMList, 1, sz2359);
    }

    EnableWindow(hAMPMList, !g_b24HR);

    Edit_LimitText(hYear, (bDate && g_bLZero[YEAR]) ? 4 : 2);
    Edit_LimitText(hMonth, 2);
    Edit_LimitText(hDay, 2);

    SetDlgItemText(hDlg, nSepID, pszSep);
    SetDlgItemText(hDlg, nSepID + 1, pszSep);
}


////////////////////////////////////////////////////////////////////////////
//
//  myitoa
//
////////////////////////////////////////////////////////////////////////////

void myitoa(
    int intValue,
    LPTSTR lpStr)
{
    LPTSTR lpString;
    TCHAR c;

    //
    //  lpString points to 1st char.
    //
    lpString = lpStr;

    do
    {
        *lpStr++ = (TCHAR)(intValue % 10 + TEXT('0'));
    } while ((intValue /= 10) > 0);

    //
    //  lpStr points to last char.
    //
    *lpStr-- = TEXT('\0');

    //
    //  Now reverse the string.
    //
    while (lpString < lpStr)
    {
      c = *lpString;
      *(lpString++) = *lpStr;
      *(lpStr--) = c;
    }
}


////////////////////////////////////////////////////////////////////////////
//
//  UpdateItem
//
//  This displays the information in the control from the array
//  of global values. Also selects the control. Also adds leading 0's
//  as well as rounding years to 2 digits and 24 or AM/PM hours.
//
////////////////////////////////////////////////////////////////////////////

void UpdateItem(
    HWND hDlg,
    int i)
{
    TCHAR szNum[5];
    int nNum = g_Modified ? wPrevDateTime[i] : wDateTime[i];

    //
    //  Use internal time.
    //
    if (i <= SECOND && i >= HOUR)
    {
        nNum = g_Time[i];

        //
        //  Do not paint un-necessarily.
        //
        if ((nNum == g_LastTime[i]) && (nNum >= 10))
        {
            return;
        }

        g_LastTime[i] = nNum;

        if (i == HOUR)
        {
            if (IsAMPM(nNum))
            {
                g_bPM = TRUE;
            }
            ReflectAMPM(hDlg, nNum);
        }
    }

    if (i == YEAR)
    {
        //
        //  Round the years to last 2 digits.
        //
        if (!g_bLZero[i])
        {
            nNum %= 100;
        }
    }
    else if ((i == HOUR) && !g_b24HR)
    {
        //
        //  nNum came from our internal date time.
        //  Remove 12 hours if not 24hour.
        //
        if (g_bPM)
        {
            nNum %= 12;
        }

        //
        //  00 hours is actually 12AM.
        //
        if (!nNum)
        {
            nNum = 12;
        }
    }

    //
    //  See if we need leading zeros.
    //  We only deal with 2 character numbers MAX.
    //
    if ((nNum < 10) && (g_bLZero[i] || (i == YEAR)))
    {
        szNum[0] = TEXT('0');
        szNum[1] = (TCHAR)(TEXT('0') + nNum);
        szNum[2] = TEXT('\0');
    }
    else
    {
        myitoa(nNum, szNum);
    }

    //
    //  Reflect the value in the appropriate control.
    //
    SetDlgItemText(hDlg, DATETIME_HOUR + i, szNum);

    //
    //  Select the field too.
    //
    SendDlgItemMessage(hDlg, DATETIME_HOUR + i, EM_SETSEL, 0, MAKELONG(0, 32767));

    //
    //  If we changed year or month, then we may have altered the leap year
    //  state.
    //
    if (i == MONTH || i == YEAR)
    {
        g_sDateInfo[DAY].nMax = MonthUpperBound( wDateTime[MONTH],
                                                 wDateTime[YEAR] );
    }
}


////////////////////////////////////////////////////////////////////////////
//
//  _ShowTZ
//
////////////////////////////////////////////////////////////////////////////

TCHAR c_szFirstBootTZ[] = TEXT("!!!First Boot!!!");

void _ShowTZ(
    HWND hDlg)
{
    HWND ctl = GetDlgItem(hDlg, DATETIME_CURTZ);
    TIME_ZONE_INFORMATION info;
    TCHAR final[64 + TZNAME_SIZE];
    TCHAR name[TZNAME_SIZE];
    DWORD TimeZoneId;

    if (g_bFirstBoot)
    {
        ShowWindow(ctl, SW_HIDE);
    }
    else
    {
        TimeZoneId = GetTimeZoneInformation(&info);
#ifdef UNICODE
        lstrcpy( name,
                 (TimeZoneId == TIME_ZONE_ID_STANDARD)
                   ? info.StandardName
                   : info.DaylightName );
#else
        WideStrToStr(name,  (TimeZoneId == TIME_ZONE_ID_STANDARD)
                   ? info.StandardName
                   : info.DaylightName );
#endif //UNICODE

        //
        //  Display nothing if it is our special 1st boot marker.
        //
        if (*name && (lstrcmpi(name, c_szFirstBootTZ) != 0))
        {
            static TCHAR format[128] = TEXT("");

            if (!*format)
            {
                GetWindowText( ctl,
                               format,
                               ARRAYSIZE(format) );
            }
            wsprintf(final, format, name);
        }
        else
        {
            *final = 0;
        }

        SetWindowText(ctl, final);
    }
}


////////////////////////////////////////////////////////////////////////////
//
//  UnhookTime
//
//  To stop the clock calling us back all the time (around exit).
//
////////////////////////////////////////////////////////////////////////////

void UnhookTimer(
    HWND hDlg)
{
    SendDlgItemMessage(hDlg, DATETIME_CLOCK, CLM_TIMEHWND, CLF_SETHWND, 0);
}


////////////////////////////////////////////////////////////////////////////
//
//  TimeProvider
//
//  Called by the clock to find out what time it is.
//
////////////////////////////////////////////////////////////////////////////

void TimeProvider(
    LPSYSTEMTIME lpSystemTime,
    HWND hDlg)
{
    short wTemp[7];

    //
    //  If the user has modified the time, the clock should
    //  display the edit controls, otherwise its just the SystemTime.
    //
    if (g_Modified)
    {
        lpSystemTime->wHour   = (WORD)g_Time[HOUR];
        lpSystemTime->wMinute = (WORD)g_Time[MINUTE];
        lpSystemTime->wSecond = (WORD)g_Time[SECOND];
    }
    else
    {
#ifdef WIN32
        GetLocalTime(lpSystemTime);
#else
        GetTime();
        if (wDateTime[HOUR] >= 0 && wDateTime[HOUR] <= 24)
        {
            lpSystemTime->wHour = wDateTime[HOUR];
        }
        lpSystemTime->wMinute = wDateTime[MINUTE];
        lpSystemTime->wSecond = wDateTime[SECOND];

#endif
        //
        //  Copy the time and display it for us too.
        //
        g_bPM = IsAMPM(lpSystemTime->wHour);
        g_Time[HOUR]   = lpSystemTime->wHour;
        g_Time[MINUTE] = lpSystemTime->wMinute;
        g_Time[SECOND] = lpSystemTime->wSecond;

        //
        //  Check for date rollover.
        //
        if (!fDateDirty)
        {
            wTemp[DAY]   = wDateTime[DAY];
            wTemp[MONTH] = wDateTime[MONTH];
            wTemp[YEAR]  = wDateTime[YEAR];

            GetDate();

            if ((wDateTime[DAY]   != wTemp[DAY])   ||
                (wDateTime[MONTH] != wTemp[MONTH]) ||
                (wDateTime[YEAR]  != wTemp[YEAR]))
            {
                InvalidateRect(GetDlgItem(hDlg, DATETIME_CALENDAR), NULL, TRUE);

                if (wDateTime[MONTH] != wTemp[MONTH])
                {
                    ComboBox_SetCurSel( GetDlgItem(hDlg, DATETIME_MONTHNAME),
                                        wDateTime[MONTH] - 1 );
                }

                if (wDateTime[YEAR] != wTemp[YEAR])
                {
                    UpdateItem(hDlg, YEAR);
                }

                _ShowTZ(hDlg);
            }
        }
        
        UpdateItem(hDlg, HOUR);
        UpdateItem(hDlg, MINUTE);
        UpdateItem(hDlg, SECOND);
        ReflectAMPM(hDlg, g_Time[HOUR]);
    }
}

////////////////////////////////////////////////////////////////////////////
//
//  bSupportedCalendar
//
//  Returns True if the current calendar is not Hijri nor Hebrew
//
//  Otherwise it returns FALSE.
//
////////////////////////////////////////////////////////////////////////////

BOOL bSupportedCalendar()
{
    TCHAR tchCalendar[32];
    CALTYPE defCalendar = CAL_GREGORIAN;

    if (GetLocaleInfo(LOCALE_USER_DEFAULT,
                      LOCALE_ICALENDARTYPE,
                      tchCalendar,
                      ARRAYSIZE(tchCalendar)))
    {
        defCalendar = StrToInt(tchCalendar);
    }

    return (!(defCalendar == CAL_HIJRI || defCalendar == CAL_HEBREW));
}

////////////////////////////////////////////////////////////////////////////
//
//  InitDateTimeDlg
//
//  Called to init the dialog.
//
////////////////////////////////////////////////////////////////////////////

void InitDateTimeDlg(
    HWND hDlg,
    UINT message,
    WPARAM wParam,
    LPARAM lParam)
{
    int nMaxDigitWidth;
    int i;
    TCHAR szNum[5];
    TCHAR szMonth[64];
    TCHAR szShortDate[12];
    HDC hDC;
    HFONT hFont;
    HWND hwndCB;
    CALID calId;
    static int nInc[] = { 1, 5, 5, 1, 1, 5 };

    HWND hwndScroll;
    UDACCEL udAccel[2];
    HWND hwndTBorder;

    HCURSOR oldcursor = SetCursor(LoadCursor(NULL, IDC_WAIT));

    LCID lcid = LOCALE_USER_DEFAULT;

    InitCommonControls();

    //
    //  Sets the Leading zero status of the 6 controls.
    //
    g_bLZero[HOUR]   = g_bLZero[MONTH]  = g_bLZero[DAY]  = FALSE;
    g_bLZero[MINUTE] = g_bLZero[SECOND] = g_bLZero[YEAR] = TRUE;

    hDC = GetDC(hDlg);

    if (hFont = (HFONT)SendMessage(hDlg, WM_GETFONT, 0, 0L))
    {
        hFont = SelectObject( hDC, hFont );
    }

    if (hFont)
    {
        SelectObject(hDC, hFont);
    }

    AdjustAMPMPosition(hDlg);

    nMaxDigitWidth = GetMaxCharWidth(hDC);
    ReleaseDC(hDlg, hDC);

    g_bLZero[HOUR] = GetProfileInt(szIntl, TEXT("iTLZero"), 0);
    //
    // Initialize szShortDate in case GetProfileString fails.
    //
    lstrcpyn(szShortDate, IntlDef.sShortDate, ARRAYSIZE(szShortDate));
    GetProfileString(szIntl, TEXT("sShortDate"), IntlDef.sShortDate, szShortDate, ARRAYSIZE(szShortDate));
    ReadShortDate(szShortDate, g_bLZero + MONTH, g_bLZero + DAY, g_bLZero + YEAR);

    g_bLZero[YEAR] = TRUE;      //we always want the year to be 4 digits (this will be bad in late 9999)
    
    //
    //  Setup the TIME stuff.
    //
    GetTime();

    g_Time[HOUR]   = wDateTime[HOUR];
    g_Time[MINUTE] = wDateTime[MINUTE];
    g_Time[SECOND] = wDateTime[SECOND];

    GetProfileString(szIntl, TEXT("sTime"), IntlDef.sTime, szNum, 3);
    DateTimeInit(hDlg, DATETIME_HOUR, DATETIME_TSEP1, szNum, nMaxDigitWidth, FALSE);

    //
    //  Force all entries to be re-drawn,
    //
    g_LastTime[HOUR] = g_LastTime[MINUTE] = g_LastTime[SECOND] = -1;
    UpdateItem(hDlg, HOUR);
    UpdateItem(hDlg, MINUTE);
    UpdateItem(hDlg, SECOND);
    ReflectAMPM(hDlg, wDateTime[HOUR]);

    //
    //  Setup the Date stuff.
    //
    GetDate();

    g_sDateInfo[DAY].nMax = MonthUpperBound(wDateTime[MONTH], wDateTime[YEAR]);

    if (!g_bLZero[YEAR])
    {
        wDateTime[YEAR] %= 100;
        g_sDateInfo[YEAR].nMax = 99;
        g_sDateInfo[YEAR].nMin = 0;
    }
    else
    {
        g_sDateInfo[YEAR].nMax = 2099;
        g_sDateInfo[YEAR].nMin = 1980;
    }

    for (i = MONTH; i <= YEAR; i++)
    {
        wPrevDateTime[i] = -1;
    }

    //
    //  Get the month names. And select this month.
    //
    hwndCB = GetDlgItem(hDlg, DATETIME_MONTHNAME);
    ComboBox_ResetContent(hwndCB);
    //
    // If the current calendar is Hijri or Hebrew then use the Gregorian one.
    //
    if (!bSupportedCalendar())
        lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT);

    GetLocaleInfo(lcid, LOCALE_ICALENDARTYPE, szMonth, ARRAYSIZE(szMonth));
    calId = (CALID)StrToInt(szMonth);

    for (i = 0; i < 12; i++)
    {
#ifdef WINNT
        GetCalendarInfo(lcid, calId, CAL_SMONTHNAME1 + i, szMonth, ARRAYSIZE(szMonth), NULL);
#else
        GetLocaleInfo( LOCALE_USER_DEFAULT,
                       LOCALE_SMONTHNAME1 + i,
                       szMonth,
                       sizeof(szMonth) );

        if (*szMonth && !IsDBCSLeadByte(*szMonth))
        {
            *szMonth = (TCHAR)CharUpper((LPTSTR)(DWORD)(TBYTE)*szMonth);
        }

#endif
        ComboBox_AddString(hwndCB, szMonth);
    }

    ComboBox_SetCurSel(hwndCB, wDateTime[MONTH] - 1);

    //
    //  Set the default modifier for the Year Updown arrows.
    //
    wParam -= DATETIME_HOUR;
    hwndScroll = GetDlgItem(hDlg, DATETIME_YARROW);
    SendMessage( hwndScroll,
                 UDM_SETRANGE,
                 0,
                 MAKELPARAM(g_sDateInfo[YEAR].nMax, g_sDateInfo[YEAR].nMin) );

    udAccel[0].nSec = 0;
    udAccel[0].nInc = 1;
    udAccel[1].nSec = 2;
    udAccel[1].nInc = nInc[YEAR];

    SendMessage(hwndScroll, UDM_SETACCEL, 2, (LPARAM)(LPUDACCEL)udAccel);
    SendMessage(hwndScroll, UDM_SETBUDDY, (WPARAM)GetDlgItem(hDlg, DATETIME_YEAR), 0L);

    //
    //  Set the default modifier for the time arrows.
    //  It should control the HOURS by default as per joelgros
    //
    hwndScroll = GetDlgItem(hDlg, DATETIME_TARROW);
    SendMessage( hwndScroll,
                 UDM_SETRANGE,
                 0,
                 MAKELPARAM( g_sDateInfo[HOUR].nMax,
                             g_sDateInfo[HOUR].nMin) );
    udAccel[0].nSec = 0;
    udAccel[0].nInc = 1;
    udAccel[1].nSec = 2;
    udAccel[1].nInc = nInc[HOUR];
    
    SendMessage( hwndScroll, UDM_SETACCEL, 2, (LPARAM)(LPUDACCEL)udAccel );
    SendMessage( hwndScroll, UDM_SETBUDDY, (WPARAM)GetDlgItem(hDlg, DATETIME_HOUR), 0L );

    //
    //  Make the 'well' for the digits appear.
    //
    hwndTBorder = GetDlgItem(hDlg, DATETIME_TBORDER);
    SetWindowLong( hwndTBorder,
                   GWL_EXSTYLE,
                   GetWindowLong(hwndTBorder, GWL_EXSTYLE) | WS_EX_CLIENTEDGE );

    //
    //  Display the border right now.
    //
    SetWindowPos( hwndTBorder,
                  NULL,
                  0, 0, 0, 0,
                  SWP_NOMOVE | SWP_NOSIZE | SWP_DRAWFRAME | SWP_SHOWWINDOW );

    //
    //  Display month->year.
    //
    for (i = MONTH; i <= YEAR; i++)
    {
        if ((wDateTime[i] != wPrevDateTime[i]) &&
            (GetFocus() != GetDlgItem(hDlg, DATETIME_HOUR + i)))
        {
            //
            //  Update previous date-time.
            //
            wPrevDateTime[i] = wDateTime[i];

            if (i == YEAR)
            {
                UpdateItem(hDlg, i);
            }
        }
    }

    g_Modified = FALSE;

    //
    //  Tell the clock that we have a time provider - must be done last.
    //
    SendDlgItemMessage( hDlg,
                        DATETIME_CLOCK,
                        CLM_TIMEHWND,
                        CLF_SETHWND,
                        (LPARAM)(LPINT)hDlg );

    SetCursor(oldcursor);
}


////////////////////////////////////////////////////////////////////////////
//
//  CheckNum
//
////////////////////////////////////////////////////////////////////////////

LRESULT CheckNum(
    HWND hDlg,
    UINT nScrollID,
    HWND hCtl)
{
    static int cReenter = 0;

    LRESULT lRet;

    //
    //  If this is an illegal value, (but not blank), then kill the last char
    //  that was entered.
    //
    lRet = SendDlgItemMessage(hDlg, nScrollID, UDM_GETPOS, 0, 0L);

    //
    //  Guard against re-entrance.
    //
    ++cReenter;

    if (cReenter <= 4)
    {
        SendMessage( hCtl,
                     HIWORD(lRet) && GetWindowTextLength(hCtl)
                         ? EM_UNDO
                         : EM_EMPTYUNDOBUFFER,
                     0,
                     0L );
    }

    --cReenter;

    return (lRet);
}


////////////////////////////////////////////////////////////////////////////
//
//  DateTimeDlgProc
//
//  Main dialog proc.
//
////////////////////////////////////////////////////////////////////////////

INT_PTR CALLBACK DateTimeDlgProc(
    HWND hDlg,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam)
{
    int i;

    switch (uMsg)
    {
        case (WM_INITDIALOG):
        {
            AddInternetPageAsync(GetParent(hDlg), hDlg);

            InitDateTimeDlg(hDlg, uMsg, wParam, lParam);
            g_PrevIMCForDateField = ImmAssociateContext(GetDlgItem(hDlg, DATETIME_YEAR), 0);
            break;
        }
        case ( WM_DESTROY ) :
        {
            if (g_PrevIMCForDateField)
            {
                ImmAssociateContext( GetDlgItem(hDlg, DATETIME_YEAR),
                                     g_PrevIMCForDateField );
            }
            UnhookTimer(hDlg);
            break;
        }
#ifdef WIN32
        case ( WM_CTLCOLORSTATIC ) :
#endif
        case ( WM_CTLCOLOR ) :
        {
            //
            //  Set the background color of the time controls to the the
            //  color of the edit controls.
            //
            if ((GET_WM_CTLCOLOR_HWND(wParam, lParam, uMsg) ==
                 GetDlgItem(hDlg, DATETIME_TSEP1)) ||
                (GET_WM_CTLCOLOR_HWND(wParam, lParam, uMsg) ==
                 GetDlgItem(hDlg, DATETIME_TSEP2)) ||
                (GET_WM_CTLCOLOR_HWND(wParam, lParam, uMsg) ==
                 GetDlgItem(hDlg, DATETIME_TBORDER)))
            {
#ifndef WIN32
                //
                //  Make the statics the color of the edits.
                //
                lParam = GET_WM_CTLCOLOR_MPS(
                            GET_WM_CTLCOLOR_HDC(wParam, lParam, uMsg),
                            GET_WM_CTLCOLOR_HWND(wParam, lParam, uMsg),
                            CTLCOLOR_EDIT );

                return ((INT_PTR)DefWindowProc(hDlg, uMsg, wParam, lParam));
#else
                return ((INT_PTR)DefWindowProc(hDlg, WM_CTLCOLOREDIT, wParam, lParam));
#endif
            }
            return (0);
            break;
        }
        case ( WM_NOTIFY ) :
        {
            //
            //  Property sheet handler stuff.
            //
            switch (((NMHDR *)lParam)->code)
            {
                case ( PSN_SETACTIVE ) :
                {
                    _ShowTZ(hDlg);
                    break;
                }
                case ( PSN_RESET ) :
                {
                    UnhookTimer(hDlg);

                    SetFocus(GetDlgItem(hDlg, (int)wParam));

                    GetDate();
                    GetTime();
                    break;
                }
                case ( PSN_APPLY ) :
                {
                    wDateTime[MINUTE] = (WORD)g_Time[MINUTE];
                    wDateTime[SECOND] = (WORD)g_Time[SECOND];

                    if (g_b24HR)
                    {
                        wDateTime[HOUR] = (WORD)g_Time[HOUR];
                    }
                    else
                    {
                        wDateTime[HOUR] = g_Time[HOUR] % 12;

                        if (g_bPM)
                        {
                            wDateTime[HOUR] += 12;
                        }
                    }
                    
                    g_WasModified = g_Modified;
                    SetTime();

                    g_LastTime[HOUR] = g_LastTime[MINUTE] = g_LastTime[SECOND] = -1;

                    for (i = MONTH; i <= YEAR; i++)
                    {
                        wPrevDateTime[i] = -1;
                    }

                    g_Modified = FALSE;

                    wPrevDateTime[HOUR]    = wDateTime[HOUR];
                    wPrevDateTime[MINUTE]  = wDateTime[MINUTE];
                    wPrevDateTime[SECOND]  = wDateTime[SECOND];
                    wPrevDateTime[MONTH]   = wDateTime[MONTH];
                    wPrevDateTime[DAY]     = wDateTime[DAY];
                    wPrevDateTime[YEAR]    = wDateTime[YEAR];
                    wPrevDateTime[WEEKDAY] = wDateTime[WEEKDAY];

                    //
                    //  We handled it - no repaint.
                    //
                    return (TRUE);
                }
            }
            break;
        }
        case ( WM_VSCROLL ) :
        {
            switch (GET_WM_VSCROLL_CODE(wParam, lParam))
            {
                case ( SB_THUMBPOSITION ) :
                {
                    SYSTEMTIME SystemTime;
                    HWND hBuddy = (HWND)SendMessage(
                                           GET_WM_VSCROLL_HWND(wParam, lParam),
                                           UDM_GETBUDDY,
                                           0,
                                           0L );

                    if (hBuddy == GetDlgItem(hDlg, DATETIME_HOUR))
                    {
                        g_Time[HOUR] = GET_WM_VSCROLL_POS(wParam, lParam);
                    }
                    else if (hBuddy == GetDlgItem(hDlg, DATETIME_MINUTE))
                    {
                        g_Time[MINUTE] = GET_WM_VSCROLL_POS(wParam, lParam);
                    }
                    else if (hBuddy == GetDlgItem(hDlg, DATETIME_SECOND))
                    {
                        g_Time[SECOND] = GET_WM_VSCROLL_POS(wParam, lParam);
                    }
                //  else if (hBuddy == GetDlgItem(hDlg, DATETIME_AMPM))

                    if (hBuddy != GetDlgItem(hDlg, DATETIME_YEAR))  
                        g_Modified = TRUE;

                    //
                    //  Light the apply now button.
                    //
                    PropSheet_Changed(GetParent(hDlg), hDlg);

                    //
                    //  Force the clock to reflect this setting.
                    //
                    TimeProvider(&SystemTime, hDlg);

                    SendDlgItemMessage( hDlg,
                                        DATETIME_CLOCK,
                                        CLM_UPDATETIME,
                                        CLF_SETTIME,
                                        (LPARAM)(LPSYSTEMTIME)&SystemTime );

                    //
                    //  Fall thru to update the year...
                    //
                }
                case ( SB_ENDSCROLL ) :
                {
                    //
                    //  If this is the year, have the calendar repaint.
                    //
                    if ((HWND)SendMessage( GET_WM_VSCROLL_HWND(wParam, lParam),
                                           UDM_GETBUDDY,
                                           0,
                                           0L ) == GetDlgItem(hDlg, DATETIME_YEAR))
                    {
                        //
                        //  Have it update the information.
                        //
                        GetTime();
                        AdjustDelta(hDlg, YEAR);
                        UpdateItem(hDlg, YEAR);

                        InvalidateRect( GetDlgItem(hDlg, DATETIME_CALENDAR),
                                        NULL,
                                        TRUE );
                    }

                    break;
                }
            }
            break;
        }
        case ( CLM_UPDATETIME ) :
        {
            //
            //  The clock updating/reflecting the time.
            //
            switch (wParam)
            {
                case ( CLF_SETTIME ) :
                {
                    //
                    //  Clock telling us what the time is.
                    //
                    g_Modified = TRUE;
                    g_Time[HOUR] = ((LPSYSTEMTIME)lParam)->wHour;
                    g_Time[MINUTE] = ((LPSYSTEMTIME)lParam)->wMinute;
                    g_Time[SECOND] = ((LPSYSTEMTIME)lParam)->wSecond;
                    g_bPM = IsAMPM(g_Time[HOUR]);
                    break;
                }
                case ( CLF_GETTIME ) :
                {
                    //
                    //  We tell the clock what time it is.
                    //
                    TimeProvider((LPSYSTEMTIME)lParam, hDlg);
                    break;
                }
            }
            break;
        }
        case ( WM_COMMAND ) :
        {
            //
            //  Command processing.
            //
            switch (GET_WM_COMMAND_ID(wParam, lParam))
            {
                case ( DATETIME_AMPM ) :
                {
                    //
                    //  Deals with the AMPM control.
                    //
                    UDACCEL udAccel;
                    HWND hwndScroll = GetDlgItem(hDlg, DATETIME_TARROW);
                    HWND hwndThisCtl = GET_WM_COMMAND_HWND(wParam, lParam);

                    //
                    //  We only care if we get/loose the focus.
                    //
                    switch (GET_WM_COMMAND_CMD(wParam, lParam))
                    {
                        case ( LBN_SETFOCUS ) :
                        {
                            //
                            //  If we get the focus, then the UD control
                            //  should deal with the AMPM.
                            //
                            //  Select the visible entry.
                            //
                            ReflectAMPM(hDlg, wDateTime[HOUR]);

                            // if it has a buddy, remove it...
                            if ((HWND)SendMessage( hwndScroll,
                                               UDM_GETBUDDY,
                                               0,
                                               0 ) != NULL)
                            {
                                SendMessage(hwndScroll, UDM_SETBUDDY, 0, 0);
                            } 
                            
                            //
                            //  Tell the UD control how to manipulate AM/PM.
                            //
                            SendMessage( hwndScroll,
                                         UDM_SETRANGE,
                                         0,
                                         MAKELPARAM(1, 0) );
                            udAccel.nSec = 0;
                            udAccel.nInc = 1;
                            SendMessage( hwndScroll,
                                         UDM_SETACCEL,
                                         1,
                                         (LPARAM)(LPUDACCEL)&udAccel );
                            SendMessage( hwndScroll,
                                         UDM_SETBUDDY,
                                         (WPARAM)hwndThisCtl,
                                         0 );
                            break;
                        }
                        case ( LBN_KILLFOCUS ) :
                        {
                            //
                            //  When we loose focus, the g_bPM flag is updated.
                            //
                            //  Remove selection from the AM/PM.
                            //
                            ListBox_SetCurSel(hwndThisCtl, -1);

                            if ((HWND)SendMessage( hwndScroll,
                                                   UDM_GETBUDDY,
                                                   0,
                                                   0 ) == hwndThisCtl)
                            {
                                SendMessage(hwndScroll, UDM_SETBUDDY, 0, 0);
                            }

                            break;
                        }
                        case ( LBN_SELCHANGE ) :
                        {
                            if ((g_Modified == FALSE) &&
                                (g_bPM == (BOOL)ListBox_GetTopIndex(hwndThisCtl)))
                            {
                                break;
                            }

                            //
                            //  Find the visible entry.
                            //
                            g_Modified = TRUE;

                            //
                            //  Light the apply now button.
                            //
                            PropSheet_Changed(GetParent(hDlg), hDlg);
                            g_bPM = (BOOL)ListBox_GetTopIndex(hwndThisCtl);
                            break;
                        }
                    }
                    break;
                }
                case ( DATETIME_HOUR ) :
                case ( DATETIME_MINUTE ) :
                case ( DATETIME_SECOND ) :
                {
                    switch (GET_WM_COMMAND_CMD(wParam, lParam))
                    {
                        case ( EN_CHANGE ) :
                        {
                            SYSTEMTIME SystemTime;

                            g_Modified = TRUE;

                            //
                            //  Light the apply now button.
                            //
                            PropSheet_Changed(GetParent(hDlg), hDlg);

                            //
                            //  Work out what the change was too.
                            //
                            g_Time[GET_WM_COMMAND_ID(wParam, lParam) -
                                   DATETIME_HOUR] =
                              (int)SendDlgItemMessage( hDlg,
                                                       DATETIME_TARROW,
                                                       UDM_GETPOS,
                                                       0,
                                                       0 );

                            //
                            //  Force the clock to reflect this setting.
                            //
                            TimeProvider(&SystemTime, hDlg);
                            SendDlgItemMessage( hDlg,
                                                DATETIME_CLOCK,
                                                CLM_UPDATETIME,
                                                0,
                                                (LPARAM)(LPSYSTEMTIME)&SystemTime );
                            break;
                        }
                    }

                    //  fall thru...
                }
                case ( DATETIME_MONTH ) :
                case ( DATETIME_YEAR ) :
                case ( DATETIME_DAY ) :
                {
                    switch (GET_WM_COMMAND_CMD(wParam, lParam))
                    {
                        case ( EN_CHANGE ) :
                        {
                            CheckNum( hDlg,
                                      GET_WM_COMMAND_ID(wParam, lParam) <= DATETIME_SECOND
                                          ? DATETIME_TARROW
                                          : DATETIME_YARROW,
                                      GET_WM_COMMAND_HWND(wParam, lParam) );

                            // Changing the year may alter the number of days in February.
                            // Yes this is a hack, but this entire applet is a giant
                            // broken hack and I want to change it as little as possible.
                            if (GET_WM_COMMAND_ID(wParam, lParam) == DATETIME_YEAR && wDateTime[MONTH] == 2)
                            {
                                g_sDateInfo[DAY].nMax = MonthUpperBound( wDateTime[MONTH],
                                                                         wDateTime[YEAR] );
                                if (wDateTime[DAY] > g_sDateInfo[DAY].nMax)
                                {
                                    wDateTime[DAY] = (WORD)g_sDateInfo[DAY].nMax;
                                    fDateDirty = TRUE;
                                }
                                InvalidateRect( GetDlgItem(hDlg, DATETIME_CALENDAR),
                                                NULL,
                                                TRUE );
                            }
                            break;
                        }
                        case ( EN_SETFOCUS ) :
                        {
                            UINT id = GET_WM_COMMAND_ID(wParam, lParam) - DATETIME_HOUR;

                            if (id <= SECOND)
                            {
                                UDACCEL udAccel[2];
                                static int nInc[] = { 1, 5, 5, 1, 1, 5 };
                                HWND hwndScroll = GetDlgItem(hDlg, DATETIME_TARROW);

                                // if it has a buddy, remove it...
                                if ((HWND)SendMessage( hwndScroll,
                                                   UDM_GETBUDDY,
                                                   0,
                                                   0 ) != NULL)
                                {
                                    SendMessage(hwndScroll, UDM_SETBUDDY, 0, 0);
                                }    

                                //
                                // now set the new one
                                //
                                SendMessage( hwndScroll,
                                             UDM_SETRANGE,
                                             0,
                                             MAKELPARAM( g_sDateInfo[id].nMax,
                                                         g_sDateInfo[id].nMin) );
                                udAccel[0].nSec = 0;
                                udAccel[0].nInc = 1;
                                udAccel[1].nSec = 2;
                                udAccel[1].nInc = nInc[id];
                                SendMessage( hwndScroll,
                                             UDM_SETACCEL,
                                             2,
                                             (LPARAM)(LPUDACCEL)udAccel );

                                //
                                //  Set the UD to update this control.
                                //
                                SendMessage( hwndScroll,
                                             UDM_SETBUDDY,
                                             (WPARAM)GET_WM_COMMAND_HWND(wParam,
                                                                         lParam),
                                             0 );
                            }
                            break;
                        }
                        case ( EN_KILLFOCUS ) :
                        {
                            //
                            //  Gets in range HMS MDY.
                            //
                            UINT id = GET_WM_COMMAND_ID(wParam, lParam) - DATETIME_HOUR;

                            AdjustDelta(hDlg, id);
                            UpdateItem(hDlg, id);                           

                            //
                            //  If control is YEAR.
                            //
                            if (id == (DATETIME_YEAR - DATETIME_HOUR))
                            {
                                InvalidateRect( GetDlgItem(hDlg, DATETIME_CALENDAR),
                                                NULL,
                                                TRUE );
                            }

                            break;
                        }
                        default :
                        {
                            break;
                        }
                    }
                    break;
                }
                case ( DATETIME_MONTHNAME ) :
                {
                    if (GET_WM_COMMAND_CMD(wParam, lParam) == CBN_SELCHANGE)
                    {
                        int nIndex = 1 + (int)ComboBox_GetCurSel(
                                              GetDlgItem( hDlg,
                                                          DATETIME_MONTHNAME ));

                        if (wDateTime[MONTH] != nIndex)
                        {
                            AdjustDeltaMonth(nIndex);
                            InvalidateRect( GetDlgItem(hDlg, DATETIME_CALENDAR),
                                            NULL,
                                            TRUE );
                            PropSheet_Changed(GetParent(hDlg), hDlg);
                        }
                    }
                    break;
                }
                case ( DATETIME_CALENDAR ) :
                {
                    //
                    //  If the calendar sent us a change, we will assume
                    //  that it is to allow the apply now to work.
                    //
                    PropSheet_Changed(GetParent(hDlg), hDlg);
                    break;
                }
            }
            break;
        }
        case ( WM_WININICHANGE ) :
        {
            //
            //  Reinitialize if there is a time format change.
            //
            InitDateTimeDlg(hDlg, uMsg, wParam, lParam);
            InvalidateRect(GetDlgItem(hDlg, DATETIME_CALENDAR), NULL, TRUE);
            break;
        }
        case ( WM_TIMECHANGE ) :
        {
            //
            //  Forward time change messages to the clock control.
            //
            SendDlgItemMessage( hDlg,
                                DATETIME_CLOCK,
                                WM_TIMECHANGE,
                                wParam,
                                lParam );

            break;
        }

        case ( WMUSER_ADDINTERNETTAB ) :
        {
            AddInternetTab(hDlg);
            break;
        }

        case ( WM_HELP ) :             // F1
        {
            WinHelp( (HWND)((LPHELPINFO)lParam)->hItemHandle,
                     NULL,
                     HELP_WM_HELP,
                     (DWORD_PTR)(LPTSTR)aDateTimeHelpIds );
            break;
        }
        case ( WM_CONTEXTMENU ) :      // right mouse click
        {
            WinHelp( (HWND)wParam,
                     NULL,
                     HELP_CONTEXTMENU,
                     (DWORD_PTR)(LPTSTR)aDateTimeHelpIds );
            break;
        }
        default :
        {
            return (FALSE);
        }
    }
    return (TRUE);
}


////////////////////////////////////////////////////////////////////////////
//
//  SetZoneState
//
//  Sets the display state of a time zone in the map control.
//
////////////////////////////////////////////////////////////////////////////

void SetZoneState(
    HWND map,
    PTZINFO zone,
    BOOL highlight)
{
    if (zone)
    {
        if (zone->SeaIndex >= 0)
        {
            MapControlSetSeaRegionHighlight( map,
                                             zone->SeaIndex,
                                             highlight,
                                             zone->MapLeft,
                                             zone->MapWidth );
        }

        if (zone->LandIndex >= 0)
        {
            MapControlSetLandRegionHighlight( map,
                                              zone->LandIndex,
                                              highlight,
                                              zone->MapLeft,
                                              zone->MapWidth );
        }
    }
}


////////////////////////////////////////////////////////////////////////////
//
//  SetZoneFamilyState
//
//  Sets the display state of a time zone family in the map control.
//
////////////////////////////////////////////////////////////////////////////

void SetZoneFamilyState(
    HWND map,
    PTZINFO family,
    BOOL highlight)
{
    if (family)
    {
        PTZINFO zone = family;

        do
        {
            SetZoneState(map, zone, highlight);
            zone = zone->next;
        }
        while(zone && (zone != family));
    }
}


////////////////////////////////////////////////////////////////////////////
//
//  ParseMapInfo
//
//  Parses the color table information about the world bitmap we display.
//
//  Expected format: "sea,land"
//    where sea and land are color table indices or -1.
//
////////////////////////////////////////////////////////////////////////////

void ParseMapInfo(
    PTZINFO zone,
    const TCHAR *text)
{
    const TCHAR *p = text;

    zone->SeaIndex = zone->LandIndex = -1;

    if (*p)
    {
        if (*p != TEXT('-'))
        {
            zone->SeaIndex = 0;

            while (*p && (*p != TEXT(',')))
            {
                zone->SeaIndex = (10 * zone->SeaIndex) + (*p - TEXT('0'));
                p++;
            }
        }
        else
        {
            do
            {
                p++;
            } while (*p && (*p != TEXT(',')));
        }

        if (*p == TEXT(','))
        {
            p++;
        }

        if (*p)
        {
            if (*p != TEXT('-'))
            {
                zone->LandIndex = 0;

                while (*p)
                {
                    zone->LandIndex = (10 * zone->LandIndex) + (*p - TEXT('0'));
                    p++;
                }
            }
        }
    }
}


////////////////////////////////////////////////////////////////////////////
//
//  ReadZoneData
//
//  Reads the data for a time zone from the registry.
//
////////////////////////////////////////////////////////////////////////////

BOOL ReadZoneData(
    PTZINFO zone,
    HKEY key,
    LPCTSTR keyname)
{
    TCHAR mapinfo[16];
    DWORD len;

    len = sizeof(zone->szDisplayName);

    if (RegQueryValueEx( key,
                         c_szTZDisplayName,
                         0,
                         NULL,
                         (LPBYTE)zone->szDisplayName,
                         &len ) != ERROR_SUCCESS)
    {
        return (FALSE);
    }

    //
    //  Under NT, the keyname is the "Standard" name.  Values stored
    //  under the keyname contain the other strings and binary info
    //  related to the time zone.  Every time zone must have a standard
    //  name, therefore, we save registry space by using the Standard
    //  name as the subkey name under the "Time Zones" key.
    //
    len = sizeof(zone->szStandardName);

    if (RegQueryValueEx( key,
                         c_szTZStandardName,
                         0,
                         NULL,
                         (LPBYTE)zone->szStandardName,
                         &len ) != ERROR_SUCCESS)
    {
        //
        //  Use keyname if can't get StandardName value.
        //
        lstrcpyn( zone->szStandardName,
                  keyname,
                  sizeof(zone->szStandardName) );
    }

    len = sizeof(zone->szDaylightName);

    if (RegQueryValueEx( key,
                         c_szTZDaylightName,
                         0,
                         NULL,
                         (LPBYTE)zone->szDaylightName,
                         &len ) != ERROR_SUCCESS)
    {
        return (FALSE);
    }

    len = sizeof(zone->Bias) +
          sizeof(zone->StandardBias) +
          sizeof(zone->DaylightBias) +
          sizeof(zone->StandardDate) +
          sizeof(zone->DaylightDate);

    if (RegQueryValueEx( key,
                         c_szTZI,
                         0,
                         NULL,
                         (LPBYTE)&zone->Bias,
                         &len ) != ERROR_SUCCESS)
    {
        return (FALSE);
    }

    len = sizeof(mapinfo);

    if (RegQueryValueEx( key,
                         c_szTZMapInfo,
                         0,
                         NULL,
                         (LPBYTE)mapinfo,
                         &len ) != ERROR_SUCCESS)
    {
        *mapinfo = TEXT('\0');
    }

    ParseMapInfo(zone, mapinfo);

    //
    //  Generate phony MapLeft and MapRight until they show up in the
    //  registry.
    //
    zone->MapLeft = ((zone->Bias * ZONE_IMAGE_SCALE) / ZONE_BIAS_SCALE) +
                    ZONE_IMAGE_LEFT;

    zone->MapWidth = ZONE_IMAGE_WIDTH;

    return (TRUE);
}


////////////////////////////////////////////////////////////////////////////
//
//  AddZoneToList
//
//  Inserts a new time zone into a list, sorted by bias and then name.
//
////////////////////////////////////////////////////////////////////////////

void AddZoneToList(
    PTZINFO *list,
    PTZINFO zone)
{
    if (*list)
    {
        PTZINFO curr = NULL;
        PTZINFO next = *list;

        while (next && zone->Bias <= next->Bias)
        {
            if (zone->Bias == next->Bias)
            {
                if (CompareString( GetUserDefaultLCID(),
                                   0,
                                   zone->szDisplayName,
                                   -1,
                                   next->szDisplayName,
                                   -1 ) == CSTR_LESS_THAN)
                {
                    break;
                }
            }
            curr = next;
            next = curr->next;
        }

        zone->next = next;

        if (curr)
        {
            curr->next = zone;
        }
        else
        {
            *list = zone;
        }
    }
    else
    {
        *list = zone;
    }
}


////////////////////////////////////////////////////////////////////////////
//
//  FreeTimezoneList
//
//  Frees all time zones in the passed list, setting the head to NULL.
//
////////////////////////////////////////////////////////////////////////////

void FreeTimezoneList(
    PTZINFO *list)
{
    while (*list)
    {
        PTZINFO next = (*list)->next;

        LocalFree((HANDLE)*list);

        *list = next;
    }
}


////////////////////////////////////////////////////////////////////////////
//
//  ReadTimezones
//
//  Reads the time zone information from the registry.
//  Returns num read, -1 on failure.
//
////////////////////////////////////////////////////////////////////////////

int ReadTimezones(
    PTZINFO *list)
{
    HKEY key = NULL;
    int count = -1;

    *list = NULL;

    if (RegOpenKey( HKEY_LOCAL_MACHINE,
                    c_szTimeZones,
                    &key ) == ERROR_SUCCESS)
    {
        TCHAR name[TZNAME_SIZE];
        PTZINFO zone = NULL;
        int i;

        count = 0;

        for (i = 0;
             RegEnumKey(key, i, name, TZNAME_SIZE) == ERROR_SUCCESS;
             i++)
        {
            HKEY subkey = NULL;

            if (!zone &&
                ((zone = (PTZINFO)LocalAlloc(LPTR, sizeof(TZINFO))) == NULL))
            {
                zone = *list;
                *list = NULL;
                count = -1;
                break;
            }

            zone->next = NULL;

            if (RegOpenKey(key, name, &subkey) == ERROR_SUCCESS)
            {
                //
                //  Each sub key name under the Time Zones key is the
                //  "Standard" name for the Time Zone.
                //
                lstrcpyn(zone->szStandardName, name, TZNAME_SIZE);

                if (ReadZoneData(zone, subkey, name))
                {
                    AddZoneToList(list, zone);
                    zone = NULL;
                    count++;
                }

                RegCloseKey(subkey);
            }
        }

        FreeTimezoneList(&zone);
        RegCloseKey(key);
    }

    return (count);
}


////////////////////////////////////////////////////////////////////////////
//
//  InitZoneMapping
//
//  Initializes map and map lookup for a specific time zone.
//
////////////////////////////////////////////////////////////////////////////

void InitZoneMapping(
    PTZINFO *lookup,
    PTZINFO list,
    HWND map)
{
    PTZINFO zone = list;    // not needed but more readable

    while (zone)
    {
        if (zone->SeaIndex >= 0)
        {
            lookup[zone->SeaIndex] = zone;
        }

        if (zone->LandIndex >= 0)
        {
            lookup[zone->LandIndex] = zone;
        }

        SetZoneState(map, zone, FALSE);
        zone = zone->next;
    }
}


////////////////////////////////////////////////////////////////////////////
//
//  BreakZonesIntoFamilies
//
//  Breaks the passed list into many circular lists.
//  Each list consists of all time zones with a particular bias.
//  Assumes the passed list is sorted by bias.
//
////////////////////////////////////////////////////////////////////////////

void BreakZonesIntoFamilies(
    PTZINFO head)
{
    PTZINFO subhead = NULL;
    PTZINFO last = NULL;
    PTZINFO zone = head;

    while (zone)
    {
        subhead = zone;

        do
        {
            last = zone;
            zone = zone->next;
        }
        while (zone && (zone->Bias == subhead->Bias));

        last->next = subhead;
    }

    //
    //  Merge -12 and +12 zones into a single group.
    //  Assumes populated registry and depends on sort order.
    //
    if ((subhead) &&
        (subhead->Bias == BIAS_PLUS_12) &&
        (head->Bias == BIAS_MINUS_12))
    {
        PTZINFO next = head;

        do
        {
            zone = next;
            next = zone->next;
        }
        while (next != head);

        zone->next = subhead;
        last->next = head;
    }
}


////////////////////////////////////////////////////////////////////////////
//
//  InitTimezones
//
//  Initializes time zone stuff, UI and otherwise.
//
////////////////////////////////////////////////////////////////////////////

BOOL InitTimezones(
    HWND page,
    PTZINFO *lookup)
{
    PTZINFO list = NULL;

    if ((g_nTimeZones = ReadTimezones(&list)) >= 0)
    {
        HWND combo = GetDlgItem(page, IDD_TIMEZONES);
        PTZINFO zone = list;

        SetWindowRedraw(combo, FALSE);

        while (zone)
        {
            int index = ComboBox_AddString(combo, zone->szDisplayName);

            if (index < 0)
            {
                break;
            }
            zone->ComboIndex = index;
            ComboBox_SetItemData(combo, index, (LPARAM)zone);
            zone = zone->next;
        }

        SetWindowRedraw(combo, TRUE);

        if (!zone)
        {
            InitZoneMapping(lookup, list, GetDlgItem(page, IDD_TIMEMAP));
            BreakZonesIntoFamilies(list);
            return (TRUE);
        }

        FreeTimezoneList(&list);
        ComboBox_ResetContent(combo);
    }

    return (FALSE);
}


////////////////////////////////////////////////////////////////////////////
//
//  ChangeZone
//
//  Updates the current zone, making sure new zone's family is highlighted.
//
////////////////////////////////////////////////////////////////////////////

void ChangeZone(
    HWND page,
    TZPAGE_STATE *state,
    PTZINFO zone)
{
    if (zone || state->zone)
    {
        BOOL newfamily = (!zone || !state->zone ||
                          (zone->Bias != state->zone->Bias));
        HWND map = GetDlgItem(page, IDD_TIMEMAP);
        BOOL dayval = (zone && (zone->StandardDate.wMonth != 0));

        if (newfamily && state->zone)
        {
            SetZoneFamilyState(map, state->zone, FALSE);
        }

        state->zone = zone;

        if (newfamily && state->zone)
        {
            SetZoneFamilyState(map, state->zone, TRUE);
        }

        if (newfamily)
        {
            MapControlInvalidateDirtyRegions(map);
        }

        ShowWindow(GetDlgItem(page, IDD_AUTOMAGIC), (dayval != 0 ? SW_SHOW : SW_HIDE));

        if (!state->initializing)
        {
            PropSheet_Changed(GetParent(page), page);
        }
    }
}


////////////////////////////////////////////////////////////////////////////
//
//  HotTrackZone
//
//  Updates the map highlighting and combo selection for a given map index.
//  Expects to be called with dups.
//
////////////////////////////////////////////////////////////////////////////

void HotTrackZone(
    HWND page,
    TZPAGE_STATE *state,
    int index)
{
    PTZINFO zone = state->lookup[index];

    if (zone && (zone != state->zone))
    {
        ComboBox_SetCurSel( GetDlgItem(page, IDD_TIMEZONES),
                            (zone ? zone->ComboIndex : -1) );
        ChangeZone(page, state, zone);
    }
}


////////////////////////////////////////////////////////////////////////////
//
//  CenterZone
//
//  Updates the map highlighting and combo selection for a given map index.
//  Expects to be called with dups.
//
////////////////////////////////////////////////////////////////////////////

void CenterZone(
    HWND page,
    TZPAGE_STATE *state,
    BOOL animate)
{
    PTZINFO zone = state->zone;

    if (zone)
    {
        HWND map = GetDlgItem(page, IDD_TIMEMAP);

        MapControlRotateTo(map, zone->MapLeft + zone->MapWidth / 2, animate);
    }
}


////////////////////////////////////////////////////////////////////////////
//
//  GetPTZ
//
//  Returns the pointer for the iItem time zone.
//  If iItem is -1 on entry, use the currently selected time zone.
//
////////////////////////////////////////////////////////////////////////////

PTZINFO GetPTZ(
    HWND hDlg,
    int iItem)
{
    HWND hCtl = GetDlgItem(hDlg, IDD_TIMEZONES);

    if (iItem == -1)
    {
        iItem = (int)ComboBox_GetCurSel(hCtl);
    }

    if (iItem < 0)
    {
        return (NULL);
    }

    return ((PTZINFO)ComboBox_GetItemData(hCtl, iItem));
}


////////////////////////////////////////////////////////////////////////////
//
//  GetAllowLocalTimeChange
//
////////////////////////////////////////////////////////////////////////////

TCHAR c_szRegPathTZControl[] = REGSTR_PATH_TIMEZONE;
TCHAR c_szRegValDisableTZUpdate[] = REGSTR_VAL_TZNOAUTOTIME;

BOOL GetAllowLocalTimeChange()
{
    //
    //  Assume allowed until we see a disallow flag.
    //
    BOOL result = TRUE;
    HKEY key;

    if (RegOpenKey( HKEY_LOCAL_MACHINE,
                    c_szRegPathTZControl,
                    &key ) == ERROR_SUCCESS)
    {
        //
        //  Assume no disallow flag until we see one.
        //
        DWORD value = 0;
        long len = sizeof(value);
        DWORD type;

        if ((RegQueryValueEx( key,
                              c_szRegValDisableTZUpdate,
                              NULL,
                              &type,
                              (LPBYTE)&value,
                              &len ) == ERROR_SUCCESS) &&
            ((type == REG_DWORD) || (type == REG_BINARY)) &&
            (len == sizeof(value)) && value)
        {
            //
            //  Okay, we have a nonzero value, it is either:
            //
            //  1) 0xFFFFFFFF
            //      this is set in an inf file for first boot to prevent
            //      the base from performing any cutovers during setup.
            //
            //  2) some other value
            //      this signifies that the user actualy disabled cutovers
            //     *return that local time changes are disabled
            //
            if (value != 0xFFFFFFFF)
            {
                result = FALSE;
            }
        }

        RegCloseKey(key);
    }

    return (result);
}


////////////////////////////////////////////////////////////////////////////
//
//  SetAllowLocalTimeChange
//
////////////////////////////////////////////////////////////////////////////

void SetAllowLocalTimeChange(
    BOOL fAllow)
{
    HKEY key = NULL;

    if (fAllow)
    {
        //
        //  Remove the disallow flag from the registry if it exists.
        //
        if (RegOpenKey( HKEY_LOCAL_MACHINE,
                        c_szRegPathTZControl,
                        &key ) == ERROR_SUCCESS)
        {
            RegDeleteValue(key, c_szRegValDisableTZUpdate);
        }
    }
    else
    {
        //
        //  Add/set the nonzero disallow flag.
        //
        if (RegCreateKey( HKEY_LOCAL_MACHINE,
                          c_szRegPathTZControl,
                          &key ) == ERROR_SUCCESS)
        {
            DWORD value = 1;

            RegSetValueEx( key,
                           (LPCTSTR)c_szRegValDisableTZUpdate,
                           0UL,
                           REG_DWORD,
                           (LPBYTE)&value,
                           sizeof(value) );
        }
    }

    if (key)
    {
        RegCloseKey(key);
    }
}


////////////////////////////////////////////////////////////////////////////
//
//  InitTimeZonePage
//
//  This function initializes everything to do with the Time Zones.
//
////////////////////////////////////////////////////////////////////////////

BOOL InitTimeZonePage(
    HWND hDlg,
    TZPAGE_STATE *state)
{
    TIME_ZONE_INFORMATION tziCurrent;
    DWORD dwTZID;
    PTZINFO ptzi;
    int j ,iCurrentTZ;
    BOOL fForceSelection = FALSE;
    TCHAR temp[TZNAME_SIZE];
    TCHAR oldTzMapName[TZNAME_SIZE], newTzMapName[TZNAME_SIZE];


    //
    //  Get the current time zone information.
    //
    dwTZID = GetTimeZoneInformation(&tziCurrent);

    LoadString(g_hInst, IDS_ISRAELTIMEZONE, oldTzMapName, TZNAME_SIZE);
    LoadString(g_hInst, IDS_JERUSALEMTIMEZONE, newTzMapName, TZNAME_SIZE);
    
    // this is a hack for Win95 or WinNT 4 to Win98/Win2k migration. "Israel" became "Jerusalem"
#ifdef UNICODE
    if (!lstrcmpi(oldTzMapName, tziCurrent.StandardName))
    {
        lstrcpy(tziCurrent.StandardName, newTzMapName);
        fForceSelection = TRUE;
    }
#else
    if (!AnsiWideStrCmpI(oldTzMapName, tziCurrent.StandardName))
    {
        StrToWideStr(tziCurrent.StandardName, newTzMapName);
        fForceSelection = TRUE;
    }
#endif

    //
    //  Check for bogus time zone info.
    //
    if (dwTZID != TIME_ZONE_ID_INVALID)
    {
        //
        //  Copy the name out so we can check for first boot.
        //
#ifdef UNICODE
        lstrcpy(temp, tziCurrent.StandardName);
#else
        WideStrToStr(temp, tziCurrent.StandardName);
#endif
    }
    else
    {
        //
        //  Treat bogus time zones like first boot.
        //
        lstrcpy(temp, c_szFirstBootTZ);
    }

    if (lstrcmpi(temp, c_szFirstBootTZ) == 0)
    {
        //
        //  The 'default' value of the time zone key specifies the
        //  default zone.
        //
        TCHAR szDefaultName[TZNAME_SIZE];
        LONG len = sizeof(szDefaultName);

        if (RegQueryValue( HKEY_LOCAL_MACHINE,
                           c_szTimeZones,
                           szDefaultName,
                           &len ) == ERROR_SUCCESS)
        {
#ifdef UNICODE
            lstrcpy(tziCurrent.StandardName, szDefaultName);
#else
            StrToWideStr(tziCurrent.StandardName, szDefaultName);
#endif
        }
        else
        {
            tziCurrent.StandardName[0] = 0;
        }

        //
        //  If we can't find it by name, use GMT.
        //
        tziCurrent.StandardBias = tziCurrent.DaylightBias = tziCurrent.Bias = 0;

        //
        //  Force the user to make a valid choice before quitting.
        //
        fForceSelection = TRUE;
    }

    //
    //  Get the Time Zones from the registry.
    //
    InitTimezones(hDlg, state->lookup);

    //
    //  Try to select the 'current' one or some equivalent.
    //

    //
    //  Start with an invalid index.
    //
    iCurrentTZ = g_nTimeZones;

    //
    //  Try to find by name.
    //
    for (j = 0; j < g_nTimeZones; j++)
    {
        ptzi = GetPTZ(hDlg, j);

#ifdef UNICODE
        if (!lstrcmpi(ptzi->szStandardName, tziCurrent.StandardName))
#else
        if (!AnsiWideStrCmpI(ptzi->szStandardName, tziCurrent.StandardName))
#endif
        {
            iCurrentTZ = j;
            break;
        }
    }

    //
    //  If it hasn't been found yet, try to find a nearby zone using biases.
    //
    if (iCurrentTZ == g_nTimeZones)
    {
        int nBestHitCount = TZ_HIT_NONE;

        for (j = 0; j < g_nTimeZones; j++)
        {
            ptzi = GetPTZ(hDlg, j);

            if (ptzi->Bias == tziCurrent.Bias)
            {
                int nHitCount = TZ_HIT_BASE +
                            ((ptzi->StandardBias == tziCurrent.StandardBias) +
                             (ptzi->DaylightBias == tziCurrent.DaylightBias));

                if (nHitCount > nBestHitCount)
                {
                    nBestHitCount = nHitCount;
                    iCurrentTZ = j;

                    if (nHitCount >= TZ_HIT_EXACT)
                    {
                        break;
                    }
                }
            }
        }
    }

    //
    //  Still didn't find it?
    //
    if (iCurrentTZ == g_nTimeZones)
    {
        //
        //  Punt.
        //
        iCurrentTZ = 0;

        fForceSelection = TRUE;
    }

    //
    //  Set up the dialog using this time zone's info.
    //

    //
    //  Always use our rules for the allow-daylight muck.
    //
#ifndef WINNT
    if ((ptzi = GetPTZ(hDlg, iCurrentTZ)) != NULL)
    {
        tziCurrent.StandardDate = ptzi->StandardDate;
        tziCurrent.DaylightDate = ptzi->DaylightDate;
    }
#endif

    //
    //  If wMonth is 0, then this Time Zone does not support DST.
    //
    if ((tziCurrent.StandardDate.wMonth == 0) ||
        (tziCurrent.DaylightDate.wMonth == 0))
    {
        ShowWindow(GetDlgItem(hDlg, IDD_AUTOMAGIC), SW_HIDE);
    }

    //
    //  Always get "allow DLT" flag even if this zone is disabled.
    //
    CheckDlgButton(hDlg, IDD_AUTOMAGIC, GetAllowLocalTimeChange());

    ComboBox_SetCurSel(GetDlgItem(hDlg, IDD_TIMEZONES), iCurrentTZ);

    ChangeZone(hDlg, state, GetPTZ(hDlg, -1));
    CenterZone(hDlg, state, FALSE);

    if (fForceSelection || g_bFirstBoot)
    {
        PropSheet_Changed(GetParent(hDlg), hDlg);
        PropSheet_CancelToClose(GetParent(hDlg));
    }

    return (TRUE);
}


////////////////////////////////////////////////////////////////////////////
//
//  SetTheTimezone
//
//  Apply the User's time zone selection.
//
////////////////////////////////////////////////////////////////////////////

void SetTheTimezone(
    BOOL bAutoMagicTimeChange,
    BOOL bAutoMagicEnabled,
    PTZINFO ptzi)
{
    TIME_ZONE_INFORMATION tzi;
    HCURSOR hCurOld;

    if (!ptzi)
    {
        return;
    }

    tzi.Bias = ptzi->Bias;

#ifdef WINNT
    if ((bAutoMagicTimeChange == 0) ||
        (ptzi->StandardDate.wMonth == 0))
    {
        //
        //  Standard Only.
        //
        tzi.StandardBias = ptzi->StandardBias;
        tzi.DaylightBias = ptzi->StandardBias;
        tzi.StandardDate = ptzi->StandardDate;
        tzi.DaylightDate = ptzi->StandardDate;

#ifdef UNICODE
        lstrcpy(tzi.StandardName, ptzi->szStandardName);
        lstrcpy(tzi.DaylightName, ptzi->szStandardName);
#else
        StrToWideStr(tzi.StandardName, ptzi->szStandardName);
        StrToWideStr(tzi.DaylightName, ptzi->szStandardName);
#endif
    }
    else
#endif
    {
        //
        //  Automatically adjust for Daylight Saving Time.
        //
        tzi.StandardBias = ptzi->StandardBias;
        tzi.DaylightBias = ptzi->DaylightBias;
        tzi.StandardDate = ptzi->StandardDate;
        tzi.DaylightDate = ptzi->DaylightDate;

#ifdef UNICODE
        lstrcpy(tzi.StandardName, ptzi->szStandardName);
        lstrcpy(tzi.DaylightName, ptzi->szDaylightName);
#else
        StrToWideStr(tzi.StandardName, ptzi->szStandardName);
        StrToWideStr(tzi.DaylightName, ptzi->szDaylightName);
#endif
    }

    SetAllowLocalTimeChange(bAutoMagicTimeChange);

    SetTimeZoneInformation(&tzi);

    hCurOld = SetCursor(LoadCursor(NULL, IDC_WAIT));

    SetCursor(hCurOld);
}


////////////////////////////////////////////////////////////////////////////
//
//  TimeZoneDlgProc
//
////////////////////////////////////////////////////////////////////////////

INT_PTR CALLBACK TimeZoneDlgProc(
    HWND hDlg,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam)
{
    TZPAGE_STATE *state = (TZPAGE_STATE *)GetWindowLongPtr(hDlg, DWLP_USER);

    int  i;

    switch (uMsg)
    {
        case ( WM_INITDIALOG ) :
        {
            state = (TZPAGE_STATE *)LocalAlloc(LPTR, sizeof(TZPAGE_STATE));

            SetWindowLongPtr(hDlg, DWLP_USER, (LONG_PTR)state);

            if (!state)
            {
                EndDialog(hDlg, -1);
                break;
            }

            state->initializing = TRUE;
            InitTimeZonePage(hDlg, state);
            state->initializing = FALSE;

            break;
        }
        case ( WM_DESTROY ) :
        {
            for (i = 0; i < g_nTimeZones; i++)
            {
                LocalFree((HLOCAL)GetPTZ(hDlg, i));
            }

            if (state)
            {
                LocalFree((HANDLE)state);
                SetWindowLongPtr(hDlg, DWLP_USER, 0L);
            }
            break;
        }
        case ( WM_NOTIFY ) :
        {
            switch (((NMHDR *)lParam)->idFrom)
            {
                case ( 0 ) :
                {
                    switch (((NMHDR *)lParam)->code)
                    {
                        case ( PSN_APPLY ) :
                        {
                            g_ptziCurrent = NULL;

                            //
                            //  Find out which listbox item was selected.
                            //
                            SetTheTimezone(
                                IsDlgButtonChecked(hDlg, IDD_AUTOMAGIC),
                                IsWindowVisible(GetDlgItem(hDlg, IDD_AUTOMAGIC)),
                                GetPTZ(hDlg, -1) );

                            //
                            // if the user had modified the time as well as the timezone,
                            // then we should honor the time that they gave us since they
                            // explicitly said this was the time.  If we don't then the
                            // time they entered will be offset by the timezone change
                            //
                       
                            if (g_WasModified)
                            {
                                g_WasModified = FALSE;
                                SetTime();
                            }
                            break;
                        }
                    }
                    break;
                }
                case ( IDD_TIMEMAP ) :
                {
                    NFYMAPEVENT *event = (NFYMAPEVENT *)lParam;

                    switch (event->hdr.code)
                    {
                        case ( MAPN_TOUCH ) :
                        {
                            HotTrackZone(hDlg, state, event->index);
                            break;
                        }
                        case ( MAPN_SELECT ) :
                        {
                            CenterZone(hDlg, state, TRUE);
                            break;
                        }
                    }
                    break;
                }
            }
            break;
        }
        case ( WM_COMMAND ) :
        {
            switch (GET_WM_COMMAND_ID(wParam, lParam))
            {
                case ( IDD_TIMEZONES ) :    // combo box
                {
                    if (GET_WM_COMMAND_CMD(wParam, lParam) == CBN_SELCHANGE)
                    {
                        ChangeZone(hDlg, state, GetPTZ(hDlg, -1));
                        CenterZone(hDlg, state, TRUE);
                    }
                    break;
                }
                case ( IDD_AUTOMAGIC ) :    // check box
                {
                    if (GET_WM_COMMAND_CMD(wParam, lParam) == BN_CLICKED)
                    {
                        PropSheet_Changed(GetParent(hDlg), hDlg);
                    }
                    break;
                }
            }
            break;
        }
        case ( WM_HELP ) :             // F1
        {
            WinHelp( (HWND)((LPHELPINFO)lParam)->hItemHandle,
                     NULL,
                     HELP_WM_HELP,
                     (DWORD_PTR)(LPTSTR)aDateTimeHelpIds );
            break;
        }
        case ( WM_CONTEXTMENU ) :      // right mouse click
        {
            WinHelp( (HWND)wParam,
                     NULL,
                     HELP_CONTEXTMENU,
                     (DWORD_PTR)(LPTSTR)aDateTimeHelpIds );
            break;
        }
        default :
        {
            return (FALSE);
        }
    }

    return (TRUE);
}


////////////////////////////////////////////////////////////////////////////
//
//  GetClInt
//
//  Steal an int from the command line.
//
////////////////////////////////////////////////////////////////////////////

static int GetClInt(
    const TCHAR *p)
{
    BOOL neg = FALSE;
    int v = 0;

    //
    //  Skip spaces.
    //
    while (*p == TEXT(' '))
    {
        p++;
    }

    //
    //  See if it's negative.
    //
    if (*p == TEXT('-'))
    {
        //
        //  It's negative.  Remember that it's negative and skip the
        //  '-' char.
        //
        neg = TRUE;
        p++;
    }

    //
    //  Parse the absolute portion.  Digits only.
    //
    while ((*p >= TEXT('0')) && (*p <= TEXT('9')))
    {
        //
        //  Accumulate the value.
        //
        v = v * 10 + *p++ - TEXT('0');
    }

    //
    //  Return the result.
    //
    return (neg ? -v : v);
}


////////////////////////////////////////////////////////////////////////////
//
//  SelectZoneByName
//
////////////////////////////////////////////////////////////////////////////

BOOL SelectZoneByName(
    LPCTSTR cmdline)
{
    BOOL result = FALSE;
    HKEY key = NULL;

    while (*cmdline == TEXT(' '))
    {
        cmdline++;
    }

    if (!*cmdline)
    {
        return (FALSE);
    }

    if (RegOpenKey( HKEY_LOCAL_MACHINE,
                    c_szTimeZones,
                    &key ) == ERROR_SUCCESS)
    {
        TCHAR name[TZNAME_SIZE];
        HKEY subkey = NULL;
        TZINFO zone;

        //
        //  User can pass key name.
        //
        if (RegOpenKey(key, cmdline, &subkey) == ERROR_SUCCESS)
        {
            if (ReadZoneData(&zone, subkey, cmdline))
            {
                result = TRUE;
            }
        }
        else
        {
            //
            //  User can also pass display name.
            //
            int i;
            int CmdLen = lstrlen(cmdline);

            for (i = 0;
                 RegEnumKey(key, i, name, TZNAME_SIZE) == ERROR_SUCCESS;
                 i++)
            {
                if (RegOpenKey(key, name, &subkey) == ERROR_SUCCESS)
                {
                    LONG len = sizeof(zone.szDisplayName);

                    if ((RegQueryValueEx( subkey,
                                          c_szTZDisplayName,
                                          0,
                                          NULL,
                                          (LPBYTE)&zone.szDisplayName,
                                          &len ) == ERROR_SUCCESS) &&
                        (CompareString( GetUserDefaultLCID(),
                                        NORM_IGNORECASE  | NORM_IGNOREKANATYPE |
                                        NORM_IGNOREWIDTH | NORM_IGNORENONSPACE,
                                        zone.szDisplayName,
                                        (CmdLen < 15)
                                            ? -1
                                            : min(lstrlen(zone.szDisplayName),
                                                  CmdLen),
                                        cmdline,
                                        -1 ) == CSTR_EQUAL))
                    {
                        if (ReadZoneData(&zone, subkey, name))
                        {
                            result = TRUE;
                        }
                    }

                    RegCloseKey(subkey);
                }

                if (result)
                {
                    break;
                }
            }
        }

        RegCloseKey(key);

        if (result)
        {
            SetTheTimezone(1, 1, &zone);
        }
    }

    return (result);
}


////////////////////////////////////////////////////////////////////////////
//
//  OpenDateTimePropertySheet
//
//  Opens a DateTime property sheet.
//  Set the page for the property sheet.
//
////////////////////////////////////////////////////////////////////////////

BOOL OpenDateTimePropertySheet(
    HWND hwnd,
    LPCTSTR cmdline)
{
    //  Make this an array for multiple pages.
    PROPSHEETPAGE apsp[3];
    PROPSHEETHEADER psh;
    HDC hDC;
    HFONT   hFont;
    int wMaxDigitWidth;
    BOOL fReturn;
    HRESULT hrOle;

    hDC = GetDC(hwnd);

    wMaxDigitWidth = GetMaxSubstitutedCharWidth(hDC);
    ReleaseDC(hwnd, hDC);

    psh.nStartPage = (UINT)-1;

    if (cmdline && *cmdline)
    {
        if (*cmdline == TEXT('/'))
        {
            BOOL fAutoSet = FALSE;

            //
            //  Legend:
            //    zZ: first boot batch mode setup "/z pacific" etc
            //    fF: regular first boot
            //    mM: time zone change forced local time change message
            //
            switch (*++cmdline)
            {
                case ( TEXT('z') ) :
                case ( TEXT('Z') ) :
                {
                    fAutoSet = TRUE;

                    //
                    //  Fall thru...
                    //
                }
                case ( TEXT('f') ) :
                case ( TEXT('F') ) :
                {
                    g_bFirstBoot = TRUE;

                    if (fAutoSet && SelectZoneByName(cmdline + 1))
                    {
                        return (TRUE);
                    }

                    //
                    //  Start on time zone page.
                    //
                    psh.nStartPage = 1;
                    break;
                }
                case ( TEXT('m') ) :
                case ( TEXT('M') ) :
                {
                    MSGBOXPARAMS params =
                                  {
                                    sizeof(params),
                                    hwnd,
                                    g_hInst,
                                    MAKEINTRESOURCE(IDS_WARNAUTOTIMECHANGE),
                                    MAKEINTRESOURCE(IDS_WATC_CAPTION),
                                    MB_OK | MB_USERICON,
                                    MAKEINTRESOURCE(IDI_TIMEDATE),
                                    0,
                                    NULL,
                                    0
                                  };

                    MessageBoxIndirect(&params);

                    //
                    //  Show time/date page for user to verify.
                    //
                    psh.nStartPage = 0;

                    break;
                }
                default :
                {
                    //
                    //  Fall out, maybe it's a number...
                    //
                    break;
                }
            }
        }
    }

    if (psh.nStartPage == (UINT)-1)
    {
        if (cmdline && (*cmdline >= TEXT('0')) && (*cmdline <= TEXT('9')))
        {
            psh.nStartPage = GetClInt(cmdline);
        }
        else
        {
            psh.nStartPage = 0;
        }
    }

    //
    //  Register our classes.
    //
    ClockInit(g_hInst);
    CalendarInit(g_hInst);
    RegisterMapControlStuff(g_hInst);

    psh.dwSize = sizeof(psh);
    if (g_bFirstBoot)
    {
        //
        //  Disable Apply button for first boot.
        //
        psh.dwFlags = PSH_PROPTITLE | PSH_PROPSHEETPAGE | PSH_NOAPPLYNOW;
    }
    else
    {
        psh.dwFlags = PSH_PROPTITLE | PSH_PROPSHEETPAGE;
    }
    psh.hwndParent = hwnd;
    psh.hInstance = g_hInst;
    psh.pszIcon = NULL;

    //
    //  psh.nStartPage is set above.
    //
    psh.pszCaption = MAKEINTRESOURCE(IDS_TIMEDATE);
    psh.nPages = 2;
    psh.ppsp = apsp;

    apsp[0].dwSize = sizeof(PROPSHEETPAGE);
    apsp[0].dwFlags = PSP_DEFAULT;
    apsp[0].hInstance = g_hInst;
    apsp[0].pszTemplate = wMaxDigitWidth > 8 ? MAKEINTRESOURCE(DLG_DATETIMEWIDE) : MAKEINTRESOURCE(DLG_DATETIME);
    apsp[0].pfnDlgProc = DateTimeDlgProc;
    apsp[0].lParam = 0;

    apsp[1].dwSize = sizeof(PROPSHEETPAGE);
    apsp[1].dwFlags = PSP_DEFAULT;
    apsp[1].hInstance = g_hInst;
    apsp[1].pszTemplate = MAKEINTRESOURCE(DLG_TIMEZONE);
    apsp[1].pfnDlgProc = TimeZoneDlgProc;
    apsp[1].lParam = 0;

    if (psh.nStartPage >= psh.nPages)
    {
        psh.nStartPage = 0;
    }

    // We use the HyperLink control and that requires OLE (for IAccessible)
    hrOle = CoInitialize(0);

    fReturn = (BOOL)PropertySheet(&psh);
    if (SUCCEEDED(hrOle))
    {
        CoUninitialize();
    }

    return fReturn;
}
