Calendar Conversions

Has anyone tried to convert a Western-style calendar to either the Islamic or Hebrew calendar system? I've spent a bunch of time looking for a Pascal unit to do just that so I could include Muslim and Jewish holidays in my holiday class but haven't found any great resources. I finally found some C/C++ code that did the work and converted it over to Pascal. Keeping in mind that this is still a new conversion and that I didn't write the actual conversion logic, I'm publishing the code to help other would-be calendar converters. All of the conversion algorithms (and credit for them) are from Calendrical Calculations by Nachum Dershowitz and Edward Reingold.

I added the Day and Month name strings from various Google searches on things like Hebrew Month Names and Islamic Month Names, etc. I've compared the resulting output to both published calendars and to other conversion tools that do not provide their source. So far, I haven't seen any discrepancies other than some naming conventions. Apparently, Hebrew and Arab names are "open to interpretation" when you write them in Western languages. Feel free to change them to your particular dialect. Actually, I'd like to know what the differences are/could be/should be if someone wants to enlighten me. I don't speak either language and am relying on other translations to make this work.

Enjoy the code.



unit calconv;

interface
// The following Pascal code is translated from the Lisp code in
// ``Calendrical Calculations'' by Nachum Dershowitz and Edward M. Reingold,
// Software---Practice & Experience, vol. 20, no. 9 (September, 1990),
// pp. 899--928.

// This code is in the public domain, but any use of it
// should acknowledge its source.

// Classes GregorianDate, JulianDate, IslamicDate, and HebrewDate

// Translated from C++ / Lisp sources as published by Edward Reingold
// at http://emr.cs.iit.edu/~reingold/calendars/shtml

// Pascal translation by Marshall Fryman, 2008

uses sysutils;

type

TBaseDate = class
private
fYear:integer;
fMonth:integer;
fDay:integer;
public
class function cf_CalDateAsAbsDate(m,d,y:integer):integer; virtual; abstract;
class function DayName(d:integer):string; virtual; abstract;
class function LastDayOfMonth(m,y:integer):integer; virtual; abstract;
function CalDateAsAbsoluteDate:integer;
procedure AbsoluteDateToCalDate(absdate:integer); virtual; abstract;
property Year:integer read fYear;
property Month:integer read fMonth;
property Day:integer read fDay;
end;

TGregorianDate = class(TBaseDate)
// inherited from base
// fYear : integer; // 1...
// fMonth : integer; // 1 == January, ..., 12 == December
// fDay : integer; // 1..LastDayOfMonth(fMonth, fYear)
public
constructor Create(m,d,y:integer); overload; { fMonth = m; fDay = d; fYear = y; }
constructor Create(DT:TDateTime); overload;
constructor Create(absdate:integer); overload;
function DateAsText:string;
procedure AbsoluteDateToCalDate(absdate:integer); override;
class function cf_CalDateAsAbsDate(m,d,y:integer):integer; override;
class function DayName(d:integer):string; override;
class function MonthName(m:integer):string;
class function LastDayOfMonth(m,y:integer):integer; override;
end;

TJulianDate = class(TBaseDate)
// inherited from base
// int fYear; // 1...
// int fMonth; // 1 == January, ..., 12 == December
// int fDay; // 1..LastDayOfMonth(fMonth, fYear)
public
constructor Create(m,d,y:integer); overload; { fMonth = m; fDay = d; fYear = y; }
constructor Create(DT:TDateTime); overload;
constructor Create(absdate:integer); overload;
procedure AbsoluteDateToCalDate(absdate:integer); override;
class function cf_CalDateAsAbsDate(m,d,y:integer):integer; override;
class function DayName(d:integer):string; override;
class function MonthName(m:integer):string;
class function LastDayOfMonth(m,y:integer):integer; override;
end;

