{************************************************************
 ***               DATE & TIME OBJECTS                    ***
 ************************************************************}
{$I compflgs}
unit DatTime;

{*****************************
 ***     INTERFACE         ***
 *****************************}
INTERFACE

uses objects; {for streams - load & store}

var
	DatErr : byte; {Global error variable for Date object}

const
	{Text date display types}
	daFull 		= 1;  {Full: 			 28th January 1993}
	daAbbr 		= 2;  {Short text: 28 Jan 93}
	daExpand 	= 3;  {extended: 	 28 January 1993}

	daDigit10 = 4;
	daDigit8 	= 5;
	daAge 		= 6; 		{age - months up to 23, then yrs}

	{for addday, etc}
	Forwards = True;
	Back = False;

const
	{for input time, also askey}
	itHM 		= $01;  {hours mins}
	itHMS 	= $02; {and seconds}
	itHMS1 	= $03; {and 100ths of seconds}

	itZeroBlank = $10;

	itNeg 	= $20;  {for askey - negative}
	itTime 	= $40;	{for inputtime - validate for time - ie check hours}


{========== DATES =================}
type
	PDate = ^TDate;
	TDate = object
		Day : 0..32;    {Allow 1 either way so that we can add a day/month, and then check for validation}
		Month : 0..13;
		Year : Word;
		procedure SetToStr(S : string);
		procedure SetToNum(const D, M : byte; const Y : word);
		procedure SetToDate(const Date : TDate);
		procedure SetToToday;
		procedure Store(var S : TStream);
		procedure Load(var S : TStream);
		procedure Clear;
		procedure AddDay( B : boolean);
		procedure AddWeek(B : boolean);
		procedure AddMonth(B : boolean);
		procedure AddYear(B : boolean);
		procedure AddDays(D : integer);
{  	procedure SetToDays(D : integer); {corresponds to days function below}

		function Blank : boolean;
		function Digit8 : string;
		function Digit10: string;
		function Text(da : byte) : string;
		function Age : integer;
		function AgeMonths : integer;
		function AgeDays : integer;

		function DayOfWeek : byte;
		function DayOfYear : integer;
		function WeekOfYear : integer;
		function Days : integer;
		function AsKey : string;
		procedure SetToKey(const S : String);
	end;

type
	PToday = ^TToday;
	TToday = object(TDate)

		function Digit8 : string;
		function Digit10: string;
		function Text(da : byte) : string;
		function Age : integer;
		function Days : integer;
		function AgeDays : integer;
	end;

	PDateRange = ^TDateRange;
	TDateRange = object(TObject)
		Start : TDate;
		Finish : Tdate;

		function Blank : boolean;
		function Text(da : byte) : string; {date - date}
		function InRange(Date : TDate) : boolean; {checks if date is within range}
	end;

	TTimeStr = string[11];

{=========== TIMES =================}
var
	TimErr : byte; {Global error variable}

type
	PTime = ^TTime;
	TTime = object
		 Hour : byte;
		 Min : 0..59;
		 Sec : 0..59;
		 S100 : 0..99;
		procedure Clear;
		function Blank : boolean;
		procedure SetToStr(const S : string);
		procedure SetToNum(const H, M, S : byte);
		procedure SetToNow;
		procedure SetTo(const NewTime : TTime);
		procedure Add(const NewTime : TTime);
		procedure SetToSecs(NewSecs : longint);
		procedure Store(var S : TStream);
		procedure Load(var S : TStream);
		function Mins : longint;
		function Secs : longint;
		function inS100 : longint;
		function Digit5 : TTimeStr;
		function Digit8 : TTimeStr;
		function Digit11 : TTimeStr;
		function AsKey(const itType : byte) : string;
	end;

type
	PTimer = ^TTimer;
	TTimer = object
		StartT,StopT : TTime;
		procedure Clear;
		procedure Start;
		procedure Stop;
		function Secs : longint;
		function S100 : longint;
		function Digit8 : TTimeStr;
	end;


var
	Today : TDate;  {For general ease-of-use}
										{no need for it to be a TToday, as KameleonApplication's
											status line keeps it uptodate}
	Timer : TTimer; {ditto}
	TimeNow : TTime;


{=========== USEFUL DATE ROUTINES & CONSTANTS =================}
const
	DoWeekName: array[0..6] of string[9] = ('Sunday','Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday');
	MonthName: array[1..12] of string[9] = ('January', 'February', 'March', 'April', 'May', 'June', 'July', 'August',
																						'September','October','November','December');

function DaysInMonth(const Month : byte; const Year : word) : byte;
function DayofWeek(const Day, Month, Year : Integer ) : Integer;

function DateErrorMsg(Err : byte) : string;

{*******************************
 ***      IMPLEMENTATION     ***
 *******************************}

IMPLEMENTATION

uses	windos, {for getting current date/time}
			minilib;

{==== Converting DatErr to string =============}
function DateErrorMsg;
var S : string;
begin
	S := '';
	case (Err and $0F) of
		01 : S := 'Date of Month ';
		02 : S := 'Month ';
		03 : S := 'Year ';
	end;

	case (Err and $F0) of
		$10 : S := S + 'Range ';
		$20 : S := S + 'Format ';
		$30 : S := S + 'Value ';
	end;

	if S<>'' then S := S + 'error';

	DateErrorMsg := S;
end;




{*****************************************************
 ***  MISCELLANEOUS DATE CALCULATIONS              ***
 *****************************************************}
const
	UsualDiMonth: array[1..12] of byte = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
	DaysBeforeMonth : array [1..12] of Integer =
									 (0,31,59,90,120,151,181,212,243,273,304,334);


{=== Core Date routines (off internet (anonymous)) ====}
function IsLeap(const Year : word) : boolean;
begin
	Isleap := (Year mod 4 = 0) and not ((Year mod 100 = 0) and not (Year mod 400 = 0));
end;

function FirstThursday (const Year: word) : Integer;
begin
	FirstThursday := 7 - (1 + (Year-1600) + (Year-1597) div 4
		- (Year-1501) div 100 + (Year-1201) div 400) mod 7;
end;

{=== Work out days in the month =============}
{Allows for February's leap year}
function DaysInMonth(const Month : byte; const Year : word) : byte;
begin
	if (month>0) and (month<=12) then begin
		if (month = 2) and IsLeap(Year) then
			DaysInMonth := 29
		else
			DaysInMonth := UsualDiMonth[Month];
	end else
		DaysInMonth := 0;  {Error in parameter}
end;

{=== Work out which day of the week it is =========}
{Pinched from calendar.pas in the tv examples}
{much more efficient? one below}
{function DayOfWeek(const Day, Month, Year: Integer) : Integer;
var
	century, yr, dw: Integer;
begin
	if Month < 3 then
	begin
		Inc(Month, 10);
		Dec(Year);
	end
	else
		 Dec(Month, 2);
	century := Year div 100;
	yr := year mod 100;
	dw := (((26 * month - 2) div 10) + day + yr + (yr div 4) +
		(century div 4) - (2 * century)) mod 7;

	if dw < 0 then DayOfWeek := dw + 7
	else DayOfWeek := dw;
end;

{========= DAY OF THE YEAR ================}
function DayOfYear(const Day, Month, Year : Integer) : Integer;
begin
	if (Month > 2) and IsLeap(Year) then DayOfYear:=DaysBeforeMonth[Month]+Day+1
	else DayOfYear :=DaysBeforeMonth[Month]+Day;
end;

{=========== WEEK OF YEAR =================}
function WeekOfYear(const Day, Month, Year : Integer ) : Integer;
begin
	if (Month = 1) and (Day < FirstThursday(Year)-3) then
		WeekOfYear := WeekOfYear(31,12,Pred(Year))
	else
		if (Month = 12) and (Day > FirstThursday(Succ(Year))+27) then
		WeekOfYear := 1
	else
		WeekOfYear := (DayOfYear(Day,Month,Year)-FirstThursday(Year)+10) div 7;
end;

{=========== DAY OF WEEK =================}
function DayofWeek(const Day, Month, Year : Integer ) : Integer;
begin
	DayOfWeek:=(DayOfYear(Day,Month,Year)-FirstThursday(Year)+4) mod 7;
end;


{=========== EASTER DATE CALC ==================
(C)1993 D. Engler, msf00011@llpptn.linknet.com}
{pass year, returns day & month for that year}

procedure GetEaster(var Day, Month : integer; Year : integer);
var a,b,c,d,f :word;
begin
	a:= Year mod 19;
	b:= Year mod 4;
	c:= Year mod 7;
	d:=(19*a+24) mod 30;

	{correction for centuries}

{original code	f:=0; if x<2500 then f:=3;
				if x<2300 then f:=2;
				if x<2200 then f:=1;
				if x<2100 then f:=0;
				if x<1900 then f:=6;
				if x<1800 then f:=5;
				if x<1700 then f:=4;{}
	case Year of
		0..1699 		: f := 4;
		1700..1799 	: f := 5;
		1800..1899	: f := 6;
		2100..2199  : f := 1;
		2200..2299  : f := 2;
	else
		F := 0;
	end;

	Day :=(2*b+4*c+6*d+5+f) mod 7;
	Day :=22+d+ Day;

	Month:=3; if Day>31 then begin Month :=4; Day := Day-31 end;
end;



{*******************************************
 ***        DATE OBJECT DEF              ***
 *******************************************}

	{==== Clear Date =============}
	procedure TDate.Clear;
	begin
		Year := 0;
		Month := 0;
		Day := 0;
	end;

	{**********************************
	 ** STORE & VALIDATE from STRING **
	 **********************************}

	procedure TDate.SetToStr(S : string);
	var
		D, M, Y : integer;
		DS,MS : string[2];
		YS : string[4];
		SS : string[20];

{		function DigitOrSpace(C : char) : boolean;
		begin
			if (C = ' ') or ( (C>='0') and (C<='9') ) then DigitOrSpace := True else DigitOrSpace := False;
		end;{}

	begin
		{=== VALIDATE ====================}
		DatErr := 0; {All OK}
		Clear; {start by leaving blank}

		if S = space(Length(S)) then exit; {blank date}

		{split by characters hyphen, full stop or colon}
		{domonth}
		SS := SplitBy(S, '/.-:');  {half a smily?!}
		if (SS='') or (Length(SS)>2) then DatErr := $21;
		if DatErr = 0 then begin
			D := S2Num(SS);
			if ValErr <>0 then DatErr := $31;
			if (D<0) or (D>31) then DatErr := $11; {proper range checking in setToNum}
		end;

		{month}
		if DatErr = 0 then begin
			SS := SplitBy(S, '/.-:');
			if (SS='') or (Length(SS)>2) then DatErr := $22;
			if DatErr = 0 then begin
				M := S2Num(SS);
				if ValErr <>0 then DatErr := $32;
				if (M<0) or (M>12) then DatErr := $12;
			end;
		end;

		{year}
		if DatErr = 0 then begin
			SS := S; {SplitBy(S, '/.-:');{}
			if (SS='') or (Length(SS)>4) then DatErr := $23;
			if DatErr = 0 then begin
				Y := S2Num(SS);
				if ValErr <>0 then DatErr := $33;
				if (Y<0) then DatErr := $13;
				if DatErr = 0 then begin
					{auto year correct}
					if (Y=0) and (D<>0) then Y := Y + 2000; {Only 2000 if not blank}
					if (Y>0) and (Y<20) then Y := Y + 2000;  {Assume two digit code from 2000}
					if (Y>=20) and (Y<100) then Y := Y + 1900; {Assume two digit code from 1900}
					if Y>3000 then DatErr := $13;               {Assume no-ones putting in large date - catches 9393 mistakes etc}
					if Y<0 then DatErr := $13;
				end;
			end;
		end;

		{=== STORE ===========}
		if DatErr = 0 then SetToNum(D,M,Y); {range checking and store}
	end;

	{==== STORE (& VALIDATE) DATE FROM NUMBERS ==============}
	{Returns Err, Low nybble:
										$01 : Day problem
										$02 : Month problem
										$03 : Year error
										$08 : overall error (eg wrong length)
								High:
										$00 : ValErr
										$10 : Range error
	}

	procedure TDate.SetToNum(const D, M : byte; const Y : word);
	begin
		DatErr := 0; {All OK}

		{--- Month ---}
		if DatErr = 0 then
			if (M<0) or (M>12) then
				DatErr := $12
			else
				Month := M;

		{--- Year ---}
		if (DatErr=0) then
				Year := Y;

		{---- Days given Month & year ---}
		if DatErr=0 then
			if (D<0) or (D>DaysInMonth(Month,Year)) then DatErr := $11
				else day := D;

		{allow zero days & months}
	end;


	procedure TDate.SetToDate;
	begin
		SetToNum(Date.Day, Date.Month, Date.Year);
	end;

	procedure TDate.SetToToday;
	var
		D, M, Y, DoW : word;
		Db, Mb : byte;

	begin
		GetDate(Y, M, D, DoW);
		Db := D; Mb := M;
		SetToNum(Db, Mb, Y);
	end;

	{========= ADDING/SUBTRACTING =====================}
	procedure TDate.AddDay(B : boolean);
	begin
		if Blank then SetToToday;
		if (Day=1) and (B=False) then begin
			{backwards to prev month}
			AddMOnth(False);
			Day := DaysInMonth(Month, Year);
		end else
			if (Day=DaysinMonth(Month, Year)) and (B=True) then begin
				{forwards to next month}
				AddMonth(True);
				Day := 1;
			end else
				if B then inc(Day) else Dec(Day);
	end;

	procedure TDate.AddWeek(B : boolean);
	var I :integer;
	begin
		for I := 1 to 7 do AddDay(B);
	end;

	procedure TDate.AddMonth(B : boolean);
	begin
		if Blank then SetToToday;
		if (Month=1) and (B=False) then begin
			AddYear(False);
			Month := 12;
		end else
			if (Month=12) and (B=True) then begin
				AddYear(True);
				Month := 1;
			end else
				if B=True then inc(Month) else dec(Month);

		{check days still valid}
		if Day>DaysInMonth(Month, Year) then Day := DaysinMonth(Month, Year);
	end;

	procedure TDate.AddYear(B : Boolean);
	begin
		if Blank then SetToToday;
		if B then inc(Year) else dec(Year);
	end;

	procedure TDate.AddDays(D : integer);
	var I : integer;
	begin
		if D>0 then for I := 1 to D do AddDay(True)
			else for I := 1 to -D do AddDay(False);
	end;

	{Given number of days since 1980, work out date.  Not done yet}
{  procedure TDate.SetToDays(D : integer);
	begin
	var
		D,i : integer;

	begin
		dat.Year :=
		if Blank then
			Days := 0
		else begin
			D:=0;
			if Dat.Year >2069 then   {#Days >32767 leading to overflow}
{				Days := 32767  {Max}
{			else begin
				if Dat.Year >1980 then D:=366 + ((Dat.Year-1980) * 365) + ((Dat.Year-1980) div 4);
				for i:=1 to (Dat.Month-1) do D := D + DoMonth(i,Dat.Year);
				Days:= D + Dat.Day;
			end;
		end;
	end;


	{=== STORE ONTO STREAM ===============================}
	procedure TDate.Store(var S : TStream);
  begin
		S.Write(Day, 1);
    S.Write(Month, 1);
    S.Write(Year, 2);
	end;

  {=== LOAD FROM STREAM ===============================}
	procedure TDate.Load(var S : TStream);
  begin
		S.Read(Day, 1);
    S.REad(Month, 1);
    S.Read(Year, 2);
	end;

	{==== Is date blank? ======}
	function TDate.Blank : boolean;
	begin
		Blank := (Day = 0) and (Month = 0) and (Year = 0);
	end;

	{==== Day of week ====}
	function TDate.DayOfWeek;
	begin
		DayOfWeek := DatTime.DayOfWeek(Day, Month, Year);
	end;

	{=== Week of Year ===}
	function TDate.WeekOfYear;
	begin
		WeekOfYear := DatTime.WeekOfYear(Day, Month, Year);
	end;

	{=== Day of Year ====}
	function TDate.DayOfYear;
	begin
		DayOfYear := DatTime.DayOfYear(Day, Month, Year);
	end;

	{==== RETURN NUMBER OF DAYS SINCE BEG 1980 ===============}
	function TDate.Days : integer;
	var
		D,i : integer;

	begin
		if Blank then
			Days := 0
		else begin
			D:=0;
			if Year >2069 then   {#Days >32767 leading to overflow}
				Days := 32767  {Max}
			else
				if Year<1980 then
					Days := -32767
				else begin
					D:=366 + ((Year-1980) * 365) + ((Year-1980) div 4);
					for i:=1 to (Month-1) do D := D + DaysInMonth(i,Year);
					Days:= D + Day;
				end;

		end;
	end;

	{==== AGE OF DATE ================================}
	{Ie number of years between date and today}
	function TDate.Age : integer;
	begin
		if Blank then
			Age := -$FF
		else
			Age := AgeMonths div 12;
	end;

	{==== AGE OF DATE IN MONTHS ================================}
	{Ie number of months between date and today}
	function TDate.AgeMonths : integer;
	var AMonths : integer;

	begin
		if Blank then
			AgeMonths := -$FF
		else
			if Today.Year=Year then
				AgeMOnths := Today.Month - Month
			else
				if Today.Year<Year then
					AgeMonths := 0
				else begin
					{months to start of this year, plus *whole* number of years between today
						and "birthday"}
					AMonths := Today.Month + (Today.Year - Year -1)*12;
					{Plus number of *whole* months from "birthday" to end of birthday year}
					AMOnths := AMonths + (12 - Month) -1;
					{Plus one month if the birthday day of month is before todays day of month}
					if Day<=Today.Day then inc(Amonths);

					{there we go....}
					AgeMonths := AMonths;
				end;
	end;

	{=== DAYS TO DATE =================================}
	{ie number of days between date and today}
	function TDate.AgeDays : integer;
	begin
		if Blank then
			AgeDays := 0
		else
			AgeDays := Today.Days - Days;
	end;

	{=== RETRIEVE DATE AS AN 8-DIGIT STRING ==============}
	function TDate.Digit8 : string;
	begin
		if Blank then
			Digit8 := '  -  -  '
		else
			Digit8 := Tens(Day)+Units(Day)+'-'+Tens(Month)+Units(Month)+'-'
								+Tens(Year)+Units(Year);
	end;

	{=== RETRIEVE DATE AS AN 10-DIrGIT STRING ==============}
	function TDate.Digit10 : string;
	var
		D : string;

	begin
		if Blank then
			Digit10 := '  -  -    '
		else begin
			if Day=0 then D := '  ' else D := tens(Day)+Units(Day);
			if Month=0 then D := D+'-  ' else D := D + '-' + Tens(Month)+Units(Month);
			Digit10 := D + '-' + N2Str(Year);
		end;
	end;

	{===== PRINT DATE AS TEXT ============================}
	function TDate.Text;
	var S : String;

	{Really want a way of passing a parameter string, eg
		D - day/date
		W - day of week
		M - month
		Y - Year
		adding $ to D puts a th/st/rd/etc on the end
		adding $ to M makes it a text month, not a number
		adding $3 to M makes it a 3 character month
		Adding $ to Y makes it a 4 digit year}

	begin
		if Blank then
			Text := ''
		else case da of
			daFull : Text := Copy(DoWeekName[DayOfWeek],1,3)+' '
												+AddOrdinator(Day) +' '+MonthName[Month]+' '+N2Str(Year);
			daAbbr : begin
				S := '';
				if Day>0 		then S := S+ PadSpaceL(N2Str(Day),2)+' ';
				if Month>0 	then S := S + copy(MonthName[Month],1,3)+' ';
				Text := S+Right(N2Str(Year),2);
			end;
			daExpand : begin
				S := '';
				if Day>0	 then S := S+ N2Str(Day)+' ';
				if Month>0 then S := S+ MonthName[Month]+' ';
				Text := S+N2Str(Year);
			end;
			daDigit10 : Text := Digit10;
			daDigit8 : Text := Digit8;
			daAge : if AgeMonths<24 then Text := N2Str(AgeMonths)+'mnths' else Text := N2Str(Age)+'yrs';
		end;
	end;

	{Returns string suitable for index keystring, etc, year first, etc}
	function TDate.AsKey;
	begin
		AsKey := char(Year div $100)+char(Year and $FF)+char(Month)+char(Day);
	end;

	{Sets from key as above}
	procedure TDate.SetToKey;
	begin
		SetToNum(ord(S[4]), ord(S[3]), ord(S[2]) + (ord(S[1])*$100));
	end;


{************************************
 ***  SPECIAL TODAY DATE          ***
 ************************************}
function TToday.Digit8;		begin SetToToday; Digit8 := inherited Digit8; end;
function TToday.Digit10;	begin SetToToday; Digit10 := inherited Digit10; end;
function TToday.Text; 		begin SetToToday; Text := inherited Text(da); end;
function TToday.Age;			begin SetToToday; Age := inherited Age; end;
function TToday.Days;			begin SetToToday; Days := inherited Days; end;
function TToday.AgeDays;  begin SetToToday; AgeDays := 0; end;


{========== DATE RANGE ==============}
function TDateRange.Text;
begin
	if Start.Blank and Finish.Blank then Text := 'All Dates'
	else
		if Start.Blank then Text := 'All Dates - '+Finish.Text(da)
		else
			if Finish.Blank then Text := Start.Text(da)+' - All Dates'
			else
				Text := Start.Text(da)+' - '+Finish.Text(da);
end;

function TDateRange.inRange; {inclusive}
begin
	if ((Date.Days>=Start.Days) or (Start.Blank)) and
		 ((Date.Days<=Finish.Days) or (Finish.Blank)) then
				inRange := True
	else
				inRange := False;
end;


function TDateRange.Blank;
begin
	if Start.Blank and Finish.Blank then Blank := True else Blank := False;
end;

{**************************************************************************
 ***                           TIME OBJECTS                             ***
 **************************************************************************}

procedure TTime.Clear;
begin
	Hour := 0;
	Min := 0;
	Sec := 0;
	S100 := 0;
end;

function TTime.Blank;
begin
	{OK, so could be midnight, but still}
	if (Hour = 0) and (Min = 0) and (Sec = 0) and (S100 = 0) then
		Blank := True
	else
		Blank := False;  {No measure of this so far}
end;

	{==== STORE (& VALIDATE) DATE FROM STRING ==============}
	{Returns Low nybble:
						$01 : Hour out of range
						$02 : Min out of range
						$08 : overall error (eg wrong length)
					 High nybble:
						$10 : Range error
						$00 : ValErr
	}

	procedure TTime.SetToStr(const S : string);
	var		H,M, Se,S1 : word;

	begin
		TimErr := 0; {All OK}
		if Length(S)<5 then TimErr:= $08;  {Overall Error}

		{--- Hour ---}
		if TimErr = 0 then begin
			H := S2Num(S[1]+S[2]);
			if (ValErr <> 0) then TimErr := $01;
		end;

		{--- Minute ---}
		if TimErr = 0 then begin
			M := S2Num(S[4]+S[5]);
			if (ValErr <> 0) then TimErr := $02;
		end;

		{--- Seconds ---}
		if TimErr = 0 then begin
			Se := S2Num(Copy(s,7,2));
			if (ValErr <> 0) then TimErr := $03;
		end;

		if TimErr = 0 then SetToNum(H,M, Se) else Clear;
	end;

	{=== STORE FROM NUMBER =======================================}
	{Returns Low nybble:
						$01 : Hour out of range
						$02 : Min out of range
						$03 : Secs out of range
						$08 : overall error (eg wrong length)
					 High nybble:
						$10 : Range error
						$00 : ValErr
	}

	procedure TTime.SetToNum(const H, M, S : byte);
	begin
		TimErr := 0; {All OK}

		{--- Hour ---}
		if TimErr = 0 then
				if (H<0) or (H>99) then TimErr := $11 {allow lots of hours}
					else Hour := H;

		{--- Minute ---}
		if TimErr = 0 then
				if (M<0) or (M>59) then TimErr := $12
					else Min := M;

		{--- Seconds ---}
		if TimErr = 0 then
				if (S<0) or (S>59) then TimErr := $13
					else Sec := S;

		S100 := 0;
	end;

	procedure TTime.SetTo;
	begin
		Hour := NewTime.Hour;
		Min := NewTime.Min;
		Sec := NewTime.Sec;
		S100 := NewTime.S100;
	end;

	procedure TTime.Add;
	begin
		SetToSecs(Secs + NewTime.Secs);
	end;

  procedure TTime.SetToSecs;
	begin
		if (NewSecs>0) and (NewSecs<3600*99) then begin
			Hour := trunc(NewSecs/3600);
			Min  := trunc(NewSecs/60) mod 60;
			Sec  := NewSecs mod 60;
			S100 := 0;
		end else
			Clear;
	end;


	{=== STORE ONTO STREAM ===============================}
	procedure TTime.Store(var S : TStream);
	begin
		S.Write(Hour, 1);
		S.Write(Min, 1);
		S.Write(Sec, 1);
		S.Write(S100,1);
	end;

	{=== LOAD FROM STREAM ===============================}
	procedure TTime.Load(var S : TStream);
	begin
		S.Read(Hour, 1);
		S.Read(Min, 1);
		S.REad(Sec, 1);
		S.Read(S100,1);
	end;

	{======= MINUTES SINCE MIDNIGHT ===============}
	function TTime.Mins : longint;
	begin
		Mins := Hour * 60 + Min;
	end;

	{======= SECONDS SINCE MIDNIGHT ================}
	function TTime.Secs : longint;
	begin
		Secs := Mins * 60 + Sec;
	end;

	function TTime.inS100 : longint;
	begin
		inS100 := Secs * 100 + S100;
	end;

	{=== PRINTABLE TIME ==============}
	function TTime.Digit5 : TTimeStr;
	begin
{		Digit5 := PadZero(N2Str(Hour), 2)+':'+PadZero(N2Str(Min),2);{old reliable}

		{new fast fancy}
		Digit5 := Tens(Hour) + Units(Hour) + ':' + Tens(Min) + Units(Min);{}
	end;

	function TTime.Digit8 : TTimeStr;
	begin
{		Digit8 := Digit5 + ':'+PadZero(N2Str(Sec),2); {old reliable}
		Digit8 := Digit5 + ':'+Tens(Sec)+Units(Sec);
	end;

	function TTime.Digit11 : TTimeStr;
	begin
		Digit11 := Digit8 + ':'+Tens(S100)+Units(S100);
	end;

	procedure TTime.SetToNow;
	var  H, M, S, Sec100 : word;   {used to change type}

	begin
		GetTime(H, M, S, Sec100); {assume will return a valid value...}
		Hour := H;
		Min := M;
		Sec := S;
		S100 := Sec100;
	end;

	function TTime.AsKey;
	begin
		if (itType and itNeg)>0 then
			case itType and $0F of
				itHM 		: AsKey := char(255-Hour)+char(255-Min); {original}
				itHMS 	: AsKey := char(255-Hour)+char(255-Min)+char(255-Sec);
				itHMS1 	: AsKey := char(255-Hour)+char(255-Min)+char(255-Sec)+char(255-S100);
			end
		else
			case itType and $0F of
				itHM 		: AsKey := PadSpaceL(N2Str(mins),4); {original}
				itHMS 	: AsKey := char(Hour)+char(Min)+char(Sec);
				itHMS1 	: AsKey := char(Hour)+char(Min)+char(Sec)+char(S100);
			end;
	end;


{*******************************
 **           TIMER           **
 *******************************}

procedure TTimer.Clear;
begin
	StartT.Clear;
	StopT.Clear;
end;

procedure TTimer.STart;
begin
	StartT.SetToNow;
	StopT.Clear;
end;

procedure TTimer.Stop;
begin
	StopT.SetToNow;
end;

function TTimer.Secs;
var S : longint;
begin
	if StopT.Blank then begin
		TimeNow.SetToNow;
		S := TimeNow.Secs - StartT.Secs;
	end else
		S := StopT.Secs - StartT.Secs;

	if S<0 then S := S+24*60*60; {crossed midnight}
	Secs := S;
end;

function TTimer.S100;
begin
	if StopT.Blank then begin
		TimeNow.SetToNow;
		S100 := TimeNow.inS100 - StartT.inS100;
	end else
		S100 := StopT.inS100 - StartT.inS100;
end;

function TTimer.Digit8;
var S : longint;
begin
	S := Secs;
	Digit8 :=  PadZero(N2Str(S div 3600),2)+':'
						+PadZero(N2Str((S div 60) mod 60),2)+':'
						+PadZero(N2Str(S mod 60), 2);
end;



begin
{$IFDEF fixit}	writeln('Initialising Dattime unit'); {$ENDIF}
	{so they're ready for other unit inits}
	Today.SetToToday;
	TimeNow.SetToNow;
end.
