Warning: "continue" targeting switch is equivalent to "break". Did you mean to use "continue 2"? in /homepages/13/d92059571/htdocs/jeremymurray/inc/parser/handler.php on line 1458

Batch Files and Daylight Savings

Anyone who sets out to write a date/time library is insane. Daylight Savings is a hard problem that requires constant maintenance. Fortunately this is the sad responsibility of someone working on our OS, so we can just ask the OS and get back sensible answers, right?

Well, sort of.

Problem

From a DOS prompt, find the current date, time, and timezone.

Catch

Batch only - no new executables, no required installs outside of the base OS.

Targets

Win XP, Win Vista, Win 7

Expected Output

Standard date format for today: 04/07/2010 11:00 AM Pacific Daylight Time

Investigation

The date and time are provided as environment variables. We can parse those variables and save off the parts needed.

|h datetime.bat
@echo off
setlocal
FOR %%A IN (%Date%) do (
	set CURRENTDATE=%%A
)
 
for /f "tokens=1,2" %%A in ('time /t') do (
	set CURRENTTIME=%%A %%B
)
 
echo %CURRENTDATE% %CURRENTTIME%
endlocal

There is a registry entry in XP that gives the current timezone info. It looks like this:

XP TimeZoneInformation
C:\>reg query HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\TimeZoneInformation
 
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\TimeZoneInformation
    Bias           REG_DWORD  0x1e0
    StandardName   REG_SZ     Pacific Standard Time
    StandardBias   REG_DWORD  0x0
    StandardStart  REG_BINARY 00000B00010002000000000000000000
    DaylightName   REG_SZ     Pacific Daylight Time
    DaylightBias   REG_DWORD  0xffffffc4
    DaylightStart  REG_BINARY 00000300020002000000000000000000
    ActiveTimeBias REG_DWORD  0x1a4
    ActiveTime     REG_BINARY 93DFFFFF
    StandardTime   REG_BINARY 98ADFFFF

DaylightName and StandardName are the strings we are looking for. We will parse the output of the reg query and store off both to pick from later.

Figuring out if we are currently in DST or not requires looking at the Bias and ActiveTimeBias keys. If they are equal, the timezone is the StandardName. If they are not equal, the timezone is the DaylightName. Since we are not examining arbitrary dates, just the current date, we can ignore the DaylightStart and StandardStart keys.

Putting that together in a bat file looks like this:

|h XP now.bat
@echo off
setlocal
FOR %%A IN (%Date%) do (
	set CURRENTDATE=%%A
)
 
for /f "tokens=1,2" %%A in ('time /t') do (
	set CURRENTTIME=%%A %%B
)
 
for /f "tokens=1,2*" %%K in ('reg query HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\TimeZoneInformation') do (
	if %%K==ActiveTimeBias set /a ACTIVETIMEBIAS=%%M
	if %%K==Bias set /a BIAS=%%M
	if %%K==DaylightName set DAYLIGHTTIMEZONE=%%M
	if %%K==StandardName set STANDARDTIMEZONE=%%M
)
 
if ("%ACTIVETIMEBIAS%") == ("%BIAS%") set CURRENTTIMEZONE=%STANDARDTIMEZONE%
if not ("%ACTIVETIMEBIAS%") == ("%BIAS%") set CURRENTTIMEZONE=%DAYLIGHTTIMEZONE%
 
echo %CURRENTDATE% %CURRENTTIME% %CURRENTTIMEZONE%
endlocal
XP now.bat Output
C:\>now
04/07/2010 11:21 AM Pacific Daylight Time

This worked pretty well up until Vista came along.

Win 7 Bad now.bat Output
C:\>now
04/07/2010 11:21 AM @tzres.dll,-211

The registry entries changed:

Win 7 TimeZoneInformation
C:\>reg query HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\TimeZoneInformation
 
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\TimeZoneInformation
    Bias                        REG_DWORD  0x1e0
    DaylightBias                REG_DWORD  0xffffffc4
    DaylightName                REG_SZ     @tzres.dll,-211
    DaylightStart               REG_BINARY 00000300020002000000000000000000
    StandardBias                REG_DWORD  0x0
    StandardName                REG_SZ     @tzres.dll,-212
    StandardStart               REG_BINARY 00000B00010002000000000000000000
    TimeZoneKeyName             REG_SZ     Pacific Standard Time
    DynamicDaylightTimeDisabled REG_DWORD  0x0
    ActiveTimeBias              REG_DWORD  0x1a4
    StandardTime                REG_BINARY 9D4BFFFF