TIslamicDate = class(TBaseDate)
// inherited from base
// int fYear; // 1...
// int fMonth; // 1..12 (12 in a common Year)
// int fDay; // 1..LastDayOfMonth(fMonth,fYear)
public
constructor Create(m,d,y:integer); overload; { fMonth=m; fDay=d; fYear =y; }
constructor Create(DT:TDateTime); overload;
constructor Create(absdate:integer); overload;
function DateAsText:string;
procedure AbsoluteDateToCalDate(absdate:integer); override;
class function cf_CalDateAsAbsDate(m,d,y:integer):integer; override;
class function DayName(d:integer):string; override;
class function MonthName(m:integer):string;
class function LastDayOfMonth(m,y:integer):integer; override;
class function IsLeapYear(y:integer):boolean;
end;

THebrewDate = class(TBaseDate)
// inherited from base
// int fYear; // 1...
// int fMonth; // 1..LastMonthOfYear(fYear)
// int fDay; // 1..LastDayOfMonth(fMonth, fYear)
constructor Create(m,d,y:integer); overload; { fMonth=m; fDay=d; fYear =y; }
constructor Create(DT:TDateTime); overload;
constructor Create(absdate:integer); overload;
function DateAsText:string;
procedure AbsoluteDateToCalDate(absdate:integer); override;
class function cf_CalDateAsAbsDate(m,d,y:integer):integer; override;
class function DayName(d:integer):string; override;
class function MonthName(m,y:integer):string;
class function LastDayOfMonth(m,y:integer):integer; override;
class function IsLeapYear(y:integer):boolean;
class function LastMonthOfYear(y:integer):integer;
class function ShortKislev(y:integer):boolean;
class function LongHeshvan(y:integer):boolean;
class function DaysInYear(y:integer):integer;
class function HebrewCalendarElapsedDays(y:integer):integer;
end;

implementation

// Absolute dates

// "Absolute date" means the number of days elapsed since the Gregorian date
// Sunday, December 31, 1 BC. (Since there was no fYear 0, the fYear following
// 1 BC is 1 AD.) Thus the Gregorian date January 1, 1 AD is absolute date
// number 1.

const
JulianEpoch = -2; // Absolute date of start of Julian calendar
IslamicEpoch = 227014; // Absolute date of start of Islamic calendar
HebrewEpoch = -1373429; // Absolute date of start of Hebrew calendar



// Absolute date of the x-fDay on or before absolute date d.
// x=0 means Sunday, x=1 means Monday, and so on.
function XdayOnOrBefore(d,x:integer):integer;
begin
result:=(d - ((d - x) mod 7));
end;



{ TGregorianDate }

constructor TGregorianDate.Create(m,d,y:integer);
begin
fMonth :=m;
fDay :=d;
fYear :=y;
end;

// Computes the Gregorian date from the absolute date
constructor TGregorianDate.Create(absdate:integer);
begin
AbsoluteDateToCalDate(absdate);
end;


// Computes the absolute date from the Gregorian date.
constructor TGregorianDate.Create(DT: TDateTime);
var y,m,d:word;
begin
DecodeDate(DT,y,m,d);
fYear:=y;
fMonth:=m;
fDay:=d;
end;

procedure TGregorianDate.AbsoluteDateToCalDate(absdate: integer);
begin
// Search forward fYear by fYear from approximate fYear
fYear := absdate div 366;
while (absdate >= TGregorianDate.cf_CalDateAsAbsDate(1,1,fYear+1)) do
inc(fYear);
// Search forward fMonth by fMonth from January
fMonth := 1;
while (absdate > TGregorianDate.cf_CalDateAsAbsDate(fMonth, LastDayOfMonth(fMonth,fYear), fYear)) do
inc(fMonth);
fDay := absdate - TGregorianDate.cf_CalDateAsAbsDate(fMonth,1,fYear) + 1;
end;

class function TGregorianDate.cf_CalDateAsAbsDate(m,d,y:integer):integer;
var iD,iM:integer;
begin
iD := d; // days this fMonth
for iM := m - 1 downto 1 do // days in prior months this fYear
iD := iD + LastDayOfMonth(iM, y);
result:=(iD // days this fYear
+ 365 * (y - 1) // days in previous years ignoring leap days
+ (y - 1) div 4 // Julian leap days before this fYear...
- (y - 1) div 100 // ...minus prior century years...
+ (y - 1) div 400); // ...plus prior years divisible by 400
end;

