mirror of
https://salsa.debian.org/dskoll/remind.git
synced 2026-04-17 14:59:20 +02:00
580 lines
19 KiB
C
580 lines
19 KiB
C
/***************************************************************/
|
||
/* */
|
||
/* HBCAL.C */
|
||
/* */
|
||
/* Support for the Hebrew calendar */
|
||
/* */
|
||
/* This file is part of REMIND. */
|
||
/* Copyright (C) 1992-2026 by Dianne Skoll */
|
||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||
/* */
|
||
/* Derived from code written by Amos Shapir in 1978; revised */
|
||
/* 1985. */
|
||
/* */
|
||
/***************************************************************/
|
||
|
||
#include "config.h"
|
||
|
||
#include <stdio.h> /* For FILE used by protos.h - sigh. */
|
||
#include "types.h"
|
||
#include "protos.h"
|
||
#include "globals.h"
|
||
#include "err.h"
|
||
#include <string.h>
|
||
|
||
#ifdef HAVE_STRINGS_H
|
||
#include <strings.h>
|
||
#endif
|
||
|
||
#define HOUR 1080L
|
||
#define DAY (24L*HOUR)
|
||
#define WEEK (7L*DAY)
|
||
#define M(h,p) ((long)(h*HOUR+p))
|
||
#define MONTH (DAY+M(12,793))
|
||
|
||
/* Correction to convert base reference to 1990. NOTE: If you change
|
||
the value of BASE in config.h, this will NOT WORK! You'll have to
|
||
add the appropriate number of days to CORRECTION. */
|
||
|
||
#define CORRECTION 732774L
|
||
|
||
#define TISHREY 0
|
||
#define HESHVAN 1
|
||
#define KISLEV 2
|
||
#define TEVET 3
|
||
#define SHVAT 4
|
||
#define ADARA 5
|
||
#define ADARB 6
|
||
#define NISAN 7
|
||
#define IYAR 8
|
||
#define SIVAN 9
|
||
#define TAMUZ 10
|
||
#define AV 11
|
||
#define ELUL 12
|
||
#define ADAR 13
|
||
|
||
#define JAHR_NONE 0
|
||
#define JAHR_FORWARD 1
|
||
#define JAHR_BACKWARD 2
|
||
|
||
#define ADAR2ADARB 0
|
||
#define ADAR2ADARA 1
|
||
#define ADAR2BOTH 2
|
||
|
||
struct AltMonthName {
|
||
char const *name;
|
||
int mon;
|
||
};
|
||
|
||
static char const *HebMonthNames[] = {
|
||
"Tishrey", "Heshvan", "Kislev", "Tevet", "Shvat", "Adar A", "Adar B",
|
||
"Nisan", "Iyar", "Sivan", "Tamuz", "Av", "Elul", "Adar"};
|
||
|
||
static char const *IvritMonthNames[] = {
|
||
"תשרי",
|
||
"חשוון",
|
||
"כסלו",
|
||
"טבת",
|
||
"שבט",
|
||
"אדר א'",
|
||
"אדר ב'",
|
||
"ניסן",
|
||
"אייר",
|
||
"סיון",
|
||
"תמוז",
|
||
"אב",
|
||
"אלול",
|
||
"אדר" };
|
||
|
||
/* Alternate spellings */
|
||
static struct AltMonthName AltMonthSpellings[] = {
|
||
{ "Tishri", TISHREY },
|
||
{ "Tishrei", TISHREY },
|
||
|
||
{ "Cheshvan", HESHVAN },
|
||
{ "Kheshvan", HESHVAN },
|
||
|
||
{ "Shevat", SHVAT },
|
||
|
||
{ "Tammuz", TAMUZ },
|
||
|
||
{ "Adar 1", ADARA },
|
||
{ "Adar I", ADARA },
|
||
{ "אדר א", ADARA },
|
||
{ "אדר 1", ADARA },
|
||
{ "אדר I", ADARA },
|
||
|
||
{ "Adar 2", ADARB },
|
||
{ "Adar II", ADARB },
|
||
{ "אדר ב", ADARB },
|
||
{ "אדר 2", ADARB },
|
||
{ "אדר II", ADARB },
|
||
|
||
{ "Iyyar", IYAR }
|
||
};
|
||
|
||
static char MaxMonLen[] = {
|
||
30, 30, 30, 29, 30, 30, 29, 30, 29, 30, 29, 30, 29, 29};
|
||
|
||
static char HebIsLeap[] = {0,0,1,0,0,1,0,1,0,0,1,0,0,1,0,0,1,0,1};
|
||
|
||
static long DaysToHebYear(int y);
|
||
|
||
/***************************************************************/
|
||
/* */
|
||
/* RoshHashana */
|
||
/* */
|
||
/* Return DSE date for Rosh Hashana of specified */
|
||
/* Hebrew year. (ie, 5751, not 1990) */
|
||
/* */
|
||
/***************************************************************/
|
||
static int RoshHashana(int i)
|
||
{
|
||
long j;
|
||
j = DaysToHebYear(i-3744) - CORRECTION;
|
||
return (int) j; /* No overflow check... very trusting! */
|
||
}
|
||
|
||
/***************************************************************/
|
||
/* */
|
||
/* DaysToHebYear */
|
||
/* */
|
||
/* Return the number of days to RH of specified Hebrew year */
|
||
/* from new moon before Tishrey 1 5701. */
|
||
/* */
|
||
/***************************************************************/
|
||
static long DaysToHebYear(int y)
|
||
{
|
||
long m, nm, dw, s, l;
|
||
|
||
l = y*7+1; /* no. of leap months */
|
||
m = y*12+l/19; /* total no. of months */
|
||
nm = m*MONTH+M(1,779); /* molad at 197 cycles */
|
||
s = m*28+nm/DAY-2;
|
||
|
||
nm %= WEEK;
|
||
l %= 19L;
|
||
dw = nm/DAY;
|
||
nm %= DAY;
|
||
|
||
/* special cases of Molad Zaken */
|
||
if (nm >= 18*HOUR ||
|
||
(l < 12 && dw==3 && nm>=M(9,204)) ||
|
||
(l < 7 && dw==2 && nm>=M(15,589)))
|
||
s++,dw++;
|
||
/* ADU */
|
||
if(dw == 1 || dw == 4 || dw == 6)
|
||
s++;
|
||
return s;
|
||
}
|
||
|
||
/***************************************************************/
|
||
/* */
|
||
/* DaysInHebYear */
|
||
/* */
|
||
/* Return the number of days in the Hebrew year. */
|
||
/* */
|
||
/* */
|
||
/***************************************************************/
|
||
static int DaysInHebYear(int y)
|
||
{
|
||
long thisyear, nextyear;
|
||
|
||
thisyear = DaysToHebYear(y-3744);
|
||
nextyear = DaysToHebYear(y-3743);
|
||
return (int) (nextyear - thisyear);
|
||
}
|
||
|
||
/***************************************************************/
|
||
/* */
|
||
/* DaysInHebMonths */
|
||
/* */
|
||
/* Return a pointer to an array giving lengths of months */
|
||
/* given the LENGTH of the Hebrew year. */
|
||
/* */
|
||
/***************************************************************/
|
||
static char const *DaysInHebMonths(int ylen)
|
||
{
|
||
static char monlen[14] =
|
||
{30, 29, 30, 29, 30, 0, 29, 30, 29, 30, 29, 30, 29, 29};
|
||
|
||
|
||
if (ylen > 355) {
|
||
monlen[ADARA] = 30;
|
||
ylen -= 30;
|
||
} else monlen[ADARA] = 0;
|
||
|
||
if (ylen == 353) monlen[KISLEV] = 29; else monlen[KISLEV] = 30;
|
||
if (ylen == 355) monlen[HESHVAN] = 30; else monlen[HESHVAN] = 29;
|
||
|
||
return monlen;
|
||
}
|
||
|
||
/***************************************************************/
|
||
/* */
|
||
/* HebToDSE */
|
||
/* */
|
||
/* Convert a Hebrew date to DSE. */
|
||
/* Hebrew months range from 0-12, but Adar A has 0 length in */
|
||
/* non-leap-years. */
|
||
/* */
|
||
/***************************************************************/
|
||
int HebToDSE(int hy, int hm, int hd)
|
||
{
|
||
int ylen;
|
||
char const *monlens;
|
||
int rh;
|
||
int m;
|
||
|
||
/* Do some range checking */
|
||
if (hy - 3761 < BASE || hy - 3760 > BASE+YR_RANGE) return -1;
|
||
|
||
ylen = DaysInHebYear(hy);
|
||
monlens = DaysInHebMonths(ylen);
|
||
|
||
/* Get the Rosh Hashana of the year */
|
||
rh = RoshHashana(hy);
|
||
|
||
/* Bump up to the appropriate month */
|
||
for (m=0; m<hm; m++) rh += monlens[m];
|
||
|
||
/* Add in appropriate number of days */
|
||
rh += hd - 1;
|
||
return rh;
|
||
}
|
||
|
||
/***************************************************************/
|
||
/* */
|
||
/* DSEToHeb */
|
||
/* */
|
||
/* Convert a DSE to Hebrew. */
|
||
/* Hebrew months range from 0-12, but Adar A has 0 length in */
|
||
/* non-leap-years. */
|
||
/* */
|
||
/***************************************************************/
|
||
void DSEToHeb(int dse, int *hy, int *hm, int *hd)
|
||
{
|
||
int y, m, d;
|
||
int rh;
|
||
int ylen;
|
||
char const *monlen;
|
||
/* Get the common year */
|
||
FromDSE(dse, &y, &m, &d);
|
||
y += 3763; /* Over-estimate a bit to be on the safe side below... */
|
||
|
||
/* Find the RH just before desired date */
|
||
while ((rh=RoshHashana(y))>dse) y--;
|
||
|
||
/* Got the year - now find the month */
|
||
dse -= rh;
|
||
ylen = DaysInHebYear(y);
|
||
monlen = DaysInHebMonths(ylen);
|
||
m = 0;
|
||
while((dse >= monlen[m]) || !monlen[m]) {
|
||
dse -= monlen[m];
|
||
m++;
|
||
}
|
||
|
||
*hy = y;
|
||
*hm = m;
|
||
*hd = dse+1;
|
||
}
|
||
|
||
/***************************************************************/
|
||
/* */
|
||
/* HebNameToNum */
|
||
/* */
|
||
/* Convert a Hebrew month's name to its number, given the */
|
||
/* year. */
|
||
/* */
|
||
/***************************************************************/
|
||
int HebNameToNum(char const *mname)
|
||
{
|
||
int i;
|
||
int m=-1;
|
||
|
||
for (i=0; i<14; i++) {
|
||
if (!strcasecmp(mname, HebMonthNames[i])) {
|
||
m = i;
|
||
break;
|
||
}
|
||
}
|
||
if (m == -1) {
|
||
for (i=0; i<14; i++) {
|
||
if (!strcmp(mname, IvritMonthNames[i])) {
|
||
m = i;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Try the alternate spellings */
|
||
if (m == -1) {
|
||
for (i=0; i < (int) (sizeof(AltMonthSpellings) / sizeof(AltMonthSpellings[0])); i++) {
|
||
if (!strcasecmp(mname, AltMonthSpellings[i].name)) {
|
||
m = AltMonthSpellings[i].mon;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
return m;
|
||
}
|
||
|
||
/***************************************************************/
|
||
/* */
|
||
/* HebMonthname */
|
||
/* */
|
||
/* Convert a Hebrew month's number to its name, given the */
|
||
/* year. */
|
||
/* */
|
||
/***************************************************************/
|
||
char const *HebMonthName(int m, int y)
|
||
{
|
||
if (m != ADARA && m != ADARB) return HebMonthNames[m];
|
||
|
||
if (!HebIsLeap[(y-1)%19]) return HebMonthNames[ADAR];
|
||
else return HebMonthNames[m];
|
||
}
|
||
|
||
/***************************************************************/
|
||
/* */
|
||
/* IvritMonthname */
|
||
/* */
|
||
/* Convert a Hebrew month's number to its name in Hebrew */
|
||
/* script, given the year. */
|
||
/* */
|
||
/***************************************************************/
|
||
char const *IvritMonthName(int m, int y)
|
||
{
|
||
if (m != ADARA && m != ADARB) return IvritMonthNames[m];
|
||
|
||
if (!HebIsLeap[(y-1)%19]) return IvritMonthNames[ADAR];
|
||
else return IvritMonthNames[m];
|
||
}
|
||
|
||
/***************************************************************/
|
||
/* */
|
||
/* GetValidHebDate */
|
||
/* */
|
||
/* Given the day of a month, a Hebrew month number, and a */
|
||
/* year, return a valid year number, month number, and day */
|
||
/* number. Returns 0 for success, non-0 for failure. */
|
||
/* If *dout is set to -1, then date is completely invalid. */
|
||
/* Otherwise, date is only invalid in specified year. */
|
||
/* */
|
||
/* Algorithm: */
|
||
/* - Convert references to Adar to Adar B. */
|
||
/* If jahr == 0 then */
|
||
/* - If no such date in current Hebrew year, return */
|
||
/* failure. */
|
||
/* else follow jahrzeit rules: */
|
||
/* - If jahr == 1: Convert 30 Kislev to 1 Tevet and */
|
||
/* 30 Heshvan to 1 Kislev if chaser. */
|
||
/* Convert 30 Adar A to 1 Nisan in nonleap */
|
||
/* This rule is NOT appropriate for a */
|
||
/* jahrzeit on 30 Adar A. Use rule 2 for */
|
||
/* that. However, I believe it is correct */
|
||
/* for smachot. */
|
||
/* - If jahr == 2: Convert 30 Kislev to 29 Kislev and */
|
||
/* 30 Heshvan to 29 Heshvan if chaser. */
|
||
/* Change 30 Adar A to 30 Shvat in nonleap */
|
||
/* */
|
||
/***************************************************************/
|
||
int GetValidHebDate(int yin, int min, int din, int adarbehave,
|
||
int *mout, int *dout, int jahr)
|
||
{
|
||
char const *monlen;
|
||
int ylen;
|
||
|
||
*mout = min;
|
||
*dout = din;
|
||
|
||
/* Do some error checking */
|
||
if (din < 1 || din > MaxMonLen[min] || min < 0 || min > 13) {
|
||
*dout = -1;
|
||
return E_BAD_HEBDATE;
|
||
}
|
||
|
||
ylen = DaysInHebYear(yin);
|
||
monlen = DaysInHebMonths(ylen);
|
||
|
||
/* Convert ADAR as necessary */
|
||
if (min == ADAR) {
|
||
switch(adarbehave) {
|
||
case ADAR2ADARA: if (monlen[ADARA]) *mout = min = ADARA;
|
||
else *mout = min = ADARB;
|
||
break;
|
||
|
||
case ADAR2ADARB: *mout = min = ADARB; break;
|
||
|
||
default:
|
||
Eprint(tr("GetValidHebDate: Bad adarbehave value %d"), adarbehave);
|
||
return E_SWERR;
|
||
}
|
||
}
|
||
|
||
if (din <= monlen[min]) return OK;
|
||
|
||
switch(jahr) {
|
||
case JAHR_NONE: return E_BAD_DATE;
|
||
|
||
case JAHR_FORWARD:
|
||
if (min == KISLEV) {
|
||
*mout = TEVET;
|
||
*dout = 1;
|
||
return OK;
|
||
} else if (min == HESHVAN) {
|
||
*mout = KISLEV;
|
||
*dout = 1;
|
||
return OK;
|
||
} else if (min == ADARA) {
|
||
if (din > 29) {
|
||
*dout = 1;
|
||
*mout = NISAN;
|
||
} else {
|
||
*dout = din;
|
||
*mout = ADARB;
|
||
}
|
||
return OK;
|
||
}
|
||
|
||
Eprint("GetValidHebDate: (1) software error! %d", jahr);
|
||
return E_SWERR;
|
||
|
||
case JAHR_BACKWARD:
|
||
if (min == KISLEV) {
|
||
*mout = KISLEV;
|
||
*dout = 29;
|
||
return OK;
|
||
} else if (min == HESHVAN) {
|
||
*mout = HESHVAN;
|
||
*dout = 29;
|
||
return OK;
|
||
} else if (min == ADARA) {
|
||
if (din > 29) {
|
||
*dout = 30;
|
||
*mout = SHVAT;
|
||
} else {
|
||
*mout = ADARB;
|
||
*dout = din;
|
||
}
|
||
return OK;
|
||
}
|
||
|
||
Eprint("GetValidHebDate: (2) software error! %d", jahr);
|
||
return E_SWERR;
|
||
|
||
default:
|
||
Eprint("GetValidHebDate: (3) software error! %d", jahr);
|
||
return E_SWERR;
|
||
}
|
||
}
|
||
|
||
|
||
/***************************************************************/
|
||
/* */
|
||
/* GetNextHebrewDate */
|
||
/* */
|
||
/* Get the next Hebrew date on or after specified date. */
|
||
/* */
|
||
/* Returns 0 for success, non-zero for failure. */
|
||
/* */
|
||
/***************************************************************/
|
||
int GetNextHebrewDate(int dsestart, int hm, int hd,
|
||
int jahr, int adarbehave, int *ans)
|
||
{
|
||
int r, yout, mout, dout, dse=1;
|
||
int adarflag = adarbehave;
|
||
|
||
/* I initialize dse above to stop gcc from complaining about
|
||
possible use of uninitialized variable. You can take it
|
||
out if the small inefficiency really bothers you. */
|
||
|
||
/* If adarbehave == ADAR2BOTH, set adarflag to ADAR2ADARA for now */
|
||
if (adarbehave == ADAR2BOTH) adarflag = ADAR2ADARA;
|
||
|
||
DSEToHeb(dsestart, &yout, &mout, &dout);
|
||
|
||
r = 1;
|
||
while(r) {
|
||
r = GetValidHebDate(yout, hm, hd, adarflag, &mout, &dout, jahr);
|
||
if (dout == -1) return r;
|
||
if (r) {
|
||
if (adarbehave == ADAR2BOTH && hm == ADAR) {
|
||
if (adarflag == ADAR2ADARA) {
|
||
adarflag = ADAR2ADARB;
|
||
} else {
|
||
adarflag = ADAR2ADARA;
|
||
yout++;
|
||
}
|
||
} else yout++;
|
||
continue;
|
||
}
|
||
dse = HebToDSE(yout, mout, dout);
|
||
if (dse < 0) return E_DATE_OVER;
|
||
if (dse >= dsestart) break;
|
||
else {
|
||
if (adarbehave == ADAR2BOTH && hm == ADAR) {
|
||
if (adarflag == ADAR2ADARA) {
|
||
adarflag = ADAR2ADARB;
|
||
} else {
|
||
adarflag = ADAR2ADARA;
|
||
yout++;
|
||
}
|
||
} else yout++;
|
||
r=1; /* Force loop to continue */
|
||
}
|
||
}
|
||
*ans = dse;
|
||
return OK;
|
||
}
|
||
|
||
/***************************************************************/
|
||
/* */
|
||
/* ComputeJahr */
|
||
/* */
|
||
/* Given a date of death, compute the value to use for jahr. */
|
||
/* */
|
||
/***************************************************************/
|
||
int ComputeJahr(int y, int m, int d, int *ans)
|
||
{
|
||
char const *monlen;
|
||
int len;
|
||
|
||
*ans = JAHR_NONE;
|
||
|
||
len = DaysInHebYear(y);
|
||
monlen = DaysInHebMonths(len);
|
||
|
||
/* Check for Adar A */
|
||
if (m == ADARA && monlen[m] == 0) {
|
||
Eprint(tr("No Adar A in %d"), y);
|
||
return E_BAD_HEBDATE;
|
||
}
|
||
|
||
|
||
if (d < 1 || d > MaxMonLen[m] || m < 0 || m > 13) {
|
||
return E_BAD_HEBDATE;
|
||
}
|
||
|
||
if (d > monlen[m]) {
|
||
Eprint("%d %s %d: %s", d, HebMonthNames[m], y, GetErr(E_BAD_HEBDATE));
|
||
return E_BAD_HEBDATE;
|
||
}
|
||
|
||
/* If the jahrzeit was in Adar A, we always use JAHR_BACKWARD */
|
||
if (m == ADARA) {
|
||
*ans = JAHR_BACKWARD;
|
||
return OK;
|
||
}
|
||
|
||
/* Get lengths of months in year following jahrzeit */
|
||
len = DaysInHebYear(y+1);
|
||
monlen = DaysInHebMonths(len);
|
||
|
||
if (d > monlen[m]) *ans = JAHR_FORWARD;
|
||
else *ans = JAHR_BACKWARD;
|
||
|
||
return OK;
|
||
}
|