time/src/WIN32/DateWin32.m3


Copyright (C) 1994, Digital Equipment Corp.

UNSAFE MODULE DateWin32 EXPORTS Date;

IMPORT Text, Time;
IMPORT WinDef, WinBase, WinNT, TimeWin32;

REVEAL TimeZone = BRANDED OBJECT METHODS fromTime(t: Time.T): T END;

PROCEDURE FromTimeLocal(<*UNUSED*> z: TimeZone; t: Time.T): T =
  VAR
    ft, lft: WinBase.FILETIME;
    st: 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 Window's time calculation messes
       up.  So, if t is in the first day of the PC epoch then
       add 1 day (86400 seconds) do the converstion to a date
       and then 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.FileTimeToLocalFileTime(ADR(ft), ADR(lft));
    <*ASSERT status # 0*>
    status := WinBase.FileTimeToSystemTime(ADR(lft), ADR(st));
    <*ASSERT status # 0*>
    d := FromSystemTime(st);
    d.offset := ROUND(t - TimeWin32.FromFileTime(lft));
    tzrc := WinBase.GetTimeZoneInformation(ADR(tz));
    <*ASSERT tzrc # -1 *>
    CASE tzrc OF <*NOWARN*>
    | 0 (* TIME_ZONE_ID_UNKNOWN *) =>
        d.zone := "\"Unknown zone\"";
    | 1 (* TIME_ZONE_ID_STANDARD *) =>
        d.zone := CopyTimeZoneName(tz.StandardName);
    | 2 (* TIME_ZONE_ID_DAYLIGHT *) =>
        d.zone := CopyTimeZoneName(tz.DaylightName);
        INC(d.offset, tz.DaylightBias);
    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 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;

PROCEDURE FromTimeUTC(<*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 := "UT";
    RETURN d
  END FromTimeUTC;

PROCEDURE FromSystemTime(st: WinBase.SYSTEMTIME): T =
Set all fields of d except offset and zone, from st.
  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;

PROCEDURE FromTime(t: Time.T; z: TimeZone := NIL): T =
  BEGIN
    IF z = NIL THEN z := Local END;
    RETURN z.fromTime(t)
  END FromTime;

PROCEDURE ToTime(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.