function TGregorianDate.DateAsText: string;
begin
result := DayName(CalDateAsAbsoluteDate mod 7)+', '+MonthName(Month)+' '+IntToStr(Day)+', '+IntToStr(Year);
end;

class function TGregorianDate.DayName(d: integer): string;
begin
if d>6 then d:=d mod 7;
case d of
0 : result:='Sunday';
1 : result:='Monday';
2 : result:='Tuesday';
3 : result:='Wednesday';
4 : result:='Thursday';
5 : result:='Friday';
6 : result:='Saturday';
else
result:='Invalid Day';
end;
end;

// Compute the last date of the fMonth for the Gregorian calendar.
class function TGregorianDate.LastDayOfMonth(m, y: integer): integer;
begin
result:=31;
case m of
2: if ((((y mod 4) = 0) and ((y mod 100) <>0)) OR ((y mod 400) = 0)) then
result:= 29
else
result:= 28;
4,
6,
9,
11: result := 30;
end;
end;

class function TGregorianDate.MonthName(m: integer): string;
begin
case m of
1 : result := 'January';
2 : result := 'February';
3 : result := 'March';
4 : result := 'April';
5 : result := 'May';
6 : result := 'June';
7 : result := 'July';
8 : result := 'August';
9 : result := 'September';
10 : result := 'October';
11 : result := 'November';
12 : result := 'December';
else
result:='Invalid Month';
end;
end;

{ TJulianDate }

constructor TJulianDate.Create(m, d, y: integer);
begin
fmonth := m;
fday := d;
fyear := y;
end;

// Computes the Julian date from the absolute date.
constructor TJulianDate.Create(absdate: integer);
begin
AbsoluteDateToCalDate(absdate);
end;

// Computes the absolute date from the Julian date.
constructor TJulianDate.Create(DT: TDateTime);
var gd:TGregorianDate;
begin
gd:=TGregorianDate.Create(DT);
AbsoluteDateToCalDate(gd.CalDateAsAbsoluteDate);
gd.Free;
end;

procedure TJulianDate.AbsoluteDateToCalDate(absdate: integer);
begin
// Search forward fYear by fYear from approximate fYear
fYear := (absdate + JulianEpoch) div 366;
while (absdate >= TJulianDate.cf_CalDateAsAbsDate(1,1,fYear+1)) do
inc(fYear);
// Search forward fMonth by fMonth from January
fMonth := 1;
while (absdate > TJulianDate.cf_CalDateAsAbsDate(fMonth, LastDayOfMonth(fMonth,fYear), fYear)) do
inc(fMonth);
fDay := absdate - TJulianDate.cf_CalDateAsAbsDate(fMonth,1,fYear) + 1;
end;

class function TJulianDate.cf_CalDateAsAbsDate(m, d, y: integer): integer;
var iD, iM:integer;
begin
iD:=d; // days this Month
for iM:=m-1 downto 1 do // days in prior months this Year
iD := iD + LastDayOfMonth(m, y);
result := (iD // days this fYear
+ 365 * (y - 1) // days in previous years ignoring leap days
+ (y - 1) div 4 // leap days before this Year...
+ JulianEpoch); // days elapsed before absolute date 1

end;

class function TJulianDate.DayName(d: integer): string;
begin
if d>6 then d:=d mod 7;

case d of
0 : result:='Sunday';
1 : result:='Monday';
2 : result:='Tuesday';
3 : result:='Wednesday';
4 : result:='Thursday';
5 : result:='Friday';
6 : result:='Saturday';
else
result:='Invalid Day';
end;
end;

// Compute the last date of the fMonth for the Julian calendar.
class function TJulianDate.LastDayOfMonth(m, y: integer): integer;
begin
case m of
2: if ((y mod 4) = 0) then
result := 29
else
result := 28;
4,
6,
9,
11: result := 30;
else result := 31;
end; {of case}
end;

