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 | Arne Perschke <a.perschke@hctec.net> |
17 +----------------------------------------------------------------------+
18 */
19
20 #include "php.h"
21 #include "php_calendar.h"
22 #include "sdncal.h"
23 #include <time.h>
24
25 /**
26 * If `gm` is true this will return the timestamp at midnight on Easter of the given year. If it is false this
27 * will return the number of days Easter is after March 21st.
28 */
_cal_easter(INTERNAL_FUNCTION_PARAMETERS,bool gm)29 static void _cal_easter(INTERNAL_FUNCTION_PARAMETERS, bool gm)
30 {
31 /* based on code by Simon Kershaw, <webmaster@ely.anglican.org> */
32
33 struct tm te;
34 zend_long year, golden, solar, lunar, pfm, dom, tmp, easter, result;
35 zend_long method = CAL_EASTER_DEFAULT;
36 bool year_is_null = 1;
37
38 if (zend_parse_parameters(ZEND_NUM_ARGS(),
39 "|l!l", &year, &year_is_null, &method) == FAILURE) {
40 RETURN_THROWS();
41 }
42
43 /* Default to the current year if year parameter is not given */
44 if (year_is_null) {
45 time_t a;
46 struct tm b, *res;
47 time(&a);
48 res = php_localtime_r(&a, &b);
49 if (!res) {
50 year = 1900;
51 } else {
52 year = 1900 + b.tm_year;
53 }
54 }
55
56 #ifdef ZEND_ENABLE_ZVAL_LONG64
57 /* Compiling for 64bit, allow years between 1970 and 2.000.000.000 */
58 if (gm && year < 1970) {
59 /* timestamps only start after 1970 */
60 zend_argument_value_error(1, "must be a year after 1970 (inclusive)");
61 RETURN_THROWS();
62 }
63
64 if (gm && year > 2000000000) {
65 /* timestamps only go up to the year 2.000.000.000 */
66 zend_argument_value_error(1, "must be a year before 2.000.000.000 (inclusive)");
67 RETURN_THROWS();
68 }
69 #else
70 /* Compiling for 32bit, allow years between 1970 and 2037 */
71 if (gm && (year < 1970 || year > 2037)) {
72 zend_argument_value_error(1, "must be between 1970 and 2037 (inclusive)");
73 RETURN_THROWS();
74 }
75 #endif
76
77
78 golden = (year % 19) + 1; /* the Golden number */
79
80 if ((year <= 1582 && method != CAL_EASTER_ALWAYS_GREGORIAN) ||
81 (year >= 1583 && year <= 1752 && method != CAL_EASTER_ROMAN && method != CAL_EASTER_ALWAYS_GREGORIAN) ||
82 method == CAL_EASTER_ALWAYS_JULIAN) { /* JULIAN CALENDAR */
83
84 dom = (year + (year/4) + 5) % 7; /* the "Dominical number" - finding a Sunday */
85 if (dom < 0) {
86 dom += 7;
87 }
88
89 pfm = (3 - (11*golden) - 7) % 30; /* uncorrected date of the Paschal full moon */
90 if (pfm < 0) {
91 pfm += 30;
92 }
93 } else { /* GREGORIAN CALENDAR */
94 dom = (year + (year/4) - (year/100) + (year/400)) % 7; /* the "Domincal number" */
95 if (dom < 0) {
96 dom += 7;
97 }
98
99 solar = (year-1600)/100 - (year-1600)/400; /* the solar and lunar corrections */
100 lunar = (((year-1400) / 100) * 8) / 25;
101
102 pfm = (3 - (11*golden) + solar - lunar) % 30; /* uncorrected date of the Paschal full moon */
103 if (pfm < 0) {
104 pfm += 30;
105 }
106 }
107
108 if ((pfm == 29) || (pfm == 28 && golden > 11)) { /* corrected date of the Paschal full moon */
109 pfm--; /* - days after 21st March */
110 }
111
112 tmp = (4-pfm-dom) % 7;
113 if (tmp < 0) {
114 tmp += 7;
115 }
116
117 easter = pfm + tmp + 1; /* Easter as the number of days after 21st March */
118
119 if (gm) { /* return a timestamp */
120 te.tm_isdst = -1;
121 te.tm_year = year-1900;
122 te.tm_sec = 0;
123 te.tm_min = 0;
124 te.tm_hour = 0;
125
126 if (easter < 11) {
127 te.tm_mon = 2; /* March */
128 te.tm_mday = easter+21;
129 } else {
130 te.tm_mon = 3; /* April */
131 te.tm_mday = easter-10;
132 }
133 result = mktime(&te);
134 } else { /* return the days after March 21 */
135 result = easter;
136 }
137 ZVAL_LONG(return_value, result);
138 }
139
140 /* {{{ Return the timestamp of midnight on Easter of a given year (defaults to current year) */
PHP_FUNCTION(easter_date)141 PHP_FUNCTION(easter_date)
142 {
143 _cal_easter(INTERNAL_FUNCTION_PARAM_PASSTHRU, true);
144 }
145 /* }}} */
146
147 /* {{{ Return the number of days after March 21 that Easter falls on for a given year (defaults to current year) */
PHP_FUNCTION(easter_days)148 PHP_FUNCTION(easter_days)
149 {
150 _cal_easter(INTERNAL_FUNCTION_PARAM_PASSTHRU, false);
151 }
152 /* }}} */
153