UNSAFE MODULEDateWin32 EXPORTSDate ; IMPORT Text, Time; IMPORT WinDef, WinBase, WinNT, TimeWin32; REVEAL TimeZone = BRANDED OBJECT METHODS fromTime(t: Time.T): T END; PROCEDUREFromTimeLocal (<*UNUSED*> z: TimeZone; t: Time.T): T =
Implementation note: This implementation is buggy on Windows 95 due to the fact that, as of 4/97, theSystemTimeToTzSpecificLocalTime
function is documented as not being implemented on Windows 95. If the call to that procedure fails, measures are taken to try to compute the correct result. Unfortunately, theFileTimeToLocalTime
function adjusts for daylight savings time based on the current time rather than on the time passed as its argument.As a result, the returned date may be reported to be in the wrong time zone. For example, if the program is run at a time when daylight savings is in effect, but
Date.FromTime
is passed a time that does not fall in daylight savings time, the returned result will have anhour
value one hour larger than it should, but theoffset
field will be one hour smaller than it should be, and thezone
field will incorrectly indicate that daylight savings was in effect at that time.We decided not to try to duplicate the functionality of
SystemTimeToTzSpecificLocalTime
because some necessary information, the rule for deciding when daylight savings time is in effect, apparently is not always available. The Win32 specification forTIME_ZONE_INFORMATION
says it may contain either a rule applicable to any year, or a pair of dates for the current year. In the latter case, it is not obvious how to handle a date in a different year.
CONST SecsPerMin = 60; VAR ft, lft: WinBase.FILETIME; st, lst: WinBase.SYSTEMTIME; d: T; tz: WinBase.TIME_ZONE_INFORMATION; tzrc: WinDef.DWORD; status: INTEGER; firstDayOfEpoch := FALSE; BEGIN (* If the time given is before the PC epoch (less than the time zone offset) then Windows' time calculation fails. So: if t is in the first day of the PC epoch then add 1 day (86400 seconds), do the conversion to a date, and finally subtract the day out of the date. NOTE: a negative time will either give incorrect results or crash. *) IF t < 86400.0D0 THEN t := t + 86400.0D0; <*ASSERT t > 0.0D0*> firstDayOfEpoch := TRUE END; ft := TimeWin32.ToFileTime(t); status := WinBase.FileTimeToSystemTime(ADR(ft), ADR(st)); <*ASSERT status # 0*> tzrc := WinBase.GetTimeZoneInformation(ADR(tz)); <*ASSERT tzrc # -1 *> status := WinBase.SystemTimeToTzSpecificLocalTime( ADR(tz), ADR(st), ADR(lst)); IF status # 0 THEN (* implemented *) status := WinBase.SystemTimeToFileTime(ADR(lst), ADR(lft)); <*ASSERT status # 0*> ELSE (* not implemented *) (* Unfortunately, FileTimeToLocalTime adjusts for daylight savings time based on the current time rather than on the time passed as its argument. *) status := WinBase.FileTimeToLocalFileTime(ADR(ft), ADR(lft)); <*ASSERT status # 0*> status := WinBase.FileTimeToSystemTime(ADR(lft), ADR(lst)); <*ASSERT status # 0*> END; d := FromSystemTime(lst); d.offset := ROUND(t - TimeWin32.FromFileTime(lft)); IF tz.StandardDate.wMonth # 0 AND d.offset = SecsPerMin * (tz.Bias+tz.StandardBias) THEN d.zone := CopyTimeZoneName(tz.StandardName) ELSIF tz.DaylightDate.wMonth # 0 AND d.offset = SecsPerMin * (tz.Bias+tz.DaylightBias) THEN d.zone := CopyTimeZoneName(tz.DaylightName) ELSE d.zone := "[Unknown zone]" END; IF firstDayOfEpoch THEN IF d.day = 2 THEN d.day := 1 ELSE d.day := 31; d.month := Month.Dec; DEC(d.year) END END; RETURN d END FromTimeLocal; PROCEDURE****** PROCEDURE CopyTimeZoneName( READONLY name: ARRAY [0 .. 31] OF WinNT.WCHAR): TEXT = VAR chars: ARRAY [0..31] OF CHAR; j := 0; BEGIN FOR i := 0 TO LAST(name) DO WITH char = VAL(name[i], CHAR) DO IF char = '\000' THEN EXIT ELSE chars[j] := char; INC(j) END END END; RETURN Text.FromChars(SUBARRAY(chars, 0, j)) END CopyTimeZoneName; ****CopyTimeZoneName ( READONLY name: ARRAY [0 .. 31] OF WinNT.WCHAR): TEXT = VAR chars: ARRAY [0..31] OF WIDECHAR; j := 0; BEGIN WHILE (j < NUMBER(name)) AND (name[j] # 0) DO chars[j] := VAL (name[j], WIDECHAR); INC(j) END; RETURN Text.FromWideChars(SUBARRAY(chars, 0, j)) END CopyTimeZoneName;
PROCEDUREFromTimeUTC (<*UNUSED*> z: TimeZone; t: Time.T): T = VAR d: T; st: WinBase.SYSTEMTIME; ft: WinBase.FILETIME; status: INTEGER; BEGIN ft := TimeWin32.ToFileTime(t); status := WinBase.FileTimeToSystemTime(ADR(ft), ADR(st)); <*ASSERT status # 0 *> d := FromSystemTime(st); d.offset := 0; d.zone := "UTC"; RETURN d END FromTimeUTC; PROCEDUREFromSystemTime (st: WinBase.SYSTEMTIME): T =
Set all fields ofd
exceptoffset
andzone
, fromst
.
VAR d: T; BEGIN d.year := st.wYear; d.month := VAL(st.wMonth-1, Month); d.day := st.wDay; d.hour := st.wHour; d.minute := st.wMinute; d.second := st.wSecond; d.weekDay := VAL(st.wDayOfWeek, WeekDay); RETURN d END FromSystemTime; PROCEDUREFromTime (t: Time.T; z: TimeZone := NIL): T = BEGIN IF z = NIL THEN z := Local END; RETURN z.fromTime(t) END FromTime; PROCEDUREToTime (READONLY d: T): Time.T = VAR st: WinBase.SYSTEMTIME; ft: WinBase.FILETIME; t: Time.T; status: INTEGER; BEGIN st.wYear := d.year; st.wMonth := ORD(d.month)+1; (* st.wDayOfWeek ignored *) st.wDay := d.day; st.wHour := d.hour; st.wMinute := d.minute; st.wSecond := d.second; st.wMilliseconds := 0; status := WinBase.SystemTimeToFileTime(ADR(st), ADR(ft)); <*ASSERT status # 0*> t := TimeWin32.FromFileTime(ft); RETURN t + FLOAT(d.offset, LONGREAL) END ToTime; BEGIN Local := NEW(TimeZone, fromTime := FromTimeLocal); UTC := NEW(TimeZone, fromTime := FromTimeUTC) END DateWin32.