class function TJulianDate.MonthName(m: integer): string;
begin
case m of
1 : result := 'January';
2 : result := 'February';
3 : result := 'March';
4 : result := 'April';
5 : result := 'May';
6 : result := 'June';
7 : result := 'July';
8 : result := 'August';
9 : result := 'September';
10 : result := 'October';
11 : result := 'November';
12 : result := 'December';
else
result:='Invalid Month';
end;
end;

{ TIslamicDate }

constructor TIslamicDate.Create(m, d, y: integer);
begin
fmonth := m;
fday := d;
fyear := y;
end;

// Computes the Islamic date from the absolute date.
constructor TIslamicDate.Create(absdate: integer);
begin
AbsoluteDateToCalDate(absdate);
end;

procedure TIslamicDate.AbsoluteDateToCalDate(absdate: integer);
begin
if (absdate <= IslamicEpoch) then // Date is pre-Islamic
begin
fMonth := 0;
fDay := 0;
fYear := 0;
end
else
begin
// Search forward fYear by fYear from approximate fYear
fYear := (absdate - IslamicEpoch) div 355;
while (absdate >= TIslamicDate.cf_CalDateAsAbsDate(1,1,fYear+1)) do
inc(fYear);
// Search forward fMonth by fMonth from Muharram
fMonth := 1;
while (absdate > TIslamicDate.cf_CalDateAsAbsDate(fMonth, LastDayOfMonth(fMonth,fYear), fYear)) do
inc(fMonth);
fDay := absdate - TIslamicDate.cf_CalDateAsAbsDate(fMonth,1,fYear) + 1;
end;
end;

class function TIslamicDate.cf_CalDateAsAbsDate(m, d, y: integer): integer;
begin
result := (d // days so far this fMonth
+ 29 * (m - 1) // days so far...
+ m div 2 // ...this fYear
+ 354 * (y - 1) // non-leap days in prior years
+ (3 + (11 * y)) div 30 // leap days in prior years
+ IslamicEpoch); // days before start of calendar

end;

constructor TIslamicDate.Create(DT: TDateTime);
var gd:TGregorianDate;
begin
gd:=TGregorianDate.Create(DT);
AbsoluteDateToCalDate(gd.CalDateAsAbsoluteDate);
gd.Free;
end;

function TIslamicDate.DateAsText: string;
begin
result := DayName(Day)+', '+MonthName(Month)+' '+IntToStr(Day)+', '+IntToStr(Year);
end;

class function TIslamicDate.DayName(d: integer): string;
begin
if d>6 then d:=d mod 7;
case d of
0 : result := 'As-Sabt';
1 : result := 'Al-Ahad';
2 : result := 'Al-`ithnayn';
3 : result := 'Ath-Thulatha';
4 : result := 'Al-`Arba`aa';
5 : result := 'Al-Khamees';
6 : result := 'Al-Jum`ah';
else result:='Invalid Day';
end;
end;

// True if fYear is an Islamic leap fYear
class function TIslamicDate.IsLeapYear(y: integer): boolean;
begin
result:=((((11 * y) + 14) mod 30) < 11);
end;

// Last Day in fMonth during fYear on the Islamic calendar.
class function TIslamicDate.LastDayOfMonth(m, y: integer): integer;
begin
if (((m mod 2) = 1) OR ((m = 12) and IsLeapYear(y))) then
result:=30
else
result:=29;
end;

class function TIslamicDate.MonthName(m: integer): string;
begin
case m of
1: result := 'Muharram';
2: result := 'Safar';
3: result := 'Rabi`al-Awwal';
4: result := 'Rabi`ath-Thani';
5: result := 'Jumada l-Ula';
6: result := 'Jumada t=Tania';
7: result := 'Rajab';
8: result := 'Sha`ban';
9: result := 'Ramadan';
10: result := 'Shawwal';
11: result := 'Dhul `l-Qa`da';
12: result := 'Dhul `l-Hijja';
else result:='Invalid Month';
end;
end;

