persian_date-8.x-4.1/src/Library/Jalali/jDateTime.php
src/Library/Jalali/jDateTime.php
<?php
namespace Drupal\persian_date\Library\Jalali;
use Drupal\persian_date\Library\Carbon\Carbon;
/**
* Class jDateTime
* @package Morilog\Jalali
*/
class jDateTime
{
private static $temp;
/**
* Converts a Gregorian date to Jalali.
*
* @param $gy
* @param $gm
* @param $gd
* @return array
* 0: Year
* 1: Month
* 2: Day
*/
public static function toJalali($gy, $gm, $gd)
{
return self::d2j(self::g2d($gy, $gm, $gd));
}
/**
* Converts a Jalali date to Gregorian.
*
* @param int $jy
* @param int $jm
* @param int $jd
* @return array
* 0: Year
* 1: Month
* 2: Day
*/
public static function toGregorian($jy, $jm, $jd)
{
return self::d2g(self::j2d($jy, $jm, $jd));
}
/**
* Converts a Jalali date to Gregorian.
*
* @param int $jy
* @param int $jm
* @param int $jd
* @return Gregorian DateTime
*/
public static function toGregorianDate($jy, $jm, $jd)
{
$georgianDateArr = self::toGregorian($jy, $jm, $jd);
$year = $georgianDateArr[0];
$month = $georgianDateArr[1];
$day = $georgianDateArr[2];
$georgianDate = new \DateTime();
$georgianDate->setDate($year, $month, $day);
return $georgianDate;
}
/**
* Checks whether a Jalaali date is valid or not.
*
* @param int $jy
* @param int $jm
* @param int $jd
* @return bool
*/
public static function isValidateJalaliDate($jy, $jm, $jd)
{
return $jy >= -61 && $jy <= 3177
&& $jm >= 1 && $jm <= 12
&& $jd >= 1 && $jd <= self::jalaliMonthLength($jy, $jm);
}
/**
* Checks whether a date is valid or not.
*
* @param $year
* @param $month
* @param $day
* @param bool $isJalali
* @return bool
*/
public static function checkDate($year, $month, $day, $isJalali = true)
{
return $isJalali === true ? self::isValidateJalaliDate($year, $month, $day) : checkdate($month, $day, $year);
}
/**
* Is this a leap year or not?
*
* @param $jy
* @return bool
*/
public static function isLeapJalaliYear($jy)
{
return self::jalaliCal($jy)['leap'] === 0;
}
/**
* Number of days in a given month in a Jalaali year.
*
* @param int $jy
* @param int $jm
* @return int
*/
public static function jalaliMonthLength($jy, $jm)
{
if ($jm <= 6) {
return 31;
}
if ($jm <= 11) {
return 30;
}
return self::isLeapJalaliYear($jy) ? 30 : 29;
}
/**
* This function determines if the Jalaali (Persian) year is
* leap (366-day long) or is the common year (365 days), and
* finds the day in March (Gregorian calendar) of the first
* day of the Jalaali year (jy).
*
* @param int $jy Jalaali calendar year (-61 to 3177)
* @return array
* leap: number of years since the last leap year (0 to 4)
* gy: Gregorian year of the beginning of Jalaali year
* march: the March day of Farvardin the 1st (1st day of jy)
* @see: http://www.astro.uni.torun.pl/~kb/Papers/EMP/PersianC-EMP.htm
* @see: http://www.fourmilab.ch/documents/calendar/
*/
public static function jalaliCal($jy)
{
$breaks = [-61, 9, 38, 199, 426, 686, 756, 818, 1111, 1181, 1210
, 1635, 2060, 2097, 2192, 2262, 2324, 2394, 2456, 3178
];
$breaksCount = count($breaks);
$gy = $jy + 621;
$leapJ = -14;
$jp = $breaks[0];
if ($jy < $jp || $jy >= $breaks[$breaksCount - 1]) {
throw new \InvalidArgumentException('Invalid Jalali year : ' . $jy);
}
$jump = 0;
for ($i = 1; $i < $breaksCount; $i += 1) {
$jm = $breaks[$i];
$jump = $jm - $jp;
if ($jy < $jm) {
break;
}
$leapJ = $leapJ + self::div($jump, 33) * 8 + self::div(self::mod($jump, 33), 4);
$jp = $jm;
}
$n = $jy - $jp;
$leapJ = $leapJ + self::div($n, 33) * 8 + self::div(self::mod($n, 33) + 3, 4);
if (self::mod($jump, 33) === 4 && $jump - $n === 4) {
$leapJ += 1;
}
$leapG = self::div($gy, 4) - self::div((self::div($gy, 100) + 1) * 3, 4) - 150;
$march = 20 + $leapJ - $leapG;
if ($jump - $n < 6) {
$n = $n - $jump + self::div($jump + 4, 33) * 33;
}
$leap = self::mod(self::mod($n + 1, 33) - 1, 4);
if ($leap === -1) {
$leap = 4;
}
return [
'leap' => $leap,
'gy' => $gy,
'march' => $march
];
}
/**
* @param $a
* @param $b
* @return float
*/
public static function div($a, $b)
{
return ~~($a / $b);
}
/**
* @param $a
* @param $b
* @return mixed
*/
public static function mod($a, $b)
{
return $a - ~~($a / $b) * $b;
}
/**
* @param $jdn
* @return array
*/
public static function d2g($jdn)
{
$j = 4 * $jdn + 139361631;
$j += self::div(self::div(4 * $jdn + 183187720, 146097) * 3, 4) * 4 - 3908;
$i = self::div(self::mod($j, 1461), 4) * 5 + 308;
$gd = self::div(self::mod($i, 153), 5) + 1;
$gm = self::mod(self::div($i, 153), 12) + 1;
$gy = self::div($j, 1461) - 100100 + self::div(8 - $gm, 6);
return [$gy, $gm, $gd];
}
/**
* Calculates the Julian Day number from Gregorian or Julian
* calendar dates. This integer number corresponds to the noon of
* the date (i.e. 12 hours of Universal Time).
* The procedure was tested to be good since 1 March, -100100 (of both
* calendars) up to a few million years into the future.
*
* @param int $gy Calendar year (years BC numbered 0, -1, -2, ...)
* @param int $gm Calendar month (1 to 12)
* @param int $gd Calendar day of the month (1 to 28/29/30/31)
* @return int Julian Day number
*/
public static function g2d($gy, $gm, $gd)
{
return (
self::div(($gy + self::div($gm - 8, 6) + 100100) * 1461, 4)
+ self::div(153 * self::mod($gm + 9, 12) + 2, 5)
+ $gd - 34840408
) - self::div(self::div($gy + 100100 + self::div($gm - 8, 6), 100) * 3, 4) + 752;
}
/**
* Converts a date of the Jalaali calendar to the Julian Day number.
*
* @param int $jy Jalaali year (1 to 3100)
* @param int $jm Jalaali month (1 to 12)
* @param int $jd Jalaali day (1 to 29/31)
* @return int Julian Day number
*/
public static function j2d($jy, $jm, $jd)
{
$jCal = self::jalaliCal($jy);
return self::g2d($jCal['gy'], 3, $jCal['march']) + ($jm - 1) * 31 - self::div($jm, 7) * ($jm - 7) + $jd - 1;
}
/**
* Converts the Julian Day number to a date in the Jalaali calendar.
*
* @param int $jdn Julian Day number
* @return array
* 0: Jalaali year (1 to 3100)
* 1: Jalaali month (1 to 12)
* 2: Jalaali day (1 to 29/31)
*/
public static function d2j($jdn)
{
$gy = self::d2g($jdn)[0];
$jy = $gy - 621;
$jCal = self::jalaliCal($jy);
$jdn1f = self::g2d($gy, 3, $jCal['march']);
$k = $jdn - $jdn1f;
if ($k >= 0) {
if ($k <= 185) {
$jm = 1 + self::div($k, 31);
$jd = self::mod($k, 31) + 1;
return [$jy, $jm, $jd];
} else {
$k -= 186;
}
} else {
$jy -= 1;
$k += 179;
if ($jCal['leap'] === 1) {
$k += 1;
}
}
$jm = 7 + self::div($k, 30);
$jd = self::mod($k, 30) + 1;
return [$jy, $jm, $jd];
}
/**
* @param $format
* @param bool $stamp
* @param bool $timezone
* @return mixed
*/
public static function date($format, $stamp = false, $timezone = null)
{
$stamp = ($stamp !== false) ? $stamp : time();
$dateTime = static::createDateTime($stamp, $timezone);
//Find what to replace
$chars = (preg_match_all('/([a-zA-Z]{1})/', $format, $chars)) ? $chars[0] : array();
//Intact Keys
$intact = array('B', 'h', 'H', 'g', 'G', 'i', 's', 'I', 'U', 'u', 'Z', 'O', 'P');
$intact = self::filterArray($chars, $intact);
$intactValues = array();
foreach ($intact as $k => $v) {
$intactValues[$k] = $dateTime->format($v);
}
//End Intact Keys
//Changed Keys
list($year, $month, $day) = array($dateTime->format('Y'), $dateTime->format('n'), $dateTime->format('j'));
list($jYear, $jMonth, $jDay) = self::toJalali($year, $month, $day);
$keys = array(
'd',
'D',
'j',
'l',
'N',
'S',
'w',
'z',
'W',
'F',
'm',
'M',
'n',
't',
'L',
'o',
'Y',
'y',
'a',
'A',
'c',
'r',
'e',
'T'
);
$keys = self::filterArray($chars, $keys, array('z'));
$values = array();
foreach ($keys as $k => $key) {
$v = '';
switch ($key) {
//Day
case 'd':
$v = sprintf("%02d", $jDay);
break;
case 'D':
$v = self::getDayNames($dateTime->format('D'), true);
break;
case 'j':
$v = $jDay;
break;
case 'l':
$v = self::getDayNames($dateTime->format('l'));
break;
case 'N':
$v = self::getDayNames($dateTime->format('l'), false, 1, true);
break;
case 'S':
$v = 'ام';
break;
case 'w':
$v = self::getDayNames($dateTime->format('l'), false, 1, true) - 1;
break;
case 'z':
if ($jMonth > 6) {
$v = 186 + (($jMonth - 6 - 1) * 30) + $jDay;
} else {
$v = (($jMonth - 1) * 31) + $jDay;
}
self::$temp['z'] = $v;
break;
//Week
case 'W':
$v = is_int(self::$temp['z'] / 7) ? (self::$temp['z'] / 7) : intval(self::$temp['z'] / 7 + 1);
break;
//Month
case 'F':
$v = self::getMonthNames($jMonth);
break;
case 'm':
$v = sprintf("%02d", $jMonth);
break;
case 'M':
$v = self::getMonthNames($jMonth, true);
break;
case 'n':
$v = $jMonth;
break;
case 't':
$v = ($jMonth == 12) ? 29 : (($jMonth > 6 && $jMonth != 12) ? 30 : 31);
break;
//Year
case 'L':
$tmpObj = static::createDateTime(time() - 31536000, $timezone);
$v = $tmpObj->format('L');
break;
case 'o':
case 'Y':
$v = $jYear;
break;
case 'y':
$v = $jYear % 100;
break;
//Time
case 'a':
$v = ($dateTime->format('a') == 'am') ? 'ق.ظ' : 'ب.ظ';
break;
case 'A':
$v = ($dateTime->format('A') == 'AM') ? 'قبل از ظهر' : 'بعد از ظهر';
break;
//Full Dates
case 'c':
$v = $jYear . '-' . sprintf("%02d", $jMonth) . '-' . sprintf("%02d", $jDay) . 'T';
$v .= $dateTime->format('H') . ':' . $dateTime->format('i') . ':' . $dateTime->format('s') . $dateTime->format('P');
break;
case 'r':
$v = self::getDayNames($dateTime->format('D'), true) . ', ' . sprintf("%02d",
$jDay) . ' ' . self::getMonthNames($jMonth, true);
$v .= ' ' . $jYear . ' ' . $dateTime->format('H') . ':' . $dateTime->format('i') . ':' . $dateTime->format('s') . ' ' . $dateTime->format('P');
break;
//Timezone
case 'e':
$v = $dateTime->format('e');
break;
case 'T':
$v = $dateTime->format('T');
break;
}
$values[$k] = $v;
}
//End Changed Keys
//Merge
$keys = array_merge($intact, $keys);
$values = array_merge($intactValues, $values);
return strtr($format, array_combine($keys, $values));
}
/**
* @param $format
* @param bool $stamp
* @param null $timezone
* @return mixed
*/
public static function strftime($format, $stamp = false, $timezone = null)
{
$str_format_code = array(
"%a",
"%A",
"%d",
"%e",
"%j",
"%u",
"%w",
"%U",
"%V",
"%W",
"%b",
"%B",
"%h",
"%m",
"%C",
"%g",
"%G",
"%y",
"%Y",
"%H",
"%I",
"%l",
"%M",
"%p",
"%P",
"%r",
"%R",
"%S",
"%T",
"%X",
"%z",
"%Z",
"%c",
"%D",
"%F",
"%s",
"%x",
"%n",
"%t",
"%%",
);
$date_format_code = array(
"D",
"l",
"d",
"j",
"z",
"N",
"w",
"W",
"W",
"W",
"M",
"F",
"M",
"m",
"y",
"y",
"y",
"y",
"Y",
"H",
"h",
"g",
"i",
"A",
"a",
"h:i:s A",
"H:i",
"s",
"H:i:s",
"h:i:s",
"H",
"H",
"D j M H:i:s",
"d/m/y",
"Y-m-d",
"U",
"d/m/y",
"\n",
"\t",
"%",
);
//Change Strftime format to Date format
$format = str_replace($str_format_code, $date_format_code, $format);
//Convert to date
return self::date($format, $stamp, $timezone);
}
private static function getDayNames($day, $shorten = false, $len = 1, $numeric = false)
{
switch (strtolower($day)) {
case 'sat':
case 'saturday':
$ret = 'شنبه';
$n = 1;
break;
case 'sun':
case 'sunday':
$ret = 'یکشنبه';
$n = 2;
break;
case 'mon':
case 'monday':
$ret = 'دوشنبه';
$n = 3;
break;
case 'tue':
case 'tuesday':
$ret = 'سه شنبه';
$n = 4;
break;
case 'wed':
case 'wednesday':
$ret = 'چهارشنبه';
$n = 5;
break;
case 'thu':
case 'thursday':
$ret = 'پنجشنبه';
$n = 6;
break;
case 'fri':
case 'friday':
$ret = 'جمعه';
$n = 7;
break;
default:
$ret = '';
$n = -1;
}
return ($numeric) ? $n : (($shorten) ? mb_substr($ret, 0, $len, 'UTF-8') : $ret);
}
private static function getMonthNames($month, $shorten = false, $len = 3)
{
$ret = '';
switch ($month) {
case '1':
$ret = 'فروردین';
break;
case '2':
$ret = 'اردیبهشت';
break;
case '3':
$ret = 'خرداد';
break;
case '4':
$ret = 'تیر';
break;
case '5':
$ret = 'مرداد';
break;
case '6':
$ret = 'شهریور';
break;
case '7':
$ret = 'مهر';
break;
case '8':
$ret = 'آبان';
break;
case '9':
$ret = 'آذر';
break;
case '10':
$ret = 'دی';
break;
case '11':
$ret = 'بهمن';
break;
case '12':
$ret = 'اسفند';
break;
}
return ($shorten) ? mb_substr($ret, 0, $len, 'UTF-8') : $ret;
}
private static function filterArray($needle, $haystack, $always = array())
{
foreach ($haystack as $k => $v) {
if (!in_array($v, $needle) && !in_array($v, $always)) {
unset($haystack[$k]);
}
}
return $haystack;
}
/**
* @param $format
* @param $date
* @return array
*/
public static function parseFromFormat($format, $date)
{
if ($format === 'Y-m-d') {
list($year, $month, $day) = explode('-',$date);
return [
'year' => $year,
'month' => $month,
'day' => $day,
'hour' => 12,
'minute' => 0,
'second' => 0,
];
}
// reverse engineer date formats
$keys = array(
'Y' => array('year', '\d{4}'),
'y' => array('year', '\d{2}'),
'm' => array('month', '\d{2}'),
'n' => array('month', '\d{1,2}'),
'M' => array('month', '[A-Z][a-z]{3}'),
'F' => array('month', '[A-Z][a-z]{2,8}'),
'd' => array('day', '\d{2}'),
'j' => array('day', '\d{1,2}'),
'D' => array('day', '[A-Z][a-z]{2}'),
'l' => array('day', '[A-Z][a-z]{6,9}'),
'u' => array('hour', '\d{1,6}'),
'h' => array('hour', '\d{2}'),
'H' => array('hour', '\d{2}'),
'g' => array('hour', '\d{1,2}'),
'G' => array('hour', '\d{1,2}'),
'i' => array('minute', '\d{2}'),
's' => array('second', '\d{2}'),
);
// convert format string to regex
$regex = '';
$chars = str_split($format);
foreach ($chars as $n => $char) {
$lastChar = isset($chars[$n - 1]) ? $chars[$n - 1] : '';
$skipCurrent = '\\' == $lastChar;
if (!$skipCurrent && isset($keys[$char])) {
$regex .= '(?P<' . $keys[$char][0] . '>' . $keys[$char][1] . ')';
} else {
if ('\\' == $char) {
$regex .= $char;
} else {
$regex .= preg_quote($char);
}
}
}
$dt = array();
$dt['error_count'] = 0;
// now try to match it
if (preg_match('#^' . $regex . '$#', $date, $dt)) {
foreach ($dt as $k => $v) {
if (is_int($k)) {
unset($dt[$k]);
}
}
if (!jDateTime::checkdate($dt['month'], $dt['day'], $dt['year'], false)) {
$dt['error_count'] = 1;
}
} else {
$dt['error_count'] = 1;
}
$dt['errors'] = array();
$dt['fraction'] = '';
$dt['warning_count'] = 0;
$dt['warnings'] = array();
$dt['is_localtime'] = 0;
$dt['zone_type'] = 0;
$dt['zone'] = 0;
$dt['is_dst'] = '';
if (strlen($dt['year']) == 2) {
$now = jDate::forge('now');
$x = $now->format('Y') - $now->format('y');
$dt['year'] += $x;
}
$dt['year'] = isset($dt['year']) ? (int)$dt['year'] : 0;
$dt['month'] = isset($dt['month']) ? (int)$dt['month'] : 0;
$dt['day'] = isset($dt['day']) ? (int)$dt['day'] : 0;
$dt['hour'] = isset($dt['hour']) ? (int)$dt['hour'] : 0;
$dt['minute'] = isset($dt['minute']) ? (int)$dt['minute'] : 0;
$dt['second'] = isset($dt['second']) ? (int)$dt['second'] : 0;
return $dt;
}
/**
* @param $format
* @param $str
* @param null $timezone
* @return \DateTime
*/
public static function createDatetimeFromFormat($format, $str, $timezone = null)
{
$pd = self::parseFromFormat($format, $str);
$gd = self::toGregorian($pd['year'], $pd['month'], $pd['day']);
$date = self::createDateTime('now', $timezone);
$date->setDate($gd[0], $gd[1], $gd[2]);
$date->setTime($pd['hour'], $pd['minute'], $pd['second']);
return $date;
}
/**
* @param $format
* @param $str
* @param null $timezone
* @return Carbon
*/
public static function createCarbonFromFormat($format, $str, $timezone = null)
{
$dateTime = self::createDatetimeFromFormat($format, $str, $timezone);
return Carbon::createFromTimestamp($dateTime->getTimestamp(), $dateTime->getTimezone());
}
/**
* Convert Latin numbers to persian numbers
*
* @param string $string
* @return string
*/
public static function convertNumbers($string)
{
$farsi_array = array("۰", "۱", "۲", "۳", "۴", "۵", "۶", "۷", "۸", "۹");
$english_array = array("0", "1", "2", "3", "4", "5", "6", "7", "8", "9");
return str_replace($english_array, $farsi_array, $string);
}
/**
* @param $timestamp
* @param null $timezone
* @return \DateTime|static
*/
public static function createDateTime($timestamp = null, $timezone = null)
{
$timezone = static::createTimeZone($timezone);
if ($timestamp === null) {
return Carbon::now($timezone);
}
if ($timestamp instanceof \DateTimeInterface) {
return $timestamp;
}
if (is_string($timestamp)) {
return new \DateTime($timestamp, $timezone);
}
if (is_numeric($timestamp)) {
return Carbon::createFromTimestamp($timestamp, $timezone);
}
throw new \InvalidArgumentException('timestamp is not valid');
}
/**
* @param null $timezone
* @return \DateTimeZone|null
*/
public static function createTimeZone($timezone = null)
{
if ($timezone instanceof \DateTimeZone) {
return $timezone;
}
if ($timezone === null) {
return new \DateTimeZone(date_default_timezone_get());
}
if (is_string($timezone)) {
return new \DateTimeZone($timezone);
}
throw new \InvalidArgumentException('timezone is not valid');
}
}