// calend.c // // by John D. de Boer #include "common.h" #include "calend.h" static const long JDofReform = 2299161L; // Julian Day number of noon on 1582 Oct 15 (G) // int dayofweek(long JD) { return mod(JD + 1,7); } int leapyear(long YEAR, bool Greg) { if (YEAR<0) YEAR++; if (YEAR % 4) return 0; if (!Greg || YEAR % 100) return 1; return !(YEAR % 400); } int jdisgreg(long JD) { return JD>=JDofReform; } int dateisgreg(Date *D) { if (!D) return 0; if (D->year<1582) return 0; if (D->year>1582) return 1; if (D->month<9) return 0; if (D->month>9) return 1; return D->date>=14; } // Conversion between Julian Day numbers and the Julian and Gregorian calendars // 1 B.C. Jan 1 (J) is JD 1721058, exactly 4712 years after JD zero. // 1 B.C. Mar 1 (J) was 60 days later. static const long BC1Mar1Jul = 1721118L; // 1 B.C. Jan 1 (G) was two days after the same date in the Julian calendar, on JD 1721060. // 1 B.C. Mar 1 (G), the day after a 400-year exception, is a convenient date to work from. static const long BC1Mar1Greg = 1721120L; static const long Greg400 = 146097L; // The number of days in a 400-year Gregorian period static const long Greg100 = 36524L; // The number of days in a Gregorian century static const long FourYears = 1461L; // The number of days in 4 years, in either calendar // Month data, starting with March static int LengthOf[12] = { 31,30,31,30, 31, 31, 30, 31, 30, 31, 31, 29 }; static int DaysBefore[12] = { 0,31,61,92,122,153,184,214,245,275,306,337 }; void jdtojul(Date *D) { long JD,M,L; if (!D) return; JD= D->jd - BC1Mar1Jul; M= JD / FourYears; JD%=FourYears; if (JD<0) { JD+=FourYears; M--; } D->year= 4 * M; M= JD / 365; JD%=365; if (M==4) { JD=365; M=3; } D->year+=M; M=0; while (M<11) { L= JD - LengthOf[M]; if (L<0) break; JD=L; M++; } M+=2; if (M>=12) { M-=12; D->year++; } D->month=M; D->date=JD; if (D->year<1) D->year--; D->calend=JulianCalendar; D->good=1; } void jultojd(Date *D) { long M,Y; if (!D) return; Y=D->year; if (Y<0) Y++; M= D->month - 2; Y+= M / 12; M%=12; if (M<0) { M+=12; Y--; } D->jd= DaysBefore[M] + D->date; M= Y / 4; Y%=4; if (Y<0) { Y+=4; M--; } D->jd+= BC1Mar1Jul + (FourYears * M) + (365 * Y); D->calend=JulianCalendar; D->good=1; } void jdtogreg(Date *D) { long JD,M,L; if (!D) return; JD= D->jd - BC1Mar1Greg; M= JD / Greg400; JD%=Greg400; if (JD<0) { JD+=Greg400; M--; } D->year= 400 * M; M= JD / Greg100; JD%=Greg100; if (M==4) { JD=Greg100; M=3; } D->year+= M * 100; M= JD / FourYears; JD%=FourYears; D->year+= 4 * M; M= JD / 365; JD%=365; if (M==4) { JD=365; M=3; } D->year+=M; M=0; while (M<11) { L= JD - LengthOf[M]; if (L<0) break; JD=L; M++; } M+=2; if (M>=12) { M-=12; D->year++; } D->month=M; D->date=JD; if (D->year<1) D->year--; D->calend=GregorianCalendar; D->good=1; } void gregtojd(Date *D) { long JD,M,Y; if (!D) return; Y=D->year; if (Y<0) Y++; M= D->month - 2; Y+= M / 12; M%=12; if (M<0) { M+=12; Y--; } JD= DaysBefore[M] + D->date; M= (Y / 400); Y%=400; if (Y<0) { Y+=400; M--; } JD+= BC1Mar1Greg + (Greg400 * M); M= Y / 100; Y%=100; JD+= Greg100 * M; M= Y / 4; Y%=4; D->jd= JD + (FourYears * M) + (365 * Y); D->calend=GregorianCalendar; D->good=1; } // Date conversion long datetojd(int YEAR, int MONTH, int DATE) { Date D; D.year=YEAR; D.month=MONTH; D.date=DATE; if (dateisgreg(&D)) gregtojd(&D); else jultojd(&D); return D.jd; } void jdtodate(long JD, short *YEAR, short *MONTH, short *DATE) { Date D; D.jd=JD; if (jdisgreg(JD)) jdtogreg(&D); else jdtojul(&D); if (YEAR) *YEAR=D.year; if (MONTH) *MONTH=D.month; if (DATE) *DATE=D.date; } // the Hebrew calendar typedef struct { long weeks, // whole weeks after epoch days, // 0 = Sunday hours, // 0 = 6 p.m. parts; // 0 to 1079 } Molad; typedef struct { long start; // the first day of the year in days since the epoch short length, // the length of the whole year march, // the length of Marcheshvan kislev; // the length of Kislev bool leap; // whether the year is a leap year } Character; static const long HebEpoch = 347997L; // Sunday noon, the day before 1 A.M. Tishri 1 static bool hebleap(int YEAR) { return (((YEAR * 7) + 1) % 19) < 7; } static void addmonths(Molad *M, int MONTHS) { if (!M) return; M->weeks+= 4 * MONTHS; M->days += 1 * MONTHS; M->hours+= 12 * MONTHS; M->parts+= 793 * MONTHS; M->hours+= M->parts / 1080; M->parts%=1080; M->days += M->hours / 24; M->hours%= 24; M->weeks+= M->days / 7; M->days %= 7; } static void molad(int YEAR, Molad *M) { int MK,LM; if (!M) return; YEAR--; MK= YEAR / 19; YEAR%=19; // number of full Metonic cycles (machzor koton of 235 months) M->weeks=0; M->days=1; M->hours=5; M->parts=204; // BHRD M->weeks+= 991 * MK; M->days+= 2 * MK; M->hours+= 16 * MK; M->parts+= 595 * MK; LM=YEAR; if (LM>=8) LM++; LM/=3; // number of extra months preceding a given year offset addmonths(M,(YEAR * 12) + LM); } static void dechiyos1and2(const Molad *M, short *Rosh) { short RH; if (!M) return; RH=M->days; if (M->hours>18 || (M->hours==18 && M->parts>0)) RH++; if (RH==0 || RH==3 || RH==5 || RH==7) RH++; if (Rosh) *Rosh=RH; } static void dechiyos3and4(bool LeapBefore, const Molad *M1, bool Leap, const Molad *M2, short RH2, short *RH1) { if (!M1 || !M2 || !RH1) return; if (!Leap && M1->days==2 /*&& M2->days==6*/ && (RH2 % 7 == 1)) (*RH1)=4; if (LeapBefore && M1->days==1 && (M1->hours>15 || (M1->hours==15 && M1->parts>589))) (*RH1)=2; } static void determinecharacter(int YEAR, Character *C) { bool LeapBefore, // whether the previous year was a leap year LeapAfter; // whether the year following is a leap year Molad Mo1,Mo2,Mo3; // the moment of the molads at the start and end of the given year, plus the one after that short RH1,RH2,RH3; // number of days Rosh HaShanah is after the Sunday of the week of the molad; [0,8] long END; // start and end of the year, in days after the epoch if (!C) return; C->leap=hebleap(YEAR); LeapBefore= C->leap ? 0 : hebleap(YEAR - 1); LeapAfter= C->leap ? 0 : hebleap(YEAR + 1); molad(YEAR,&Mo1); Mo2=Mo1; addmonths(&Mo2,C->leap ? 13 : 12); dechiyos1and2(&Mo1,&RH1); dechiyos1and2(&Mo2,&RH2); dechiyos3and4(LeapBefore,&Mo1,C->leap,&Mo2,RH2,&RH1); Mo3=Mo2; addmonths(&Mo3,LeapAfter ? 13 : 12); dechiyos1and2(&Mo3,&RH3); dechiyos3and4(C->leap,&Mo2,LeapAfter,&Mo3,RH3,&RH2); C->start= (7 * Mo1.weeks) + RH1; END= (7 * Mo2.weeks) + RH2; C->length= END - C->start; C->march= ((C->length % 10) == 5) ? 30 : 29; C->kislev= ((C->length % 10) == 3) ? 29 : 30; } void hebtojd(Date *D) { Character C; if (!D) return; D->calend=HebrewCalendar; D->good=0; if (D->year<1 || !range(D->month,14)) return; determinecharacter(D->year,&C); D->jd= HebEpoch + C.start; switch (D->month) { case 6: D->jd+=D->date; D->good=range(D->date,30); return; case 7: D->jd+= 30 + D->date; D->good=range(D->date,C.march); return; case 8: D->jd+= 30 + C.march + D->date; D->good=range(D->date,C.kislev); return; } D->jd+= 30 + C.march + C.kislev; switch (D->month) { case 9: D->jd+=D->date; D->good=range(D->date,29); return; case 10: D->jd+= 29 + D->date; D->good=range(D->date,30); return; case 11: case 12: D->jd+= 59 + D->date; D->good=range(D->date,30); return; } D->jd+= 59 + (C.leap ? 30 : 0); switch (D->month) { case 13: D->jd+=D->date; D->good=range(D->date,29); return; case 0: D->jd+= 29 + D->date; D->good=range(D->date,30); return; case 1: D->jd+= 59 + D->date; D->good=range(D->date,29); return; case 2: D->jd+= 88 + D->date; D->good=range(D->date,30); return; case 3: D->jd+= 118 + D->date; D->good=range(D->date,29); return; case 4: D->jd+= 147 + D->date; D->good=range(D->date,30); return; case 5: D->jd+= 177 + D->date; D->good=range(D->date,29); return; } } void jdtoheb(Date *D) { long DAYS; int TRY; Character C; if (!D) return; D->calend=HebrewCalendar; D->good=0; DAYS= D->jd - HebEpoch; if (DAYS<1) return; // We divide the number of day since the epoch by the length of a Metonic year. // If the day JD is not in the year TRY, it will be either in the Elul // immediately preceding it or in the Tishri of the following year. TRY= 1 + ((DAYS + 10) / 365.2468222); determinecharacter(TRY,&C); DAYS-=C.start; if (DAYS<0) { D->year= TRY - 1; D->month=5; D->date= DAYS + 29; // preceding Elul D->good=range(D->date,29); return; } if (DAYS>=C.length) { D->year= TRY + 1; D->month=6; D->date= DAYS - C.length; // following Tishri D->good=range(D->date,30); return; } D->year=TRY; D->good=1; if (DAYS<30) { D->month= 6; D->date=DAYS; return; } DAYS-=30; // Tishri if (DAYSmonth= 7; D->date=DAYS; return; } DAYS-=C.march; // Marcheshvan if (DAYSmonth= 8; D->date=DAYS; return; } DAYS-=C.kislev; // Kislev if (DAYS<29) { D->month= 9; D->date=DAYS; return; } DAYS-=29; // Teves if (DAYS<30) { D->month=10; D->date=DAYS; return; } DAYS-=30; // Shevat if (C.leap) { if (DAYS<30) { D->month=12; D->date=DAYS; return; } DAYS-=30; } // Adar I if (DAYS<29) { D->month= C.leap ? 13 : 11; D->date=DAYS; return; } DAYS-=29; // Adar or Adar II if (DAYS<30) { D->month=0; D->date=DAYS; return; } DAYS-=30; // Nisan if (DAYS<29) { D->month=1; D->date=DAYS; return; } DAYS-=29; // Iyar if (DAYS<30) { D->month=2; D->date=DAYS; return; } DAYS-=30; // Sivan if (DAYS<29) { D->month=3; D->date=DAYS; return; } DAYS-=29; // Tammuz if (DAYS<30) { D->month=4; D->date=DAYS; return; } DAYS-=30; // Av if (DAYS<29) { D->month=5; D->date=DAYS; return; } // Elul D->good=0; } // Proper names for days of the week and months of the year static const char *WeekDay[7] = { "Sunday","Monday","Tuesday","Wednesday", "Thursday","Friday","Saturday" }; const char *MonthName[12] = { "January","February","March","April","May","June", "July","August","September","October","November","December" }; static const char *MonthShort[12] = { "Jan","Feb","Mar","Apr","May","Jun", "Jul","Aug","Sep","Oct","Nov","Dec" }; const int UNIQUEMOCHAR[12] = { 2,1,3,2,3,3,3,2,1,1,1,1 }; const char *HebMonths[14] = { "Nisan","Iyyar","Sivan","Tammuz","Av","Elul","Tishri", "Marcheshvan","Kislev","Teveth","Shevat","Adar","Adar I","Adar II" }; const char *HebAltMonths[14] = { "","Iyar","","","Ab","","Tishrei", "Heshvan","","Tebeth","Shebat","","Adar Rishon","Adar Sheni" }; char *dayname(int N) { if (range(N,7)) return (char *)WeekDay[N]; return NULL; } bool parsedayname(const char *S, short *D) { int i; if (!ends(S,"day")) return 0; for (i=0;i<7;i++) if (streqci(S,WeekDay[i])) { *D=i; return 1; } return 0; } char *monthname(int N) { if (range(N,12)) return (char *)MonthName[N]; return NULL; } char *monthshortform(int N) { if (range(N,12)) return (char *)MonthShort[N]; return NULL; } bool parsemonthname(const char *S, short *M) { int i; for (i=0;i<12;i++) if (streqci(S,MonthName[i])) { *M=i; return 1; } return 0; } char *hebmonthname(int N) { if (range(N,14)) return (char *)HebMonths[N]; return NULL; } bool parsehebmonth(const char *S, short *M) { int i; char *A; for (i=0;i<14;i++) if (streqci(S,HebMonths[i])) { *M=i; return 1; } for (i=0;i<14;i++) { A=(char *)HebAltMonths[i]; if (A && *A && streqci(S,A)) { *M=i; return 1; } } if (streqci(S,"Abib")) { *M=0; return 1; } if (streqci(S,"Ziv")) { *M=1; return 1; } if (streqci(S,"Ethanim")) { *M=6; return 1; } if (streqci(S,"Bul")) { *M=7; return 1; } if (streqci(S,"Teves")) { *M=9; return 1; } return 0; } // Test function #include "cstring.h" void testcalend(void) { Date D; long i,j; //char *S; long Weekday[7]; long OFFSET; /* D.date=12; for (i=0;i<7;i++) Weekday[i]=0; for (i=1600;i<2000;i++) for (j=0;j<12;j++) { D.year=i; D.month=j; gregtojd(&D); Weekday[dayofweek(D.jd)]++; } OFFSET=Weekday[0]; for (i=0;i<7;i++) Weekday[i]-=OFFSET; S=NULL; for (i=0;i<7;i++) S=cat3(5,S," ",fig(Weekday[i])); error(S); */ j=29; for (i= HebEpoch + 1;i<2454000L;i++) { D.jd=i; jdtoheb(&D); if (!D.good) error(cat(2,"testcalend() error #1: ",fig(i))); if (D.date==0) { if (j<28 || j>29) error(cat(2,"testcalend() error #2: ",fig(i))); } else if (D.date != j + 1) error(cat(2,"testcalend() error #3: ",fig(i))); j=D.date; hebtojd(&D); if (!D.good) error(cat(2,"testcalend() error #4: ",fig(i))); if (D.jd!=i) error(cat(2,"testcalend() error #5: ",fig(i))); //if (i % 100000L == 0) beep(); } error("testcalend() complete"); }