Turns out Vista added tzres.dll, a library of localized timezone strings. Somewhere inside (probably at index 211 adjusted for English) is “Pacific Daylight Time”, but there doesn't seem to be a way to get that string directly from DOS.

A little more digging on the internet turned up a new utility shipped with Vista that seems to be exactly what we want: tzutil.exe. tzutil can be used to set the current timezone from the commandline, which is a great thing for system administrators psexec'ing into their remote machines. It has a /g flag to output the current setting.

tzutil
C:\>tzutil /g
Pacific Standard Time

That looks great, but it is wrong. Daylight Savings already began here, so the current timezone should be Pacific Daylight Time. I'm glad I didn't try to work this out a few months ago - I would have assumed this utility returned the right answer. As far as I can tell, tzutil /g just returns TimeZoneKeyName.

There is another set of registry entries that caches the localized strings from tzutil.dll. We will need to add the TimeZoneKeyName to the query to get the specific ones we're looking for.

Win 7 Time Zones Pacific Standard Time
C:\>reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\Pacific Standard Time"
 
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\Pacific Standard Time
    MUI_Display REG_SZ     @tzres.dll,-210
    MUI_Dlt     REG_SZ     @tzres.dll,-211
    MUI_Std     REG_SZ     @tzres.dll,-212
    Display     REG_SZ     (UTC-08:00) Pacific Time (US & Canada)
    Dlt         REG_SZ     Pacific Daylight Time
    Std         REG_SZ     Pacific Standard Time
    TZI         REG_BINARY E001000000000000C4FFFFFF00000B0000000100020000000000000000000300000002000200000000000000

There we go! The Dlt and Std keys point to the strings we want to report. We just need to wrap it up in a Win 7/Vista/XP compatible batch file.

Answer

|h now.bat
@echo off
setlocal
FOR %%A IN (%Date%) do (
	set CURRENTDATE=%%A
)
 
for /f "tokens=1,2" %%A in ('time /t') do (
	set CURRENTTIME=%%A %%B
)
 
set TIMEZONEKEYNAME=
for /f "tokens=1,2*" %%K in ('reg query HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\TimeZoneInformation') do (
	if %%K==ActiveTimeBias set /a ACTIVETIMEBIAS=%%M
	if %%K==Bias set /a BIAS=%%M
	if %%K==DaylightName set DAYLIGHTTIMEZONE=%%M
	if %%K==StandardName set STANDARDTIMEZONE=%%M
	if %%K==TimeZoneKeyName set TIMEZONEKEYNAME=%%M
)

rem This changed in Vista/Win 7.  The info in the registry are now references to tzres.dll.
rem Key off of the TimeZoneKeyName in the registry to see if we're on a system that supports it.
rem This second lookup is because TIMEZONEKEYNAME is always non-DST - this other registry entry gives both.
if not ("%TIMEZONEKEYNAME%") == ("") for /f "tokens=1,2*" %%K in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\%TIMEZONEKEYNAME%"') do (
	if %%K==Dlt set DAYLIGHTTIMEZONE=%%M
	if %%K==Slt set STANDARDTIMEZONE=%%M
)
 
if ("%ACTIVETIMEBIAS%") == ("%BIAS%") set CURRENTTIMEZONE=%STANDARDTIMEZONE%
if not ("%ACTIVETIMEBIAS%") == ("%BIAS%") set CURRENTTIMEZONE=%DAYLIGHTTIMEZONE%
 
echo %CURRENTDATE% %CURRENTTIME% %CURRENTTIMEZONE%
endlocal
XP Output
C:\>now
04/07/2010 11:29 AM Pacific Daylight Time
Win 7 Output
C:\>now
04/07/2010 11:29 AM Pacific Daylight Time

Issues

  • Have not tested with international sites yet.

References

 
blog/20100407_batch_files_and_daylight_savings.txt · Last modified: 2010/04/07 17:20 by Jeremy Murray · []
Recent changes RSS feed Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki