Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations John Tel on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Set Workstation TimeZone

Status
Not open for further replies.

JRB-Bldr

Programmer
May 17, 2001
3,281
US
In my continuing effort to stop users from changing workstation Date/Time settings and, consequently, throw off saved TimeStamps, I need to know how to Force the workstation TimeZone to a specific setting.

Any good ideas out there?

Thanks,
JRB-Bldr
 
An API to reset the time would work.

I can also say that as a user at work I don't have time changing permissions, so there must be a user setting for that too.

A function to synchronize your Local-System-Time to your Server-System-Time?
faq184-3505

Brian
 
I have a utility which will synchronize the workstation with the international Atomic Clock via the Web.

However it returns GMT Time and it is converted to Local time. I am guessing that this conversion uses the workstation's TimeZone to make this conversion.

By Forcing the TimeZone to its known setting, I want to prevent the possibility of the the users trying to "trick" the routine by changing the TimeZone.

I have found where TimeZone is kept in the Registry
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\TimeZoneInformation
but there are more than a single value under the Key.
A couple of the values are easy to understand. But there are others (Bias & Start values) which I am not certain about.

Advice and/or suggestions would be greatly appreciated.

Thanks,
JRB-Bldr
 
JRB-Bldr,
While you can get the information from the registry, in this case, I think the WinAPI call is much more straight forward. Notice that Current Bias is in minutes from GMT.
Code:
*-- The program displays the timezone information
*   using the GetTimeZoneInformation() Win32 API function.

declare integer GetTimeZoneInformation ;
	in Win32API ;
	string @lcResultStructure

*-- lcResultStructure is of type:
*!*     typedef struct _TIME_ZONE_INFORMATION { // tzi  
*!*         LONG       Bias;
*!*         WCHAR      StandardName[ 32 ];
*!*         SYSTEMTIME StandardDate;
*!*         LONG       StandardBias;
*!*         WCHAR      DaylightName[ 32 ];
*!*         SYSTEMTIME DaylightDate;
*!*         LONG       DaylightBias;
*!*     } TIME_ZONE_INFORMATION;
*-- Total structure length: 172 chars
*-- LONG = 16 bit signed integer
*-- WCHAR = Unicode char (16 bit)

*-- Initialize the output variable
lcResultStructure = replicate(chr(0), 172)

*-- Get the timezone info
= GetTimeZoneInformation(@lcResultStructure)

*-- The LongToNumber(), WordToNumber() and UnicodeToANSI()
*   functions are defined at the end of this program.

*-- Extract/Display the results
? "Current bias: ", LongToNumber(left(lcResultStructure, 4))
?
? "Standard Time: ", UnicodeToANSI(substr(lcResultStructure, 5, 64))
? "Transition from daylight to standard time occurs on:"
? "Year: ", WordToNumber(substr(lcResultStructure, 69, 2))
? "Month: ", WordToNumber(substr(lcResultStructure, 71, 2))
? "Day of week: ", WordToNumber(substr(lcResultStructure, 73, 2))
? "Day: ", WordToNumber(substr(lcResultStructure, 75, 2))
? "Hour: ", WordToNumber(substr(lcResultStructure, 77, 2))
? "Minute: ", WordToNumber(substr(lcResultStructure, 79, 2))
? "Second: ", WordToNumber(substr(lcResultStructure, 81, 2))
? "MiliSec: ", WordToNumber(substr(lcResultStructure, 83, 2))
? "Bias: ", LongToNumber(substr(lcResultStructure, 85, 4))
?
? "Daylight Time: ", UnicodeToANSI(substr(lcResultStructure, 89, 64))
? "Transition from standard to daylight time occurs on:"
? "Year: ", WordToNumber(substr(lcResultStructure, 153, 2))
? "Month: ", WordToNumber(substr(lcResultStructure, 155, 2))
? "Day of week: ", WordToNumber(substr(lcResultStructure, 157, 2))
? "Day: ", WordToNumber(substr(lcResultStructure, 159, 2))
? "Hour: ", WordToNumber(substr(lcResultStructure, 161, 2))
? "Minute: ", WordToNumber(substr(lcResultStructure, 163, 2))
? "Second: ", WordToNumber(substr(lcResultStructure, 165, 2))
? "MiliSec: ", WordToNumber(substr(lcResultStructure, 167, 2))
? "Bias: ", LongToNumber(substr(lcResultStructure, 169, 4))
return && Main function

