xref: /PHP-8.1/ext/calendar/easter.c (revision aff36587)
1 /*
2    +----------------------------------------------------------------------+
3    | Copyright (c) The PHP Group                                          |
4    +----------------------------------------------------------------------+
5    | This source file is subject to version 3.01 of the PHP license,      |
6    | that is bundled with this package in the file LICENSE, and is        |
7    | available through the world-wide-web at the following url:           |
8    | https://www.php.net/license/3_01.txt                                 |
9    | If you did not receive a copy of the PHP license and are unable to   |
10    | obtain it through the world-wide-web, please send a note to          |
11    | license@php.net so we can mail you a copy immediately.               |
12    +----------------------------------------------------------------------+
13    | Authors: Shane Caraveo             <shane@caraveo.com>               |
14    |          Colin Viebrock            <colin@easydns.com>               |
15    |          Hartmut Holzgraefe        <hholzgra@php.net>                |
16    +----------------------------------------------------------------------+
17  */
18 
19 #include "php.h"
20 #include "php_calendar.h"
21 #include "sdncal.h"
22 #include <time.h>
23 
_cal_easter(INTERNAL_FUNCTION_PARAMETERS,zend_long gm)24 static void _cal_easter(INTERNAL_FUNCTION_PARAMETERS, zend_long gm)
25 {
26 	/* based on code by Simon Kershaw, <webmaster@ely.anglican.org> */
27 
28 	struct tm te;
29 	zend_long year, golden, solar, lunar, pfm, dom, tmp, easter, result;
30 	zend_long method = CAL_EASTER_DEFAULT;
31 	bool year_is_null = 1;
32 
33 	if (zend_parse_parameters(ZEND_NUM_ARGS(),
34 		"|l!l", &year, &year_is_null, &method) == FAILURE) {
35 			RETURN_THROWS();
36 	}
37 
38 	/* Default to the current year if year parameter is not given */
39 	if (year_is_null) {
40 		time_t a;
41 		struct tm b, *res;
42 		time(&a);
43 		res = php_localtime_r(&a, &b);
44 		if (!res) {
45 			year = 1900;
46 		} else {
47 			year = 1900 + b.tm_year;
48 		}
49 	}
50 
51 	if (gm && (year<1970 || year>2037)) {				/* out of range for timestamps */
52 		zend_argument_value_error(1, "must be between 1970 and 2037 (inclusive)");
53 		RETURN_THROWS();
54 	}
55 
56 	golden = (year % 19) + 1;					/* the Golden number */
57 
58 	if ((year <= 1582 && method != CAL_EASTER_ALWAYS_GREGORIAN) ||
59 	    (year >= 1583 && year <= 1752 && method != CAL_EASTER_ROMAN && method != CAL_EASTER_ALWAYS_GREGORIAN) ||
60 	     method == CAL_EASTER_ALWAYS_JULIAN) {		/* JULIAN CALENDAR */
61 
62 		dom = (year + (year/4) + 5) % 7;			/* the "Dominical number" - finding a Sunday */
63 		if (dom < 0) {
64 			dom += 7;
65 		}
66 
67 		pfm = (3 - (11*golden) - 7) % 30;			/* uncorrected date of the Paschal full moon */
68 		if (pfm < 0) {
69 			pfm += 30;
70 		}
71 	} else {							/* GREGORIAN CALENDAR */
72 		dom = (year + (year/4) - (year/100) + (year/400)) % 7;	/* the "Domincal number" */
73 		if (dom < 0) {
74 			dom += 7;
75 		}
76 
77 		solar = (year-1600)/100 - (year-1600)/400;		/* the solar and lunar corrections */
78 		lunar = (((year-1400) / 100) * 8) / 25;
79 
80 		pfm = (3 - (11*golden) + solar - lunar) % 30;		/* uncorrected date of the Paschal full moon */
81 		if (pfm < 0) {
82 			pfm += 30;
83 		}
84 	}
85 
86 	if ((pfm == 29) || (pfm == 28 && golden > 11)) {		/* corrected date of the Paschal full moon */
87 		pfm--;							/* - days after 21st March                 */
88 	}
89 
90 	tmp = (4-pfm-dom) % 7;
91 	if (tmp < 0) {
92 		tmp += 7;
93 	}
94 
95 	easter = pfm + tmp + 1;	    					/* Easter as the number of days after 21st March */
96 
97 	if (gm) {							/* return a timestamp */
98 		te.tm_isdst = -1;
99 		te.tm_year = year-1900;
100 		te.tm_sec = 0;
101 		te.tm_min = 0;
102 		te.tm_hour = 0;
103 
104 		if (easter < 11) {
105 			te.tm_mon = 2;			/* March */
106 			te.tm_mday = easter+21;
107 		} else {
108 			te.tm_mon = 3;			/* April */
109 			te.tm_mday = easter-10;
110 		}
111 	    result = mktime(&te);
112 	} else {							/* return the days after March 21 */
113 	    result = easter;
114 	}
115 	ZVAL_LONG(return_value, result);
116 }
117 
118 /* {{{ Return the timestamp of midnight on Easter of a given year (defaults to current year) */
PHP_FUNCTION(easter_date)119 PHP_FUNCTION(easter_date)
120 {
121 	_cal_easter(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
122 }
123 /* }}} */
124 
125 /* {{{ Return the number of days after March 21 that Easter falls on for a given year (defaults to current year) */
PHP_FUNCTION(easter_days)126 PHP_FUNCTION(easter_days)
127 {
128 	_cal_easter(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
129 }
130 /* }}} */
131