Contributor: ALAN GRAFF


 (* * * * * * * * * * * * * * * * * * * * * * *)
 (*  UNIT: DTIME - By Alan Graff, Nov. 92*)
 (* Compiled from routines found in: *)
 (*  DATEPAK4: W.G.Madison, Nov. 87 *)
 (*  UNIXDATE: Brian Stark, Jan. 92 *)
 (*  Plus various things of my own creation *)
 (*  and extracted from Fidonet PASCAL echo *)
 (*  messages and other sources. *)
 (* Contributed to the Public Domain *)
 (* Version 1.1 - Nov. 1992 *)
 (* * * * * * * * * * * * * * * * * * * * * * *)

UNIT DTime;
{**************************************************************}
INTERFACE
uses crt,dos;

TYPE DATETYPE = record
 day:WORD;
 MONTH:WORD;
 YEAR:WORD;
 dow:word;
 end;

 (* Sundry determinations of current date/time variables *)
Function DayOfYear:word; (* Returns 1 to 365 *)
Function DayOfMonth:word; (* Returns 1 to 31 *)
Function DayOfWeek:word;  (* Returns 1 to 7  *)
Function MonthOfYear:word; (* Returns 1 to 12 *)
Function ThisYear:word;(* Returns current year *)
Function ThisHour:word;(* Returns 1 to 24 *)
Function ThisMinute:word; (* Returns 0 to 59 *)
 (* Calculate what day of the week a particular date falls on *)
Procedure WkDay(Year,Month,Day:Integer; var WeekDay:Integer);
  (* Full Julian conversions *)
Procedure GregorianToJulianDN(Year,Month,Day:Integer;var JulianDN:LongInt);
Procedure JulianDNToGregorian(JulianDN:LongInt;var Year,Month,Day:Integer);
  (* 365 day Julian conversions *)
Procedure GregorianToJulianDate(Year,Month,Day:Integer;var JulianDate:Integer);
Procedure JulianToGregorianDate(JulianDate,Year:Integer;var Month,Day:Integer);
  (* Sundry string things *)
Function DateString:String; (* Returns system date as "mm-dd-yy" string *)
Function TimeString:String; (* Returns system time as "00:00:00" string *)
 (* Create current YYMMDD string to use as a file name *)
Function DateAFile(dy,dm,dd:word):string;
 (* Return YY-MM-DD string from filename created by DateAFile func *)
Function Parsefile(s:string):string;
  (* Return values of 1 day ago *)
Procedure Yesterday(Var y,m,d:integer);
  (* Return values of 1 day ahead *)
Procedure Tomorrow(Var y,m,d:integer);
 (* Adjust time based on "TZ" environment *)
Function GetTimeZone : ShortInt;
Function IsLeapYear(Source : Word) : Boolean; (* What it says :-) *)
 (* Unix date conversions *)
Function Norm2Unix(Y,M,D,H,Min,S:Word):LongInt;
Procedure Unix2Norm(Date:LongInt;Var Y,M,D,H,Min,S:Word);
 (* Determines what day of year Easter falls on *)
Procedure Easter(Year:Word;Var Date:DateType);
 (* Determines what day of year Thanksgiving falls on *)
Procedure Thanksgiving(Year:Word;Var Date:DateType);
 (* Determine what percentage of moon is lit on a particular night *)
Function MoonPhase(Date:Datetype):Real;

IMPLEMENTATION

const
 D0 =1461;
 D1 = 146097;
 D2 = 1721119;
 DaysPerMonth : Array[1..12] of ShortInt =
(031,028,031,030,031,030,031,031,030,031,030,031);
 DaysPerYear : Array[1..12] of Integer =
(031,059,090,120,151,181,212,243,273,304,334,365);
 DaysPerLeapYear :Array[1..12] of Integer =
(031,060,091,121,152,182,213,244,274,305,335,366);
 SecsPerYear : LongInt = 31536000;
 SecsPerLeapYear : LongInt = 31622400;
 SecsPerDay  : LongInt = 86400;
 SecsPerHour : Integer = 3600;
 SecsPerMinute: ShortInt = 60;

Procedure GregorianToJulianDN;
var
 Century,
 XYear: LongInt;
begin {GregorianToJulianDN}
 If Month <= 2 then begin
Year := pred(Year);
Month := Month + 12;
end;
 Month := Month - 3;
 Century := Year div 100;
 XYear := Year mod 100;
 Century := (Century * D1) shr 2;
 XYear := (XYear * D0) shr 2;
 JulianDN := ((((Month * 153) + 2) div 5) + Day) + D2 + XYear + Century;
 end; {GregorianToJulianDN}
{**************************************************************}
Procedure JulianDNToGregorian;
var
 Temp,
 XYear  : LongInt;
 YYear,
 YMonth,
 YDay: Integer;