*==========================================
*-- Converts a number from LONG binary format
*   to VFP numeric format.
function LongToNumber
lparameter tcDWord

local lnReturn, lnI

*-- Check the parameter
if (type("tcDWord") <> "C") or (len(tcDWord) < 4)
	return 0
endif

*-- Convert the number
lnReturn = 0
for lnI = 1 to 4
	lnReturn = asc(substr(tcDWord, 5-lnI, 1)) + lnReturn
	if lnI <> 4
		lnReturn = lnReturn * 256
	endif
endfor

*-- Adjust for signed numbers.
*   (LONG is a signed numeric format)
if lnReturn > 2147483647
	lnReturn = lnReturn - 0x100000000
endif

return lnReturn && LongToNumber

*==========================================
*-- Converts a number from WORD binary format
*   to VFP numeric format.
function WordToNumber
parameter tcWord

local lnReturn, lnI

*-- Check the parameter
if (type("tcWord") <> "C") or (len(tcWord) < 2)
	return 0
endif

*-- Convert the number
lnReturn = asc(left(tcWord, 1)) + asc(substr(tcWord, 2, 1)) * 256

return lnReturn && WordToNumber

*==========================================
*-- Converts a Unicode string to ANSI string.
function UnicodeToANSI
lparameter tcUnicodeString

local lcReturn, lnAt

lcReturn = strconv(strconv(tcUnicodeString, 6), 2)

lnAt = at(chr(0), lcReturn)
if lnAt > 0
	lcReturn = left(lcReturn, lnAt - 1)
endif

return lcReturn && UnicodeToANSI
Rick
 
If you want to use the registry setting, the following code will work to retrieve the adjusted Bias. Note: You will need my "extended" version of the Registry.PRG (or .VCX) available at
Code:
* Program....: TESTREGBIN.PRG
* Version....: 1.0
* Author.....: ** Richard G Bean **
* Date.......: February 23, 1999
* Notice.....: [b]Copyleft[/b] 1999 ** Melange Computer Services, Inc. **
* Abstract...: Test new REGISTRY functionallity - Return 32-bit binary values
* Changes....:

#DEFINE HKEY_LOCAL_MACHINE          -2147483646  && BITSET(0,31)+2

LOCAL lcRegValue, lcRegfile, lnRetVal, oReg

lnRegValue = 0 && by passing a numeric value, it will assume requested is 32-bit binary
lcRegfile = "REGISTRY"
lnRetVal = 0

SET PROCEDURE TO (m.lcRegfile) ADDITIVE
oReg = CreateObject("Registry")

lnRetVal = oReg.GetRegKey("ActiveTimeBias",@lnRegValue,;
		"System\CurrentControlSet\control\TimeZoneInformation", HKEY_LOCAL_MACHINE)

IF lnRetVal = 0
	IF lnRegValue > 0x80000000	&& East of GMT
		lnRegValue = lnRegValue-0xFFFFFFFF -1
	ENDIF
ELSE
	lnRegValue = 0	&& should probably return "special" value
ENDIF

RELEASE oReg
RELEASE PROC (m.lcRegfile)

RETURN lnRegValue

*!* EOP: TESTREGBIN.PRG
Rick
 
Rick - Thank you for your valuable posting.

By manually playing with the TimeZone setting on my development workstation, I think I have figured out which values need to change and what new value to change them to (nothing guaranteed!!).

So in addition to Reading the value, I also need to Set the values so as to FORCE the TimeZone to my desired setting.

Suggestions?

Thanks,
JRB-Bldr
 
I have played around with VFP's Registry Class and discoverd another "gotcha".

While 2 of the Registry values that I need to Set are strings, 2 others are DWORDS with Hex values
ActiveTimeBias = 360 (0x00000168)
Bias = 420 (0x000001A4)


Apparently VFP's Registry Class utilities will only write String value (?).

