MODULE Dates;

	(* THIS IS TEXT COPY OF BlackBox 1.6-rc6 System/Mod/Dates.odc *)
	(* DO NOT EDIT *)

	IMPORT Kernel;

	CONST
		monday* = 0;
		tuesday* = 1;
		wednesday* = 2;
		thursday* = 3;
		friday* = 4;
		saturday* = 5;
		sunday* = 6;

		short* = 0;
		long* = 1;
		abbreviated* = 2;
		plainLong* = 3;
		plainAbbreviated* = 4;

	TYPE
		Date* = RECORD
			year*, month*, day*: INTEGER
		END;

		Time* = RECORD
			hour*, minute*, second*: INTEGER
		END;

		Hook* = POINTER TO ABSTRACT RECORD (Kernel.Hook) END;

	VAR M, N: ARRAY 8 OF INTEGER; hook: Hook;

	PROCEDURE (h: Hook) GetTime* (OUT d: Date; OUT t: Time), NEW, ABSTRACT;
	PROCEDURE (h: Hook) GetUTCTime* (OUT d: Date; OUT t: Time), NEW, ABSTRACT;
	PROCEDURE (h: Hook) GetUTCBias* (OUT bias: INTEGER), NEW, ABSTRACT;
	PROCEDURE (h: Hook) DateToString* (d: Date; format: INTEGER; OUT str: ARRAY OF CHAR), NEW, ABSTRACT;
	PROCEDURE (h: Hook) TimeToString* (t: Time; OUT str: ARRAY OF CHAR), NEW, ABSTRACT;

	PROCEDURE SetHook* (h: Hook);
	BEGIN
		hook := h
	END SetHook;

	PROCEDURE  ValidTime* (IN t: Time): BOOLEAN;
	BEGIN
		RETURN
			(t.hour >= 0) & (t.hour <= 23)
			& (t.minute >= 0) & (t.minute <= 59)
			& (t.second >= 0) & (t.second <= 59)
	END ValidTime;
	
	PROCEDURE ValidDate* (IN d: Date): BOOLEAN;
		VAR y, m, d1: INTEGER;
	BEGIN
		IF (d.year < 1) OR (d.year > 9999) OR (d.month < 1) OR (d.month > 12) OR (d.day < 1) THEN
			RETURN FALSE
		ELSE
			y := d.year; m := d.month;
			IF m = 2 THEN
				IF (y < 1583) & (y MOD 4 = 0)
				OR (y MOD 4 = 0) & ((y MOD 100 # 0) OR (y MOD 400 = 0)) THEN
					d1 := 29
				ELSE d1 := 28
				END
			ELSIF m IN {1, 3, 5, 7, 8, 10, 12} THEN d1 := 31
			ELSE d1 := 30
			END;
			IF (y = 1582) & (m = 10) & (d.day > 4) & (d.day < 15) THEN RETURN FALSE END;
			RETURN d.day <= d1
		END
	END ValidDate;

	PROCEDURE Day* (IN d: Date): INTEGER;
		VAR y, m, n: INTEGER;
	BEGIN
		y := d.year; m := d.month - 3;
		IF m < 0 THEN INC(m, 12); DEC(y) END;
		n := y * 1461 DIV 4 + (m * 153 + 2) DIV 5 + d.day - 306;
		IF n > 577737 THEN n := n - (y DIV 100 * 3 - 5) DIV 4 END;
		RETURN n
	END Day;

	PROCEDURE DayToDate* (n: INTEGER; OUT d: Date);
		VAR c, y, m: INTEGER;
	BEGIN
		IF n > 577737 THEN
			n := n * 4 + 1215; c := n DIV 146097; n := n MOD 146097 DIV 4
		ELSE
			n := n + 305; c := 0
		END;
		n := n * 4 + 3; y := n DIV 1461; n := n MOD 1461 DIV 4;
		n := n * 5 + 2; m := n DIV 153; n := n MOD 153 DIV 5;
		IF m > 9 THEN m := m - 12; INC(y) END;
		d.year := SHORT(100 * c + y);
		d.month := SHORT(m + 3);
		d.day := SHORT(n + 1)
	END DayToDate;

	PROCEDURE GetDate* (OUT d: Date);
		VAR t: Time;
	BEGIN
		ASSERT(hook # NIL, 100);
		hook.GetTime(d, t)
	END GetDate;

	PROCEDURE GetTime* (OUT t: Time);
		VAR d: Date;
	BEGIN
		ASSERT(hook # NIL, 100);
		hook.GetTime(d, t)
	END GetTime;

	(* UTC = Coordinated Universal Time, also konown as Greenwich Mean time (GMT). *)

	PROCEDURE GetUTCDate* (OUT d: Date);
		VAR t: Time;
	BEGIN
		ASSERT(hook # NIL, 100);
		hook.GetUTCTime(d, t)
	END GetUTCDate;

	PROCEDURE GetUTCTime* (OUT t: Time);
		VAR d: Date;
	BEGIN
		ASSERT(hook # NIL, 100);
		hook.GetUTCTime(d, t)
	END GetUTCTime;
	
	PROCEDURE GetUTCBias* (OUT bias: INTEGER);
	(*
		Returns the current bias, in minutes, for local time translation on this computer. The bias is the difference, 
		in minutes, between Coordinated Universal Time (UTC) and local time. All translations between UTC and
		local time are based on the following formula: 
			UTC = local time + bias 
 	*)		
	BEGIN
		ASSERT(hook # NIL, 100);
		hook.GetUTCBias(bias)
	END GetUTCBias;
	

	PROCEDURE GetEasterDate* (year: INTEGER; OUT d: Date);
		VAR  k, m, n, a, b, c, d0, e, o: INTEGER; month, day: INTEGER;
	BEGIN
		ASSERT((year >= 1583) & (year <= 2299), 20);
		k := year DIV 100 - 15;
		m := M[k]; n := N[k];
		a := year MOD 19; b := year MOD 4; c := year MOD 7;
		d0 := (19*a + m) MOD 30; e := (2*b+4*c+6*d0+n) MOD 7;
		o := 21+d0+e; month := 3+o DIV 31; day := o MOD 31+1;
		IF month = 4 THEN
			IF day = 26 THEN day := 19
			ELSIF (day = 25) & (d0=28) & (e = 6) & (a > 10) THEN day := 18
			END
		END;
		d.year := year;
		d.month := month;
		d.day := day
	END GetEasterDate;

	PROCEDURE  DayOfWeek* (IN d: Date): INTEGER;
	(** post: res = 0: Monday .. res = 6: Sunday **)
	BEGIN
		RETURN SHORT((4+Day(d)) MOD 7)
	END DayOfWeek;

	PROCEDURE DateToString* (IN d: Date; format: INTEGER; OUT str: ARRAY OF CHAR);
	BEGIN
		ASSERT(hook # NIL, 100);
		hook.DateToString(d, format, str)
	END DateToString;

	PROCEDURE TimeToString* (IN t: Time; OUT str: ARRAY OF CHAR);
	BEGIN
		ASSERT(hook # NIL, 100);
		hook.TimeToString(t, str)
	END TimeToString;

BEGIN
	M[0] := 22; N[0] := 2;
	M[1] := 22; N[1] := 2;
	M[2] := 23; N[2] := 3;
	M[3] := 23; N[3] := 4;
	M[4] := 24; N[4] := 5;
	M[5] := 24; N[5] := 5;
	M[6] := 24; N[6] := 6;
	M[7] := 25; N[7] := 0;
END Dates.