begin {JulianDNToGregorian}
 Temp := (((JulianDN - D2) shl 2) - 1);
 XYear := (Temp mod D1) or 3;
 JulianDN := Temp div D1;
 YYear := (XYear div D0);
 Temp := ((((XYear mod D0) + 4) shr 2) * 5) - 3;
 YMonth := Temp div 153;
 If YMonth >= 10 then begin
YYear := YYear + 1;
YMonth := YMonth - 12;
end;
 YMonth := YMonth + 3;
 YDay := Temp mod 153;
 YDay := (YDay + 5) div 5;
 Year := YYear + (JulianDN * 100);
 Month := YMonth;
 Day := YDay;
 end; {JulianDNToGregorian}
{**************************************************************}
Procedure GregorianToJulianDate;
var
 Jan1,
 Today : LongInt;
begin {GregorianToJulianDate}
 GregorianToJulianDN(Year, 1, 1, Jan1);
 GregorianToJulianDN(Year, Month, Day, Today);
 JulianDate := (Today - Jan1 + 1);
 end; {GregorianToJulianDate}
{**************************************************************}
Procedure JulianToGregorianDate;
var
 Jan1 : LongInt;
begin
 GregorianToJulianDN(Year, 1, 1, Jan1);
 JulianDNToGregorian((Jan1 + JulianDate - 1), Year, Month, Day);
 end; {JulianToGregorianDate}
{**************************************************************}
Procedure WkDay;
var
 DayNum : LongInt;
begin
 GregorianToJulianDN(Year, Month, Day, DayNum);
 DayNum := ((DayNum + 1) mod 7);
 WeekDay := (DayNum) + 1;
 end; {DayOfWeek}
{**************************************************************}
Procedure Yesterday(Var Y,M,D:integer);
var jdn:longint;
begin
GregorianToJulianDN(Y,M,D,JDN);
JDN:=JDN-1;
JulianDNToGregorian(JDN,Y,M,D);
end;
{**************************************************************}
Procedure Tomorrow(Var Y,M,D:integer);
var JDN:longint;
begin
GregorianToJulianDN(Y,M,D,JDN);
JDN:=JDN+1;
JulianDNToGregorian(JDN,Y,M,D);
end;
{**************************************************************}
Function TimeString:string;
var hr,mn,sec,hun:word;
s,q:string;
begin
 q:='';
 gettime(hr,mn,sec,hun);
 if hr<10 then q:=q+'0';
 str(hr:1,s);
 q:=q+s+':';
 if mn<10 then q:=q+'0';
 str(mn:1,s);
 q:=q+s;
 TimeString:=q;
end;
{**************************************************************}
Function ThisHour:Word;
var hr,mn,sec,hun:word;
begin
 gettime(hr,mn,sec,hun);
 ThisHour:=hr;
end;
{**************************************************************}
Function ThisMinute:Word;
var hr,mn,sec,hun:word;
begin
 gettime(hr,mn,sec,hun);
 ThisMinute:=mn;
end;
{**************************************************************}
Function DateString:string;
var yr,mo,dy,dow:word;
s,q:string;
begin
 q:='';
 getdate(yr,mo,dy,dow);
 if mo<10 then q:=q+'0';
 str(mo:1,s);
 q:=q+s+'-';
 if dy<10 then q:=q+'0';
 str(dy:1,s);
 q:=q+s+'-';
 while yr>100 do yr:=yr-100;
 if yr<10 then q:=q+'0';
 str(yr:1,s);
 q:=q+s;
 Datestring:=q;
end;
{**************************************************************}
Function parsefile(s:string):string; { Return date string from a file name }
var mo,errcode:word; { in either YYMMDD.EXT or MMDDYY.EXT }
st:string;{ format. }
begin
st:=copy(s,1,2)+'-'+copy(s,3,2)+'-'+copy(s,5,2);
parsefile:=st;
end;
{**************************************************************}
function dateafile(dy,dm,dd:word):string;
var s1,s2:string;
begin
while dy>100 do dy:=dy-100;
str(dy,s1);
while length(s1)<2 do s1:='0'+s1;
s2:=s1;
str(dm,s1);
while length(s1)<2 do s1:='0'+s1;
s2:=s2+s1;
str(dd,s1);
while length(s1)<2 do s1:='0'+s1;
s2:=s2+s1;
dateafile:=s2;
end;
{**************************************************************}
Function DayOfMonth:Word;
var yr,mo,dy,dow:word;
begin
 getdate(yr,mo,dy,dow);
 DayOfMonth:=dy;
end;
{**************************************************************}
Function ThisYear:Word;
var yr,mo,dy,dow:word;
begin
 getdate(yr,mo,dy,dow);
 ThisYear:=yr;
end;

