1

The following dBase code invokes a win32 API function to convert a local DST time to a system time. The first parameter set to "null" means that the function takes the current active time zone. What value do I have to put instead of "null" to specify another time zone?

The following page refers to lpTimeZoneInformation as a pointer to a TIME_ZONE_INFORMATION structure that specifies the time zone for the localtime input to this function (lpLocalTime), but is is unclear to me what kind of pointer this is.

I have tried 'Brisbane', 'E. Australia Standard Time', '10:00' and '+10:00' but none returns the expected value.

https://learn.microsoft.com/en-us/windows/win32/api/timezoneapi/nf-timezoneapi-tzspecificlocaltimetosystemtime

ITOH and HTOI are Integer TO Hex and vice-versa conversion functions

localtime and systemtime structures work, I tried to replicate that for the time_zone_information part but without success so far

As it stands, the return value is 13.20

Thanks for any help!


d=new date("31/12/2020 5:08")
offset1=getLocalTimeOffset(d)/60

function getLocalTimeOffset(d_in)
    // todo typechecking of the parameter
    extern clogical TzSpecificLocalTimeToSystemTime(cptr,cptr,cptr) kernel32
    extern culong GetLastError(cvoid) kernel32
    local systemtime,localtime,tmp  
    localtime = replicate(chr(0),16)
    systemtime = replicate(chr(0),16)
    TZI = replicate(chr(0),16)
    TZIa=itoh(-600,4)
    TZIb=itoh(-60,4)
    TZI.setbyte(1,htoi(left(TZIa,2)))
    TZI.setbyte(0,htoi(right(TZIa,2)))  
    TZI.setbyte(9,htoi(left(TZIb,2)))
    TZI.setbyte(8,htoi(right(TZIb,2)))
tmp = itoh(d_in.year,4) 
    localtime.setbyte(1,htoi(left(tmp,2)))      // fill the systemtime structure
    localtime.setbyte(0,htoi(right(tmp,2))) // seconds and ms are of no concern
    localtime.setbyte(2,d_in.month+1)
    localtime.setbyte(4,d_in.day)
    localtime.setbyte(6,d_in.date)
    localtime.setbyte(8,d_in.hour)
    localtime.setbyte(10,d_in.minute)   
    if TzSpecificLocalTimeToSystemTime(TZI,localtime,systemtime) = 0
        tmp = getlasterror() ; ? "Error: "+tmp ; return 9999
    endif
    tmp = sign(d_in.date-systemtime.getbyte(6))*24*60  // consider day boundary
    if (d_in.date = 1 or systemtime.getbyte(6) = 1) and (d_in.month+1 <> systemtime.getbyte(2))
        tmp = -tmp      // adjust for month boundaries
    endif
    tmp += (d_in.hour - systemtime.getbyte(8))*60
    tmp += d_in.minute - systemtime.getbyte(10)
