<?php
namespace Fisharebest\ExtCalendar;
/**
* interface CalendarInterface - each calendar implementation needs to provide
* these methods.
*
* @author Greg Roach <fisharebest@gmail.com>
* @copyright (c) 2014 Greg Roach
* @license This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
interface CalendarInterface {
public function daysInMonth($year, $month);
public function dayOfWeek($jd);
public function jdToYmd($jd);
public function leapYear($year);
public function ymdToJd($year, $month, $day);
}
/**
* class Calendar - generic base class for specific calendars.
*
* @author Greg Roach <fisharebest@gmail.com>
* @copyright (c) 2014 Greg Roach
* @license This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
abstract class Calendar {
/** Same as PHP’s ext/calendar extension */
const PHP_CALENDAR_NAME = 'Undefined';
/** Same as PHP’s ext/calendar extension */
const PHP_CALENDAR_NUMBER = -1;
/** Same as PHP’s ext/calendar extension */
const PHP_CALENDAR_SYMBOL = 'CAL_UNDEFINED';
/** See the GEDCOM specification */
const GEDCOM_CALENDAR_ESCAPE = '@#DUNKNOWN@';
/** The earliest Julian Day number that can be converted into this calendar. */
const JD_START = 1;
/** The latest Julian Day number that can be converted into this calendar. */
const JD_END = 2147483647;
/** The maximum number of months in any year */
const MAX_MONTHS_IN_YEAR = 12;
/** The maximum number of days in any month */
const MAX_DAYS_IN_MONTH = 31;
/**
* The concrete class needs to provide the implementation.
*
* @return string[]
*/
protected abstract function monthNames();
/**
* English names for the days of the week.
*
* @return string[]
*/
protected function dayNames() {
return array(
'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday',
);
}
/**
* Abbreviated English names for the days of the week.
*
* @return string[]
*/
protected function dayNamesAbbreviated() {
return array(
'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat',
);
}
/**
* Convert a Julian Day number into a calendar date.
*
* @param $jd
* @return int[] Array of month, day and year
*/
public function calFromJd($jd) {
$dow = $this->dayOfWeek($jd);
if ($jd >= static::JD_START && $jd <= static::JD_END) {
list($year, $month, $day) = $this->jdToYmd($jd);
return array(
'date' => $month . '/' . $day . '/' . $year,
'month' => $month,
'day' => $day,
'year' => $year,
'dow' => $dow,
'abbrevdayname' => $this->dayNameAbbreviated($dow),
'dayname' => $this->dayName($dow),
'abbrevmonth' => $this->jdMonthNameAbbreviated($jd),
'monthname' => $this->jdMonthName($jd),
);
} else {
return array(
'date' => '0/0/0',
'month' => 0,
'day' => 0,
'year' => 0,
'dow' => $dow,
'abbrevdayname' => $this->dayNameAbbreviated($dow),
'dayname' => $this->dayName($dow),
'abbrevmonth' => '',
'monthname' => '',
);
}
}
/**
* Provide information about this calendar.
*
* @return array
*/
public function phpCalInfo() {
return array(
'months' => $this->monthNames(),
'abbrevmonths' => $this->monthNamesAbbreviated(),
'maxdaysinmonth' => static::MAX_DAYS_IN_MONTH,
'calname' => static::PHP_CALENDAR_NAME,
'calsymbol' => static::PHP_CALENDAR_SYMBOL,
);
}
/**
* Calculate the day of the week for a given Julian Day number.
*
* @param int $jd
*
* @return int 0=Sunday ... 6=Saturday
*/
public function dayOfWeek($jd) {
$dow = ($jd + 1) % 7;
if ($dow < 0) {
return $dow + 7;
} else {
return $dow;
}
}
/**
* English name for a day of the week.
*
* @param int $dow Day of the week
*
* @return string
*/
public function dayName($dow) {
$days = $this->dayNames();
return $days[$dow];
}
/**
* Abbreviated English name for a day of the week.
*
* @param int $dow Day of the week
*
* @return string
*/
public function dayNameAbbreviated($dow) {
$days = $this->dayNamesAbbreviated();
return $days[$dow];
}
/**
* Calculate the name of a month, for a specified Julian Day number.
*
* @param $jd
*
* @return string
*/
public function jdMonthName($jd) {
list(, $month) = $this->jdToYmd($jd);
$months = $this->monthNames();
return $months[$month];
}
/**
* Calculate the name of a month, for a specified Julian Day number.
*
* @param int $jd
*
* @return string
*/
public function jdMonthNameAbbreviated($jd) {
list(, $month) = $this->jdToYmd($jd);
$months = $this->monthNamesAbbreviated();
return $months[$month];
}
/**
* Unless otherwise defined, abbreviated month names are the same as full names.
*
* @return string[]
*/
public function monthNamesAbbreviated() {
return $this->monthNames();
}
}
/**
* class JulianCalendar - calculations for the Julian calendar.
*
* @author Greg Roach <fisharebest@gmail.com>
* @copyright (c) 2014 Greg Roach
* @license This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class JulianCalendar extends Calendar implements CalendarInterface {
/** Same as PHP’s ext/calendar extension */
const PHP_CALENDAR_NAME = 'Julian';
/** Same as PHP’s ext/calendar extension */
const PHP_CALENDAR_NUMBER = 1;
/** Same as PHP’s ext/calendar extension */
const PHP_CALENDAR_SYMBOL = 'CAL_JULIAN';
/** See the GEDCOM specification */
const GEDCOM_CALENDAR_ESCAPE = '@#DJULIAN@';
/**
* Month lengths for regular years and leap-years.
*
* @var int[][]
*/
protected static $DAYS_IN_MONTH = array(
0 => array(1 => 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31),
1 => array(1 => 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31),
);
/**
* English month names.
*
* @return string[]
*/
public function monthNames() {
return array(
1 => 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December',
);
}
/**
* Abbreviated English month names.
*
* @return string[]
*/
public function monthNamesAbbreviated() {
return array(
1 => 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec',
);
}
/**
* Determine whether a year is a leap year.
*
* @param int $year
* @return bool
*/
public function leapYear($year) {
if ($year < 0) {
$year++;
}
return $year % 4 == 0;
}
/**
* Convert a Julian day number into a year/month/day.
*
* @param $jd
*
* @return int[];
*/
public function jdToYmd($jd) {
$c = $jd + 32082;
$d = (int)((4 * $c + 3) / 1461);
$e = $c - (int)(1461 * $d / 4);
$m = (int)((5 * $e + 2) / 153);
$day = $e - (int)((153 * $m + 2) / 5) + 1;
$month = $m + 3 - 12 * (int)($m / 10);
$year = $d - 4800 + (int)($m / 10);
if ($year < 1) {
// 0=1BC, -1=2BC, etc.
$year--;
}
return array($year, $month, $day);
}
/**
* Convert a year/month/day into a Julian day number
*
* @param int $year
* @param int $month
* @param int $day
*
* @return int
*/
public function ymdToJd($year, $month, $day) {
if ($year < 0) {
// 1 B.C.E. => 0, 2 B.C.E> => 1, etc.
++$year;
}
$a = (int)((14 - $month) / 12);
$year = $year + 4800 - $a;
$month = $month + 12 * $a - 3;
return $day + (int)((153 * $month + 2) / 5) + 365 * $year + (int)($year / 4) - 32083;
}
/**
* Calculate the number of days in a month.
*
* @param int $year
* @param int $month
*
* @return int
*/
public function daysInMonth($year, $month) {
if ($year == 0 || $month < 1 || $month > 12) {
return trigger_error('invalid date.', E_USER_WARNING);
} else {
return static::$DAYS_IN_MONTH[$this->leapYear($year)][$month];
}
}
/**
* Get the number of days after March 21 that easter falls, for a given year.
*
* Uses the algorithm found in PHP’s ext/calendar/easter.c
*
* @param int $year
*
* @return int
*/
public function easterDays($year) {
// The “golden” number
$golden = 1 + $year % 19;
// The “dominical” number (finding a Sunday)
$dom = ($year + (int)($year / 4) + 5) % 7;
if ($dom < 0) {
$dom += 7;
}
// The uncorrected “Paschal full moon” date
$pfm = (3 - 11 * $golden - 7) % 30;
if ($pfm < 0) {
$pfm += 30;
}
// The corrected “Paschal full moon” date
if ($pfm == 29 || $pfm == 28 && $golden > 11) {
$pfm--;
}
$tmp = (4 - $pfm - $dom) % 7;
if ($tmp < 0) {
$tmp += 7;
}
return $pfm + $tmp + 1;
}
}