Therefore the approach needs to be modified so as to Write both String and DWORD values to the Registry.

Advice/Suggestions are greatly appreciated.

Thanks,
JRB-Bldr
 
JRB-Bldr,
Actually my modified Registry.PRG (and Registry.VCX & .VCT) allow you to read and write DWORDS. Check it out - between the Readme file and the sample code, it's just as easy to use as Strings! If you've got questions, just post them.

Rick
 
Rick

I tried the following code and it "sort of" worked, but it gave me wrong values for the DWORD key's

Code:
* --- Set Specifically To RGBean's Class ---
SET CLASSLIB TO "F:\Campaign Management\Program\Other Programs\Registry.vcx" ADDITIVE

#DEFINE HKEY_LOCAL_MACHINE -2147483646 && BITSET(0,31)+2
#DEFINE REG_RUN "System\CurrentControlSet\Control\TimeZoneInformation"

loReg = CreateObject('Registry')

lnRegStat = loReg.OpenKey(REG_RUN,HKEY_LOCAL_MACHINE,.T.)
IF lnRegStat = 0
   loReg.SetKeyValue("ActiveTimeBias", 360)
   loReg.SetKeyValue("Bias", 420)
   loReg.SetKeyValue("DaylightName","Mountain Daylight Time")
   loReg.SetKeyValue("StandardName","Mountain Daylight Time")
ENDIF

RELEASE loReg

I began by manually opening the Registry and examining the values desired:
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\TimeZoneInformation
ActiveTimeBias = 360 (0x00000168)
Bias = 420 (0x000001A4)
DaylightName = "Mountain Daylight Time"
StandardName = "Mountain Daylight Time"

I then used Windows Control Panel to manually change the TimeZone to "Central Time"

I again examined the resultant Registry entries in question:
ActiveTimeBias = 300 (0x0000012C)
Bias = 360 (0x00000168)
DaylightName = "Central Daylight Time"
StandardName = "Central Daylight Time"

Ran the above code to change the Registry values.

It successfully changed the Name values, but I ended up with:
ActiveTimeBias = 300 (0x0000012C)
Bias = 360 (0x00000168)

Where/How have I mis-used your code?

Thanks,
JRB-Bldr
 
I'll have to do a bit more research, but this worked:
Code:
....
loReg = CreateObject('Registry')

*!*	lnRegStat = loReg.OpenKey(REG_RUN,HKEY_LOCAL_MACHINE,.T.)
*!*	IF lnRegStat = 0
*!*	   loReg.SetKeyValue("ActiveTimeBias", 360)
*!*	   loReg.SetKeyValue("Bias", 420)
*!*	   loReg.SetKeyValue("DaylightName","Mountain Daylight Time")
*!*	   loReg.SetKeyValue("StandardName","Mountain Daylight Time")
*!*	ENDIF

lnRetVal = loReg.SetRegKey("ActiveTimeBias", 360,;
		REG_RUN, HKEY_LOCAL_MACHINE)
lnRetVal = loReg.SetRegKey("Bias", 420,;
		REG_RUN, HKEY_LOCAL_MACHINE)
lnRetVal = loReg.SetRegKey("DaylightName","Mountain Daylight Time",;
		REG_RUN, HKEY_LOCAL_MACHINE)
lnRetVal = loReg.SetRegKey("StandardName","Mountain Daylight Time",;
		REG_RUN, HKEY_LOCAL_MACHINE)
...
[code]
It looks like there are some differences in using .OpenKey and .SetKeyValue() compared to .SetRegKey().

Rick
 
Thanks Rick.

You are correct.
There must be a difference between SetRegKey & SetKeyValue because your code above works for DWORD values and mine does not.

Now the next issue...
(Admittedly this is turning out to be much more of a headache than anticipated.)

I can indeed now change the values in
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\TimeZoneInformation
or
..\ControlSet001\Control\TimeZoneInformation
..\ControlSet002\Control\TimeZoneInformation
etc.

And they look exactly like they should look.

