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.