{ THebrewDate }

constructor THebrewDate.Create(m, d, y: integer);
begin
fmonth := m;
fday := d;
fyear := y;
end;

// Computes the Hebrew date from the absolute date.
constructor THebrewDate.Create(absdate: integer);
begin
AbsoluteDateToCalDate(absdate);
end;

procedure THebrewDate.AbsoluteDateToCalDate(absdate: integer);
begin
fYear := (absdate + HebrewEpoch) div 366; // Approximation from below.
// Search forward for fYear from the approximation.
while (absdate >= THebrewDate.cf_CalDateAsAbsDate(7,1,fYear + 1)) do
inc(fYear);
// Search forward for month from either Tishri or Nisan.
if (absdate < THebrewDate.cf_CalDateAsAbsDate(1, 1, fYear)) then
fMonth := 7 // Start at Tishri
else
fMonth := 1; // Start at Nisan
while (absdate > THebrewDate.cf_CalDateAsAbsDate(fMonth, (LastDayOfMonth(fMonth,fYear)), fYear)) do
inc(fMonth);
// Calculate the fDay by subtraction.
fDay := absdate - THebrewDate.cf_CalDateAsAbsDate(fMonth, 1, fYear) + 1;
end;

class function THebrewDate.cf_CalDateAsAbsDate(m, d, y: integer): integer;
var DayInYear:integer;
iM:integer;
begin
DayInYear := d; // Days so far this Month.
if (m < 7) then // Before Tishri, so add days in prior months
begin // this Year before and after Nisan.
iM := 7;
while (iM <= (LastMonthOfYear(y))) do
begin
DayInYear := DayInYear + LastDayOfMonth(iM, y);
inc(iM);
end;
iM := 1;
while (iM < m) do
begin
DayInYear := DayInYear + LastDayOfMonth(iM, y);
inc(iM);
end;
end
else // Add days in prior months this Year
begin
iM := 7;
while (iM < m) do
begin
DayInYear := DayInYear + LastDayOfMonth(iM, y);
inc(iM);
end;
end;

result := (DayInYear +
(HebrewCalendarElapsedDays(y)// Days in prior years.
+ HebrewEpoch)); // Days elapsed before absolute date 1.
end;

constructor THebrewDate.Create(DT: TDateTime);
var gd:TGregorianDate;
begin
gd:=TGregorianDate.Create(DT);
AbsoluteDateToCalDate(gd.CalDateAsAbsoluteDate);
gd.Free;
end;

function THebrewDate.DateAsText: string;
begin
result := DayName(Day)+', '+MonthName(Month,Year)+' '+IntToStr(Day)+', '+IntToStr(Year);
end;

class function THebrewDate.DayName(d: integer): string;
begin
if d>6 then d:=d mod 7;
case d of
0 : result := 'Yom Ree-Shoun';
1 : result := 'Yom She-Nee';
2 : result := 'Yom Shelee-She';
3 : result := 'Yom Re-Ve-ee';
4 : result := 'Yom Hah-Mee-Shee';
5 : result := 'Yom Shee-Shee';
6 : result := 'Yom Sha-Bat';
else result := 'Invalid Day';
end;
end;

// Number of days in Hebrew Year.
class function THebrewDate.DaysInYear(y: integer): integer;
begin
result := ((HebrewCalendarElapsedDays(y + 1)) -
(HebrewCalendarElapsedDays(y)));
end;

