-- TimeConvert.Mesa  Edited by Johnsson on 29-Jul-80  9:25:55
-- Copyright  Xerox Corporation 1979, 1980

DIRECTORY
  InlineDefs USING [BcplLongNumber, DIVMOD, LDIVMOD, LongNumber],
  StringDefs USING [AppendChar, AppendString],
  Time USING [],
  TimeDefs USING [
    BaseYear, currentParameters, currentTime, DaysInFourYears, DefaultTime,
    PackedTime, StartWeekDay, UnpackedTime, WestEast];

TimeConvert: PROGRAM
  IMPORTS InlineDefs, StringDefs EXPORTS TimeDefs, Time SHARES TimeDefs =
  BEGIN OPEN TimeDefs;

  UP: TYPE = POINTER TO UnpackedTime;
  Number: TYPE = InlineDefs.LongNumber;

  DivideTime: PROCEDURE [num: Number, den: CARDINAL]
    RETURNS [quotient: PackedTime, remainder: CARDINAL] =
    BEGIN OPEN InlineDefs;
    q: Number;
    t: CARDINAL;
    [q.highbits, t] ← LDIVMOD[num.highbits, 0, den];
    [q.lowbits, remainder] ← LDIVMOD[num.lowbits, t, den];
    RETURN[quotient: q.lc, remainder: remainder]
    END;

  CurrentDayTime, Current: PUBLIC PROCEDURE RETURNS [PackedTime] =
    BEGIN
    t: InlineDefs.BcplLongNumber ← currentTime↑;
    RETURN[LOOPHOLE[Number[num[highbits: t.highbits, lowbits: t.lowbits]]]]
    END;

  TP: TYPE = RECORD [beginDST, endDST: CARDINAL, zone, zoneminutes: INTEGER];

  TimeParameters: PROCEDURE RETURNS [p: TP] =
    BEGIN OPEN p;
    direction: WestEast;
    [beginDST: beginDST, endDST: endDST, zone: zone, zoneminutes: zoneminutes,
      direction: direction] ← currentParameters↑;
    IF direction # west THEN BEGIN zone ← -zone; zoneminutes ← -zoneminutes END;
    RETURN
    END;

  MonthTable: ARRAY [0..12] OF CARDINAL =
    [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366];

  Unpack: PUBLIC PROCEDURE [p: PackedTime ← 0] RETURNS [unpacked: UnpackedTime] =
    UnpackDT;

  UnpackDT: PUBLIC PROCEDURE [p: PackedTime ← DefaultTime]
    RETURNS [unp: UnpackedTime] =
    BEGIN
    u: UP = @unp;
    day4, day, yr4: CARDINAL;
    month: CARDINAL ← 1;
    t: Number;
    parms: TP;
    parms ← TimeParameters[];
    IF p = DefaultTime THEN p ← CurrentDayTime[];
    [p, u.second] ← DivideTime[[lc[p]], 60];
    p ← p - parms.zoneminutes; -- ignore underflow
    [p, u.minute] ← DivideTime[[lc[p]], 60];
    u.zone ← parms.zone;
    u.dst ← FALSE;
    p ← p - parms.zone; -- ignore underflow
    DO
      -- have to repeat if DST
      [t.lc, u.hour] ← DivideTime[[lc[p]], 24];
      u.weekday ← (t.lowbits + StartWeekDay) MOD 7;
      [yr4, day4] ← InlineDefs.DIVMOD[t.lowbits, DaysInFourYears];
      day4 ←
	(IF day4 >= 2*365 + 31 + 28 THEN 3 ELSE (day4 + (365 - 31 - 28))/365) +
	  day4;
      [day4, day] ← InlineDefs.DIVMOD[day4, 366];
      u.year ← BaseYear + yr4*4 + day4;
      WHILE day >= MonthTable[month] DO month ← month + 1 ENDLOOP;
      u.month ← month ← month - 1;
      day ← day + 1;
      u.day ← day - MonthTable[month];
      IF u.dst OR ~CheckDateGE[u, day, parms.beginDST, 2] OR CheckDateGE[
	u, day, parms.endDST, 1] THEN EXIT;
      p ← p + 1;
      u.dst ← TRUE;
      ENDLOOP;
    RETURN
    END;

  InvalidTime, Invalid: PUBLIC ERROR = CODE;

  Pack: PUBLIC PROCEDURE [UnpackedTime, BOOLEAN] RETURNS [time: PackedTime] =
    PackDT;

  PackDT: PUBLIC PROCEDURE [unp: UnpackedTime, computeDST: BOOLEAN ← TRUE]
    RETURNS [time: PackedTime] =
    BEGIN
    u: UP = @unp;
    year, month, day, day1, hour, minute, second: CARDINAL;
    zone: INTEGER;
    dst: BOOLEAN;
    yr3: [0..3];
    t: Number;
    tp: TP ← TimeParameters[];
    [year: year, month: month, day: day, hour: hour, minute: minute,
      second: second, zone: zone, dst: dst] ← u↑;
    IF (year ← year - BaseYear) >= 136 OR month >= 12 OR day ~IN [1..31] OR hour
      >= 24 OR minute >= 60 OR second >= 60 THEN ERROR InvalidTime;
    yr3 ← year MOD 4;
    IF day > LOOPHOLE[MonthTable[month + 1] - MonthTable[month], CARDINAL] OR
      (month = 1 AND day = 29 AND yr3 # 3) THEN ERROR InvalidTime;
    -- compute days this year in day1
    day1 ← MonthTable[month] + day;
    IF yr3 # 3 THEN
      BEGIN
      tp.beginDST ← tp.beginDST - 1;
      tp.endDST ← tp.endDST - 1;
      IF month >= 2 THEN day1 ← day1 - 1;
      END;
    t ← Number[
      num[highbits: 0, lowbits: (year/4)*DaysInFourYears + yr3*365 + day1 - 1]];
    u.weekday ← (t.lowbits + TimeDefs.StartWeekDay) MOD 7;
    IF computeDST THEN
      BEGIN OPEN tp;
      IF CheckDateGE[u, day1, beginDST, 2] AND ~CheckDateGE[u, day1, endDST, 2]
	THEN zone ← zone - 1
      END
    ELSE
      BEGIN
      tp.zone ← zone;
      tp.zoneminutes ← 0;
      IF dst THEN tp.zone ← tp.zone - 1;
      END;
    RETURN[((t.lc*24 + hour + tp.zone)*60 + minute + tp.zoneminutes)*60 + second]
    END;

  Append: PUBLIC PROCEDURE [STRING, UnpackedTime, BOOLEAN] = AppendDayTime;

  AppendDayTime: PUBLIC PROCEDURE [
    s: STRING, unp: UnpackedTime, zone: BOOLEAN ← FALSE] =
    BEGIN
    z: INTEGER;
    KnownZones: TYPE = [4..10];
    zones: PACKED ARRAY KnownZones OF CHARACTER ← ['A, 'E, 'C, 'M, 'P, 'Y, 'H];
    u: UP = @unp;
    p: CARDINAL ← s.length;
    m: CARDINAL;

    w2d: PROCEDURE [v: CARDINAL] =
      BEGIN
      d1, d2: CARDINAL;
      [d1, d2] ← InlineDefs.DIVMOD[v, 10];
      IF d1 # 0 THEN s[p] ← '0 + d1;
      s[p + 1] ← '0 + d2;
      p ← p + 3;
      END;

    StringDefs.AppendString[s, " 0-xxx-00  0:00:00"L];
    w2d[u.day];
    m ← u.month*3;
    THROUGH [0..2] DO
      s[p] ← ("JanFebMarAprMayJunJulAugSepOctNovDec"L)[m];
      p ← p + 1;
      m ← m + 1;
      ENDLOOP;
    p ← p + 1;
    w2d[u.year MOD 100];
    w2d[u.hour];
    w2d[u.minute];
    w2d[u.second];
    IF zone AND (z ← unp.zone) IN KnownZones THEN
      BEGIN OPEN StringDefs;
      c: CHARACTER ← IF unp.dst THEN 'D ELSE 'S;
      AppendChar[s, ' ];
      AppendChar[s, zones[z]];
      AppendChar[s, c];
      AppendChar[s, 'T];
      END;
    RETURN
    END;

  CheckDateGE: PROCEDURE [u: UP, days, dstDay, dstHour: INTEGER]
    RETURNS [BOOLEAN] =
    BEGIN
    weekday: INTEGER ← u.weekday;
    RETURN[
      IF days < dstDay - 6 THEN FALSE
      ELSE
	IF days > dstDay THEN TRUE
	ELSE
	  IF weekday = 6 THEN u.hour >= dstHour ELSE days - weekday > dstDay - 6]
    END;


  END..