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