// Number of days elapsed from the Sunday prior to the start of the
// Hebrew calendar to the mean conjunction of Tishri of Hebrew fYear.
class function THebrewDate.HebrewCalendarElapsedDays(y: integer): integer;
var MonthsElapsed,
PartsElapsed,
HoursElapsed,
ConjunctionDay,
ConjunctionParts,
AlternativeDay : integer;
begin
MonthsElapsed :=
(235 * ((y - 1) div 19)) // Months in complete cycles so far.
+ (12 * ((y - 1) mod 19)) // Regular months in this cycle.
+ (7 * ((y - 1) mod 19) + 1) div 19; // Leap months this cycle
PartsElapsed := 204 + (793 * (MonthsElapsed mod 1080));
HoursElapsed :=
5 + (12 * MonthsElapsed) + (793 * (MonthsElapsed div 1080))
+ (PartsElapsed div 1080);
ConjunctionDay := 1 + (29 * MonthsElapsed) + (HoursElapsed div 24);
ConjunctionParts := (1080 * (HoursElapsed mod 24)) + (PartsElapsed mod 1080);
if ((ConjunctionParts >= 19440) // If new moon is at or after midday,
OR (((ConjunctionDay mod 7) = 2) // ...or is on a Tuesday...
and (ConjunctionParts >= 9924) // at 9 hours, 204 parts or later...
and not (IsLeapYear(y))) // ...of a common year,
OR (((ConjunctionDay mod 7) = 1) // ...or is on a Monday at...
and (ConjunctionParts >= 16789) // 15 hours, 589 parts or later...
and (IsLeapYear(y - 1)))) then // at the end of a leap year
// Then postpone Rosh HaShanah one Day
AlternativeDay := ConjunctionDay + 1
else
AlternativeDay := ConjunctionDay;
if (((AlternativeDay mod 7) = 0) // If Rosh HaShanah would occur on Sunday,
OR ((AlternativeDay mod 7) = 3) // or Wednesday,
OR ((AlternativeDay mod 7) = 5)) then // or Friday
// Then postpone it one (more) Day
result := (1 + AlternativeDay)
else
result := AlternativeDay;
end;

// True if y is an Hebrew leap year
class function THebrewDate.IsLeapYear(y: integer): boolean;
begin
result := ((((7 * y) + 1) mod 19) < 7);
end;

// Last fDay of Month in Hebrew Year.
class function THebrewDate.LastDayOfMonth(m, y: integer): integer;
begin
if ((m = 2)
OR (m = 4)
OR (m = 6)
OR ((m = 8) and not (LongHeshvan(y)))
OR ((m = 9) and ShortKislev(y))
OR (m = 10)
OR ((m = 12) and not (IsLeapYear(y)))
OR (m = 13)) then
result := 29
else
result:= 30;

end;

// Last Month of Hebrew Year y.
class function THebrewDate.LastMonthOfYear(y: integer): integer;
begin
if (IsLeapYear(y)) then
result := 13
else
result := 12;
end;

// True if Heshvan is long in Hebrew fYear.
class function THebrewDate.LongHeshvan(y: integer): boolean;
begin
result := ((DaysInYear(y) mod 10) = 5);
end;

class function THebrewDate.MonthName(m,y: integer): string;
begin
result:='Invalid Month';
if not IsLeapYear(y) then
case m of
1 : result := 'Nissan';
2 : result := 'Iyar';
3 : result := 'Sivan';
4 : result := 'Tammuz';
5 : result := 'Av';
6 : result := 'Elul';
7 : result := 'Tishrei';
8 : result := 'Heshvan';
9 : result := 'Kislev';
10: result := 'Teves';
11: result := 'Shevat';
12: result := 'Adar';
end
else
case m of
1 : result := 'Nissan';
2 : result := 'Iyar';
3 : result := 'Sivan';
4 : result := 'Tammuz';
5 : result := 'Av';
6 : result := 'Elul';
7 : result := 'Tishrei';
8 : result := 'Heshvan';
9 : result := 'Kislev';
10: result := 'Teves';
11: result := 'Shevat';
12: result := 'Adar I';
13: result := 'Adar II';
end
end;

// True if Kislev is short in Hebrew fYear.
class function THebrewDate.ShortKislev(y: integer): boolean;
begin
result := ((DaysInYear(y) mod 10) = 3);
end;

{ TBaseDate }

function TBaseDate.CalDateAsAbsoluteDate: integer;
begin
result := self.cf_CalDateAsAbsDate(Month,Day,Year);
end;

end.