1<?php 2namespace Fisharebest\ExtCalendar; 3 4use InvalidArgumentException; 5 6/** 7 * Class PersianCalendar - calculations for the Persian (Jalali) calendar. 8 * 9 * Algorithms for Julian days based on https://www.fourmilab.ch/documents/calendar/ 10 * 11 * @author Greg Roach <fisharebest@gmail.com> 12 * @copyright (c) 2014-2017 Greg Roach 13 * @license This program is free software: you can redistribute it and/or modify 14 * it under the terms of the GNU General Public License as published by 15 * the Free Software Foundation, either version 3 of the License, or 16 * (at your option) any later version. 17 * 18 * This program is distributed in the hope that it will be useful, 19 * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 * GNU General Public License for more details. 22 * 23 * You should have received a copy of the GNU General Public License 24 * along with this program. If not, see <http://www.gnu.org/licenses/>. 25 */ 26class PersianCalendar implements CalendarInterface 27{ 28 /** 29 * In each 128 year cycle, the following years are leap years. 30 * 31 * @var int[] 32 */ 33 private static $LEAP_YEAR_CYCLE = array( 34 0, 5, 9, 13, 17, 21, 25, 29, 34, 38, 42, 46, 50, 54, 58, 62, 67, 71, 75, 79, 83, 87, 91, 95, 100, 104, 108, 112, 116, 120, 124 35 ); 36 37 /** 38 * Determine the number of days in a specified month, allowing for leap years, etc. 39 * 40 * @param int $year 41 * @param int $month 42 * 43 * @return int 44 */ 45 public function daysInMonth($year, $month) 46 { 47 if ($month <= 6) { 48 return 31; 49 } elseif ($month <= 11 || $this->isLeapYear($year)) { 50 return 30; 51 } else { 52 return 29; 53 } 54 } 55 56 /** 57 * Determine the number of days in a week. 58 * 59 * @return int 60 */ 61 public function daysInWeek() 62 { 63 return 7; 64 } 65 66 /** 67 * The escape sequence used to indicate this calendar in GEDCOM files. 68 * 69 * @return string 70 */ 71 public function gedcomCalendarEscape() 72 { 73 return '@#DJALALI@'; 74 } 75 76 /** 77 * Determine whether or not a given year is a leap-year. 78 * 79 * @param int $year 80 * 81 * @return bool 82 */ 83 public function isLeapYear($year) 84 { 85 return in_array((($year + 2346) % 2820) % 128, self::$LEAP_YEAR_CYCLE); 86 } 87 88 /** 89 * What is the highest Julian day number that can be converted into this calendar. 90 * 91 * @return int 92 */ 93 public function jdEnd() 94 { 95 return PHP_INT_MAX; 96 } 97 98 /** 99 * What is the lowest Julian day number that can be converted into this calendar. 100 * 101 * @return int 102 */ 103 public function jdStart() 104 { 105 return 1948321; // 1 Farvardīn 0001 AP, 19 MAR 0622 AD 106 } 107 108 /** 109 * Convert a Julian day number into a year/month/day. 110 * 111 * @param int $julian_day 112 * 113 * @return int[] 114 */ 115 public function jdToYmd($julian_day) 116 { 117 $depoch = $julian_day - 2121446; // 1 Farvardīn 475 118 $cycle = (int) floor($depoch / 1029983); 119 $cyear = $this->mod($depoch, 1029983); 120 if ($cyear == 1029982) { 121 $ycycle = 2820; 122 } else { 123 $aux1 = (int) ($cyear / 366); 124 $aux2 = $cyear % 366; 125 $ycycle = (int) (((2134 * $aux1) + (2816 * $aux2) + 2815) / 1028522) + $aux1 + 1; 126 } 127 $year = $ycycle + (2820 * $cycle) + 474; 128 129 // If we allowed negative years, we would deal with them here. 130 $yday = $julian_day - $this->ymdToJd($year, 1, 1) + 1; 131 $month = ($yday <= 186) ? ceil($yday / 31) : ceil(($yday - 6) / 30); 132 $day = $julian_day - $this->ymdToJd($year, $month, 1) + 1; 133 134 return array((int) $year, (int) $month, (int) $day); 135 } 136 137 /** 138 * Determine the number of months in a year (if given), 139 * or the maximum number of months in any year. 140 * 141 * @param int|null $year 142 * 143 * @return int 144 */ 145 public function monthsInYear($year = null) 146 { 147 return 12; 148 } 149 150 /** 151 * Convert a year/month/day to a Julian day number. 152 * 153 * @param int $year 154 * @param int $month 155 * @param int $day 156 * 157 * @return int 158 */ 159 public function ymdToJd($year, $month, $day) 160 { 161 if ($month < 1 || $month > $this->monthsInYear()) { 162 throw new InvalidArgumentException('Month ' . $month . ' is invalid for this calendar'); 163 } 164 165 $epbase = $year - (($year >= 0) ? 474 : 473); 166 $epyear = 474 + $this->mod($epbase, 2820); 167 168 return 169 $day + 170 (($month <= 7) ? (($month - 1) * 31) : ((($month - 1) * 30) + 6)) + 171 (int) ((($epyear * 682) - 110) / 2816) + 172 ($epyear - 1) * 365 + 173 (int) (floor($epbase / 2820)) * 1029983 + 174 $this->jdStart() - 1; 175 } 176 177 /** 178 * The PHP modulus function returns a negative modulus for a negative dividend. 179 * This algorithm requires a "traditional" modulus function where the modulus is 180 * always positive. 181 * 182 * @param int $dividend 183 * @param int $divisor 184 * 185 * @return int 186 */ 187 public function mod($dividend, $divisor) 188 { 189 if ($divisor === 0) { 190 return 0; 191 } 192 193 $modulus = $dividend % $divisor; 194 if ($modulus < 0) { 195 $modulus += $divisor; 196 } 197 198 return $modulus; 199 } 200} 201