{**************************************************************}
Function DayOfWeek:word;
var yr,mo,dy,dow:word;
begin
 getdate(yr,mo,dy,dow);(* Turbo Pascal authors never saw a *)
 dow:=dow+1;  (* calendar. Their first day of*)
 if dow=8 then dow:=1; (* week is Monday....  *)
 DayOfWeek:=dow;
end;
{**************************************************************}
Function MonthOfYear:Word;
var yr,mo,dy,dow:word;
begin
 getdate(yr,mo,dy,dow);
 monthofyear:=mo;
end;
{**************************************************************}
Function GetTimeZone : ShortInt;
Var
 Environment : String;
 Index : Integer;
Begin
 GetTimeZone := 0;{Assume UTC}
 Environment := GetEnv('TZ');  {Grab TZ string}
 For Index := 1 To Length(Environment) Do
Environment[Index] := Upcase(Environment[Index]);
 If Environment = 'EST05'Then GetTimeZone := -05; {USA EASTERN}
 If Environment = 'EST05EDT' Then GetTimeZone := -06;
 If Environment = 'CST06'Then GetTimeZone := -06; {USA CENTRAL}
 If Environment = 'CST06CDT' Then GetTimeZone := -07;
 If Environment = 'MST07'Then GetTimeZone := -07; {USA MOUNTAIN}
 If Environment = 'MST07MDT' Then GetTimeZone := -08;
 If Environment = 'PST08'Then GetTimeZone := -08;
 If Environment = 'PST08PDT' Then GetTimeZone := -09;
 If Environment = 'YST09'Then GetTimeZone := -09;
 If Environment = 'AST10'Then GetTimeZone := -10;
 If Environment = 'BST11'Then GetTimeZone := -11;
 If Environment = 'CET-1'Then GetTimeZone := 01;
 If Environment = 'CET-01'  Then GetTimeZone := 01;
 If Environment = 'EST-10'  Then GetTimeZone := 10;
 If Environment = 'WST-8'Then GetTimeZone := 08; {Perth,W.Austrailia}
 If Environment = 'WST-08'  Then GetTimeZone := 08;
End;
{**************************************************************}
Function IsLeapYear(Source : Word) : Boolean;
Begin
 If (Source Mod 4 = 0) Then
IsLeapYear := True
 Else
IsLeapYear := False;
End;
{**************************************************************}
Function Norm2Unix(Y,M,D,H,Min,S : Word) : LongInt;
Var
 UnixDate : LongInt;
 Index: Word;
Begin
 UnixDate := 0; {initialize}
 Inc(UnixDate,S);  {add seconds}
 Inc(UnixDate,(SecsPerMinute * Min));  {add minutes}
 Inc(UnixDate,(SecsPerHour * H)); {add hours}
 UnixDate := UnixDate - (GetTimeZone * SecsPerHour); {UTC offset}
 If D > 1 Then {has one day already passed?}
Inc(UnixDate,(SecsPerDay * (D-1)));
 If IsLeapYear(Y) Then
DaysPerMonth[02] := 29
 Else
DaysPerMonth[02] := 28; {Check for Feb. 29th}
 Index := 1;
 If M > 1 Then For Index := 1 To (M-1) Do {has one month already passed?}
Inc(UnixDate,(DaysPerMonth[Index] * SecsPerDay));
 While Y > 1970 Do
 Begin
If IsLeapYear((Y-1)) Then
 Inc(UnixDate,SecsPerLeapYear)
Else
 Inc(UnixDate,SecsPerYear);
Dec(Y,1);
 End;
 Norm2Unix := UnixDate;
End; Procedure Unix2Norm(Date : LongInt; Var Y, M, D, H, Min, S : Word);
{}
Var
 LocalDate : LongInt; Done : Boolean; X : ShortInt; TotDays : Integer;
Begin
 Y  := 1970; M := 1; D := 1; H := 0; Min := 0; S := 0;
 LocalDate := Date + (GetTimeZone * SecsPerHour); {Local time date}
 Done := False;
 While Not Done Do
 Begin
If LocalDate >= SecsPerYear Then
Begin
 Inc(Y,1);
 Dec(LocalDate,SecsPerYear);
End
Else
 Done := True;
If (IsLeapYear(Y+1)) And (LocalDate >= SecsPerLeapYear) And
  (Not Done) Then
Begin
 Inc(Y,1);
 Dec(LocalDate,SecsPerLeapYear);
End;
 End;
 M := 1; D := 1;
 Done := False;
 TotDays := LocalDate Div SecsPerDay;
 If IsLeapYear(Y) Then
 Begin
DaysPerMonth[02] := 29;
X := 1;
Repeat
 If (TotDays <= DaysPerLeapYear[x]) Then
 Begin