return tmp
  • The first parameter is a pointer to a [`TIME_ZONE_INFORMATION`](https://learn.microsoft.com/en-us/windows/win32/api/timezoneapi/ns-timezoneapi-time_zone_information) structure. You would need to fill-in the contents of the structure using registry information from `HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones` per [Remarks](https://learn.microsoft.com/en-us/windows/win32/api/timezoneapi/ns-timezoneapi-time_zone_information#remarks) on the same page. – dxiv Jan 04 '21 at 05:26
  • Thanks, I got that far but I can't understand how to fill the structure outlined on that page with a single variable. Could you provide an example? – Gaetano De Luisi Jan 04 '21 at 05:38
  • There is some C code under [How do you get info for an arbitrary time zone in Windows?](https://stackoverflow.com/questions/3623471/how-do-you-get-info-for-an-arbitrary-time-zone-in-windows). Don't know how that translates to dBase, though. – dxiv Jan 04 '21 at 05:49
  • Thanks for your help anyway, that is exactly my challenge as well as I have never used C++ and the examples are very obscure for the people who don't know C++ on the MS page – Gaetano De Luisi Jan 04 '21 at 07:53
  • The Win32 API *is* C so you can't avoid (some of) that. Can't help you with dBase, but I'll just note that `TzSpecificLocalTimeToSystemTime` takes three pointers to different structures as arguments, and it looks like you know how to deal with two of them. Now you have to do the same for the remaining one. – dxiv Jan 04 '21 at 07:59
  • correct but the other pointers are timestamps, it's way easier to figure out what a date structure is than TIME_ZONE_INFORMATION. The registry key has 6 lines while the link you shared shows only 5 members. Only 2 lines have values as 00000000 A8 FD FF FF 00 00 00 00 = 168 253 255 0 0 0 0 00000008 C4 FF FF FF 00 00 00 00 = 196 255 255 0 0 0 0 I plugged each sequence in a structure the same way I did for SYSTEMTIME and supplied it to the function but the result was 16 (hours offset to GMT) while I am expecting 10 for Brisbane. – Gaetano De Luisi Jan 04 '21 at 22:07
  • The TZI key has 5 fields, and the remaining 2 ones in TIME_ZONE_INFORMATION are the names, which can be left blank ("*this string can be empty*"). As for the rest, there must be something off with your values or offsets in the structure. I posted a worked out example below to compare. – dxiv Jan 05 '21 at 00:16

1 Answers1

0

(Too long for a comment.)   The first parameter to TzSpecificLocalTimeToSystemTime must be either NULL, or otherwise point to a TIME_ZONE_INFORMATION structure, filled-in with the target timezone data from HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones per Remarks on the same page.

In OP's case, Brisbane falls under the E. Australia Standard Time key, and TZI data parses as:

typedef struct _REG_TZI_FORMAT
{
    LONG               Bias;  //  -600    A8 FD FF FF
    LONG       StandardBias;  //     0    00 00 00 00
    LONG       DaylightBias;  //   -60    C4 FF FF FF
    SYSTEMTIME StandardDate;  //   n/a    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    SYSTEMTIME DaylightDate;  //   n/a    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
} REG_TZI_FORMAT;

Following is the C code to fill-in a TIME_ZONE_INFORMATION structure with the same data, and successfully convert a Brisbane local time to UTC:

#include <windows.h>
#include <stdio.h>

int main()
{
    TIME_ZONE_INFORMATION tzEAST =          //  [offset]  bytes
    {
         -600,  //  LONG       Bias;                 [0]  A8 FD FF FF
        { 0 },  //  WCHAR      StandardName[32];     [4]  00 .. 00
        { 0 },  //  SYSTEMTIME StandardDate;        [68]  00 .. 00
            0,  //  LONG       StandardBias;        [84]  00 00 00 00
        { 0 },  //  WCHAR      DaylightName[32];    [88]  00 .. 00
        { 0 },  //  SYSTEMTIME DaylightDate;       [152]  00 .. 00
          -60   //  LONG       DaylightBias;       [168]  C4 FF FF FF
    };
    SYSTEMTIME stEAST = { 2021, 1, 1, 4, 12 }, stUTC = { 0 };

    if(!TzSpecificLocalTimeToSystemTime(&tzEAST, &stEAST, &stUTC)) return 1;

    printf("EAST %d-%02d-%02d %02d:%02d:%02d = UTC %d-%02d-%02d %02d:%02d:%02d\n",
           stEAST.wYear, stEAST.wMonth, stEAST.wDay, stEAST.wHour, stEAST.wMinute, stEAST.wSecond,
           stUTC.wYear, stUTC.wMonth, stUTC.wDay, stUTC.wHour, stUTC.wMinute, stUTC.wSecond);

    return 0;
}

Output:

EAST 2021-01-04 12:00:00 = UTC 2021-01-04 02:00:00

[ EDIT ]   Following is my guess of what the dBase code might look like. Just a guess, and nothing more than a guess, since I don't actually know dBase beyond what's been posted here.

tzBias    = itoh(-600, 8)
tzDstBias = itoh( -60, 8)

tzi = replicate(chr(0), 86)  // 86*2 = 172 = sizeof TIME_ZONE_INFORMATION

tzi.setbyte(  0, htoi(substring(   tzBias, 6, 8)))  // [  0]  LONG Bias;
tzi.setbyte(  1, htoi(substring(   tzBias, 4, 6)))
tzi.setbyte(  2, htoi(substring(   tzBias, 2, 4)))
tzi.setbyte(  3, htoi(substring(   tzBias, 0, 2)))

tzi.setbyte(168, htoi(substring(tzDstBias, 6, 8)))  // [168]  LONG DaylightBias;
tzi.setbyte(169, htoi(substring(tzDstBias, 4, 6)))
tzi.setbyte(170, htoi(substring(tzDstBias, 2, 4)))
tzi.setbyte(171, htoi(substring(tzDstBias, 0, 2)))

if TzSpecificLocalTimeToSystemTime(tzi, localtime, systemtime) = 0  // ...

[ EDIT #2 courtesy OP ]   The working dBase code to fill the structure is the following:

tzi.setbyte(  0, htoi(substr(tzBias, 7, 2)))  // [  0]  LONG Bias
tzi.setbyte(  1, htoi(substr(tzBias, 5, 2)))
tzi.setbyte(  2, htoi(substr(tzBias, 3, 2)))
tzi.setbyte(  3, htoi(substr(tzBias, 1, 2)))

tzi.setbyte(168, htoi(substr(tzDstBias, 7,2)))  // [168]  LONG DaylightBias
tzi.setbyte(169, htoi(substr(tzDstBias, 5,2)))
tzi.setbyte(170, htoi(substr(tzDstBias, 3,2)))
tzi.setbyte(171, htoi(substr(tzDstBias, 1,2)))
dxiv
  • 16,984
  • 2
  • 27
  • 49
  • Thank you dxiv, that helped clarifying the structure. I'm still not getting the expected output, so I will have to wait for some dBase and C experts to help me translate that to dBase code. I appreciate you spending the time on this. – Gaetano De Luisi Jan 07 '21 at 00:59
  • @GaetanoDeLuisi It would help the question if you [edited](https://stackoverflow.com/posts/65557900/edit) the new code into it. – dxiv Jan 07 '21 at 01:22
  • @GaetanoDeLuisi Problem there is that `TZI` is never used after being filled-in, and also it doesn't look like the right structure. I added what I believe the dBase code should be at the end of my post. – dxiv Jan 07 '21 at 03:38
  • You're da man dxiv! with minor adjustments to the syntax, your dBase code worked! – Gaetano De Luisi Jan 08 '21 at 04:16
  • it is true that TZI is never used directly, but it is used indirectly to calculate the offset to GMT, so if that output is correct, I know that TZI produced the expected value for systemtime. Any invalid TZI value had resulted in random numbers. – Gaetano De Luisi Jan 08 '21 at 04:32
  • @GaetanoDeLuisi Glad it helped, and thanks for the `substr` fix. Also, I think there should be a more direct way to copy a 32-bit integer into a structure, other than converting it to hex then extracting the individual bytes, but, as I was saying, pardon my dBase ;-) – dxiv Jan 08 '21 at 04:33