summaryrefslogtreecommitdiff
path: root/ext/calendar
diff options
context:
space:
mode:
Diffstat (limited to 'ext/calendar')
-rw-r--r--ext/calendar/CREDITS2
-rw-r--r--ext/calendar/cal_unix.c78
-rw-r--r--ext/calendar/calendar.c755
-rw-r--r--ext/calendar/config.m411
-rw-r--r--ext/calendar/config.w3210
-rw-r--r--ext/calendar/dow.c76
-rw-r--r--ext/calendar/easter.c145
-rw-r--r--ext/calendar/french.c160
-rw-r--r--ext/calendar/gregor.c270
-rw-r--r--ext/calendar/jewish.c763
-rw-r--r--ext/calendar/julian.c263
-rw-r--r--ext/calendar/package.xml73
-rw-r--r--ext/calendar/php_calendar.h48
-rw-r--r--ext/calendar/sdncal.h97
-rw-r--r--ext/calendar/tests/bug52744.phpt12
-rw-r--r--ext/calendar/tests/bug53574_1.phpt36
-rw-r--r--ext/calendar/tests/bug53574_2.phpt36
-rw-r--r--ext/calendar/tests/bug55797_1.phpt36
-rw-r--r--ext/calendar/tests/bug55797_2.phpt36
-rw-r--r--ext/calendar/tests/cal_days_in_month.phpt20
-rw-r--r--ext/calendar/tests/cal_from_jd.phpt60
-rw-r--r--ext/calendar/tests/cal_info.phpt216
-rw-r--r--ext/calendar/tests/cal_to_jd.phpt16
-rw-r--r--ext/calendar/tests/easter_date.phpt21
-rw-r--r--ext/calendar/tests/easter_days.phpt14
-rw-r--r--ext/calendar/tests/frenchtojd.phpt16
-rw-r--r--ext/calendar/tests/gregoriantojd.phpt18
-rw-r--r--ext/calendar/tests/jddayofweek.phpt130
-rw-r--r--ext/calendar/tests/jdmonthname.phpt314
-rw-r--r--ext/calendar/tests/jdtofrench.phpt20
-rw-r--r--ext/calendar/tests/jdtogregorian.phpt18
-rw-r--r--ext/calendar/tests/jdtojewish.phpt30
-rw-r--r--ext/calendar/tests/jdtojulian.phpt18
-rw-r--r--ext/calendar/tests/jdtomonthname.phpt85
-rw-r--r--ext/calendar/tests/jdtounix.phpt16
-rw-r--r--ext/calendar/tests/jewishtojd.phpt16
-rw-r--r--ext/calendar/tests/juliantojd.phpt18
-rw-r--r--ext/calendar/tests/skipif.inc4
-rw-r--r--ext/calendar/tests/unixtojd.phpt40
39 files changed, 3997 insertions, 0 deletions
diff --git a/ext/calendar/CREDITS b/ext/calendar/CREDITS
new file mode 100644
index 0000000..a2904bd
--- /dev/null
+++ b/ext/calendar/CREDITS
@@ -0,0 +1,2 @@
+Calendar
+Shane Caraveo, Colin Viebrock, Hartmut Holzgraefe, Wez Furlong
diff --git a/ext/calendar/cal_unix.c b/ext/calendar/cal_unix.c
new file mode 100644
index 0000000..c65b046
--- /dev/null
+++ b/ext/calendar/cal_unix.c
@@ -0,0 +1,78 @@
+/*
+ +----------------------------------------------------------------------+
+ | PHP Version 5 |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 1997-2013 The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Authors: Shane Caraveo <shane@caraveo.com> |
+ | Colin Viebrock <colin@easydns.com> |
+ | Hartmut Holzgraefe <hholzgra@php.net> |
+ +----------------------------------------------------------------------+
+ */
+/* $Id: */
+
+#include "php.h"
+#include "php_calendar.h"
+#include "sdncal.h"
+#include <time.h>
+
+/* {{{ proto int unixtojd([int timestamp])
+ Convert UNIX timestamp to Julian Day */
+PHP_FUNCTION(unixtojd)
+{
+ time_t ts = 0;
+ struct tm *ta, tmbuf;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l", &ts) == FAILURE) {
+ return;
+ }
+
+ if (!ts) {
+ ts = time(NULL);
+ } else if (ts < 0) {
+ RETURN_FALSE;
+ }
+
+ if (!(ta = php_localtime_r(&ts, &tmbuf))) {
+ RETURN_FALSE;
+ }
+
+ RETURN_LONG(GregorianToSdn(ta->tm_year+1900, ta->tm_mon+1, ta->tm_mday));
+}
+/* }}} */
+
+/* {{{ proto int jdtounix(int jday)
+ Convert Julian Day to UNIX timestamp */
+PHP_FUNCTION(jdtounix)
+{
+ long uday;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &uday) == FAILURE) {
+ return;
+ }
+ uday -= 2440588 /* J.D. of 1.1.1970 */;
+
+ if (uday < 0 || uday > 24755) { /* before beginning of unix epoch or behind end of unix epoch */
+ RETURN_FALSE;
+ }
+
+ RETURN_LONG(uday * 24 * 3600);
+}
+/* }}} */
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: sw=4 ts=4 fdm=marker
+ * vim<600: sw=4 ts=4
+ */
diff --git a/ext/calendar/calendar.c b/ext/calendar/calendar.c
new file mode 100644
index 0000000..010b8d4
--- /dev/null
+++ b/ext/calendar/calendar.c
@@ -0,0 +1,755 @@
+/*
+ +----------------------------------------------------------------------+
+ | PHP Version 5 |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 1997-2013 The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Authors: Shane Caraveo <shane@caraveo.com> |
+ | Colin Viebrock <colin@easydns.com> |
+ | Hartmut Holzgraefe <hholzgra@php.net> |
+ | Wez Furlong <wez@thebrainroom.com> |
+ +----------------------------------------------------------------------+
+ */
+/* $Id$ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef PHP_WIN32
+#define _WINNLS_
+#endif
+
+#include "php.h"
+#include "ext/standard/info.h"
+#include "php_calendar.h"
+#include "sdncal.h"
+
+#include <stdio.h>
+
+/* {{{ arginfo */
+ZEND_BEGIN_ARG_INFO_EX(arginfo_unixtojd, 0, 0, 0)
+ ZEND_ARG_INFO(0, timestamp)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_jdtounix, 0)
+ ZEND_ARG_INFO(0, jday)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_cal_info, 0, 0, 0)
+ ZEND_ARG_INFO(0, calendar)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_cal_days_in_month, 0)
+ ZEND_ARG_INFO(0, calendar)
+ ZEND_ARG_INFO(0, month)
+ ZEND_ARG_INFO(0, year)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_cal_to_jd, 0)
+ ZEND_ARG_INFO(0, calendar)
+ ZEND_ARG_INFO(0, month)
+ ZEND_ARG_INFO(0, day)
+ ZEND_ARG_INFO(0, year)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_cal_from_jd, 0)
+ ZEND_ARG_INFO(0, jd)
+ ZEND_ARG_INFO(0, calendar)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_jdtogregorian, 0)
+ ZEND_ARG_INFO(0, juliandaycount)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_gregoriantojd, 0)
+ ZEND_ARG_INFO(0, month)
+ ZEND_ARG_INFO(0, day)
+ ZEND_ARG_INFO(0, year)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_jdtojulian, 0)
+ ZEND_ARG_INFO(0, juliandaycount)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_juliantojd, 0)
+ ZEND_ARG_INFO(0, month)
+ ZEND_ARG_INFO(0, day)
+ ZEND_ARG_INFO(0, year)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_jdtojewish, 0, 0, 1)
+ ZEND_ARG_INFO(0, juliandaycount)
+ ZEND_ARG_INFO(0, hebrew)
+ ZEND_ARG_INFO(0, fl)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_jewishtojd, 0)
+ ZEND_ARG_INFO(0, month)
+ ZEND_ARG_INFO(0, day)
+ ZEND_ARG_INFO(0, year)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_jdtofrench, 0)
+ ZEND_ARG_INFO(0, juliandaycount)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_frenchtojd, 0)
+ ZEND_ARG_INFO(0, month)
+ ZEND_ARG_INFO(0, day)
+ ZEND_ARG_INFO(0, year)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_jddayofweek, 0, 0, 1)
+ ZEND_ARG_INFO(0, juliandaycount)
+ ZEND_ARG_INFO(0, mode)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_jdmonthname, 0)
+ ZEND_ARG_INFO(0, juliandaycount)
+ ZEND_ARG_INFO(0, mode)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_easter_date, 0, 0, 0)
+ ZEND_ARG_INFO(0, year)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_easter_days, 0, 0, 0)
+ ZEND_ARG_INFO(0, year)
+ ZEND_ARG_INFO(0, method)
+ZEND_END_ARG_INFO()
+
+/* }}} */
+
+const zend_function_entry calendar_functions[] = {
+ PHP_FE(jdtogregorian, arginfo_jdtogregorian)
+ PHP_FE(gregoriantojd, arginfo_gregoriantojd)
+ PHP_FE(jdtojulian, arginfo_jdtojulian)
+ PHP_FE(juliantojd, arginfo_juliantojd)
+ PHP_FE(jdtojewish, arginfo_jdtojewish)
+ PHP_FE(jewishtojd, arginfo_jewishtojd)
+ PHP_FE(jdtofrench, arginfo_jdtofrench)
+ PHP_FE(frenchtojd, arginfo_frenchtojd)
+ PHP_FE(jddayofweek, arginfo_jddayofweek)
+ PHP_FE(jdmonthname, arginfo_jdmonthname)
+ PHP_FE(easter_date, arginfo_easter_date)
+ PHP_FE(easter_days, arginfo_easter_days)
+ PHP_FE(unixtojd, arginfo_unixtojd)
+ PHP_FE(jdtounix, arginfo_jdtounix)
+ PHP_FE(cal_to_jd, arginfo_cal_to_jd)
+ PHP_FE(cal_from_jd, arginfo_cal_from_jd)
+ PHP_FE(cal_days_in_month, arginfo_cal_days_in_month)
+ PHP_FE(cal_info, arginfo_cal_info)
+ PHP_FE_END
+};
+
+
+zend_module_entry calendar_module_entry = {
+ STANDARD_MODULE_HEADER,
+ "calendar",
+ calendar_functions,
+ PHP_MINIT(calendar),
+ NULL,
+ NULL,
+ NULL,
+ PHP_MINFO(calendar),
+ NO_VERSION_YET,
+ STANDARD_MODULE_PROPERTIES,
+};
+
+#ifdef COMPILE_DL_CALENDAR
+ZEND_GET_MODULE(calendar)
+#endif
+
+/* this order must match the conversion table below */
+enum cal_name_type_t {
+ CAL_GREGORIAN = 0,
+ CAL_JULIAN,
+ CAL_JEWISH,
+ CAL_FRENCH,
+ CAL_NUM_CALS
+};
+
+typedef long int (*cal_to_jd_func_t) (int month, int day, int year);
+typedef void (*cal_from_jd_func_t) (long int jd, int *year, int *month, int *day);
+typedef char *(*cal_as_string_func_t) (int year, int month, int day);
+
+struct cal_entry_t {
+ char *name;
+ char *symbol;
+ cal_to_jd_func_t to_jd;
+ cal_from_jd_func_t from_jd;
+ int num_months;
+ int max_days_in_month;
+ char **month_name_short;
+ char **month_name_long;
+};
+
+static struct cal_entry_t cal_conversion_table[CAL_NUM_CALS] = {
+ {"Gregorian", "CAL_GREGORIAN", GregorianToSdn, SdnToGregorian, 12, 31,
+ MonthNameShort, MonthNameLong},
+ {"Julian", "CAL_JULIAN", JulianToSdn, SdnToJulian, 12, 31,
+ MonthNameShort, MonthNameLong},
+ {"Jewish", "CAL_JEWISH", JewishToSdn, SdnToJewish, 13, 30,
+ JewishMonthName, JewishMonthName},
+ {"French", "CAL_FRENCH", FrenchToSdn, SdnToFrench, 13, 30,
+ FrenchMonthName, FrenchMonthName}
+};
+
+/* For jddayofweek */
+enum { CAL_DOW_DAYNO, CAL_DOW_SHORT, CAL_DOW_LONG };
+
+/* For jdmonthname */
+enum { CAL_MONTH_GREGORIAN_SHORT, CAL_MONTH_GREGORIAN_LONG,
+ CAL_MONTH_JULIAN_SHORT, CAL_MONTH_JULIAN_LONG, CAL_MONTH_JEWISH,
+ CAL_MONTH_FRENCH
+};
+
+/* for heb_number_to_chars */
+static char alef_bet[25] = "0אבגדהוזחטיכלמנסעפצקרשת";
+
+#define CAL_JEWISH_ADD_ALAFIM_GERESH 0x2
+#define CAL_JEWISH_ADD_ALAFIM 0x4
+#define CAL_JEWISH_ADD_GERESHAYIM 0x8
+
+PHP_MINIT_FUNCTION(calendar)
+{
+ REGISTER_LONG_CONSTANT("CAL_GREGORIAN", CAL_GREGORIAN, CONST_CS | CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("CAL_JULIAN", CAL_JULIAN, CONST_CS | CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("CAL_JEWISH", CAL_JEWISH, CONST_CS | CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("CAL_FRENCH", CAL_FRENCH, CONST_CS | CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("CAL_NUM_CALS", CAL_NUM_CALS, CONST_CS | CONST_PERSISTENT);
+/* constants for jddayofweek */
+ REGISTER_LONG_CONSTANT("CAL_DOW_DAYNO", CAL_DOW_DAYNO, CONST_CS | CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("CAL_DOW_SHORT", CAL_DOW_SHORT, CONST_CS | CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("CAL_DOW_LONG", CAL_DOW_LONG, CONST_CS | CONST_PERSISTENT);
+/* constants for jdmonthname */
+ REGISTER_LONG_CONSTANT("CAL_MONTH_GREGORIAN_SHORT", CAL_MONTH_GREGORIAN_SHORT, CONST_CS | CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("CAL_MONTH_GREGORIAN_LONG", CAL_MONTH_GREGORIAN_LONG, CONST_CS | CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("CAL_MONTH_JULIAN_SHORT", CAL_MONTH_JULIAN_SHORT, CONST_CS | CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("CAL_MONTH_JULIAN_LONG", CAL_MONTH_JULIAN_LONG, CONST_CS | CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("CAL_MONTH_JEWISH", CAL_MONTH_JEWISH, CONST_CS | CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("CAL_MONTH_FRENCH", CAL_MONTH_FRENCH, CONST_CS | CONST_PERSISTENT);
+/* constants for easter calculation */
+ REGISTER_LONG_CONSTANT("CAL_EASTER_DEFAULT", CAL_EASTER_DEFAULT, CONST_CS | CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("CAL_EASTER_ROMAN", CAL_EASTER_ROMAN, CONST_CS | CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("CAL_EASTER_ALWAYS_GREGORIAN", CAL_EASTER_ALWAYS_GREGORIAN, CONST_CS | CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("CAL_EASTER_ALWAYS_JULIAN", CAL_EASTER_ALWAYS_JULIAN, CONST_CS | CONST_PERSISTENT);
+/* constants for Jewish date formatting */
+ REGISTER_LONG_CONSTANT("CAL_JEWISH_ADD_ALAFIM_GERESH", CAL_JEWISH_ADD_ALAFIM_GERESH, CONST_CS | CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("CAL_JEWISH_ADD_ALAFIM", CAL_JEWISH_ADD_ALAFIM, CONST_CS | CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("CAL_JEWISH_ADD_GERESHAYIM", CAL_JEWISH_ADD_GERESHAYIM, CONST_CS | CONST_PERSISTENT);
+ return SUCCESS;
+}
+
+PHP_MINFO_FUNCTION(calendar)
+{
+ php_info_print_table_start();
+ php_info_print_table_row(2, "Calendar support", "enabled");
+ php_info_print_table_end();
+}
+
+static void _php_cal_info(int cal, zval **ret)
+{
+ zval *months, *smonths;
+ int i;
+ struct cal_entry_t *calendar;
+
+ calendar = &cal_conversion_table[cal];
+ array_init(*ret);
+
+ MAKE_STD_ZVAL(months);
+ MAKE_STD_ZVAL(smonths);
+ array_init(months);
+ array_init(smonths);
+
+ for (i = 1; i <= calendar->num_months; i++) {
+ add_index_string(months, i, calendar->month_name_long[i], 1);
+ add_index_string(smonths, i, calendar->month_name_short[i], 1);
+ }
+ add_assoc_zval(*ret, "months", months);
+ add_assoc_zval(*ret, "abbrevmonths", smonths);
+ add_assoc_long(*ret, "maxdaysinmonth", calendar->max_days_in_month);
+ add_assoc_string(*ret, "calname", calendar->name, 1);
+ add_assoc_string(*ret, "calsymbol", calendar->symbol, 1);
+
+}
+
+/* {{{ proto array cal_info([int calendar])
+ Returns information about a particular calendar */
+PHP_FUNCTION(cal_info)
+{
+ long cal = -1;
+
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l", &cal) == FAILURE) {
+ RETURN_FALSE;
+ }
+
+ if (cal == -1) {
+ int i;
+ zval *val;
+
+ array_init(return_value);
+
+ for (i = 0; i < CAL_NUM_CALS; i++) {
+ MAKE_STD_ZVAL(val);
+ _php_cal_info(i, &val);
+ add_index_zval(return_value, i, val);
+ }
+ return;
+ }
+
+
+ if (cal != -1 && (cal < 0 || cal >= CAL_NUM_CALS)) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "invalid calendar ID %ld.", cal);
+ RETURN_FALSE;
+ }
+
+ _php_cal_info(cal, &return_value);
+
+}
+/* }}} */
+
+/* {{{ proto int cal_days_in_month(int calendar, int month, int year)
+ Returns the number of days in a month for a given year and calendar */
+PHP_FUNCTION(cal_days_in_month)
+{
+ long cal, month, year;
+ struct cal_entry_t *calendar;
+ long sdn_start, sdn_next;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lll", &cal, &month, &year) == FAILURE) {
+ RETURN_FALSE;
+ }
+
+ if (cal < 0 || cal >= CAL_NUM_CALS) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "invalid calendar ID %ld.", cal);
+ RETURN_FALSE;
+ }
+
+ calendar = &cal_conversion_table[cal];
+
+ sdn_start = calendar->to_jd(year, month, 1);
+
+ if (sdn_start == 0) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "invalid date.");
+ RETURN_FALSE;
+ }
+
+ sdn_next = calendar->to_jd(year, 1 + month, 1);
+
+ if (sdn_next == 0) {
+ /* If the next month is invalid, then we need to try the first month of
+ * the next year, bearing in mind that the next year after 1 BCE is
+ * actually 1 AD and not 0. */
+ if (year == -1) {
+ sdn_next = calendar->to_jd(1, 1, 1);
+ }
+ else {
+ sdn_next = calendar->to_jd(year + 1, 1, 1);
+ }
+ }
+
+ RETURN_LONG(sdn_next - sdn_start);
+}
+/* }}} */
+
+/* {{{ proto int cal_to_jd(int calendar, int month, int day, int year)
+ Converts from a supported calendar to Julian Day Count */
+PHP_FUNCTION(cal_to_jd)
+{
+ long cal, month, day, year;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "llll", &cal, &month, &day, &year) != SUCCESS) {
+ RETURN_FALSE;
+ }
+
+ if (cal < 0 || cal >= CAL_NUM_CALS) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "invalid calendar ID %ld.", cal);
+ RETURN_FALSE;
+ }
+
+ RETURN_LONG(cal_conversion_table[cal].to_jd(year, month, day));
+}
+/* }}} */
+
+/* {{{ proto array cal_from_jd(int jd, int calendar)
+ Converts from Julian Day Count to a supported calendar and return extended information */
+PHP_FUNCTION(cal_from_jd)
+{
+ long jd, cal;
+ int month, day, year, dow;
+ char date[16];
+ struct cal_entry_t *calendar;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS()TSRMLS_CC, "ll", &jd, &cal) == FAILURE) {
+ RETURN_FALSE;
+ }
+
+ if (cal < 0 || cal >= CAL_NUM_CALS) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "invalid calendar ID %ld", cal);
+ RETURN_FALSE;
+ }
+ calendar = &cal_conversion_table[cal];
+
+ array_init(return_value);
+
+ calendar->from_jd(jd, &year, &month, &day);
+
+ snprintf(date, sizeof(date), "%i/%i/%i", month, day, year);
+ add_assoc_string(return_value, "date", date, 1);
+
+ add_assoc_long(return_value, "month", month);
+ add_assoc_long(return_value, "day", day);
+ add_assoc_long(return_value, "year", year);
+
+/* day of week */
+ dow = DayOfWeek(jd);
+ add_assoc_long(return_value, "dow", dow);
+ add_assoc_string(return_value, "abbrevdayname", DayNameShort[dow], 1);
+ add_assoc_string(return_value, "dayname", DayNameLong[dow], 1);
+/* month name */
+ add_assoc_string(return_value, "abbrevmonth", calendar->month_name_short[month], 1);
+ add_assoc_string(return_value, "monthname", calendar->month_name_long[month], 1);
+}
+/* }}} */
+
+/* {{{ proto string jdtogregorian(int juliandaycount)
+ Converts a julian day count to a gregorian calendar date */
+PHP_FUNCTION(jdtogregorian)
+{
+ long julday;
+ int year, month, day;
+ char date[16];
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &julday) == FAILURE) {
+ RETURN_FALSE;
+ }
+
+ SdnToGregorian(julday, &year, &month, &day);
+ snprintf(date, sizeof(date), "%i/%i/%i", month, day, year);
+
+ RETURN_STRING(date, 1);
+}
+/* }}} */
+
+/* {{{ proto int gregoriantojd(int month, int day, int year)
+ Converts a gregorian calendar date to julian day count */
+PHP_FUNCTION(gregoriantojd)
+{
+ long year, month, day;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lll", &month, &day, &year) == FAILURE) {
+ RETURN_FALSE;
+ }
+
+ RETURN_LONG(GregorianToSdn(year, month, day));
+}
+/* }}} */
+
+/* {{{ proto string jdtojulian(int juliandaycount)
+ Convert a julian day count to a julian calendar date */
+PHP_FUNCTION(jdtojulian)
+{
+ long julday;
+ int year, month, day;
+ char date[16];
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &julday) == FAILURE) {
+ RETURN_FALSE;
+ }
+
+ SdnToJulian(julday, &year, &month, &day);
+ snprintf(date, sizeof(date), "%i/%i/%i", month, day, year);
+
+ RETURN_STRING(date, 1);
+}
+/* }}} */
+
+/* {{{ proto int juliantojd(int month, int day, int year)
+ Converts a julian calendar date to julian day count */
+PHP_FUNCTION(juliantojd)
+{
+ long year, month, day;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lll", &month, &day, &year) == FAILURE) {
+ RETURN_FALSE;
+ }
+
+ RETURN_LONG(JulianToSdn(year, month, day));
+}
+/* }}} */
+
+/* {{{ heb_number_to_chars*/
+/*
+caution: the Hebrew format produces non unique result.
+for example both: year '5' and year '5000' produce 'ה'.
+use the numeric one for calculations.
+ */
+static char *heb_number_to_chars(int n, int fl, char **ret)
+{
+ char *p, old[18], *endofalafim;
+
+ p = endofalafim = old;
+/*
+ prevents the option breaking the jewish beliefs, and some other
+ critical resources ;)
+ */
+ if (n > 9999 || n < 1) {
+ *ret = NULL;
+ return NULL;
+ }
+
+/* alafim (thousands) case */
+ if (n / 1000) {
+ *p = alef_bet[n / 1000];
+ p++;
+
+ if (CAL_JEWISH_ADD_ALAFIM_GERESH & fl) {
+ *p = '\'';
+ p++;
+ }
+ if (CAL_JEWISH_ADD_ALAFIM & fl) {
+ strcpy(p, " אלפים ");
+ p += 7;
+ }
+
+ endofalafim = p;
+ n = n % 1000;
+ }
+
+/* tav-tav (tav=400) case */
+ while (n >= 400) {
+ *p = alef_bet[22];
+ p++;
+ n -= 400;
+ }
+
+/* meot (hundreads) case */
+ if (n >= 100) {
+ *p = alef_bet[18 + n / 100];
+ p++;
+ n = n % 100;
+ }
+
+/* tet-vav & tet-zain case (special case for 15 and 16) */
+ if (n == 15 || n == 16) {
+ *p = alef_bet[9];
+ p++;
+ *p = alef_bet[n - 9];
+ p++;
+ } else {
+/* asarot (tens) case */
+ if (n >= 10) {
+ *p = alef_bet[9 + n / 10];
+ p++;
+ n = n % 10;
+ }
+
+/* yehidot (ones) case */
+ if (n > 0) {
+ *p = alef_bet[n];
+ p++;
+ }
+ }
+
+ if (CAL_JEWISH_ADD_GERESHAYIM & fl) {
+ switch (p - endofalafim) {
+ case 0:
+ break;
+ case 1:
+ *p = '\'';
+ p++;
+ break;
+ default:
+ *(p) = *(p - 1);
+ *(p - 1) = '"';
+ p++;
+ }
+ }
+
+ *p = '\0';
+ *ret = estrndup(old, (p - old) + 1);
+ p = *ret;
+ return p;
+}
+/* }}} */
+
+/* {{{ proto string jdtojewish(int juliandaycount [, bool hebrew [, int fl]])
+ Converts a julian day count to a jewish calendar date */
+PHP_FUNCTION(jdtojewish)
+{
+ long julday, fl = 0;
+ zend_bool heb = 0;
+ int year, month, day;
+ char date[16], hebdate[32];
+ char *dayp, *yearp;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l|bl", &julday, &heb, &fl) == FAILURE) {
+ RETURN_FALSE;
+ }
+
+ SdnToJewish(julday, &year, &month, &day);
+ if (!heb) {
+ snprintf(date, sizeof(date), "%i/%i/%i", month, day, year);
+ RETURN_STRING(date, 1);
+ } else {
+ if (year <= 0 || year > 9999) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Year out of range (0-9999).");
+ RETURN_FALSE;
+ }
+
+ snprintf(hebdate, sizeof(hebdate), "%s %s %s", heb_number_to_chars(day, fl, &dayp), JewishMonthHebName[month], heb_number_to_chars(year, fl, &yearp));
+
+ if (dayp) {
+ efree(dayp);
+ }
+ if (yearp) {
+ efree(yearp);
+ }
+
+ RETURN_STRING(hebdate, 1);
+
+ }
+}
+/* }}} */
+
+/* {{{ proto int jewishtojd(int month, int day, int year)
+ Converts a jewish calendar date to a julian day count */
+PHP_FUNCTION(jewishtojd)
+{
+ long year, month, day;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lll", &month, &day, &year) == FAILURE) {
+ RETURN_FALSE;
+ }
+
+ RETURN_LONG(JewishToSdn(year, month, day));
+}
+/* }}} */
+
+/* {{{ proto string jdtofrench(int juliandaycount)
+ Converts a julian day count to a french republic calendar date */
+PHP_FUNCTION(jdtofrench)
+{
+ long julday;
+ int year, month, day;
+ char date[16];
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &julday) == FAILURE) {
+ RETURN_FALSE;
+ }
+
+ SdnToFrench(julday, &year, &month, &day);
+ snprintf(date, sizeof(date), "%i/%i/%i", month, day, year);
+
+ RETURN_STRING(date, 1);
+}
+/* }}} */
+
+/* {{{ proto int frenchtojd(int month, int day, int year)
+ Converts a french republic calendar date to julian day count */
+PHP_FUNCTION(frenchtojd)
+{
+ long year, month, day;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lll", &month, &day, &year) == FAILURE) {
+ RETURN_FALSE;
+ }
+
+ RETURN_LONG(FrenchToSdn(year, month, day));
+}
+/* }}} */
+
+/* {{{ proto mixed jddayofweek(int juliandaycount [, int mode])
+ Returns name or number of day of week from julian day count */
+PHP_FUNCTION(jddayofweek)
+{
+ long julday, mode = CAL_DOW_DAYNO;
+ int day;
+ char *daynamel, *daynames;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l|l", &julday, &mode) == FAILURE) {
+ RETURN_FALSE;
+ }
+
+ day = DayOfWeek(julday);
+ daynamel = DayNameLong[day];
+ daynames = DayNameShort[day];
+
+ switch (mode) {
+ case CAL_DOW_SHORT:
+ RETURN_STRING(daynamel, 1);
+ break;
+ case CAL_DOW_LONG:
+ RETURN_STRING(daynames, 1);
+ break;
+ case CAL_DOW_DAYNO:
+ default:
+ RETURN_LONG(day);
+ break;
+ }
+}
+/* }}} */
+
+/* {{{ proto string jdmonthname(int juliandaycount, int mode)
+ Returns name of month for julian day count */
+PHP_FUNCTION(jdmonthname)
+{
+ long julday, mode;
+ char *monthname = NULL;
+ int month, day, year;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ll", &julday, &mode) == FAILURE) {
+ RETURN_FALSE;
+ }
+
+ switch (mode) {
+ case CAL_MONTH_GREGORIAN_LONG: /* gregorian or julian month */
+ SdnToGregorian(julday, &year, &month, &day);
+ monthname = MonthNameLong[month];
+ break;
+ case CAL_MONTH_JULIAN_SHORT: /* gregorian or julian month */
+ SdnToJulian(julday, &year, &month, &day);
+ monthname = MonthNameShort[month];
+ break;
+ case CAL_MONTH_JULIAN_LONG: /* gregorian or julian month */
+ SdnToJulian(julday, &year, &month, &day);
+ monthname = MonthNameLong[month];
+ break;
+ case CAL_MONTH_JEWISH: /* jewish month */
+ SdnToJewish(julday, &year, &month, &day);
+ monthname = JewishMonthName[month];
+ break;
+ case CAL_MONTH_FRENCH: /* french month */
+ SdnToFrench(julday, &year, &month, &day);
+ monthname = FrenchMonthName[month];
+ break;
+ default: /* default gregorian */
+ case CAL_MONTH_GREGORIAN_SHORT: /* gregorian or julian month */
+ SdnToGregorian(julday, &year, &month, &day);
+ monthname = MonthNameShort[month];
+ break;
+ }
+
+ RETURN_STRING(monthname, 1);
+}
+/* }}} */
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: sw=4 ts=4 fdm=marker
+ * vim<600: sw=4 ts=4
+ */
diff --git a/ext/calendar/config.m4 b/ext/calendar/config.m4
new file mode 100644
index 0000000..a80101a
--- /dev/null
+++ b/ext/calendar/config.m4
@@ -0,0 +1,11 @@
+dnl
+dnl $Id$
+dnl
+
+PHP_ARG_ENABLE(calendar,whether to enable calendar conversion support,
+[ --enable-calendar Enable support for calendar conversion])
+
+if test "$PHP_CALENDAR" = "yes"; then
+ AC_DEFINE(HAVE_CALENDAR,1,[ ])
+ PHP_NEW_EXTENSION(calendar, calendar.c dow.c french.c gregor.c jewish.c julian.c easter.c cal_unix.c, $ext_shared)
+fi
diff --git a/ext/calendar/config.w32 b/ext/calendar/config.w32
new file mode 100644
index 0000000..bd9faba
--- /dev/null
+++ b/ext/calendar/config.w32
@@ -0,0 +1,10 @@
+// $Id$
+// vim:ft=javascript
+
+ARG_ENABLE("calendar", "calendar conversion support", "yes");
+
+if (PHP_CALENDAR == "yes") {
+ EXTENSION("calendar", "calendar.c dow.c french.c gregor.c jewish.c \
+ julian.c easter.c cal_unix.c");
+ AC_DEFINE('HAVE_CALENDAR', 1, 'Have calendar');
+}
diff --git a/ext/calendar/dow.c b/ext/calendar/dow.c
new file mode 100644
index 0000000..64ae008
--- /dev/null
+++ b/ext/calendar/dow.c
@@ -0,0 +1,76 @@
+
+/* $selId: dow.c,v 2.0 1995/10/24 01:13:06 lees Exp $
+ * Copyright 1993-1995, Scott E. Lee, all rights reserved.
+ * Permission granted to use, copy, modify, distribute and sell so long as
+ * the above copyright and this permission statement are retained in all
+ * copies. THERE IS NO WARRANTY - USE AT YOUR OWN RISK.
+ */
+
+/**************************************************************************
+ *
+ * These are the externally visible components of this file:
+ *
+ * int
+ * DayOfWeek(
+ * long int sdn);
+ *
+ * Convert a SDN to a day-of-week number (0 to 6). Where 0 stands for
+ * Sunday, 1 for Monday, etc. and 6 stands for Saturday.
+ *
+ * char *DayNameShort[7];
+ *
+ * Convert a day-of-week number (0 to 6), as returned from DayOfWeek(), to
+ * the abbreviated (three character) name of the day.
+ *
+ * char *DayNameLong[7];
+ *
+ * Convert a day-of-week number (0 to 6), as returned from DayOfWeek(), to
+ * the name of the day.
+ *
+ **************************************************************************/
+
+#include "sdncal.h"
+
+int DayOfWeek(
+ long int sdn)
+{
+ int dow;
+
+ dow = (sdn + 1) % 7;
+ if (dow >= 0) {
+ return (dow);
+ } else {
+ return (dow + 7);
+ }
+}
+
+char *DayNameShort[7] =
+{
+ "Sun",
+ "Mon",
+ "Tue",
+ "Wed",
+ "Thu",
+ "Fri",
+ "Sat"
+};
+
+char *DayNameLong[7] =
+{
+ "Sunday",
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday"
+};
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: sw=4 ts=4 fdm=marker
+ * vim<600: sw=4 ts=4
+ */
diff --git a/ext/calendar/easter.c b/ext/calendar/easter.c
new file mode 100644
index 0000000..1948ff9
--- /dev/null
+++ b/ext/calendar/easter.c
@@ -0,0 +1,145 @@
+/*
+ +----------------------------------------------------------------------+
+ | PHP Version 5 |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 1997-2013 The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Authors: Shane Caraveo <shane@caraveo.com> |
+ | Colin Viebrock <colin@easydns.com> |
+ | Hartmut Holzgraefe <hholzgra@php.net> |
+ +----------------------------------------------------------------------+
+ */
+/* $Id: */
+
+#include "php.h"
+#include "php_calendar.h"
+#include "sdncal.h"
+#include <time.h>
+
+static void _cal_easter(INTERNAL_FUNCTION_PARAMETERS, int gm)
+{
+
+ /* based on code by Simon Kershaw, <webmaster@ely.anglican.org> */
+
+ struct tm te;
+ long year, golden, solar, lunar, pfm, dom, tmp, easter;
+ long method = CAL_EASTER_DEFAULT;
+
+ /* Default to the current year if year parameter is not given */
+ {
+ time_t a;
+ struct tm b, *res;
+ time(&a);
+ res = php_localtime_r(&a, &b);
+ if (!res) {
+ year = 1900;
+ } else {
+ year = 1900 + b.tm_year;
+ }
+ }
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+ "|ll", &year, &method) == FAILURE) {
+ return;
+ }
+
+ if (gm && (year<1970 || year>2037)) { /* out of range for timestamps */
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "This function is only valid for years between 1970 and 2037 inclusive");
+ RETURN_FALSE;
+ }
+
+ golden = (year % 19) + 1; /* the Golden number */
+
+ if ((year <= 1582 && method != CAL_EASTER_ALWAYS_GREGORIAN) ||
+ (year >= 1583 && year <= 1752 && method != CAL_EASTER_ROMAN && method != CAL_EASTER_ALWAYS_GREGORIAN) ||
+ method == CAL_EASTER_ALWAYS_JULIAN) { /* JULIAN CALENDAR */
+
+ dom = (year + (year/4) + 5) % 7; /* the "Dominical number" - finding a Sunday */
+ if (dom < 0) {
+ dom += 7;
+ }
+
+ pfm = (3 - (11*golden) - 7) % 30; /* uncorrected date of the Paschal full moon */
+ if (pfm < 0) {
+ pfm += 30;
+ }
+ } else { /* GREGORIAN CALENDAR */
+ dom = (year + (year/4) - (year/100) + (year/400)) % 7; /* the "Domincal number" */
+ if (dom < 0) {
+ dom += 7;
+ }
+
+ solar = (year-1600)/100 - (year-1600)/400; /* the solar and lunar corrections */
+ lunar = (((year-1400) / 100) * 8) / 25;
+
+ pfm = (3 - (11*golden) + solar - lunar) % 30; /* uncorrected date of the Paschal full moon */
+ if (pfm < 0) {
+ pfm += 30;
+ }
+ }
+
+ if ((pfm == 29) || (pfm == 28 && golden > 11)) { /* corrected date of the Paschal full moon */
+ pfm--; /* - days after 21st March */
+ }
+
+ tmp = (4-pfm-dom) % 7;
+ if (tmp < 0) {
+ tmp += 7;
+ }
+
+ easter = pfm + tmp + 1; /* Easter as the number of days after 21st March */
+
+ if (gm) { /* return a timestamp */
+ te.tm_isdst = -1;
+ te.tm_year = year-1900;
+ te.tm_sec = 0;
+ te.tm_min = 0;
+ te.tm_hour = 0;
+
+ if (easter < 11) {
+ te.tm_mon = 2; /* March */
+ te.tm_mday = easter+21;
+ } else {
+ te.tm_mon = 3; /* April */
+ te.tm_mday = easter-10;
+ }
+
+ Z_LVAL_P(return_value) = mktime(&te);
+ } else { /* return the days after March 21 */
+ Z_LVAL_P(return_value) = easter;
+ }
+
+ Z_TYPE_P(return_value) = IS_LONG;
+
+}
+
+/* {{{ proto int easter_date([int year])
+ Return the timestamp of midnight on Easter of a given year (defaults to current year) */
+PHP_FUNCTION(easter_date)
+{
+ _cal_easter(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
+}
+/* }}} */
+
+/* {{{ proto int easter_days([int year, [int method]])
+ Return the number of days after March 21 that Easter falls on for a given year (defaults to current year) */
+PHP_FUNCTION(easter_days)
+{
+ _cal_easter(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
+}
+/* }}} */
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/ext/calendar/french.c b/ext/calendar/french.c
new file mode 100644
index 0000000..5b4dd53
--- /dev/null
+++ b/ext/calendar/french.c
@@ -0,0 +1,160 @@
+/* $selId: french.c,v 2.0 1995/10/24 01:13:06 lees Exp $
+ * Copyright 1993-1995, Scott E. Lee, all rights reserved.
+ * Permission granted to use, copy, modify, distribute and sell so long as
+ * the above copyright and this permission statement are retained in all
+ * copies. THERE IS NO WARRANTY - USE AT YOUR OWN RISK.
+ */
+
+/**************************************************************************
+ *
+ * These are the externally visible components of this file:
+ *
+ * void
+ * SdnToFrench(
+ * long int sdn,
+ * int *pYear,
+ * int *pMonth,
+ * int *pDay);
+ *
+ * Convert a SDN to a French republican calendar date. If the input SDN is
+ * before the first day of year 1 or after the last day of year 14, the
+ * three output values will all be set to zero, otherwise *pYear will be in
+ * the range 1 to 14 inclusive; *pMonth will be in the range 1 to 13
+ * inclusive; *pDay will be in the range 1 to 30 inclusive. If *pMonth is
+ * 13, the SDN represents one of the holidays at the end of the year and
+ * *pDay will be in the range 1 to 6 inclusive.
+ *
+ * long int
+ * FrenchToSdn(
+ * int year,
+ * int month,
+ * int day);
+ *
+ * Convert a French republican calendar date to a SDN. Zero is returned
+ * when the input date is detected as invalid or out of the supported
+ * range. The return value will be > 0 for all valid, supported dates, but
+ * there are some invalid dates that will return a positive value. To
+ * verify that a date is valid, convert it to SDN and then back and compare
+ * with the original.
+ *
+ * char *FrenchMonthName[14];
+ *
+ * Convert a French republican month number (1 to 13) to the name of the
+ * French republican month (null terminated). An index of 13 (for the
+ * "extra" days at the end of the year) will return the string "Extra". An
+ * index of zero will return a zero length string.
+ *
+ * VALID RANGE
+ *
+ * These routines only convert dates in years 1 through 14 (Gregorian
+ * dates 22 September 1792 through 22 September 1806). This more than
+ * covers the period when the calendar was in use.
+ *
+ * I would support a wider range of dates, but I have not been able to
+ * find an authoritative definition of when leap years were to have
+ * occurred. There are suggestions that it was to skip a leap year ever
+ * 100 years like the Gregorian calendar.
+ *
+ * CALENDAR OVERVIEW
+ *
+ * The French republican calendar was adopted in October 1793 during
+ * the French Revolution and was abandoned in January 1806. The intent
+ * was to create a new calendar system that was based on scientific
+ * principals, not religious traditions.
+ *
+ * The year is divided into 12 months of 30 days each. The remaining 5
+ * to 6 days in the year are grouped at the end and are holidays. Each
+ * month is divided into three decades (instead of weeks) of 10 days
+ * each.
+ *
+ * The epoch (first day of the first year) is 22 September 1792 in the
+ * Gregorian calendar. Leap years are every fourth year (year 3, 7,
+ * 11, etc.)
+ *
+ * TESTING
+ *
+ * This algorithm has been tested from the year 1 to 14. The source
+ * code of the verification program is included in this package.
+ *
+ * REFERENCES
+ *
+ * I have found no detailed, authoritative reference on this calendar.
+ * The algorithms are based on a preponderance of less authoritative
+ * sources.
+ *
+ **************************************************************************/
+
+#include "sdncal.h"
+
+#define FRENCH_SDN_OFFSET 2375474
+#define DAYS_PER_4_YEARS 1461
+#define DAYS_PER_MONTH 30
+#define FIRST_VALID 2375840
+#define LAST_VALID 2380952
+
+void SdnToFrench(
+ long int sdn,
+ int *pYear,
+ int *pMonth,
+ int *pDay)
+{
+ long int temp;
+ int dayOfYear;
+
+ if (sdn < FIRST_VALID || sdn > LAST_VALID) {
+ *pYear = 0;
+ *pMonth = 0;
+ *pDay = 0;
+ return;
+ }
+ temp = (sdn - FRENCH_SDN_OFFSET) * 4 - 1;
+ *pYear = temp / DAYS_PER_4_YEARS;
+ dayOfYear = (temp % DAYS_PER_4_YEARS) / 4;
+ *pMonth = dayOfYear / DAYS_PER_MONTH + 1;
+ *pDay = dayOfYear % DAYS_PER_MONTH + 1;
+}
+
+long int FrenchToSdn(
+ int year,
+ int month,
+ int day)
+{
+ /* check for invalid dates */
+ if (year < 1 || year > 14 ||
+ month < 1 || month > 13 ||
+ day < 1 || day > 30) {
+ return (0);
+ }
+ return ((year * DAYS_PER_4_YEARS) / 4
+ + (month - 1) * DAYS_PER_MONTH
+ + day
+ + FRENCH_SDN_OFFSET);
+}
+
+char *FrenchMonthName[14] =
+{
+ "",
+ "Vendemiaire",
+ "Brumaire",
+ "Frimaire",
+ "Nivose",
+ "Pluviose",
+ "Ventose",
+ "Germinal",
+ "Floreal",
+ "Prairial",
+ "Messidor",
+ "Thermidor",
+ "Fructidor",
+ "Extra"
+};
+
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: sw=4 ts=4 fdm=marker
+ * vim<600: sw=4 ts=4
+ */
diff --git a/ext/calendar/gregor.c b/ext/calendar/gregor.c
new file mode 100644
index 0000000..ebb743f
--- /dev/null
+++ b/ext/calendar/gregor.c
@@ -0,0 +1,270 @@
+/* $selId: gregor.c,v 2.0 1995/10/24 01:13:06 lees Exp $
+ * Copyright 1993-1995, Scott E. Lee, all rights reserved.
+ * Permission granted to use, copy, modify, distribute and sell so long as
+ * the above copyright and this permission statement are retained in all
+ * copies. THERE IS NO WARRANTY - USE AT YOUR OWN RISK.
+ */
+
+/**************************************************************************
+ *
+ * These are the externally visible components of this file:
+ *
+ * void
+ * SdnToGregorian(
+ * long int sdn,
+ * int *pYear,
+ * int *pMonth,
+ * int *pDay);
+ *
+ * Convert a SDN to a Gregorian calendar date. If the input SDN is less
+ * than 1, the three output values will all be set to zero, otherwise
+ * *pYear will be >= -4714 and != 0; *pMonth will be in the range 1 to 12
+ * inclusive; *pDay will be in the range 1 to 31 inclusive.
+ *
+ * long int
+ * GregorianToSdn(
+ * int inputYear,
+ * int inputMonth,
+ * int inputDay);
+ *
+ * Convert a Gregorian calendar date to a SDN. Zero is returned when the
+ * input date is detected as invalid or out of the supported range. The
+ * return value will be > 0 for all valid, supported dates, but there are
+ * some invalid dates that will return a positive value. To verify that a
+ * date is valid, convert it to SDN and then back and compare with the
+ * original.
+ *
+ * char *MonthNameShort[13];
+ *
+ * Convert a Gregorian month number (1 to 12) to the abbreviated (three
+ * character) name of the Gregorian month (null terminated). An index of
+ * zero will return a zero length string.
+ *
+ * char *MonthNameLong[13];
+ *
+ * Convert a Gregorian month number (1 to 12) to the name of the Gregorian
+ * month (null terminated). An index of zero will return a zero length
+ * string.
+ *
+ * VALID RANGE
+ *
+ * 4714 B.C. to at least 10000 A.D.
+ *
+ * Although this software can handle dates all the way back to 4714
+ * B.C., such use may not be meaningful. The Gregorian calendar was
+ * not instituted until October 15, 1582 (or October 5, 1582 in the
+ * Julian calendar). Some countries did not accept it until much
+ * later. For example, Britain converted in 1752, The USSR in 1918 and
+ * Greece in 1923. Most European countries used the Julian calendar
+ * prior to the Gregorian.
+ *
+ * CALENDAR OVERVIEW
+ *
+ * The Gregorian calendar is a modified version of the Julian calendar.
+ * The only difference being the specification of leap years. The
+ * Julian calendar specifies that every year that is a multiple of 4
+ * will be a leap year. This leads to a year that is 365.25 days long,
+ * but the current accepted value for the tropical year is 365.242199
+ * days.
+ *
+ * To correct this error in the length of the year and to bring the
+ * vernal equinox back to March 21, Pope Gregory XIII issued a papal
+ * bull declaring that Thursday October 4, 1582 would be followed by
+ * Friday October 15, 1582 and that centennial years would only be a
+ * leap year if they were a multiple of 400. This shortened the year
+ * by 3 days per 400 years, giving a year of 365.2425 days.
+ *
+ * Another recently proposed change in the leap year rule is to make
+ * years that are multiples of 4000 not a leap year, but this has never
+ * been officially accepted and this rule is not implemented in these
+ * algorithms.
+ *
+ * ALGORITHMS
+ *
+ * The calculations are based on three different cycles: a 400 year
+ * cycle of leap years, a 4 year cycle of leap years and a 5 month
+ * cycle of month lengths.
+ *
+ * The 5 month cycle is used to account for the varying lengths of
+ * months. You will notice that the lengths alternate between 30
+ * and 31 days, except for three anomalies: both July and August
+ * have 31 days, both December and January have 31, and February
+ * is less than 30. Starting with March, the lengths are in a
+ * cycle of 5 months (31, 30, 31, 30, 31):
+ *
+ * Mar 31 days \
+ * Apr 30 days |
+ * May 31 days > First cycle
+ * Jun 30 days |
+ * Jul 31 days /
+ *
+ * Aug 31 days \
+ * Sep 30 days |
+ * Oct 31 days > Second cycle
+ * Nov 30 days |
+ * Dec 31 days /
+ *
+ * Jan 31 days \
+ * Feb 28/9 days |
+ * > Third cycle (incomplete)
+ *
+ * For this reason the calculations (internally) assume that the
+ * year starts with March 1.
+ *
+ * TESTING
+ *
+ * This algorithm has been tested from the year 4714 B.C. to 10000
+ * A.D. The source code of the verification program is included in
+ * this package.
+ *
+ * REFERENCES
+ *
+ * Conversions Between Calendar Date and Julian Day Number by Robert J.
+ * Tantzen, Communications of the Association for Computing Machinery
+ * August 1963. (Also published in Collected Algorithms from CACM,
+ * algorithm number 199).
+ *
+ **************************************************************************/
+
+#include "sdncal.h"
+#include <limits.h>
+
+#define GREGOR_SDN_OFFSET 32045
+#define DAYS_PER_5_MONTHS 153
+#define DAYS_PER_4_YEARS 1461
+#define DAYS_PER_400_YEARS 146097
+
+void SdnToGregorian(
+ long int sdn,
+ int *pYear,
+ int *pMonth,
+ int *pDay)
+{
+ int century;
+ int year;
+ int month;
+ int day;
+ long int temp;
+ int dayOfYear;
+
+ if (sdn <= 0 ||
+ sdn > (LONG_MAX - 4 * GREGOR_SDN_OFFSET) / 4) {
+ goto fail;
+ }
+ temp = (sdn + GREGOR_SDN_OFFSET) * 4 - 1;
+
+ /* Calculate the century (year/100). */
+ century = temp / DAYS_PER_400_YEARS;
+
+ /* Calculate the year and day of year (1 <= dayOfYear <= 366). */
+ temp = ((temp % DAYS_PER_400_YEARS) / 4) * 4 + 3;
+ year = (century * 100) + (temp / DAYS_PER_4_YEARS);
+ dayOfYear = (temp % DAYS_PER_4_YEARS) / 4 + 1;
+
+ /* Calculate the month and day of month. */
+ temp = dayOfYear * 5 - 3;
+ month = temp / DAYS_PER_5_MONTHS;
+ day = (temp % DAYS_PER_5_MONTHS) / 5 + 1;
+
+ /* Convert to the normal beginning of the year. */
+ if (month < 10) {
+ month += 3;
+ } else {
+ year += 1;
+ month -= 9;
+ }
+
+ /* Adjust to the B.C./A.D. type numbering. */
+ year -= 4800;
+ if (year <= 0)
+ year--;
+
+ *pYear = year;
+ *pMonth = month;
+ *pDay = day;
+ return;
+
+fail:
+ *pYear = 0;
+ *pMonth = 0;
+ *pDay = 0;
+}
+
+long int GregorianToSdn(
+ int inputYear,
+ int inputMonth,
+ int inputDay)
+{
+ int year;
+ int month;
+
+ /* check for invalid dates */
+ if (inputYear == 0 || inputYear < -4714 ||
+ inputMonth <= 0 || inputMonth > 12 ||
+ inputDay <= 0 || inputDay > 31) {
+ return (0);
+ }
+ /* check for dates before SDN 1 (Nov 25, 4714 B.C.) */
+ if (inputYear == -4714) {
+ if (inputMonth < 11) {
+ return (0);
+ }
+ if (inputMonth == 11 && inputDay < 25) {
+ return (0);
+ }
+ }
+ /* Make year always a positive number. */
+ if (inputYear < 0) {
+ year = inputYear + 4801;
+ } else {
+ year = inputYear + 4800;
+ }
+
+ /* Adjust the start of the year. */
+ if (inputMonth > 2) {
+ month = inputMonth - 3;
+ } else {
+ month = inputMonth + 9;
+ year--;
+ }
+
+ return (((year / 100) * DAYS_PER_400_YEARS) / 4
+ + ((year % 100) * DAYS_PER_4_YEARS) / 4
+ + (month * DAYS_PER_5_MONTHS + 2) / 5
+ + inputDay
+ - GREGOR_SDN_OFFSET);
+}
+
+char *MonthNameShort[13] =
+{
+ "",
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec"
+};
+
+char *MonthNameLong[13] =
+{
+ "",
+ "January",
+ "February",
+ "March",
+ "April",
+ "May",
+ "June",
+ "July",
+ "August",
+ "September",
+ "October",
+ "November",
+ "December"
+};
diff --git a/ext/calendar/jewish.c b/ext/calendar/jewish.c
new file mode 100644
index 0000000..f4dc7c3
--- /dev/null
+++ b/ext/calendar/jewish.c
@@ -0,0 +1,763 @@
+/* $selId: jewish.c,v 2.0 1995/10/24 01:13:06 lees Exp $
+ * Copyright 1993-1995, Scott E. Lee, all rights reserved.
+ * Permission granted to use, copy, modify, distribute and sell so long as
+ * the above copyright and this permission statement are retained in all
+ * copies. THERE IS NO WARRANTY - USE AT YOUR OWN RISK.
+ */
+
+/**************************************************************************
+ *
+ * These are the externally visible components of this file:
+ *
+ * void
+ * SdnToJewish(
+ * long int sdn,
+ * int *pYear,
+ * int *pMonth,
+ * int *pDay);
+ *
+ * Convert a SDN to a Jewish calendar date. If the input SDN is before the
+ * first day of year 1, the three output values will all be set to zero,
+ * otherwise *pYear will be > 0; *pMonth will be in the range 1 to 13
+ * inclusive; *pDay will be in the range 1 to 30 inclusive. Note that Adar
+ * II is assigned the month number 7 and Elul is always 13.
+ *
+ * long int
+ * JewishToSdn(
+ * int year,
+ * int month,
+ * int day);
+ *
+ * Convert a Jewish calendar date to a SDN. Zero is returned when the
+ * input date is detected as invalid or out of the supported range. The
+ * return value will be > 0 for all valid, supported dates, but there are
+ * some invalid dates that will return a positive value. To verify that a
+ * date is valid, convert it to SDN and then back and compare with the
+ * original.
+ *
+ * char *JewishMonthName[14];
+ *
+ * Convert a Jewish month number (1 to 13) to the name of the Jewish month
+ * (null terminated). An index of zero will return a zero length string.
+ *
+ * VALID RANGE
+ *
+ * Although this software can handle dates all the way back to the year
+ * 1 (3761 B.C.), such use may not be meaningful.
+ *
+ * The Jewish calendar has been in use for several thousand years, but
+ * in the early days there was no formula to determine the start of a
+ * month. A new month was started when the new moon was first
+ * observed.
+ *
+ * It is not clear when the current rule based calendar replaced the
+ * observation based calendar. According to the book "Jewish Calendar
+ * Mystery Dispelled" by George Zinberg, the patriarch Hillel II
+ * published these rules in 358 A.D. But, according to The
+ * Encyclopedia Judaica, Hillel II may have only published the 19 year
+ * rule for determining the occurrence of leap years.
+ *
+ * I have yet to find a specific date when the current set of rules
+ * were known to be in use.
+ *
+ * CALENDAR OVERVIEW
+ *
+ * The Jewish calendar is based on lunar as well as solar cycles. A
+ * month always starts on or near a new moon and has either 29 or 30
+ * days (a lunar cycle is about 29 1/2 days). Twelve of these
+ * alternating 29-30 day months gives a year of 354 days, which is
+ * about 11 1/4 days short of a solar year.
+ *
+ * Since a month is defined to be a lunar cycle (new moon to new moon),
+ * this 11 1/4 day difference cannot be overcome by adding days to a
+ * month as with the Gregorian calendar, so an entire month is
+ * periodically added to the year, making some years 13 months long.
+ *
+ * For astronomical as well as ceremonial reasons, the start of a new
+ * year may be delayed until a day or two after the new moon causing
+ * years to vary in length. Leap years can be from 383 to 385 days and
+ * common years can be from 353 to 355 days. These are the months of
+ * the year and their possible lengths:
+ *
+ * COMMON YEAR LEAP YEAR
+ * 1 Tishri 30 30 30 30 30 30
+ * 2 Heshvan 29 29 30 29 29 30 (variable)
+ * 3 Kislev 29 30 30 29 30 30 (variable)
+ * 4 Tevet 29 29 29 29 29 29
+ * 5 Shevat 30 30 30 30 30 30
+ * 6 Adar I 29 29 29 30 30 30 (variable)
+ * 7 Adar II -- -- -- 29 29 29 (optional)
+ * 8 Nisan 30 30 30 30 30 30
+ * 9 Iyyar 29 29 29 29 29 29
+ * 10 Sivan 30 30 30 30 30 30
+ * 11 Tammuz 29 29 29 29 29 29
+ * 12 Av 30 30 30 30 30 30
+ * 13 Elul 29 29 29 29 29 29
+ * --- --- --- --- --- ---
+ * 353 354 355 383 384 385
+ *
+ * Note that the month names and other words that appear in this file
+ * have multiple possible spellings in the Roman character set. I have
+ * chosen to use the spellings found in the Encyclopedia Judaica.
+ *
+ * Adar II, the month added for leap years, is sometimes referred to as
+ * the 13th month, but I have chosen to assign it the number 7 to keep
+ * the months in chronological order. This may not be consistent with
+ * other numbering schemes.
+ *
+ * Leap years occur in a fixed pattern of 19 years called the metonic
+ * cycle. The 3rd, 6th, 8th, 11th, 14th, 17th and 19th years of this
+ * cycle are leap years. The first metonic cycle starts with Jewish
+ * year 1, or 3761/60 B.C. This is believed to be the year of
+ * creation.
+ *
+ * To construct the calendar for a year, you must first find the length
+ * of the year by determining the first day of the year (Tishri 1, or
+ * Rosh Ha-Shanah) and the first day of the following year. This
+ * selects one of the six possible month length configurations listed
+ * above.
+ *
+ * Finding the first day of the year is the most difficult part.
+ * Finding the date and time of the new moon (or molad) is the first
+ * step. For this purpose, the lunar cycle is assumed to be 29 days 12
+ * hours and 793 halakim. A halakim is 1/1080th of an hour or 3 1/3
+ * seconds. (This assumed value is only about 1/2 second less than the
+ * value used by modern astronomers -- not bad for a number that was
+ * determined so long ago.) The first molad of year 1 occurred on
+ * Sunday at 11:20:11 P.M. This would actually be Monday, because the
+ * Jewish day is considered to begin at sunset.
+ *
+ * Since sunset varies, the day is assumed to begin at 6:00 P.M. for
+ * calendar calculation purposes. So, the first molad was 5 hours 793
+ * halakim after the start of Tishri 1, 0001 (which was Monday
+ * September 7, 4761 B.C. by the Gregorian calendar). All subsequent
+ * molads can be calculated from this starting point by adding the
+ * length of a lunar cycle.
+ *
+ * Once the molad that starts a year is determined the actual start of
+ * the year (Tishri 1) can be determined. Tishri 1 will be the day of
+ * the molad unless it is delayed by one of the following four rules
+ * (called dehiyyot). Each rule can delay the start of the year by one
+ * day, and since rule #1 can combine with one of the other rules, it
+ * can be delayed as much as two days.
+ *
+ * 1. Tishri 1 must never be Sunday, Wednesday or Friday. (This
+ * is largely to prevent certain holidays from occurring on the
+ * day before or after the Sabbath.)
+ *
+ * 2. If the molad occurs on or after noon, Tishri 1 must be
+ * delayed.
+ *
+ * 3. If it is a common (not leap) year and the molad occurs on
+ * Tuesday at or after 3:11:20 A.M., Tishri 1 must be delayed.
+ *
+ * 4. If it is the year following a leap year and the molad occurs
+ * on Monday at or after 9:32:43 and 1/3 sec, Tishri 1 must be
+ * delayed.
+ *
+ * GLOSSARY
+ *
+ * dehiyyot The set of 4 rules that determine when the new year
+ * starts relative to the molad.
+ *
+ * halakim 1/1080th of an hour or 3 1/3 seconds.
+ *
+ * lunar cycle The period of time between mean conjunctions of the
+ * sun and moon (new moon to new moon). This is
+ * assumed to be 29 days 12 hours and 793 halakim for
+ * calendar purposes.
+ *
+ * metonic cycle A 19 year cycle which determines which years are
+ * leap years and which are common years. The 3rd,
+ * 6th, 8th, 11th, 14th, 17th and 19th years of this
+ * cycle are leap years.
+ *
+ * molad The date and time of the mean conjunction of the
+ * sun and moon (new moon). This is the approximate
+ * beginning of a month.
+ *
+ * Rosh Ha-Shanah The first day of the Jewish year (Tishri 1).
+ *
+ * Tishri The first month of the Jewish year.
+ *
+ * ALGORITHMS
+ *
+ * SERIAL DAY NUMBER TO JEWISH DATE
+ *
+ * The simplest approach would be to use the rules stated above to find
+ * the molad of Tishri before and after the given day number. Then use
+ * the molads to find Tishri 1 of the current and following years.
+ * From this the length of the year can be determined and thus the
+ * length of each month. But this method is used as a last resort.
+ *
+ * The first 59 days of the year are the same regardless of the length
+ * of the year. As a result, only the day number of the start of the
+ * year is required.
+ *
+ * Similarly, the last 6 months do not change from year to year. And
+ * since it can be determined whether the year is a leap year by simple
+ * division, the lengths of Adar I and II can be easily calculated. In
+ * fact, all dates after the 3rd month are consistent from year to year
+ * (once it is known whether it is a leap year).
+ *
+ * This means that if the given day number falls in the 3rd month or on
+ * the 30th day of the 2nd month the length of the year must be found,
+ * but in no other case.
+ *
+ * So, the approach used is to take the given day number and round it
+ * to the closest molad of Tishri (first new moon of the year). The
+ * rounding is not really to the *closest* molad, but is such that if
+ * the day number is before the middle of the 3rd month the molad at
+ * the start of the year is found, otherwise the molad at the end of
+ * the year is found.
+ *
+ * Only if the day number is actually found to be in the ambiguous
+ * period of 29 to 31 days is the other molad calculated.
+ *
+ * JEWISH DATE TO SERIAL DAY NUMBER
+ *
+ * The year number is used to find which 19 year metonic cycle contains
+ * the date and which year within the cycle (this is a division and
+ * modulus). This also determines whether it is a leap year.
+ *
+ * If the month is 1 or 2, the calculation is simple addition to the
+ * first of the year.
+ *
+ * If the month is 8 (Nisan) or greater, the calculation is simple
+ * subtraction from beginning of the following year.
+ *
+ * If the month is 4 to 7, it is considered whether it is a leap year
+ * and then simple subtraction from the beginning of the following year
+ * is used.
+ *
+ * Only if it is the 3rd month is both the start and end of the year
+ * required.
+ *
+ * TESTING
+ *
+ * This algorithm has been tested in two ways. First, 510 dates from a
+ * table in "Jewish Calendar Mystery Dispelled" were calculated and
+ * compared to the table. Second, the calculation algorithm described
+ * in "Jewish Calendar Mystery Dispelled" was coded and used to verify
+ * all dates from the year 1 (3761 B.C.) to the year 13760 (10000
+ * A.D.).
+ *
+ * The source code of the verification program is included in this
+ * package.
+ *
+ * REFERENCES
+ *
+ * The Encyclopedia Judaica, the entry for "Calendar"
+ *
+ * The Jewish Encyclopedia
+ *
+ * Jewish Calendar Mystery Dispelled by George Zinberg, Vantage Press,
+ * 1963
+ *
+ * The Comprehensive Hebrew Calendar by Arthur Spier, Behrman House
+ *
+ * The Book of Calendars [note that this work contains many typos]
+ *
+ **************************************************************************/
+
+#if defined(PHP_WIN32) && _MSC_VER >= 1200
+#pragma setlocale("english")
+#endif
+
+#include "sdncal.h"
+
+#define HALAKIM_PER_HOUR 1080
+#define HALAKIM_PER_DAY 25920
+#define HALAKIM_PER_LUNAR_CYCLE ((29 * HALAKIM_PER_DAY) + 13753)
+#define HALAKIM_PER_METONIC_CYCLE (HALAKIM_PER_LUNAR_CYCLE * (12 * 19 + 7))
+
+#define JEWISH_SDN_OFFSET 347997
+#define NEW_MOON_OF_CREATION 31524
+
+#define SUNDAY 0
+#define MONDAY 1
+#define TUESDAY 2
+#define WEDNESDAY 3
+#define THURSDAY 4
+#define FRIDAY 5
+#define SATURDAY 6
+
+#define NOON (18 * HALAKIM_PER_HOUR)
+#define AM3_11_20 ((9 * HALAKIM_PER_HOUR) + 204)
+#define AM9_32_43 ((15 * HALAKIM_PER_HOUR) + 589)
+
+static int monthsPerYear[19] =
+{
+12, 12, 13, 12, 12, 13, 12, 13, 12, 12, 13, 12, 12, 13, 12, 12, 13, 12, 13
+};
+
+static int yearOffset[19] =
+{
+ 0, 12, 24, 37, 49, 61, 74, 86, 99, 111, 123,
+ 136, 148, 160, 173, 185, 197, 210, 222
+};
+
+char *JewishMonthName[14] =
+{
+ "",
+ "Tishri",
+ "Heshvan",
+ "Kislev",
+ "Tevet",
+ "Shevat",
+ "AdarI",
+ "AdarII",
+ "Nisan",
+ "Iyyar",
+ "Sivan",
+ "Tammuz",
+ "Av",
+ "Elul"
+};
+
+char *JewishMonthHebName[14] =
+{
+ "",
+ "תשרי",
+ "חשון",
+ "כסלו",
+ "טבת",
+ "שבט",
+ "אדר",
+ "'אדר ב",
+ "ניסן",
+ "אייר",
+ "סיון",
+ "תמוז",
+ "אב",
+ "אלול"
+};
+
+/************************************************************************
+ * Given the year within the 19 year metonic cycle and the time of a molad
+ * (new moon) which starts that year, this routine will calculate what day
+ * will be the actual start of the year (Tishri 1 or Rosh Ha-Shanah). This
+ * first day of the year will be the day of the molad unless one of 4 rules
+ * (called dehiyyot) delays it. These 4 rules can delay the start of the
+ * year by as much as 2 days.
+ */
+static long int Tishri1(
+ int metonicYear,
+ long int moladDay,
+ long int moladHalakim)
+{
+ long int tishri1;
+ int dow;
+ int leapYear;
+ int lastWasLeapYear;
+
+ tishri1 = moladDay;
+ dow = tishri1 % 7;
+ leapYear = metonicYear == 2 || metonicYear == 5 || metonicYear == 7
+ || metonicYear == 10 || metonicYear == 13 || metonicYear == 16
+ || metonicYear == 18;
+ lastWasLeapYear = metonicYear == 3 || metonicYear == 6
+ || metonicYear == 8 || metonicYear == 11 || metonicYear == 14
+ || metonicYear == 17 || metonicYear == 0;
+
+ /* Apply rules 2, 3 and 4. */
+ if ((moladHalakim >= NOON) ||
+ ((!leapYear) && dow == TUESDAY && moladHalakim >= AM3_11_20) ||
+ (lastWasLeapYear && dow == MONDAY && moladHalakim >= AM9_32_43)) {
+ tishri1++;
+ dow++;
+ if (dow == 7) {
+ dow = 0;
+ }
+ }
+ /* Apply rule 1 after the others because it can cause an additional
+ * delay of one day. */
+ if (dow == WEDNESDAY || dow == FRIDAY || dow == SUNDAY) {
+ tishri1++;
+ }
+ return (tishri1);
+}
+
+/************************************************************************
+ * Given a metonic cycle number, calculate the date and time of the molad
+ * (new moon) that starts that cycle. Since the length of a metonic cycle
+ * is a constant, this is a simple calculation, except that it requires an
+ * intermediate value which is bigger that 32 bits. Because this
+ * intermediate value only needs 36 to 37 bits and the other numbers are
+ * constants, the process has been reduced to just a few steps.
+ */
+static void MoladOfMetonicCycle(
+ int metonicCycle,
+ long int *pMoladDay,
+ long int *pMoladHalakim)
+{
+ register unsigned long int r1, r2, d1, d2;
+
+ /* Start with the time of the first molad after creation. */
+ r1 = NEW_MOON_OF_CREATION;
+
+ /* Calculate metonicCycle * HALAKIM_PER_METONIC_CYCLE. The upper 32
+ * bits of the result will be in r2 and the lower 16 bits will be
+ * in r1. */
+ r1 += metonicCycle * (HALAKIM_PER_METONIC_CYCLE & 0xFFFF);
+ r2 = r1 >> 16;
+ r2 += metonicCycle * ((HALAKIM_PER_METONIC_CYCLE >> 16) & 0xFFFF);
+
+ /* Calculate r2r1 / HALAKIM_PER_DAY. The remainder will be in r1, the
+ * upper 16 bits of the quotient will be in d2 and the lower 16 bits
+ * will be in d1. */
+ d2 = r2 / HALAKIM_PER_DAY;
+ r2 -= d2 * HALAKIM_PER_DAY;
+ r1 = (r2 << 16) | (r1 & 0xFFFF);
+ d1 = r1 / HALAKIM_PER_DAY;
+ r1 -= d1 * HALAKIM_PER_DAY;
+
+ *pMoladDay = (d2 << 16) | d1;
+ *pMoladHalakim = r1;
+}
+
+/************************************************************************
+ * Given a day number, find the molad of Tishri (the new moon at the start
+ * of a year) which is closest to that day number. It's not really the
+ * *closest* molad that we want here. If the input day is in the first two
+ * months, we want the molad at the start of the year. If the input day is
+ * in the fourth to last months, we want the molad at the end of the year.
+ * If the input day is in the third month, it doesn't matter which molad is
+ * returned, because both will be required. This type of "rounding" allows
+ * us to avoid calculating the length of the year in most cases.
+ */
+static void FindTishriMolad(
+ long int inputDay,
+ int *pMetonicCycle,
+ int *pMetonicYear,
+ long int *pMoladDay,
+ long int *pMoladHalakim)
+{
+ long int moladDay;
+ long int moladHalakim;
+ int metonicCycle;
+ int metonicYear;
+
+ /* Estimate the metonic cycle number. Note that this may be an under
+ * estimate because there are 6939.6896 days in a metonic cycle not
+ * 6940, but it will never be an over estimate. The loop below will
+ * correct for any error in this estimate. */
+ metonicCycle = (inputDay + 310) / 6940;
+
+ /* Calculate the time of the starting molad for this metonic cycle. */
+ MoladOfMetonicCycle(metonicCycle, &moladDay, &moladHalakim);
+
+ /* If the above was an under estimate, increment the cycle number until
+ * the correct one is found. For modern dates this loop is about 98.6%
+ * likely to not execute, even once, because the above estimate is
+ * really quite close. */
+ while (moladDay < inputDay - 6940 + 310) {
+ metonicCycle++;
+ moladHalakim += HALAKIM_PER_METONIC_CYCLE;
+ moladDay += moladHalakim / HALAKIM_PER_DAY;
+ moladHalakim = moladHalakim % HALAKIM_PER_DAY;
+ }
+
+ /* Find the molad of Tishri closest to this date. */
+ for (metonicYear = 0; metonicYear < 18; metonicYear++) {
+ if (moladDay > inputDay - 74) {
+ break;
+ }
+ moladHalakim += HALAKIM_PER_LUNAR_CYCLE * monthsPerYear[metonicYear];
+ moladDay += moladHalakim / HALAKIM_PER_DAY;
+ moladHalakim = moladHalakim % HALAKIM_PER_DAY;
+ }
+
+ *pMetonicCycle = metonicCycle;
+ *pMetonicYear = metonicYear;
+ *pMoladDay = moladDay;
+ *pMoladHalakim = moladHalakim;
+}
+
+/************************************************************************
+ * Given a year, find the number of the first day of that year and the date
+ * and time of the starting molad.
+ */
+static void FindStartOfYear(
+ int year,
+ int *pMetonicCycle,
+ int *pMetonicYear,
+ long int *pMoladDay,
+ long int *pMoladHalakim,
+ int *pTishri1)
+{
+ *pMetonicCycle = (year - 1) / 19;
+ *pMetonicYear = (year - 1) % 19;
+ MoladOfMetonicCycle(*pMetonicCycle, pMoladDay, pMoladHalakim);
+
+ *pMoladHalakim += HALAKIM_PER_LUNAR_CYCLE * yearOffset[*pMetonicYear];
+ *pMoladDay += *pMoladHalakim / HALAKIM_PER_DAY;
+ *pMoladHalakim = *pMoladHalakim % HALAKIM_PER_DAY;
+
+ *pTishri1 = Tishri1(*pMetonicYear, *pMoladDay, *pMoladHalakim);
+}
+
+/************************************************************************
+ * Given a serial day number (SDN), find the corresponding year, month and
+ * day in the Jewish calendar. The three output values will always be
+ * modified. If the input SDN is before the first day of year 1, they will
+ * all be set to zero, otherwise *pYear will be > 0; *pMonth will be in the
+ * range 1 to 13 inclusive; *pDay will be in the range 1 to 30 inclusive.
+ */
+void SdnToJewish(
+ long int sdn,
+ int *pYear,
+ int *pMonth,
+ int *pDay)
+{
+ long int inputDay;
+ long int day;
+ long int halakim;
+ int metonicCycle;
+ int metonicYear;
+ int tishri1;
+ int tishri1After;
+ int yearLength;
+
+ if (sdn <= JEWISH_SDN_OFFSET) {
+ *pYear = 0;
+ *pMonth = 0;
+ *pDay = 0;
+ return;
+ }
+ inputDay = sdn - JEWISH_SDN_OFFSET;
+
+ FindTishriMolad(inputDay, &metonicCycle, &metonicYear, &day, &halakim);
+ tishri1 = Tishri1(metonicYear, day, halakim);
+
+ if (inputDay >= tishri1) {
+ /* It found Tishri 1 at the start of the year. */
+ *pYear = metonicCycle * 19 + metonicYear + 1;
+ if (inputDay < tishri1 + 59) {
+ if (inputDay < tishri1 + 30) {
+ *pMonth = 1;
+ *pDay = inputDay - tishri1 + 1;
+ } else {
+ *pMonth = 2;
+ *pDay = inputDay - tishri1 - 29;
+ }
+ return;
+ }
+ /* We need the length of the year to figure this out, so find
+ * Tishri 1 of the next year. */
+ halakim += HALAKIM_PER_LUNAR_CYCLE * monthsPerYear[metonicYear];
+ day += halakim / HALAKIM_PER_DAY;
+ halakim = halakim % HALAKIM_PER_DAY;
+ tishri1After = Tishri1((metonicYear + 1) % 19, day, halakim);
+ } else {
+ /* It found Tishri 1 at the end of the year. */
+ *pYear = metonicCycle * 19 + metonicYear;
+ if (inputDay >= tishri1 - 177) {
+ /* It is one of the last 6 months of the year. */
+ if (inputDay > tishri1 - 30) {
+ *pMonth = 13;
+ *pDay = inputDay - tishri1 + 30;
+ } else if (inputDay > tishri1 - 60) {
+ *pMonth = 12;
+ *pDay = inputDay - tishri1 + 60;
+ } else if (inputDay > tishri1 - 89) {
+ *pMonth = 11;
+ *pDay = inputDay - tishri1 + 89;
+ } else if (inputDay > tishri1 - 119) {
+ *pMonth = 10;
+ *pDay = inputDay - tishri1 + 119;
+ } else if (inputDay > tishri1 - 148) {
+ *pMonth = 9;
+ *pDay = inputDay - tishri1 + 148;
+ } else {
+ *pMonth = 8;
+ *pDay = inputDay - tishri1 + 178;
+ }
+ return;
+ } else {
+ if (monthsPerYear[(*pYear - 1) % 19] == 13) {
+ *pMonth = 7;
+ *pDay = inputDay - tishri1 + 207;
+ if (*pDay > 0)
+ return;
+ (*pMonth)--;
+ (*pDay) += 30;
+ if (*pDay > 0)
+ return;
+ (*pMonth)--;
+ (*pDay) += 30;
+ } else {
+ *pMonth = 6;
+ *pDay = inputDay - tishri1 + 207;
+ if (*pDay > 0)
+ return;
+ (*pMonth)--;
+ (*pDay) += 30;
+ }
+ if (*pDay > 0)
+ return;
+ (*pMonth)--;
+ (*pDay) += 29;
+ if (*pDay > 0)
+ return;
+
+ /* We need the length of the year to figure this out, so find
+ * Tishri 1 of this year. */
+ tishri1After = tishri1;
+ FindTishriMolad(day - 365,
+ &metonicCycle, &metonicYear, &day, &halakim);
+ tishri1 = Tishri1(metonicYear, day, halakim);
+ }
+ }
+
+ yearLength = tishri1After - tishri1;
+ day = inputDay - tishri1 - 29;
+ if (yearLength == 355 || yearLength == 385) {
+ /* Heshvan has 30 days */
+ if (day <= 30) {
+ *pMonth = 2;
+ *pDay = day;
+ return;
+ }
+ day -= 30;
+ } else {
+ /* Heshvan has 29 days */
+ if (day <= 29) {
+ *pMonth = 2;
+ *pDay = day;
+ return;
+ }
+ day -= 29;
+ }
+
+ /* It has to be Kislev. */
+ *pMonth = 3;
+ *pDay = day;
+}
+
+/************************************************************************
+ * Given a year, month and day in the Jewish calendar, find the
+ * corresponding serial day number (SDN). Zero is returned when the input
+ * date is detected as invalid. The return value will be > 0 for all valid
+ * dates, but there are some invalid dates that will return a positive
+ * value. To verify that a date is valid, convert it to SDN and then back
+ * and compare with the original.
+ */
+long int JewishToSdn(
+ int year,
+ int month,
+ int day)
+{
+ long int sdn;
+ int metonicCycle;
+ int metonicYear;
+ int tishri1;
+ int tishri1After;
+ long int moladDay;
+ long int moladHalakim;
+ int yearLength;
+ int lengthOfAdarIAndII;
+
+ if (year <= 0 || day <= 0 || day > 30) {
+ return (0);
+ }
+ switch (month) {
+ case 1:
+ case 2:
+ /* It is Tishri or Heshvan - don't need the year length. */
+ FindStartOfYear(year, &metonicCycle, &metonicYear,
+ &moladDay, &moladHalakim, &tishri1);
+ if (month == 1) {
+ sdn = tishri1 + day - 1;
+ } else {
+ sdn = tishri1 + day + 29;
+ }
+ break;
+
+ case 3:
+ /* It is Kislev - must find the year length. */
+
+ /* Find the start of the year. */
+ FindStartOfYear(year, &metonicCycle, &metonicYear,
+ &moladDay, &moladHalakim, &tishri1);
+
+ /* Find the end of the year. */
+ moladHalakim += HALAKIM_PER_LUNAR_CYCLE * monthsPerYear[metonicYear];
+ moladDay += moladHalakim / HALAKIM_PER_DAY;
+ moladHalakim = moladHalakim % HALAKIM_PER_DAY;
+ tishri1After = Tishri1((metonicYear + 1) % 19, moladDay, moladHalakim);
+
+ yearLength = tishri1After - tishri1;
+
+ if (yearLength == 355 || yearLength == 385) {
+ sdn = tishri1 + day + 59;
+ } else {
+ sdn = tishri1 + day + 58;
+ }
+ break;
+
+ case 4:
+ case 5:
+ case 6:
+ /* It is Tevet, Shevat or Adar I - don't need the year length. */
+
+ FindStartOfYear(year + 1, &metonicCycle, &metonicYear,
+ &moladDay, &moladHalakim, &tishri1After);
+
+ if (monthsPerYear[(year - 1) % 19] == 12) {
+ lengthOfAdarIAndII = 29;
+ } else {
+ lengthOfAdarIAndII = 59;
+ }
+
+ if (month == 4) {
+ sdn = tishri1After + day - lengthOfAdarIAndII - 237;
+ } else if (month == 5) {
+ sdn = tishri1After + day - lengthOfAdarIAndII - 208;
+ } else {
+ sdn = tishri1After + day - lengthOfAdarIAndII - 178;
+ }
+ break;
+
+ default:
+ /* It is Adar II or later - don't need the year length. */
+ FindStartOfYear(year + 1, &metonicCycle, &metonicYear,
+ &moladDay, &moladHalakim, &tishri1After);
+
+ switch (month) {
+ case 7:
+ sdn = tishri1After + day - 207;
+ break;
+ case 8:
+ sdn = tishri1After + day - 178;
+ break;
+ case 9:
+ sdn = tishri1After + day - 148;
+ break;
+ case 10:
+ sdn = tishri1After + day - 119;
+ break;
+ case 11:
+ sdn = tishri1After + day - 89;
+ break;
+ case 12:
+ sdn = tishri1After + day - 60;
+ break;
+ case 13:
+ sdn = tishri1After + day - 30;
+ break;
+ default:
+ return (0);
+ }
+ }
+ return (sdn + JEWISH_SDN_OFFSET);
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: sw=4 ts=4 fdm=marker
+ * vim<600: sw=4 ts=4
+ */
diff --git a/ext/calendar/julian.c b/ext/calendar/julian.c
new file mode 100644
index 0000000..17e7bcb
--- /dev/null
+++ b/ext/calendar/julian.c
@@ -0,0 +1,263 @@
+/* $selId: julian.c,v 2.0 1995/10/24 01:13:06 lees Exp $
+ * Copyright 1993-1995, Scott E. Lee, all rights reserved.
+ * Permission granted to use, copy, modify, distribute and sell so long as
+ * the above copyright and this permission statement are retained in all
+ * copies. THERE IS NO WARRANTY - USE AT YOUR OWN RISK.
+ */
+
+/**************************************************************************
+ *
+ * These are the externally visible components of this file:
+ *
+ * void
+ * SdnToJulian(
+ * long int sdn,
+ * int *pYear,
+ * int *pMonth,
+ * int *pDay);
+ *
+ * Convert a SDN to a Julian calendar date. If the input SDN is less than
+ * 1, the three output values will all be set to zero, otherwise *pYear
+ * will be >= -4713 and != 0; *pMonth will be in the range 1 to 12
+ * inclusive; *pDay will be in the range 1 to 31 inclusive.
+ *
+ * long int
+ * JulianToSdn(
+ * int inputYear,
+ * int inputMonth,
+ * int inputDay);
+ *
+ * Convert a Julian calendar date to a SDN. Zero is returned when the
+ * input date is detected as invalid or out of the supported range. The
+ * return value will be > 0 for all valid, supported dates, but there are
+ * some invalid dates that will return a positive value. To verify that a
+ * date is valid, convert it to SDN and then back and compare with the
+ * original.
+ *
+ * VALID RANGE
+ *
+ * 4713 B.C. to at least 10000 A.D.
+ *
+ * Although this software can handle dates all the way back to 4713
+ * B.C., such use may not be meaningful. The calendar was created in
+ * 46 B.C., but the details did not stabilize until at least 8 A.D.,
+ * and perhaps as late at the 4th century. Also, the beginning of a
+ * year varied from one culture to another - not all accepted January
+ * as the first month.
+ *
+ * CALENDAR OVERVIEW
+ *
+ * Julias Ceasar created the calendar in 46 B.C. as a modified form of
+ * the old Roman republican calendar which was based on lunar cycles.
+ * The new Julian calendar set fixed lengths for the months, abandoning
+ * the lunar cycle. It also specified that there would be exactly 12
+ * months per year and 365.25 days per year with every 4th year being a
+ * leap year.
+ *
+ * Note that the current accepted value for the tropical year is
+ * 365.242199 days, not 365.25. This lead to an 11 day shift in the
+ * calendar with respect to the seasons by the 16th century when the
+ * Gregorian calendar was created to replace the Julian calendar.
+ *
+ * The difference between the Julian and today's Gregorian calendar is
+ * that the Gregorian does not make centennial years leap years unless
+ * they are a multiple of 400, which leads to a year of 365.2425 days.
+ * In other words, in the Gregorian calendar, 1700, 1800 and 1900 are
+ * not leap years, but 2000 is. All centennial years are leap years in
+ * the Julian calendar.
+ *
+ * The details are unknown, but the lengths of the months were adjusted
+ * until they finally stablized in 8 A.D. with their current lengths:
+ *
+ * January 31
+ * February 28/29
+ * March 31
+ * April 30
+ * May 31
+ * June 30
+ * Quintilis/July 31
+ * Sextilis/August 31
+ * September 30
+ * October 31
+ * November 30
+ * December 31
+ *
+ * In the early days of the calendar, the days of the month were not
+ * numbered as we do today. The numbers ran backwards (decreasing) and
+ * were counted from the Ides (15th of the month - which in the old
+ * Roman republican lunar calendar would have been the full moon) or
+ * from the Nonae (9th day before the Ides) or from the beginning of
+ * the next month.
+ *
+ * In the early years, the beginning of the year varied, sometimes
+ * based on the ascension of rulers. It was not always the first of
+ * January.
+ *
+ * Also, today's epoch, 1 A.D. or the birth of Jesus Christ, did not
+ * come into use until several centuries later when Christianity became
+ * a dominant religion.
+ *
+ * ALGORITHMS
+ *
+ * The calculations are based on two different cycles: a 4 year cycle
+ * of leap years and a 5 month cycle of month lengths.
+ *
+ * The 5 month cycle is used to account for the varying lengths of
+ * months. You will notice that the lengths alternate between 30 and
+ * 31 days, except for three anomalies: both July and August have 31
+ * days, both December and January have 31, and February is less than
+ * 30. Starting with March, the lengths are in a cycle of 5 months
+ * (31, 30, 31, 30, 31):
+ *
+ * Mar 31 days \
+ * Apr 30 days |
+ * May 31 days > First cycle
+ * Jun 30 days |
+ * Jul 31 days /
+ *
+ * Aug 31 days \
+ * Sep 30 days |
+ * Oct 31 days > Second cycle
+ * Nov 30 days |
+ * Dec 31 days /
+ *
+ * Jan 31 days \
+ * Feb 28/9 days |
+ * > Third cycle (incomplete)
+ *
+ * For this reason the calculations (internally) assume that the year
+ * starts with March 1.
+ *
+ * TESTING
+ *
+ * This algorithm has been tested from the year 4713 B.C. to 10000 A.D.
+ * The source code of the verification program is included in this
+ * package.
+ *
+ * REFERENCES
+ *
+ * Conversions Between Calendar Date and Julian Day Number by Robert J.
+ * Tantzen, Communications of the Association for Computing Machinery
+ * August 1963. (Also published in Collected Algorithms from CACM,
+ * algorithm number 199). [Note: the published algorithm is for the
+ * Gregorian calendar, but was adjusted to use the Julian calendar's
+ * simpler leap year rule.]
+ *
+ **************************************************************************/
+
+#include "sdncal.h"
+#include <limits.h>
+
+#define JULIAN_SDN_OFFSET 32083
+#define DAYS_PER_5_MONTHS 153
+#define DAYS_PER_4_YEARS 1461
+
+void SdnToJulian(
+ long int sdn,
+ int *pYear,
+ int *pMonth,
+ int *pDay)
+{
+ int year;
+ int month;
+ int day;
+ long int temp;
+ int dayOfYear;
+
+ if (sdn <= 0) {
+ goto fail;
+ }
+ /* Check for overflow */
+ if (sdn > (LONG_MAX - JULIAN_SDN_OFFSET * 4 + 1) / 4 || sdn < LONG_MIN / 4) {
+ goto fail;
+ }
+ temp = sdn * 4 + (JULIAN_SDN_OFFSET * 4 - 1);
+
+ /* Calculate the year and day of year (1 <= dayOfYear <= 366). */
+ {
+ long yearl = temp / DAYS_PER_4_YEARS;
+ if (yearl > INT_MAX || yearl < INT_MIN) {
+ goto fail;
+ }
+ year = (int) yearl;
+ }
+ dayOfYear = (temp % DAYS_PER_4_YEARS) / 4 + 1;
+
+ /* Calculate the month and day of month. */
+ temp = dayOfYear * 5 - 3;
+ month = temp / DAYS_PER_5_MONTHS;
+ day = (temp % DAYS_PER_5_MONTHS) / 5 + 1;
+
+ /* Convert to the normal beginning of the year. */
+ if (month < 10) {
+ month += 3;
+ } else {
+ year += 1;
+ month -= 9;
+ }
+
+ /* Adjust to the B.C./A.D. type numbering. */
+ year -= 4800;
+ if (year <= 0)
+ year--;
+
+ *pYear = year;
+ *pMonth = month;
+ *pDay = day;
+ return;
+
+fail:
+ *pYear = 0;
+ *pMonth = 0;
+ *pDay = 0;
+}
+
+long int JulianToSdn(
+ int inputYear,
+ int inputMonth,
+ int inputDay)
+{
+ int year;
+ int month;
+
+ /* check for invalid dates */
+ if (inputYear == 0 || inputYear < -4713 ||
+ inputMonth <= 0 || inputMonth > 12 ||
+ inputDay <= 0 || inputDay > 31) {
+ return (0);
+ }
+ /* check for dates before SDN 1 (Jan 2, 4713 B.C.) */
+ if (inputYear == -4713) {
+ if (inputMonth == 1 && inputDay == 1) {
+ return (0);
+ }
+ }
+ /* Make year always a positive number. */
+ if (inputYear < 0) {
+ year = inputYear + 4801;
+ } else {
+ year = inputYear + 4800;
+ }
+
+ /* Adjust the start of the year. */
+ if (inputMonth > 2) {
+ month = inputMonth - 3;
+ } else {
+ month = inputMonth + 9;
+ year--;
+ }
+
+ return ((year * DAYS_PER_4_YEARS) / 4
+ + (month * DAYS_PER_5_MONTHS + 2) / 5
+ + inputDay
+ - JULIAN_SDN_OFFSET);
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: sw=4 ts=4 fdm=marker
+ * vim<600: sw=4 ts=4
+ */
diff --git a/ext/calendar/package.xml b/ext/calendar/package.xml
new file mode 100644
index 0000000..82d0675
--- /dev/null
+++ b/ext/calendar/package.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!DOCTYPE package SYSTEM "../pear/package.dtd">
+<package>
+ <name>calendar</name>
+ <summary>Date conversion between different calendar formats</summary>
+ <maintainers>
+ <maintainer>
+ <user>hholzgra</user>
+ <name>Hartmut Holzgraefe</name>
+ <email>hartmut@php.net</email>
+ <role>lead</role>
+ </maintainer>
+ <maintainer>
+ <user>shane</user>
+ <name>Shane Caraveo</name>
+ <role>developer</role>
+ <email>shane@caraveo.com</email>
+ </maintainer>
+ <maintainer>
+ <user>colin</user>
+ <name>Colin Viebrock</name>
+ <role>developer</role>
+ <email>colin@easydns.com</email>
+ </maintainer>
+ <maintainer>
+ <user>wez</user>
+ <name>Wez Furlong</name>
+ <role>developer</role>
+ <email>wez@php.net</email>
+ </maintainer>
+ </maintainers>
+ <description>
+The calendar extension presents a series of functions to simplify
+converting between different calendar formats. The intermediary or
+standard it is based on is the Julian Day Count. The Julian Day Count
+is a count of days starting from January 1st, 4713 B.C. To convert
+between calendar systems, you must first convert to Julian Day Count,
+then to the calendar system of your choice. Julian Day Count is very
+different from the Julian Calendar!
+ </description>
+ <license>PHP</license>
+ <release>
+ <state>beta</state>
+ <version>5.0.0rc1</version>
+ <date>2004-03-19</date>
+ <notes>
+package.xml added to support installation using pear installer
+ </notes>
+ <filelist>
+ <file role="doc" name="CREDITS"/>
+ <file role="src" name="config.m4"/>
+ <file role="src" name="config.w32"/>
+ <file role="src" name="cal_unix.c"/>
+ <file role="src" name="calendar.c"/>
+ <file role="src" name="dow.c"/>
+ <file role="src" name="easter.c"/>
+ <file role="src" name="french.c"/>
+ <file role="src" name="gregor.c"/>
+ <file role="src" name="jewish.c"/>
+ <file role="src" name="julian.c"/>
+ <file role="src" name="package.xml"/>
+ <file role="src" name="php_calendar.h"/>
+ <file role="src" name="sdncal.h"/>
+ <file role="test" name="tests/jdtojewish.phpt"/>
+ </filelist>
+ <deps>
+ <dep type="php" rel="ge" version="5" />
+ </deps>
+ </release>
+</package>
+<!--
+vim:et:ts=1:sw=1
+-->
diff --git a/ext/calendar/php_calendar.h b/ext/calendar/php_calendar.h
new file mode 100644
index 0000000..e353fab
--- /dev/null
+++ b/ext/calendar/php_calendar.h
@@ -0,0 +1,48 @@
+#ifndef PHP_CALENDAR_H
+#define PHP_CALENDAR_H
+
+extern zend_module_entry calendar_module_entry;
+#define calendar_module_ptr &calendar_module_entry
+
+/* Functions */
+
+PHP_MINIT_FUNCTION(calendar);
+PHP_MINFO_FUNCTION(calendar);
+
+PHP_FUNCTION(jdtogregorian);
+PHP_FUNCTION(gregoriantojd);
+PHP_FUNCTION(jdtojulian);
+PHP_FUNCTION(juliantojd);
+PHP_FUNCTION(jdtojewish);
+PHP_FUNCTION(jewishtojd);
+PHP_FUNCTION(jdtofrench);
+PHP_FUNCTION(frenchtojd);
+PHP_FUNCTION(jddayofweek);
+PHP_FUNCTION(jdmonthname);
+PHP_FUNCTION(easter_days);
+PHP_FUNCTION(easter_date);
+PHP_FUNCTION(unixtojd);
+PHP_FUNCTION(jdtounix);
+PHP_FUNCTION(cal_from_jd);
+PHP_FUNCTION(cal_to_jd);
+PHP_FUNCTION(cal_days_in_month);
+PHP_FUNCTION(cal_info);
+
+#define phpext_calendar_ptr calendar_module_ptr
+
+/*
+ * Specifying the easter calculation method
+ *
+ * DEFAULT is Anglican, ie. use Julian calendar before 1753
+ * and Gregorian after that. With ROMAN, the cutoff year is 1582.
+ * ALWAYS_GREGORIAN and ALWAYS_JULIAN force the calendar
+ * regardless of date.
+ *
+ */
+
+#define CAL_EASTER_DEFAULT 0
+#define CAL_EASTER_ROMAN 1
+#define CAL_EASTER_ALWAYS_GREGORIAN 2
+#define CAL_EASTER_ALWAYS_JULIAN 3
+
+#endif
diff --git a/ext/calendar/sdncal.h b/ext/calendar/sdncal.h
new file mode 100644
index 0000000..81328d1
--- /dev/null
+++ b/ext/calendar/sdncal.h
@@ -0,0 +1,97 @@
+#ifndef SDNCAL_H
+#define SDNCAL_H
+/*
+ * This code has been modified for use with PHP
+ * by Shane Caraveo shane@caraveo.com
+ * see below for more details
+ *
+ */
+
+/* $selId: sdncal.h,v 2.0 1995/10/24 01:13:06 lees Exp $
+ * Copyright 1993-1995, Scott E. Lee, all rights reserved.
+ * Permission granted to use, copy, modify, distribute and sell so long as
+ * the above copyright and this permission statement are retained in all
+ * copies. THERE IS NO WARRANTY - USE AT YOUR OWN RISK.
+ */
+
+/**************************************************************************
+ *
+ * This package defines a set of routines that convert calendar dates to
+ * and from a serial day number (SDN). The SDN is a serial numbering of
+ * days where SDN 1 is November 25, 4714 BC in the Gregorian calendar and
+ * SDN 2447893 is January 1, 1990. This system of day numbering is
+ * sometimes referred to as Julian days, but to avoid confusion with the
+ * Julian calendar, it is referred to as serial day numbers here. The term
+ * Julian days is also used to mean the number of days since the beginning
+ * of the current year.
+ *
+ * The SDN can be used as an intermediate step in converting from one
+ * calendar system to another (such as Gregorian to Jewish). It can also
+ * be used for date computations such as easily comparing two dates,
+ * determining the day of the week, finding the date of yesterday or
+ * calculating the number of days between two dates.
+ *
+ * When using this software on 16 bit systems, be careful to store SDNs in
+ * a long int, because it will not fit in the 16 bits that some systems
+ * allocate to an int.
+ *
+ * For each calendar, there are two routines provided. One converts dates
+ * in that calendar to SDN and the other converts SDN to calendar dates.
+ * The routines are named SdnTo<CALENDAR>() and <CALENDAR>ToSdn(), where
+ * <CALENDAR> is the name of the calendar system.
+ *
+ * SDN values less than one are not supported. If a conversion routine
+ * returns an SDN of zero, this means that the date given is either invalid
+ * or is outside the supported range for that calendar.
+ *
+ * At least some validity checks are performed on input dates. For
+ * example, a negative month number will result in the return of zero for
+ * the SDN. A returned SDN greater than one does not necessarily mean that
+ * the input date was valid. To determine if the date is valid, convert it
+ * to SDN, and if the SDN is greater than zero, convert it back to a date
+ * and compare to the original. For example:
+ *
+ * int y1, m1, d1;
+ * int y2, m2, d2;
+ * long int sdn;
+ * ...
+ * sdn = GregorianToSdn(y1, m1, d1);
+ * if (sdn > 0) {
+ * SdnToGregorian(sdn, &y2, &m2, &d2);
+ * if (y1 == y2 && m1 == m2 && d1 == d2) {
+ * ... date is valid ...
+ * }
+ * }
+ *
+ **************************************************************************/
+
+/* Gregorian calendar conversions. */
+void SdnToGregorian(long int sdn, int *pYear, int *pMonth, int *pDay);
+long int GregorianToSdn(int year, int month, int day);
+extern char *MonthNameShort[13];
+extern char *MonthNameLong[13];
+
+/* Julian calendar conversions. */
+void SdnToJulian(long int sdn, int *pYear, int *pMonth, int *pDay);
+long int JulianToSdn(int year, int month, int day);
+
+/* Jewish calendar conversions. */
+void SdnToJewish(long int sdn, int *pYear, int *pMonth, int *pDay);
+long int JewishToSdn(int year, int month, int day);
+extern char *JewishMonthName[14];
+extern char *JewishMonthHebName[14];
+
+/* French republic calendar conversions. */
+void SdnToFrench(long int sdn, int *pYear, int *pMonth, int *pDay);
+long int FrenchToSdn(int inputYear, int inputMonth, int inputDay);
+extern char *FrenchMonthName[14];
+
+/* Islamic calendar conversions. */
+/* Not implemented yet. */
+
+/* Day of week conversion. 0=Sunday, 6=Saturday */
+int DayOfWeek(long int sdn);
+extern char *DayNameShort[7];
+extern char *DayNameLong[7];
+
+#endif /* SDNCAL_H */
diff --git a/ext/calendar/tests/bug52744.phpt b/ext/calendar/tests/bug52744.phpt
new file mode 100644
index 0000000..886086a
--- /dev/null
+++ b/ext/calendar/tests/bug52744.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Bug #52744 (cal_days_in_month incorrect for December 1 BCE)
+--SKIPIF--
+<?php include 'skipif.inc'; ?>
+--FILE--
+<?php
+var_dump(cal_days_in_month(CAL_GREGORIAN, 12, -1));
+var_dump(cal_days_in_month(CAL_JULIAN, 12, -1));
+?>
+--EXPECT--
+int(31)
+int(31)
diff --git a/ext/calendar/tests/bug53574_1.phpt b/ext/calendar/tests/bug53574_1.phpt
new file mode 100644
index 0000000..51f9b42
--- /dev/null
+++ b/ext/calendar/tests/bug53574_1.phpt
@@ -0,0 +1,36 @@
+--TEST--
+Bug #53574 (Integer overflow in SdnToJulian; leads to segfault)
+--SKIPIF--
+<?php
+include 'skipif.inc';
+if (PHP_INT_SIZE != 4) {
+ die("skip this test is for 32bit platform only");
+}
+?>
+--FILE--
+<?php
+$x = 882858043;
+
+var_dump(cal_from_jd($x, CAL_JULIAN));
+--EXPECT--
+array(9) {
+ ["date"]=>
+ string(5) "0/0/0"
+ ["month"]=>
+ int(0)
+ ["day"]=>
+ int(0)
+ ["year"]=>
+ int(0)
+ ["dow"]=>
+ int(5)
+ ["abbrevdayname"]=>
+ string(3) "Fri"
+ ["dayname"]=>
+ string(6) "Friday"
+ ["abbrevmonth"]=>
+ string(0) ""
+ ["monthname"]=>
+ string(0) ""
+}
+
diff --git a/ext/calendar/tests/bug53574_2.phpt b/ext/calendar/tests/bug53574_2.phpt
new file mode 100644
index 0000000..45e15cb
--- /dev/null
+++ b/ext/calendar/tests/bug53574_2.phpt
@@ -0,0 +1,36 @@
+--TEST--
+Bug #53574 (Integer overflow in SdnToJulian; leads to segfault)
+--SKIPIF--
+<?php
+include 'skipif.inc';
+if (PHP_INT_SIZE == 4) {
+ die("skip this test is for 64bit platform only");
+}
+?>
+--FILE--
+<?php
+$x = 3315881921229094912;
+
+var_dump(cal_from_jd($x, CAL_JULIAN));
+--EXPECT--
+array(9) {
+ ["date"]=>
+ string(5) "0/0/0"
+ ["month"]=>
+ int(0)
+ ["day"]=>
+ int(0)
+ ["year"]=>
+ int(0)
+ ["dow"]=>
+ int(3)
+ ["abbrevdayname"]=>
+ string(3) "Wed"
+ ["dayname"]=>
+ string(9) "Wednesday"
+ ["abbrevmonth"]=>
+ string(0) ""
+ ["monthname"]=>
+ string(0) ""
+}
+
diff --git a/ext/calendar/tests/bug55797_1.phpt b/ext/calendar/tests/bug55797_1.phpt
new file mode 100644
index 0000000..ffd617d
--- /dev/null
+++ b/ext/calendar/tests/bug55797_1.phpt
@@ -0,0 +1,36 @@
+--TEST--
+Bug #55797: Integer overflow in SdnToGregorian leads to segfault (in optimized builds)
+--SKIPIF--
+<?php
+include 'skipif.inc';
+if (PHP_INT_SIZE != 4) {
+ die("skip this test is for 32bit platform only");
+}
+?>
+--FILE--
+<?php
+$x = 882858030;
+
+var_dump(cal_from_jd($x, CAL_GREGORIAN));
+--EXPECTF--
+array(9) {
+ ["date"]=>
+ string(5) "0/0/0"
+ ["month"]=>
+ int(0)
+ ["day"]=>
+ int(0)
+ ["year"]=>
+ int(0)
+ ["dow"]=>
+ int(%d)
+ ["abbrevdayname"]=>
+ string(%d) "%s"
+ ["dayname"]=>
+ string(%d) "%s"
+ ["abbrevmonth"]=>
+ string(0) ""
+ ["monthname"]=>
+ string(0) ""
+}
+
diff --git a/ext/calendar/tests/bug55797_2.phpt b/ext/calendar/tests/bug55797_2.phpt
new file mode 100644
index 0000000..2a9183d
--- /dev/null
+++ b/ext/calendar/tests/bug55797_2.phpt
@@ -0,0 +1,36 @@
+--TEST--
+Bug #55797: Integer overflow in SdnToGregorian leads to segfault (in optimized builds)
+--SKIPIF--
+<?php
+include 'skipif.inc';
+if (PHP_INT_SIZE == 4) {
+ die("skip this test is for 64bit platform only");
+}
+?>
+--FILE--
+<?php
+$x = 9223372036854743639;
+
+var_dump(cal_from_jd($x, CAL_GREGORIAN));
+--EXPECTF--
+array(9) {
+ ["date"]=>
+ string(5) "0/0/0"
+ ["month"]=>
+ int(0)
+ ["day"]=>
+ int(0)
+ ["year"]=>
+ int(0)
+ ["dow"]=>
+ int(%d)
+ ["abbrevdayname"]=>
+ string(%d) "%s"
+ ["dayname"]=>
+ string(%d) "%s"
+ ["abbrevmonth"]=>
+ string(0) ""
+ ["monthname"]=>
+ string(0) ""
+}
+
diff --git a/ext/calendar/tests/cal_days_in_month.phpt b/ext/calendar/tests/cal_days_in_month.phpt
new file mode 100644
index 0000000..9aaf3ef
--- /dev/null
+++ b/ext/calendar/tests/cal_days_in_month.phpt
@@ -0,0 +1,20 @@
+--TEST--
+cal_days_in_month()
+--SKIPIF--
+<?php include 'skipif.inc'; ?>
+--FILE--
+<?php
+$num = cal_days_in_month(CAL_GREGORIAN, 8, 2003);
+echo "There are $num days in August 2003\n";
+$num = cal_days_in_month(CAL_GREGORIAN, 2, 2003);
+echo "There are $num days in February 2003\n";
+$num = cal_days_in_month(CAL_GREGORIAN, 2, 2004);
+echo "There are $num days in February 2004\n";
+$num = cal_days_in_month(CAL_GREGORIAN, 12, 2034);
+echo "There are $num days in December 2034\n";
+?>
+--EXPECT--
+There are 31 days in August 2003
+There are 28 days in February 2003
+There are 29 days in February 2004
+There are 31 days in December 2034
diff --git a/ext/calendar/tests/cal_from_jd.phpt b/ext/calendar/tests/cal_from_jd.phpt
new file mode 100644
index 0000000..9614522
--- /dev/null
+++ b/ext/calendar/tests/cal_from_jd.phpt
@@ -0,0 +1,60 @@
+--TEST--
+cal_from_jd()
+--SKIPIF--
+<?php include 'skipif.inc'; ?>
+--FILE--
+<?php
+print_r(cal_from_jd(1748326, CAL_GREGORIAN));
+print_r(cal_from_jd(1748324, CAL_JULIAN));
+print_r(cal_from_jd( 374867, CAL_JEWISH));
+print_r(cal_from_jd( 0, CAL_FRENCH));
+?>
+--EXPECT--
+Array
+(
+ [date] => 8/26/74
+ [month] => 8
+ [day] => 26
+ [year] => 74
+ [dow] => 0
+ [abbrevdayname] => Sun
+ [dayname] => Sunday
+ [abbrevmonth] => Aug
+ [monthname] => August
+)
+Array
+(
+ [date] => 8/26/74
+ [month] => 8
+ [day] => 26
+ [year] => 74
+ [dow] => 5
+ [abbrevdayname] => Fri
+ [dayname] => Friday
+ [abbrevmonth] => Aug
+ [monthname] => August
+)
+Array
+(
+ [date] => 8/26/74
+ [month] => 8
+ [day] => 26
+ [year] => 74
+ [dow] => 4
+ [abbrevdayname] => Thu
+ [dayname] => Thursday
+ [abbrevmonth] => Nisan
+ [monthname] => Nisan
+)
+Array
+(
+ [date] => 0/0/0
+ [month] => 0
+ [day] => 0
+ [year] => 0
+ [dow] => 1
+ [abbrevdayname] => Mon
+ [dayname] => Monday
+ [abbrevmonth] =>
+ [monthname] =>
+)
diff --git a/ext/calendar/tests/cal_info.phpt b/ext/calendar/tests/cal_info.phpt
new file mode 100644
index 0000000..2e3e612
--- /dev/null
+++ b/ext/calendar/tests/cal_info.phpt
@@ -0,0 +1,216 @@
+--TEST--
+cal_info()
+--INI--
+date.timezone=UTC
+--SKIPIF--
+<?php include 'skipif.inc'; ?>
+--FILE--
+<?php
+ print_r(cal_info());
+ print_r(cal_info(1));
+ print_r(cal_info(99999));
+?>
+--EXPECTF--
+Array
+(
+ [0] => Array
+ (
+ [months] => Array
+ (
+ [1] => January
+ [2] => February
+ [3] => March
+ [4] => April
+ [5] => May
+ [6] => June
+ [7] => July
+ [8] => August
+ [9] => September
+ [10] => October
+ [11] => November
+ [12] => December
+ )
+
+ [abbrevmonths] => Array
+ (
+ [1] => Jan
+ [2] => Feb
+ [3] => Mar
+ [4] => Apr
+ [5] => May
+ [6] => Jun
+ [7] => Jul
+ [8] => Aug
+ [9] => Sep
+ [10] => Oct
+ [11] => Nov
+ [12] => Dec
+ )
+
+ [maxdaysinmonth] => 31
+ [calname] => Gregorian
+ [calsymbol] => CAL_GREGORIAN
+ )
+
+ [1] => Array
+ (
+ [months] => Array
+ (
+ [1] => January
+ [2] => February
+ [3] => March
+ [4] => April
+ [5] => May
+ [6] => June
+ [7] => July
+ [8] => August
+ [9] => September
+ [10] => October
+ [11] => November
+ [12] => December
+ )
+
+ [abbrevmonths] => Array
+ (
+ [1] => Jan
+ [2] => Feb
+ [3] => Mar
+ [4] => Apr
+ [5] => May
+ [6] => Jun
+ [7] => Jul
+ [8] => Aug
+ [9] => Sep
+ [10] => Oct
+ [11] => Nov
+ [12] => Dec
+ )
+
+ [maxdaysinmonth] => 31
+ [calname] => Julian
+ [calsymbol] => CAL_JULIAN
+ )
+
+ [2] => Array
+ (
+ [months] => Array
+ (
+ [1] => Tishri
+ [2] => Heshvan
+ [3] => Kislev
+ [4] => Tevet
+ [5] => Shevat
+ [6] => AdarI
+ [7] => AdarII
+ [8] => Nisan
+ [9] => Iyyar
+ [10] => Sivan
+ [11] => Tammuz
+ [12] => Av
+ [13] => Elul
+ )
+
+ [abbrevmonths] => Array
+ (
+ [1] => Tishri
+ [2] => Heshvan
+ [3] => Kislev
+ [4] => Tevet
+ [5] => Shevat
+ [6] => AdarI
+ [7] => AdarII
+ [8] => Nisan
+ [9] => Iyyar
+ [10] => Sivan
+ [11] => Tammuz
+ [12] => Av
+ [13] => Elul
+ )
+
+ [maxdaysinmonth] => 30
+ [calname] => Jewish
+ [calsymbol] => CAL_JEWISH
+ )
+
+ [3] => Array
+ (
+ [months] => Array
+ (
+ [1] => Vendemiaire
+ [2] => Brumaire
+ [3] => Frimaire
+ [4] => Nivose
+ [5] => Pluviose
+ [6] => Ventose
+ [7] => Germinal
+ [8] => Floreal
+ [9] => Prairial
+ [10] => Messidor
+ [11] => Thermidor
+ [12] => Fructidor
+ [13] => Extra
+ )
+
+ [abbrevmonths] => Array
+ (
+ [1] => Vendemiaire
+ [2] => Brumaire
+ [3] => Frimaire
+ [4] => Nivose
+ [5] => Pluviose
+ [6] => Ventose
+ [7] => Germinal
+ [8] => Floreal
+ [9] => Prairial
+ [10] => Messidor
+ [11] => Thermidor
+ [12] => Fructidor
+ [13] => Extra
+ )
+
+ [maxdaysinmonth] => 30
+ [calname] => French
+ [calsymbol] => CAL_FRENCH
+ )
+
+)
+Array
+(
+ [months] => Array
+ (
+ [1] => January
+ [2] => February
+ [3] => March
+ [4] => April
+ [5] => May
+ [6] => June
+ [7] => July
+ [8] => August
+ [9] => September
+ [10] => October
+ [11] => November
+ [12] => December
+ )
+
+ [abbrevmonths] => Array
+ (
+ [1] => Jan
+ [2] => Feb
+ [3] => Mar
+ [4] => Apr
+ [5] => May
+ [6] => Jun
+ [7] => Jul
+ [8] => Aug
+ [9] => Sep
+ [10] => Oct
+ [11] => Nov
+ [12] => Dec
+ )
+
+ [maxdaysinmonth] => 31
+ [calname] => Julian
+ [calsymbol] => CAL_JULIAN
+)
+
+Warning: cal_info(): invalid calendar ID 99999. in %s on line %d
diff --git a/ext/calendar/tests/cal_to_jd.phpt b/ext/calendar/tests/cal_to_jd.phpt
new file mode 100644
index 0000000..fde1e0b
--- /dev/null
+++ b/ext/calendar/tests/cal_to_jd.phpt
@@ -0,0 +1,16 @@
+--TEST--
+cal_to_jd()
+--SKIPIF--
+<?php include 'skipif.inc'; ?>
+--FILE--
+<?php
+echo cal_to_jd(CAL_GREGORIAN, 8, 26, 74), "\n";
+echo cal_to_jd(CAL_JULIAN, 8, 26, 74), "\n";
+echo cal_to_jd(CAL_JEWISH, 8, 26, 74), "\n";
+echo cal_to_jd(CAL_FRENCH, 8, 26, 74), "\n";
+?>
+--EXPECT--
+1748326
+1748324
+374867
+0
diff --git a/ext/calendar/tests/easter_date.phpt b/ext/calendar/tests/easter_date.phpt
new file mode 100644
index 0000000..9222d3c
--- /dev/null
+++ b/ext/calendar/tests/easter_date.phpt
@@ -0,0 +1,21 @@
+--TEST--
+easter_date()
+--INI--
+date.timezone=UTC
+--SKIPIF--
+<?php include 'skipif.inc'; ?>
+--FILE--
+<?php
+putenv('TZ=UTC');
+echo date("Y-m-d", easter_date(2000))."\n";
+echo date("Y-m-d", easter_date(2001))."\n";
+echo date("Y-m-d", easter_date(2002))."\n";
+echo date("Y-m-d", easter_date(1492))."\n";
+?>
+--EXPECTF--
+2000-04-23
+2001-04-15
+2002-03-31
+
+Warning: easter_date(): This function is only valid for years between 1970 and 2037 inclusive in %s on line %d
+1970-01-01
diff --git a/ext/calendar/tests/easter_days.phpt b/ext/calendar/tests/easter_days.phpt
new file mode 100644
index 0000000..04aa7ae
--- /dev/null
+++ b/ext/calendar/tests/easter_days.phpt
@@ -0,0 +1,14 @@
+--TEST--
+easter_days()
+--SKIPIF--
+<?php include 'skipif.inc'; ?>
+--FILE--
+<?php
+echo easter_days(1999), "\n";
+echo easter_days(1492), "\n";
+echo easter_days(1913), "\n";
+?>
+--EXPECT--
+14
+32
+2
diff --git a/ext/calendar/tests/frenchtojd.phpt b/ext/calendar/tests/frenchtojd.phpt
new file mode 100644
index 0000000..73addb6
--- /dev/null
+++ b/ext/calendar/tests/frenchtojd.phpt
@@ -0,0 +1,16 @@
+--TEST--
+frenchtojd()
+--SKIPIF--
+<?php include 'skipif.inc'; ?>
+--FILE--
+<?php
+echo frenchtojd(-1,-1,-1), "\n";
+echo frenchtojd(0,0,0), "\n";
+echo frenchtojd(1,1,1), "\n";
+echo frenchtojd(14,31,15), "\n";
+?>
+--EXPECT--
+0
+0
+2375840
+0
diff --git a/ext/calendar/tests/gregoriantojd.phpt b/ext/calendar/tests/gregoriantojd.phpt
new file mode 100644
index 0000000..ec3628e
--- /dev/null
+++ b/ext/calendar/tests/gregoriantojd.phpt
@@ -0,0 +1,18 @@
+--TEST--
+gregoriantojd()
+--SKIPIF--
+<?php include 'skipif.inc'; ?>
+--FILE--
+<?php
+echo gregoriantojd( 0, 0, 0). "\n";
+echo gregoriantojd( 1, 1, 1582). "\n";
+echo gregoriantojd(10, 5, 1582). "\n";
+echo gregoriantojd( 1, 1, 1970). "\n";
+echo gregoriantojd( 1, 1, 2999). "\n";
+?>
+--EXPECT--
+0
+2298874
+2299151
+2440588
+2816423 \ No newline at end of file
diff --git a/ext/calendar/tests/jddayofweek.phpt b/ext/calendar/tests/jddayofweek.phpt
new file mode 100644
index 0000000..c33d598
--- /dev/null
+++ b/ext/calendar/tests/jddayofweek.phpt
@@ -0,0 +1,130 @@
+--TEST--
+jddayofweek()
+--SKIPIF--
+<?php include 'skipif.inc'; ?>
+--FILE--
+<?php
+foreach (array(2440588, 2452162, 2453926, -1000) as $jd) {
+ echo "### JD $jd ###\n";
+ for ($mode = 0; $mode <= 2; $mode++) {
+ echo "--- mode $mode ---\n";
+ for ($offset = 0; $offset <= 7; $offset++) {
+ echo jddayofweek($jd + $offset, $mode). "\n";
+ }
+ }
+}
+?>
+--EXPECT--
+### JD 2440588 ###
+--- mode 0 ---
+4
+5
+6
+0
+1
+2
+3
+4
+--- mode 1 ---
+Thursday
+Friday
+Saturday
+Sunday
+Monday
+Tuesday
+Wednesday
+Thursday
+--- mode 2 ---
+Thu
+Fri
+Sat
+Sun
+Mon
+Tue
+Wed
+Thu
+### JD 2452162 ###
+--- mode 0 ---
+0
+1
+2
+3
+4
+5
+6
+0
+--- mode 1 ---
+Sunday
+Monday
+Tuesday
+Wednesday
+Thursday
+Friday
+Saturday
+Sunday
+--- mode 2 ---
+Sun
+Mon
+Tue
+Wed
+Thu
+Fri
+Sat
+Sun
+### JD 2453926 ###
+--- mode 0 ---
+0
+1
+2
+3
+4
+5
+6
+0
+--- mode 1 ---
+Sunday
+Monday
+Tuesday
+Wednesday
+Thursday
+Friday
+Saturday
+Sunday
+--- mode 2 ---
+Sun
+Mon
+Tue
+Wed
+Thu
+Fri
+Sat
+Sun
+### JD -1000 ###
+--- mode 0 ---
+2
+3
+4
+5
+6
+0
+1
+2
+--- mode 1 ---
+Tuesday
+Wednesday
+Thursday
+Friday
+Saturday
+Sunday
+Monday
+Tuesday
+--- mode 2 ---
+Tue
+Wed
+Thu
+Fri
+Sat
+Sun
+Mon
+Tue
+
diff --git a/ext/calendar/tests/jdmonthname.phpt b/ext/calendar/tests/jdmonthname.phpt
new file mode 100644
index 0000000..d05d3c5
--- /dev/null
+++ b/ext/calendar/tests/jdmonthname.phpt
@@ -0,0 +1,314 @@
+--TEST--
+jdmonthname()
+--SKIPIF--
+<?php include 'skipif.inc'; ?>
+--FILE--
+<?php
+foreach (array(2440588, 2452162, 2453926) as $jd) {
+ echo "### JD $jd ###\n";
+ for ($mode = 0; $mode <= 6; $mode++) {
+ echo "--- mode $mode ---\n";
+ for ($offset = 0; $offset <= 12; $offset++) {
+ echo jdmonthname($jd + $offset * 30, $mode). "\n";
+ }
+ }
+}
+?>
+--EXPECT--
+### JD 2440588 ###
+--- mode 0 ---
+Jan
+Jan
+Mar
+Apr
+May
+May
+Jun
+Jul
+Aug
+Sep
+Oct
+Nov
+Dec
+--- mode 1 ---
+January
+January
+March
+April
+May
+May
+June
+July
+August
+September
+October
+November
+December
+--- mode 2 ---
+Dec
+Jan
+Feb
+Mar
+Apr
+May
+Jun
+Jul
+Aug
+Sep
+Oct
+Nov
+Dec
+--- mode 3 ---
+December
+January
+February
+March
+April
+May
+June
+July
+August
+September
+October
+November
+December
+--- mode 4 ---
+Tevet
+Shevat
+AdarI
+AdarII
+Nisan
+Iyyar
+Sivan
+Tammuz
+Av
+Elul
+Tishri
+Heshvan
+Kislev
+--- mode 5 ---
+
+
+
+
+
+
+
+
+
+
+
+
+
+--- mode 6 ---
+Jan
+Jan
+Mar
+Apr
+May
+May
+Jun
+Jul
+Aug
+Sep
+Oct
+Nov
+Dec
+### JD 2452162 ###
+--- mode 0 ---
+Sep
+Oct
+Nov
+Dec
+Jan
+Feb
+Mar
+Apr
+May
+Jun
+Jul
+Aug
+Sep
+--- mode 1 ---
+September
+October
+November
+December
+January
+February
+March
+April
+May
+June
+July
+August
+September
+--- mode 2 ---
+Aug
+Sep
+Oct
+Nov
+Dec
+Jan
+Feb
+Mar
+Apr
+May
+Jun
+Jul
+Aug
+--- mode 3 ---
+August
+September
+October
+November
+December
+January
+February
+March
+April
+May
+June
+July
+August
+--- mode 4 ---
+Elul
+Tishri
+Heshvan
+Kislev
+Tevet
+Shevat
+AdarI
+Nisan
+Iyyar
+Sivan
+Tammuz
+Av
+Elul
+--- mode 5 ---
+
+
+
+
+
+
+
+
+
+
+
+
+
+--- mode 6 ---
+Sep
+Oct
+Nov
+Dec
+Jan
+Feb
+Mar
+Apr
+May
+Jun
+Jul
+Aug
+Sep
+### JD 2453926 ###
+--- mode 0 ---
+Jul
+Aug
+Sep
+Oct
+Nov
+Dec
+Jan
+Feb
+Mar
+Apr
+May
+Jun
+Jul
+--- mode 1 ---
+July
+August
+September
+October
+November
+December
+January
+February
+March
+April
+May
+June
+July
+--- mode 2 ---
+Jun
+Jul
+Aug
+Sep
+Oct
+Nov
+Dec
+Jan
+Feb
+Mar
+Apr
+May
+Jun
+--- mode 3 ---
+June
+July
+August
+September
+October
+November
+December
+January
+February
+March
+April
+May
+June
+--- mode 4 ---
+Tammuz
+Av
+Elul
+Tishri
+Heshvan
+Kislev
+Tevet
+Shevat
+AdarI
+Nisan
+Iyyar
+Sivan
+Tammuz
+--- mode 5 ---
+
+
+
+
+
+
+
+
+
+
+
+
+
+--- mode 6 ---
+Jul
+Aug
+Sep
+Oct
+Nov
+Dec
+Jan
+Feb
+Mar
+Apr
+May
+Jun
+Jul
diff --git a/ext/calendar/tests/jdtofrench.phpt b/ext/calendar/tests/jdtofrench.phpt
new file mode 100644
index 0000000..2794409
--- /dev/null
+++ b/ext/calendar/tests/jdtofrench.phpt
@@ -0,0 +1,20 @@
+--TEST--
+jdtofrench()
+--SKIPIF--
+<?php include 'skipif.inc'; ?>
+--FILE--
+<?php
+echo jdtofrench(0). "\n";
+echo jdtofrench(2375840). "\n";
+echo jdtofrench(2375850). "\n";
+echo jdtofrench(2375940). "\n";
+echo jdtofrench(2376345). "\n";
+echo jdtofrench(2385940). "\n";
+?>
+--EXPECT--
+0/0/0
+1/1/1
+1/11/1
+4/11/1
+5/21/2
+0/0/0 \ No newline at end of file
diff --git a/ext/calendar/tests/jdtogregorian.phpt b/ext/calendar/tests/jdtogregorian.phpt
new file mode 100644
index 0000000..6b1956f
--- /dev/null
+++ b/ext/calendar/tests/jdtogregorian.phpt
@@ -0,0 +1,18 @@
+--TEST--
+jdtogregorian()
+--SKIPIF--
+<?php include 'skipif.inc'; ?>
+--FILE--
+<?php
+echo jdtogregorian(0). "\n";
+echo jdtogregorian(2298874). "\n";
+echo jdtogregorian(2299151). "\n";
+echo jdtogregorian(2440588). "\n";
+echo jdtogregorian(2816423). "\n";
+?>
+--EXPECT--
+0/0/0
+1/1/1582
+10/5/1582
+1/1/1970
+1/1/2999 \ No newline at end of file
diff --git a/ext/calendar/tests/jdtojewish.phpt b/ext/calendar/tests/jdtojewish.phpt
new file mode 100644
index 0000000..484b957
--- /dev/null
+++ b/ext/calendar/tests/jdtojewish.phpt
@@ -0,0 +1,30 @@
+--TEST--
+jdtojewish() function
+--SKIPIF--
+<?php include 'skipif.inc'; ?>
+--FILE--
+<?php
+
+var_dump(jdtojewish(gregoriantojd(10,28,2002))."\r\n".
+ jdtojewish(gregoriantojd(10,28,2002),true)."\r\n".
+ jdtojewish(gregoriantojd(10,28,2002),true, CAL_JEWISH_ADD_ALAFIM_GERESH)."\r\n".
+ jdtojewish(gregoriantojd(10,28,2002),true, CAL_JEWISH_ADD_ALAFIM)."\r\n".
+ jdtojewish(gregoriantojd(10,28,2002),true, CAL_JEWISH_ADD_ALAFIM_GERESH+CAL_JEWISH_ADD_ALAFIM)."\r\n".
+ jdtojewish(gregoriantojd(10,28,2002),true, CAL_JEWISH_ADD_GERESHAYIM)."\r\n".
+ jdtojewish(gregoriantojd(10,8,2002),true, CAL_JEWISH_ADD_GERESHAYIM)."\r\n".
+ jdtojewish(gregoriantojd(10,8,2002),true, CAL_JEWISH_ADD_GERESHAYIM+CAL_JEWISH_ADD_ALAFIM_GERESH)."\r\n".
+ jdtojewish(gregoriantojd(10,8,2002),true, CAL_JEWISH_ADD_GERESHAYIM+CAL_JEWISH_ADD_ALAFIM)."\r\n".
+ jdtojewish(gregoriantojd(10,8,2002),true, CAL_JEWISH_ADD_GERESHAYIM+CAL_JEWISH_ADD_ALAFIM+CAL_JEWISH_ADD_ALAFIM_GERESH)."\r\n");
+?>
+--EXPECT--
+string(184) "2/22/5763
+כב חשון התשסג
+כב חשון ה'תשסג
+כב חשון ה אלפים תשסג
+כב חשון ה' אלפים תשסג
+כ"ב חשון התשס"ג
+ב' חשון התשס"ג
+ב' חשון ה'תשס"ג
+ב' חשון ה אלפים תשס"ג
+ב' חשון ה' אלפים תשס"ג
+"
diff --git a/ext/calendar/tests/jdtojulian.phpt b/ext/calendar/tests/jdtojulian.phpt
new file mode 100644
index 0000000..6c87aa7
--- /dev/null
+++ b/ext/calendar/tests/jdtojulian.phpt
@@ -0,0 +1,18 @@
+--TEST--
+jdtojulian()
+--SKIPIF--
+<?php include 'skipif.inc'; ?>
+--FILE--
+<?php
+echo jdtojulian(0). "\n";
+echo jdtojulian(2298874). "\n";
+echo jdtojulian(2299151). "\n";
+echo jdtojulian(2440588). "\n";
+echo jdtojulian(2816423). "\n";
+?>
+--EXPECT--
+0/0/0
+12/22/1581
+9/25/1582
+12/19/1969
+12/12/2998 \ No newline at end of file
diff --git a/ext/calendar/tests/jdtomonthname.phpt b/ext/calendar/tests/jdtomonthname.phpt
new file mode 100644
index 0000000..5c55247
--- /dev/null
+++ b/ext/calendar/tests/jdtomonthname.phpt
@@ -0,0 +1,85 @@
+--TEST--
+jdtomonthname() test
+--SKIPIF--
+<?php if (!extension_loaded("calendar")) print "skip"; ?>
+--FILE--
+<?php
+
+$jd_days = Array(
+ 2453396,
+ 2440588,
+ -1,
+ array(),
+ 10000000
+ );
+
+foreach ($jd_days as $jd_day) {
+ echo "=== ", $jd_day, "\n";
+ var_dump(jdmonthname($jd_day,0));
+ var_dump(jdmonthname($jd_day,1));
+ var_dump(jdmonthname($jd_day,2));
+ var_dump(jdmonthname($jd_day,3));
+ var_dump(jdmonthname($jd_day,4));
+ var_dump(jdmonthname($jd_day,5));
+ echo "\n";
+}
+
+echo "Done\n";
+
+?>
+--EXPECTF--
+=== 2453396
+string(3) "Jan"
+string(7) "January"
+string(3) "Jan"
+string(7) "January"
+string(6) "Shevat"
+string(0) ""
+
+=== 2440588
+string(3) "Jan"
+string(7) "January"
+string(3) "Dec"
+string(8) "December"
+string(5) "Tevet"
+string(0) ""
+
+=== -1
+string(0) ""
+string(0) ""
+string(0) ""
+string(0) ""
+string(0) ""
+string(0) ""
+
+===
+Notice: Array to string conversion in %sjdtomonthname.php on line %d
+Array
+
+Warning: jdmonthname() expects parameter 1 to be long, array given in %s on line %d
+bool(false)
+
+Warning: jdmonthname() expects parameter 1 to be long, array given in %s on line %d
+bool(false)
+
+Warning: jdmonthname() expects parameter 1 to be long, array given in %s on line %d
+bool(false)
+
+Warning: jdmonthname() expects parameter 1 to be long, array given in %s on line %d
+bool(false)
+
+Warning: jdmonthname() expects parameter 1 to be long, array given in %s on line %d
+bool(false)
+
+Warning: jdmonthname() expects parameter 1 to be long, array given in %s on line %d
+bool(false)
+
+=== 10000000
+string(3) "Dec"
+string(8) "December"
+string(3) "Jul"
+string(4) "July"
+string(6) "Tishri"
+string(0) ""
+
+Done
diff --git a/ext/calendar/tests/jdtounix.phpt b/ext/calendar/tests/jdtounix.phpt
new file mode 100644
index 0000000..8d85543
--- /dev/null
+++ b/ext/calendar/tests/jdtounix.phpt
@@ -0,0 +1,16 @@
+--TEST--
+jdtounix()
+--INI--
+date.timezone=UTC
+--SKIPIF--
+<?php include 'skipif.inc'; ?>
+--FILE--
+<?php
+echo date("Y-m-d",jdtounix(2440588)). "\n";
+echo date("Y-m-d",jdtounix(2452162)). "\n";
+echo date("Y-m-d",jdtounix(2453926)). "\n";
+?>
+--EXPECT--
+1970-01-01
+2001-09-09
+2006-07-09
diff --git a/ext/calendar/tests/jewishtojd.phpt b/ext/calendar/tests/jewishtojd.phpt
new file mode 100644
index 0000000..a9a2ff0
--- /dev/null
+++ b/ext/calendar/tests/jewishtojd.phpt
@@ -0,0 +1,16 @@
+--TEST--
+jewishtojd()
+--SKIPIF--
+<?php include 'skipif.inc'; ?>
+--FILE--
+<?php
+echo jewishtojd(-1,-1,-1). "\n";
+echo jewishtojd(0,0,0). "\n";
+echo jewishtojd(1,1,1). "\n";
+echo jewishtojd(2,22,5763). "\n";
+?>
+--EXPECT--
+0
+0
+347998
+2452576 \ No newline at end of file
diff --git a/ext/calendar/tests/juliantojd.phpt b/ext/calendar/tests/juliantojd.phpt
new file mode 100644
index 0000000..9563e04
--- /dev/null
+++ b/ext/calendar/tests/juliantojd.phpt
@@ -0,0 +1,18 @@
+--TEST--
+juliantojd()
+--SKIPIF--
+<?php include 'skipif.inc'; ?>
+--FILE--
+<?php
+echo juliantojd( 0, 0, 0). "\n";
+echo juliantojd( 1, 1, 1582). "\n";
+echo juliantojd(10, 5, 1582). "\n";
+echo juliantojd( 1, 1, 1970). "\n";
+echo juliantojd( 1, 1, 2999). "\n";
+?>
+--EXPECT--
+0
+2298884
+2299161
+2440601
+2816443 \ No newline at end of file
diff --git a/ext/calendar/tests/skipif.inc b/ext/calendar/tests/skipif.inc
new file mode 100644
index 0000000..de8e4ae
--- /dev/null
+++ b/ext/calendar/tests/skipif.inc
@@ -0,0 +1,4 @@
+<?php
+if(!extension_loaded("calendar"))
+ print "skip - CALENDAR extension not available";
+?>
diff --git a/ext/calendar/tests/unixtojd.phpt b/ext/calendar/tests/unixtojd.phpt
new file mode 100644
index 0000000..4eeb1ca
--- /dev/null
+++ b/ext/calendar/tests/unixtojd.phpt
@@ -0,0 +1,40 @@
+--TEST--
+unixtojd()
+--SKIPIF--
+<?php include 'skipif.inc'; ?>
+--ENV--
+TZ=UTC
+--FILE--
+<?php
+// this line has no impact on test output on Windows
+putenv('TZ=UTC');
+// getenv('TZ') returns 'UTC' here
+// putenv (basic_functions.c) does call tzset() when the env var being put is 'TZ'
+// -adding a call direct to GetEnvironmentVariableA just before tzset() is called to check the value of 'TZ' returns 'UTC'
+// putting a call to date_default_timezone_set() here doesn't help
+//
+// on Windows, the only thing that gets this test to pass is to put TZ=UTC in --ENV-- section
+// -since putenv() is written to call tzset() when env var is TZ, I assume that putenv("TZ=UTC") is intended to work
+// and should work on all platforms(including Windows).
+// easter_date.phpt passes
+// -doesn't use --ENV-- section
+// -uses --INI-- section with date.timezone=UTC
+// -uses putenv('TZ=UTC')
+// date.timezone=UTC
+// -if ommitted from easter_date.phpt, outputs DATE_TZ_ERRMSG warning
+// -easter_date() calls mktime() and localtime()
+// -whereas unixtojd(1000000000) calls localtime(1000000000)
+// -if ommitted from unixtojd.phpt, does NOT output DATE_TZ_ERRMSG
+//
+// unixtojd() calls php_localtime_r() which for Pacific timezone systems, returns a time -8 hours
+// -this incorrect localtime is passed to the julian date conversion (GregorianToSDN) function which works (probably correctly)
+// but returns -1 day from expected because its input is -1 from expected
+
+echo unixtojd(40000). "\n";
+echo unixtojd(1000000000). "\n";
+echo unixtojd(1152459009). "\n";
+?>
+--EXPECT--
+2440588
+2452162
+2453926