M := X;
Done := True;
Dec(LocalDate,(TotDays * SecsPerDay));
D := DaysPerMonth[M]-(DaysPerLeapYear[M]-TotDays) + 1;
 End
 Else
Done := False;
 Inc(X);
Until (Done) or (X > 12);
 End
 Else
 Begin
DaysPerMonth[02] := 28;
X := 1;
Repeat
 If (TotDays <= DaysPerYear[x]) Then
 Begin
M := X;
Done := True;
Dec(LocalDate,(TotDays * SecsPerDay));
D := DaysPerMonth[M]-(DaysPerYear[M]-TotDays) + 1;
 End
 Else
Done := False;
 Inc(X);
Until Done = True or (X > 12);
 End;
 H := LocalDate Div SecsPerHour;
Dec(LocalDate,(H * SecsPerHour));
 Min := LocalDate Div SecsPerMinute;
Dec(LocalDate,(Min * SecsPerMinute));
 S := LocalDate;
End;
{**************************************************************}
Function DayOfYear;
var
 HCentury,Century,Xyear,
 Ripoff,HXYear: LongInt;
 Holdyear,Holdmonth,Holdday:Integer;
 year,month,day,dofwk:word;
begin {DayofYear}
 getdate(year,month,day,dofwk);
 Holdyear:=year-1;
 Holdmonth:=9;
 Holdday:=31;
 HCentury := HoldYear div 100;
 HXYear := HoldYear mod 100;
 HCentury := (HCentury * D1) shr 2;
 HXYear := (HXYear * D0) shr 2;
 Ripoff := ((((HoldMonth * 153) + 2) div 5) + HoldDay) + D2 + HXYear +
HCentury;
 If Month <= 2 then begin
Year := pred(Year);
Month := Month + 12;
end;
 Month := Month - 3;
 Century := Year div 100;
 XYear := Year mod 100;
 Century := (Century * D1) shr 2;
 XYear := (XYear * D0) shr 2;
 DayofYear := (((((Month * 153) + 2) div 5) + Day) + D2 + XYear + Century)-
ripoff;
 end; {DayOfYear}
Procedure Easter(Year : Word; Var Date : DateType);
  (* Calculates what day Easter falls on in a given year *)
  (* Set desired Year and result is returned in Date variable*)
Var
  GoldenNo,
  Sun,
  Century,
  LeapCent,
  LunarCorr,
  Epact,
  FullMoon : Integer;
Begin
  Date.Year := Year;
  GoldenNo := (Year Mod 19) + 1;
  Century := (Year Div 100) + 1;
  LeapCent := (3 * Century Div 4) - 12;
  LunarCorr := ((8 * Century + 5) Div 25) - 5;
  Sun := (5 * Year Div 4) - LeapCent - 10;
  Epact := Abs(11 * GoldenNo + 20 + LunarCorr - LeapCent) Mod 30;
  If ((Epact = 25) And (GoldenNo > 11)) Or (Epact = 24) then
 Inc(Epact);
  FullMoon := 44 - Epact;
  If FullMoon < 21 then
 Inc(FullMoon, 30);
  Date.Day := FullMoon + 7 - ((Sun + FullMoon) Mod 7);
  If Date.Day > 31 then
 Begin
 Dec(Date.Day, 31);
 Date.Month := 4;
 End
  Else
 Date.Month := 3;
  Date.DOW := 0;
End;
{**************************************************************}
Procedure Thanksgiving(Year : Word; Var Date : DateType);
  (* Calculates what day Thanksgiving falls on in a given year  *)
  (* Set desired Year and result is returned in Date variable*)
Var
 Counter,WeekDay:Word;
 Daynum:longint;
Begin
  Date.Year := Year;
  Date.Month := 11;
  counter:=29;
  repeat
 dec(counter);
 GregorianToJulianDN(Date.Year, Date.Month, Counter, DayNum);
 DayNum := ((DayNum + 1) mod 7);
 WeekDay := (DayNum) + 1;
  Until Weekday = 5;
  Date.Day:=Counter;
End;
{*************************************************************}
Function MoonPhase(Date:Datetype):Real;
 (* Determines APPROXIMATE phase of the moon (percentage lit)  *)
 (* 0.00 = New moon, 1.00 = Full moon  *)
 (* Due to rounding, full values may possibly never be reached *)
 (* Valid from Oct. 15, 1582 to Feb. 28, 4000  *)
 (* Calculations adapted to Turbo Pascal from routines found in *)
 (* "119 Practical Programs For The TRS-80 Pocket Computer" *)
 (* John Clark Craig, TAB Books, 1982 (Ag) *)
VAR j:longint; m:real;
Begin
 GregorianToJulianDN(Date.Year,Date.Month,Date.Day,J);
 M:=(J+4.867)/ 29.53058;
 M:=2*(M-Int(m))-1;
 MoonPhase:=Abs(M);
end;

END.