Bug 23881

Summary: Wrong calculation of easter day in calendar program
Product: Base System Reporter: elmar <elmar>
Component: miscAssignee: dwmalone
Status: Closed FIXED    
Severity: Affects Only Me    
Priority: Normal    
Version: Unspecified   
Hardware: Any   
OS: Any   
Attachments:
Description Flags
file.diff none

Description elmar 2000-12-27 10:10:00 UTC
In file
	 /usr/src/usr.bin/calendar/ostern.c
the calculation for the easter day is wrong due to wrong/incomplete
check for leap years.

How-To-Repeat: Cut and paste these two lines into your shell:

echo "Easter@Ostern 2000" | tr '@' '\009' > /tmp/cal.test
calendar  -t 22.04.2000 -f /tmp/cal.test

Calendar pretends 22.04 as easter, but its on 23.04.
Comment 1 dwmalone freebsd_committer freebsd_triage 2000-12-27 11:17:38 UTC
Responsible Changed
From-To: freebsd-bugs->dwmalone

I'll have a look at this one. I may replace the code with something 
from the calendar faq.
Comment 2 dwmalone 2000-12-27 13:59:33 UTC
The following patch to callendar(1) uses the method described in
the calendar FAQ to calculate when Easter is. This seems shorter
and is better documented than the one in ostern.c at the moment.

The patch also makes the command line parsing more robust by not
assuming that the day number and month number will be exactly two
digits long. Previously calendar would silently do the wrong thing
in this case.

I'll commit this in a few days, unless anyone objects or spots any
problems.

	David.


Index: day.c
===================================================================
RCS file: /cvs/FreeBSD-CVS/src/usr.bin/calendar/day.c,v
retrieving revision 1.13
diff -u -r1.13 day.c
--- day.c	1999/08/28 00:59:17	1.13
+++ day.c	2000/12/27 13:51:13
@@ -165,17 +165,13 @@
 time_t Mktime (dp)
     char *dp;
 {
-    char *date;
     time_t t;
-    int len;
+    int d, m, y;
     struct tm tm;
 
-    if ((date = strdup(dp)) == NULL)
-		errx(1, "strdup failed in Mktime");
     (void)time(&t);
     tp = localtime(&t);
 
-    len = strlen(date);
     tm.tm_sec = 0;
     tm.tm_min = 0;
     tm.tm_hour = 0;
@@ -184,31 +180,23 @@
     tm.tm_mon = tp->tm_mon;
     tm.tm_year = tp->tm_year;
 
-
-    /* day */
-    *(date+2) = '\0';
-    tm.tm_mday = atoi(date);
-
-    /* month */
-    if (len >= 4) {
-	*(date+5) = '\0';
-	tm.tm_mon = atoi(date+3) - 1;
-    }
-
-    /* Year */
-    if (len >= 7) {
-	tm.tm_year = atoi(date+6);
-
-	/* tm_year up 1900 ... */
-	if (tm.tm_year > 1900)
-	    tm.tm_year -= 1900;
+    switch (sscanf(dp, "%d.%d.%d", &d, &m, &y)) {
+    case 3:
+	if (y > 1900)
+	    y -= 1900;
+	tm.tm_year = y;
+	/* FALLTHROUGH */
+    case 2:
+	tm.tm_mon = m - 1;
+	/* FALLTHROUGH */
+    case 1:
+	tm.tm_mday = d;
     }
 
 #ifdef DEBUG
     fprintf(stderr, "Mktime: %d %d %d %s\n", (int)mktime(&tm), (int)t, len,
 	   asctime(&tm));
 #endif
-    free(date);
     return(mktime(&tm));
 }
 
Index: ostern.c
===================================================================
RCS file: /cvs/FreeBSD-CVS/src/usr.bin/calendar/ostern.c,v
retrieving revision 1.8
diff -u -r1.8 ostern.c
--- ostern.c	1999/08/28 00:59:18	1.8
+++ ostern.c	2000/12/27 13:41:24
@@ -35,46 +35,35 @@
 
 /* return year day for Easter */
 
+/*
+ * This code is based on the Calendar FAQ's code for how to calculate
+ * easter is. This is the Gregorian calendar version. They refer to
+ * the Algorithm of Oudin in the "Explanatory Supplement to the
+ * Astronomical Almanac".
+ */
+
 int easter (year)
     int year;            /* 0 ... abcd, NOT since 1900 */
 {
-
-    int e_a, e_b, e_c, e_d, e_e,e_f, e_g, e_h, e_i, e_k,
-        e_l, e_m, e_n, e_p, e_q;
-
-    /* silly, but it works */
-    e_a = year % 19;
-    e_b = year / 100;
-    e_c = year % 100;
-
-    e_d = e_b / 4;
-    e_e = e_b % 4;
-    e_f = (e_b + 8) / 25;
-    e_g = (e_b + 1 - e_f) / 3;
-    e_h = ((19 * e_a) + 15 + e_b - (e_d + e_g)) % 30;
-    e_i = e_c / 4;
-    e_k = e_c % 4;
-    e_l = (32 + 2 * e_e + 2 * e_i - (e_h + e_k)) % 7;
-    e_m = (e_a + 11 * e_h + 22 * e_l) / 451;
-    e_n = (e_h + e_l + 114 - (7 * e_m)) / 31;
-    e_p = (e_h + e_l + 114 - (7 * e_m)) % 31;
-    e_p = e_p + 1;
-
-    e_q = 31 + 28;
-
-    if (e_k == 0 && e_c != 0)
-	e_q += 1;
-
-    if (e_n == 4)
-	e_q += 31;
-
-    e_q += e_p;
-
-#if DEBUG
-    printf("%d %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n", e_a , e_b , e_c , e_d , e_e , e_f , e_g , e_h , e_i , e_k , e_l , e_m , e_n  , e_p , e_q);
-#endif
-
-    return (e_q);
+    int G,	/* Golden number - 1 */
+	C,	/* Century */
+	H,	/* 23 - epact % 30 */
+	I,	/* days from 21 March to Paschal full moon */
+	J,	/* weekday of full moon */
+	L;	/* days from 21 March to Sunday on of before full moon */
+
+    G = year % 19;
+    C = year / 100;
+    H = (C - C/4 - (8*C+13)/25 + 19*G + 15) % 30;
+    I = H - (H/28)*(1 - (H/28)*(29/(H + 1))*((21 - G)/11));
+    J = (year + year/4 + I + 2 - C + C/4) % 7;
+
+    L = I - J;
+
+    if (year % 400 == 0 || (year % 4 == 0 && year % 100 != 0))
+	return 31 + 29 + 21 + L + 7;
+    else
+	return 31 + 28 + 21 + L + 7;
 }
 
 /* return year day for  Easter or easter depending days
Comment 3 dwmalone freebsd_committer freebsd_triage 2001-03-05 13:05:45 UTC
State Changed
From-To: open->closed

Fixed in -current and RELENG_4