But when I:
1. ran my test of manually setting my workstation to Central Time
2. checked the Registry values
3. then ran the Time Reset code (Rick's code) to go to Moutain Time
4. checked the Registry values again
5. confirmed all was reset correctly
6. The time in the Taskbar was still showing Central Time
7. The Control Panel Date/Time utility was still showing TimeZone = Central Time

So, while I have a lot of the pieces under control, I still seem to be missing something which make the workstation "aware" of the change.

Any additional advice or suggestions?

Thanks,
JRB-Bldr
 
Well now that we know that we can do it, I found the following quote in "REG: CurrentControlSet Entries PART 3" at
Values in the TimeZoneInformation key should be maintained only by choosing the Date/Time icon in Control Panel or by applications using the Win32 APIs. Changing this information in the Registry can damage your system's local time settings.
I guess it's time to figure out the API call necessary!

I found "HOWTO: Change Time Zone Information Using Visual Basic" at - they read the Registry but use the SetTimeZoneInformation() API call to actually change it. It's much like the GetTimeZoneInformation() call I provided in the first piece of code I posted - it uses the same "structure".

As they used to say in college - "the rest is left as an exercise for the reader"! :)

Rick
 
Rick - my continued Thanks.

I guess that I'll look into the API approach and see where that gets me.

Thanks,
JRB-Bldr
 
Rick - While beginning to work through the API approach to SetTimeZoneInformation it is easy to see that it is basically the inverse of your GetTimeZoneInformation utility above.

Rather than pushing out the cobwebs on an early Monday morning, do you by chance have posted somewhere the inverse utility functions to those that you use above?

UnicodeToANSI() --> ANSItoUnicode()
WordToNumber() --> NumberToWord()
LongToNumber() --> NumberToLong()

Thanks,
JRB-Bldr
 
The second two are in the "Conversion" FAQ (it's just stuffing a string with two and four bytes), and the 1st is just getting the right STRCONV() calls. I believe this will work:
Code:
*-- Converts a ANSI string to Unicode string.
function ANSIToUnicode
lparameter tcANSIString

local lcReturn

lcReturn = strconv(strconv(tcANSIString, 1), 5)

return lcReturn && ANSIToUnicode
Rick
 
The ANSIToUnicode function is simple. However, unless the STRCONV(<string>,1) does it for us already, I think that we do still need to add a CHR(0) to terminate the Unicode string.

lnAt = AT(CHR(0), lcReturn)
if lnAt = 0
lcReturn = lcReturn + CHR(0)
endif


But I have to admit my continued confusion about creating the inverse function of
WordToNumber() --> NumberToWord()
LongToNumber() --> NumberToLong()

Yes, the Long is 4 bytes and the Word is 2 bytes, but the conversion is still confusing me. I cannot see where/how it is just "stuffing a string".

For instance I am using TRACE to watch your GetTimeZoneInfo routine execute and examining the values at different places.

Code:
For Example:
  LongToNumber(CHR(164)+CHR(1)+CHR(0)+CHR(0)) = 420
  LongToNumber(CHR(196)+CHR(255)+CHR(255)+CHR(255)) = -60
  WordToNumber(CHR(10)+CHR(0)) = 10

It does not look like BCD nor a simple Hex code representation of the number.

If anyone can eliminate my "fog" of confusion it would be greatly appreciated.

Thanks,
JRB-Bldr
 
OK,
I've got one routine that seems to work for both. (I based it on an old routine - you could update it.)
Code:
? WordToNumber(num2str(12345,2))
? LongToNumber(num2str(4567890,4))

****************
FUNCTION num2str
PARAMETERS znnum, znoutlen
PRIVATE lnii, lnjj, lcasc
IF PARAMETERS() < 2 OR TYPE('znoutlen') != 'N'
	znoutlen = 1
ENDIF
znoutlen = MAX(1, MIN(8, znoutlen))
lnii = INT(znnum)
lcasc = ''
DO WHILE lnii > 0
	znnum = lnii
	lnii = INT(znnum / 256)
	lnjj = znnum % 256
	lcasc = lcasc + CHR(lnjj)
ENDDO
IF LEN(lcasc) < znoutlen
	lcasc = PADR(lcasc, znoutlen, CHR(00))
ENDIF
RETURN lcasc
Rick
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top