/*++ Copyright (c) 1997 Microsoft Corporation Module Name: datetime.cpp Abstract: This file implements the date & time page. Environment: WIN32 User Mode Author: Wesley Witt (wesw) 1-Dec-1997 --*/ #include "ntoc.h" #pragma hdrstop #define MYDEBUG 0 #define TIMER_ID 1 #define OPEN_TLEN 450 /* < half second */ #define TZNAME_SIZE 128 #define TZDISPLAYZ 128 #define REGKEY_TIMEZONES L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones" #define REGVAL_TZ_DISPLAY L"Display" #define REGVAL_TZ_STD L"Std" #define REGVAL_TZ_DAYLIGHT L"Dlt" #define REGVAL_TZ_TZI L"TZI" #define REGVAL_TZ_INDEX L"Index" #define REGVAL_TZ_INDEXMAP L"IndexMapping" #define REGKEY_TIMEZONE_INFO L"System\\CurrentControlSet\\Control\\TimeZoneInformation" #define REGVAL_TZNOAUTOTIME L"DisableAutoDaylightTimeSet" typedef struct tagTZINFO { LIST_ENTRY ListEntry; WCHAR szDisplayName[TZDISPLAYZ]; WCHAR szStandardName[TZNAME_SIZE]; WCHAR szDaylightName[TZNAME_SIZE]; int ReferenceIndex; LONG Bias; LONG StandardBias; LONG DaylightBias; SYSTEMTIME StandardDate; SYSTEMTIME DaylightDate; } TZINFO, *PTZINFO; LIST_ENTRY ZoneList; SYSTEMTIME SelectedTime; SYSTEMTIME SelectedDate; BOOL ChangeTime; BOOL ChangeDate; PTZINFO CurrZone; BOOL AllowAutoDST; INT gUnattenedTimeZone = -1; BOOL DateTimeBadUnattend; HWND ghWnd; // global copy of the handle to the wizard page. This // is required by DateTimeCommitChanges during an // unattended installation. BOOL ReadZoneData( PTZINFO zone, HKEY key, LPCWSTR keyname ) { WCHAR mapinfo[16]; DWORD len; len = sizeof(zone->szDisplayName); if (RegQueryValueEx( key, REGVAL_TZ_DISPLAY, 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, REGVAL_TZ_STD, 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, REGVAL_TZ_DAYLIGHT, 0, NULL, (LPBYTE)zone->szDaylightName, &len ) != ERROR_SUCCESS) { return (FALSE); } len = sizeof(zone->ReferenceIndex); if (RegQueryValueEx( key, REGVAL_TZ_INDEX, 0, NULL, (LPBYTE)&zone->ReferenceIndex, &len ) != ERROR_SUCCESS) { return (FALSE); } len = sizeof(zone->Bias) + sizeof(zone->StandardBias) + sizeof(zone->DaylightBias) + sizeof(zone->StandardDate) + sizeof(zone->DaylightDate); if (RegQueryValueEx( key, REGVAL_TZ_TZI, 0, NULL, (LPBYTE)&zone->Bias, &len ) != ERROR_SUCCESS) { return (FALSE); } return (TRUE); } #if MYDEBUG void PrintZones( void ) { PLIST_ENTRY NextZone; PTZINFO zone; NextZone = ZoneList.Flink; if (NextZone) { DebugPrint(( L"----------------- time zone list -------------------------------------\n" )); while (NextZone != &ZoneList) { zone = CONTAINING_RECORD( NextZone, TZINFO, ListEntry ); NextZone = zone->ListEntry.Flink; DebugPrint(( L"%03d %s", zone->ReferenceIndex, zone->szDisplayName )); } } } #endif void AddZoneToList( PTZINFO zone ) { PLIST_ENTRY NextZone; PTZINFO ThisZone; if (IsListEmpty( &ZoneList )) { InsertHeadList( &ZoneList, &zone->ListEntry ); return; } NextZone = ZoneList.Flink; while (NextZone != &ZoneList) { ThisZone = CONTAINING_RECORD( NextZone, TZINFO, ListEntry ); NextZone = ThisZone->ListEntry.Flink; if (ThisZone->ReferenceIndex > zone->ReferenceIndex) { InsertTailList( &ThisZone->ListEntry, &zone->ListEntry ); return; } } InsertTailList( &ZoneList, &zone->ListEntry ); } int BuildTimeZoneList( void ) { HKEY key = NULL; int count = -1; InitializeListHead( &ZoneList ); if (RegOpenKey( HKEY_LOCAL_MACHINE, REGKEY_TIMEZONES, &key ) == ERROR_SUCCESS) { WCHAR 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)) { break; } 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(zone); zone = NULL; count++; } RegCloseKey(subkey); } } RegCloseKey(key); } return count; } void DateTimeInit( void ) { DWORD d; BuildTimeZoneList(); #if MYDEBUG PrintZones(); #endif if ((SetupInitComponent.SetupData.OperationFlags & SETUPOP_BATCH) == 0) { return; } HINF InfHandle = SetupInitComponent.HelperRoutines.GetInfHandle( INFINDEX_UNATTENDED, SetupInitComponent.HelperRoutines.OcManagerContext ); if (InfHandle == NULL) { DateTimeBadUnattend = TRUE; return; } INFCONTEXT InfLine; if (!SetupFindFirstLine(InfHandle, L"GuiUnattended", L"TimeZone", &InfLine )) { DateTimeBadUnattend = TRUE; return; } if (SetupGetIntField( &InfLine, 1, (PINT)&d )) { gUnattenedTimeZone = (INT) d; } else { DateTimeBadUnattend = TRUE; } } void SetAllowLocalTimeChange( BOOL fAllow ) { HKEY key = NULL; if (fAllow) { // // Remove the disallow flag from the registry if it exists. // if (RegOpenKey( HKEY_LOCAL_MACHINE, REGKEY_TIMEZONE_INFO, &key ) == ERROR_SUCCESS) { RegDeleteValue(key, REGVAL_TZNOAUTOTIME); } } else { // // Add/set the nonzero disallow flag. // if (RegCreateKey( HKEY_LOCAL_MACHINE, REGKEY_TIMEZONE_INFO, &key ) == ERROR_SUCCESS) { DWORD value = 1; RegSetValueEx( key, REGVAL_TZNOAUTOTIME, 0, REG_DWORD, (LPBYTE)&value, sizeof(value) ); } } if (key) { RegCloseKey(key); } } BOOL GetAllowLocalTimeChange( void ) { // // Assume allowed until we see a disallow flag. // BOOL result = TRUE; HKEY key; if (RegOpenKey( HKEY_LOCAL_MACHINE, REGKEY_TIMEZONE_INFO, &key ) == ERROR_SUCCESS) { // // Assume no disallow flag until we see one. // DWORD value = 0; DWORD len = sizeof(value); DWORD type; if ((RegQueryValueEx( key, REGVAL_TZNOAUTOTIME, 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); } void SetTheTimezone( BOOL bAutoMagicTimeChange, PTZINFO ptzi ) { TIME_ZONE_INFORMATION tzi; HCURSOR hCurOld; if (!ptzi) { return; } tzi.Bias = ptzi->Bias; 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; lstrcpy(tzi.StandardName, ptzi->szStandardName); lstrcpy(tzi.DaylightName, ptzi->szStandardName); } else { // // Automatically adjust for Daylight Saving Time. // tzi.StandardBias = ptzi->StandardBias; tzi.DaylightBias = ptzi->DaylightBias; tzi.StandardDate = ptzi->StandardDate; tzi.DaylightDate = ptzi->DaylightDate; lstrcpy(tzi.StandardName, ptzi->szStandardName); lstrcpy(tzi.DaylightName, ptzi->szDaylightName); } SetAllowLocalTimeChange( bAutoMagicTimeChange ); SetTimeZoneInformation( &tzi ); } void DateTimeApplyChanges( void ) { SYSTEMTIME SysTime; if (SetupInitComponent.SetupData.OperationFlags & SETUPOP_NTUPGRADE) { return; } // Note that in the unattended case we will never have ChangeTime set // as the page never is used except for the timezone stuff. There is // no support to set date/time via unattend. if (ChangeTime) { SysTime.wHour = SelectedTime.wHour; SysTime.wMinute = SelectedTime.wMinute; SysTime.wSecond = SelectedTime.wSecond; SysTime.wMilliseconds = SelectedTime.wMilliseconds; } else { GetLocalTime( &SysTime ); } // If this is an unattended setup the PSN_WIZNEXT never arrived so it is // necessary to check the state of ICD_DAYTIME which was set by DateTimeOnInitDialog(). if ((SetupInitComponent.SetupData.OperationFlags & SETUPOP_BATCH) && gUnattenedTimeZone != -1) { // This is unattended. AllowAutoDST = IsDlgButtonChecked( ghWnd, IDC_DAYLIGHT ) != 0; } else { // This is NOT UNATTENDED. SelectedDate was initialized when PSN_WIZNEXT // was processed. SysTime.wYear = SelectedDate.wYear; SysTime.wMonth = SelectedDate.wMonth; SysTime.wDayOfWeek = SelectedDate.wDayOfWeek; SysTime.wDay = SelectedDate.wDay; } // Function SetLocalTime uses Time Zone information so it is IMPERATIVE that // SetTheTimezone get called before SetLocalTime. SetTheTimezone( AllowAutoDST, CurrZone ); SetLocalTime( &SysTime ); } void DateTimeCommitChanges( void ) { return; } ///////////////////////////////////////////////////////////////////////////// //++ // // GetTimeZoneReferenceIndexFromRegistry // // Routine Description: // This funecion extracts the Time Zone reference index from information that // is stored in the registry. // // Arguments: // None // // Return Value: // The Time Zone reference index. If no valid reference index is deduced // this function will return zero. // // Note: // The logic performed by the following function was originally implemented in // DateTimeOnInitDialog. // //-- ///////////////////////////////////////////////////////////////////////////// int GetTimeZoneReferenceIndexFromRegistry( void ) { int xReferenceIndex; HKEY hKey; // Attempt to open the Time Zones registry key. if ( RegOpenKey( HKEY_LOCAL_MACHINE, REGKEY_TIMEZONES, &hKey ) == ERROR_SUCCESS ) { // The following call to RegQueryValueEx retrieves the size, in bytes, of // the IndexMapping registry value, in parameter "index". int xIndexMapSize; if ( RegQueryValueEx( hKey, REGVAL_TZ_INDEXMAP, 0, NULL, NULL, (LPDWORD) &xIndexMapSize ) == ERROR_SUCCESS ) { // Allocate memory for the IndexMap registry value. LPWSTR wszIndexMap; wszIndexMap = (LPWSTR) LocalAlloc( LPTR, xIndexMapSize ); // Was a buffer allocated successfully? if ( wszIndexMap != (LPWSTR) NULL ) { // This call to RegQueryValueEx retrieves the IndexMap value into // the buffer, wszIndexMap. if ( RegQueryValueEx( hKey, REGVAL_TZ_INDEXMAP, 0, NULL, (LPBYTE) wszIndexMap, (LPDWORD) &xIndexMapSize ) == ERROR_SUCCESS ) { // Get the language identifier. WCHAR wszLangStr[32]; if ( GetLocaleInfo( LOCALE_USER_DEFAULT, LOCALE_ILANGUAGE, wszLangStr, sizeof( wszLangStr )/sizeof( WCHAR ) ) > 0 ) { LPWSTR lang = wszLangStr; LPWSTR map = wszIndexMap; while ( *lang == L'0' ) lang++; while ( *map ) { if ( _wcsicmp( lang, map ) == 0 ) { while ( *map ) map++; map++; xReferenceIndex = _wtol( map ); break; } while ( *map ) map++; map++; while ( *map ) map++; map++; } // end of while loop } // language identifier obtained? } // IndexMapping reg value queried? LocalFree( wszIndexMap ); } // memory allocated for ImageMap reg value retrieval? else { xReferenceIndex = 0; } // memory allocated for ImageMap reg value retrieval? } // Size of ImageMap obtained? else { xReferenceIndex = 0; } // Size of ImageMap obtained? RegCloseKey( hKey ); } // TimeZones reg key opened? else { xReferenceIndex = 0; } // TimeZones reg key opened? return ( xReferenceIndex ); } BOOL DateTimeOnInitDialog( IN HWND hwnd, IN HWND hwndFocus, IN LPARAM lParam ) { PLIST_ENTRY NextZone; PTZINFO zone; HWND combo; WCHAR LangStr[32]; int DesiredZone = 0; int index; HKEY hKey; LPWSTR IndexMap; ghWnd = hwnd; // initialize the global copy of the handle to the // wizard page. ghWnd is used by DateTimeCommitChanges() // during unattended setup. SetTimer( hwnd, TIMER_ID, OPEN_TLEN, 0 ); if ( (SetupInitComponent.SetupData.OperationFlags & SETUPOP_BATCH) && gUnattenedTimeZone != -1 ) { // // We've got an unattended time zone value // // If everything were perfect DesiredZone will exactly match the ReferenceIndex // member of one of the TZINFO structures in ZoneList. Note that ZoneList was // built by BuildTimeZoneList. DesiredZone = gUnattenedTimeZone; } else { // // Base the default zone on the locale // // Extract the reference index for the desired time zone from the registry. DesiredZone = GetTimeZoneReferenceIndexFromRegistry(); } // Time zone specified in unattended setup answer file? #if MYDEBUG DebugPrint(( L"DesiredZone = %03d", DesiredZone )); #endif combo = GetDlgItem( hwnd, IDC_TIMEZONE ); SetWindowRedraw( combo, FALSE ); PTZINFO pTimeZoneInfo = (PTZINFO) NULL; // Note that ZoneList was built by BuildTimeZoneList. NextZone = ZoneList.Flink; if ( NextZone ) { // Add time zones to the combo box. while ( NextZone != &ZoneList ) { zone = CONTAINING_RECORD( NextZone, TZINFO, ListEntry ); NextZone = zone->ListEntry.Flink; index = ComboBox_AddString( combo, zone->szDisplayName ); #if MYDEBUG DebugPrint(( L"%03d,%03d %s", index, zone->ReferenceIndex, zone->szDisplayName )); #endif if ( index < 0 ) { break; } ComboBox_SetItemData( combo, index, (LPARAM)zone ); if ( DesiredZone == zone->ReferenceIndex ) { pTimeZoneInfo = zone; #if MYDEBUG DebugPrint(( L" Found DesiredZone" )); #endif } } // end of while loop } // Was a time zone that matched DesiredZone identified? if ( pTimeZoneInfo != (PTZINFO) NULL ) { // Set the GLOBAL Time Zone Info structure pointer. CurrZone = pTimeZoneInfo; } else { // The fact that pTimeZoneInfo remained unchanged from its' initialized state // means that DesiredZone is not meaningfull. // Was DesiredZone obtained from the unattended setup answer file? if ( gUnattenedTimeZone != -1 ) { // DesiredZone was obtained from the answer file. Since it is not meaningfull, // attempt to deduce it from registry information. Deducing DesiredZone from // information in the registry is the default action for ATTENDED setup. DesiredZone = GetTimeZoneReferenceIndexFromRegistry(); } // Was DesiredZone obtained from the answer file? // Is DesiredZone meaningfull now? if ( DesiredZone != 0 ) { // Scan the list of Time Zones for one that matches DesiredZone. NextZone = ZoneList.Flink; if ( NextZone ) { while ( NextZone != &ZoneList ) { zone = CONTAINING_RECORD( NextZone, TZINFO, ListEntry ); NextZone = zone->ListEntry.Flink; #if MYDEBUG DebugPrint(( L"%03d,%03d %s", index, zone->ReferenceIndex, zone->szDisplayName )); #endif if ( DesiredZone == zone->ReferenceIndex ) { pTimeZoneInfo = zone; } } // end of while loop } // Is NextZone legal? } // Is DesiredZone meaningfull now? // Was a time zone that matched DesiredZone identified? Assert( pTimeZoneInfo != (PTZINFO) NULL ); if ( pTimeZoneInfo != (PTZINFO) NULL ) { // Set the GLOBAL Time Zone Info structure pointer. CurrZone = pTimeZoneInfo; } else { // Use the first Time Zone in the list as a default. CurrZone = CONTAINING_RECORD( ZoneList.Flink, TZINFO, ListEntry ); #if MYDEBUG DebugPrint(( L"Couldn't find default timzone" )); #endif } // Was a time zone that matched DesiredZone identified? } // Was a time zone that matched DesiredZone identified? index = ComboBox_FindString( combo, 0, CurrZone->szDisplayName ); if ( index == CB_ERR ) { index = 0; } ComboBox_SetCurSel( combo, index ); EnableWindow( GetDlgItem( hwnd, IDC_DAYLIGHT ), CurrZone->StandardDate.wMonth != 0 ); CheckDlgButton( hwnd, IDC_DAYLIGHT, GetAllowLocalTimeChange() ); SetWindowRedraw(combo, TRUE); return FALSE; } BOOL DateTimeOnCommand( IN HWND hwnd, IN DWORD NotifyCode, IN DWORD ControlId, IN HWND hwndControl ) { if (NotifyCode == CBN_SELCHANGE && ControlId == IDC_TIMEZONE) { CurrZone = (PTZINFO) ComboBox_GetItemData( hwndControl, ComboBox_GetCurSel( hwndControl ) ); EnableWindow( GetDlgItem( hwnd, IDC_DAYLIGHT ), CurrZone->StandardDate.wMonth != 0 ); if (CurrZone->StandardDate.wMonth != 0) { CheckDlgButton( hwnd, IDC_DAYLIGHT, TRUE ); } else { CheckDlgButton( hwnd, IDC_DAYLIGHT, FALSE ); } return FALSE; } return TRUE; } BOOL DateTimeOnNotify( IN HWND hwnd, IN WPARAM ControlId, IN LPNMHDR pnmh ) { switch( pnmh->code ) { case PSN_SETACTIVE: if (SetupInitComponent.SetupData.OperationFlags & SETUPOP_NTUPGRADE) { SetWindowLongPtr( hwnd, DWLP_MSGRESULT, -1 ); return TRUE; } if ((SetupInitComponent.SetupData.OperationFlags & SETUPOP_BATCH) && DateTimeBadUnattend) { // No unattend value for time date in the unattend case. // make sure the wizard is shown. // note: When we get out here, only the next button is enabled. SetupInitComponent.HelperRoutines.ShowHideWizardPage( SetupInitComponent.HelperRoutines.OcManagerContext, TRUE); return FALSE; } if ((SetupInitComponent.SetupData.OperationFlags & SETUPOP_BATCH) && gUnattenedTimeZone != -1) { // // we're in unattend mode // DateTimeApplyChanges(); SetWindowLongPtr( hwnd, DWLP_MSGRESULT, -1 ); return TRUE; } // If we get here the user needs has click next or back. // Make sure the wizard page is showing. // For Whistler GUI mode we try to hide wizard pages and show a background // billboard if there is only a progress bar. // SetupInitComponent.HelperRoutines.ShowHideWizardPage( SetupInitComponent.HelperRoutines.OcManagerContext, TRUE); PropSheet_SetWizButtons(GetParent(hwnd), PSWIZB_BACK | PSWIZB_NEXT); break; case DTN_DATETIMECHANGE: if (ControlId == IDC_TIME_PICKER) { KillTimer( hwnd, TIMER_ID ); ChangeTime = TRUE; } else if (ControlId == IDC_DATE_PICKER) { ChangeDate = TRUE; } break; case PSN_WIZNEXT: SendDlgItemMessage( hwnd, IDC_TIME_PICKER, DTM_GETSYSTEMTIME, 0, (LPARAM)&SelectedTime ); SendDlgItemMessage( hwnd, IDC_DATE_PICKER, DTM_GETSYSTEMTIME, 0, (LPARAM)&SelectedDate ); AllowAutoDST = IsDlgButtonChecked( hwnd, IDC_DAYLIGHT ) != 0; DateTimeApplyChanges(); break; } return FALSE; } BOOL DateTimeOnTimer( IN HWND hwnd ) { SYSTEMTIME CurrTime; GetLocalTime( &CurrTime ); SendDlgItemMessage( hwnd, IDC_TIME_PICKER, DTM_SETSYSTEMTIME, GDT_VALID, (LPARAM)&CurrTime ); return FALSE; } LRESULT DateTimeDlgProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) { CommonWizardProc( hwnd, message, wParam, lParam, WizPageDateTime ); switch( message ) { case WM_INITDIALOG: return DateTimeOnInitDialog( hwnd, (HWND)wParam, lParam ); case WM_COMMAND: return DateTimeOnCommand( hwnd, HIWORD(wParam), LOWORD(wParam), (HWND)lParam ); case WM_TIMER: return DateTimeOnTimer( hwnd ); case WM_NOTIFY: return DateTimeOnNotify( hwnd, wParam, (LPNMHDR) lParam ); } return FALSE; }