PHPExcelDate.Class.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. * User: phperstar
  5. * Date: 2020/8/11
  6. * Time: 4:34 PM
  7. */
  8. namespace Util\PHPExcel\Shared;
  9. class PHPExcelDate
  10. {
  11. /** constants */
  12. const CALENDAR_WINDOWS_1900 = 1900; // Base date of 1st Jan 1900 = 1.0
  13. const CALENDAR_MAC_1904 = 1904; // Base date of 2nd Jan 1904 = 1.0
  14. /*
  15. * Names of the months of the year, indexed by shortname
  16. * Planned usage for locale settings
  17. *
  18. * @public
  19. * @var string[]
  20. */
  21. public static $monthNames = array(
  22. 'Jan' => 'January',
  23. 'Feb' => 'February',
  24. 'Mar' => 'March',
  25. 'Apr' => 'April',
  26. 'May' => 'May',
  27. 'Jun' => 'June',
  28. 'Jul' => 'July',
  29. 'Aug' => 'August',
  30. 'Sep' => 'September',
  31. 'Oct' => 'October',
  32. 'Nov' => 'November',
  33. 'Dec' => 'December',
  34. );
  35. /*
  36. * Names of the months of the year, indexed by shortname
  37. * Planned usage for locale settings
  38. *
  39. * @public
  40. * @var string[]
  41. */
  42. public static $numberSuffixes = array(
  43. 'st',
  44. 'nd',
  45. 'rd',
  46. 'th',
  47. );
  48. /*
  49. * Base calendar year to use for calculations
  50. *
  51. * @private
  52. * @var int
  53. */
  54. protected static $excelBaseDate = self::CALENDAR_WINDOWS_1900;
  55. /**
  56. * Set the Excel calendar (Windows 1900 or Mac 1904)
  57. *
  58. * @param integer $baseDate Excel base date (1900 or 1904)
  59. * @return boolean Success or failure
  60. */
  61. public static function setExcelCalendar($baseDate)
  62. {
  63. if (($baseDate == self::CALENDAR_WINDOWS_1900) ||
  64. ($baseDate == self::CALENDAR_MAC_1904)) {
  65. self::$excelBaseDate = $baseDate;
  66. return true;
  67. }
  68. return false;
  69. }
  70. /**
  71. * Return the Excel calendar (Windows 1900 or Mac 1904)
  72. *
  73. * @return integer Excel base date (1900 or 1904)
  74. */
  75. public static function getExcelCalendar()
  76. {
  77. return self::$excelBaseDate;
  78. }
  79. /**
  80. * Convert a date from Excel to PHP
  81. *
  82. * @param integer $dateValue Excel date/time value
  83. * @param boolean $adjustToTimezone Flag indicating whether $dateValue should be treated as
  84. * a UST timestamp, or adjusted to UST
  85. * @param string $timezone The timezone for finding the adjustment from UST
  86. * @return integer PHP serialized date/time
  87. */
  88. public static function ExcelToPHP($dateValue = 0, $adjustToTimezone = false, $timezone = null)
  89. {
  90. if (self::$excelBaseDate == self::CALENDAR_WINDOWS_1900) {
  91. $myexcelBaseDate = 25569;
  92. // Adjust for the spurious 29-Feb-1900 (Day 60)
  93. if ($dateValue < 60) {
  94. --$myexcelBaseDate;
  95. }
  96. } else {
  97. $myexcelBaseDate = 24107;
  98. }
  99. // Perform conversion
  100. if ($dateValue >= 1) {
  101. $utcDays = $dateValue - $myexcelBaseDate;
  102. $returnValue = round($utcDays * 86400);
  103. if (($returnValue <= PHP_INT_MAX) && ($returnValue >= -PHP_INT_MAX)) {
  104. $returnValue = (integer) $returnValue;
  105. }
  106. } else {
  107. $hours = round($dateValue * 24);
  108. $mins = round($dateValue * 1440) - round($hours * 60);
  109. $secs = round($dateValue * 86400) - round($hours * 3600) - round($mins * 60);
  110. $returnValue = (integer) gmmktime($hours, $mins, $secs);
  111. }
  112. $timezoneAdjustment = ($adjustToTimezone) ?
  113. PHPExcel_Shared_TimeZone::getTimezoneAdjustment($timezone, $returnValue) :
  114. 0;
  115. return $returnValue + $timezoneAdjustment;
  116. }
  117. /**
  118. * Convert a date from Excel to a PHP Date/Time object
  119. *
  120. * @param integer $dateValue Excel date/time value
  121. * @return DateTime PHP date/time object
  122. */
  123. public static function ExcelToPHPObject($dateValue = 0)
  124. {
  125. $dateTime = self::ExcelToPHP($dateValue);
  126. $days = floor($dateTime / 86400);
  127. $time = round((($dateTime / 86400) - $days) * 86400);
  128. $hours = round($time / 3600);
  129. $minutes = round($time / 60) - ($hours * 60);
  130. $seconds = round($time) - ($hours * 3600) - ($minutes * 60);
  131. $dateObj = date_create('1-Jan-1970+'.$days.' days');
  132. $dateObj->setTime($hours, $minutes, $seconds);
  133. return $dateObj;
  134. }
  135. /**
  136. * Convert a date from PHP to Excel
  137. *
  138. * @param mixed $dateValue PHP serialized date/time or date object
  139. * @param boolean $adjustToTimezone Flag indicating whether $dateValue should be treated as
  140. * a UST timestamp, or adjusted to UST
  141. * @param string $timezone The timezone for finding the adjustment from UST
  142. * @return mixed Excel date/time value
  143. * or boolean FALSE on failure
  144. */
  145. public static function PHPToExcel($dateValue = 0, $adjustToTimezone = false, $timezone = null)
  146. {
  147. $saveTimeZone = date_default_timezone_get();
  148. date_default_timezone_set('UTC');
  149. $timezoneAdjustment = ($adjustToTimezone) ?
  150. PHPExcel_Shared_TimeZone::getTimezoneAdjustment($timezone ? $timezone : $saveTimeZone, $dateValue) :
  151. 0;
  152. $retValue = false;
  153. if ((is_object($dateValue)) && ($dateValue instanceof DateTime)) {
  154. $dateValue->add(new DateInterval('PT' . $timezoneAdjustment . 'S'));
  155. $retValue = self::FormattedPHPToExcel($dateValue->format('Y'), $dateValue->format('m'), $dateValue->format('d'), $dateValue->format('H'), $dateValue->format('i'), $dateValue->format('s'));
  156. } elseif (is_numeric($dateValue)) {
  157. $dateValue += $timezoneAdjustment;
  158. $retValue = self::FormattedPHPToExcel(date('Y', $dateValue), date('m', $dateValue), date('d', $dateValue), date('H', $dateValue), date('i', $dateValue), date('s', $dateValue));
  159. } elseif (is_string($dateValue)) {
  160. $retValue = self::stringToExcel($dateValue);
  161. }
  162. date_default_timezone_set($saveTimeZone);
  163. return $retValue;
  164. }
  165. /**
  166. * FormattedPHPToExcel
  167. *
  168. * @param integer $year
  169. * @param integer $month
  170. * @param integer $day
  171. * @param integer $hours
  172. * @param integer $minutes
  173. * @param integer $seconds
  174. * @return integer Excel date/time value
  175. */
  176. public static function FormattedPHPToExcel($year, $month, $day, $hours = 0, $minutes = 0, $seconds = 0)
  177. {
  178. if (self::$excelBaseDate == self::CALENDAR_WINDOWS_1900) {
  179. //
  180. // Fudge factor for the erroneous fact that the year 1900 is treated as a Leap Year in MS Excel
  181. // This affects every date following 28th February 1900
  182. //
  183. $excel1900isLeapYear = true;
  184. if (($year == 1900) && ($month <= 2)) {
  185. $excel1900isLeapYear = false;
  186. }
  187. $myexcelBaseDate = 2415020;
  188. } else {
  189. $myexcelBaseDate = 2416481;
  190. $excel1900isLeapYear = false;
  191. }
  192. // Julian base date Adjustment
  193. if ($month > 2) {
  194. $month -= 3;
  195. } else {
  196. $month += 9;
  197. --$year;
  198. }
  199. // Calculate the Julian Date, then subtract the Excel base date (JD 2415020 = 31-Dec-1899 Giving Excel Date of 0)
  200. $century = substr($year, 0, 2);
  201. $decade = substr($year, 2, 2);
  202. $excelDate = floor((146097 * $century) / 4) + floor((1461 * $decade) / 4) + floor((153 * $month + 2) / 5) + $day + 1721119 - $myexcelBaseDate + $excel1900isLeapYear;
  203. $excelTime = (($hours * 3600) + ($minutes * 60) + $seconds) / 86400;
  204. return (float) $excelDate + $excelTime;
  205. }
  206. /**
  207. * Is a given cell a date/time?
  208. *
  209. * @param PHPExcel_Cell $pCell
  210. * @return boolean
  211. */
  212. public static function isDateTime(PHPExcel_Cell $pCell)
  213. {
  214. return self::isDateTimeFormat(
  215. $pCell->getWorksheet()->getStyle(
  216. $pCell->getCoordinate()
  217. )->getNumberFormat()
  218. );
  219. }
  220. /**
  221. * Is a given number format a date/time?
  222. *
  223. * @param PHPExcel_Style_NumberFormat $pFormat
  224. * @return boolean
  225. */
  226. public static function isDateTimeFormat(PHPExcel_Style_NumberFormat $pFormat)
  227. {
  228. return self::isDateTimeFormatCode($pFormat->getFormatCode());
  229. }
  230. private static $possibleDateFormatCharacters = 'eymdHs';
  231. /**
  232. * Is a given number format code a date/time?
  233. *
  234. * @param string $pFormatCode
  235. * @return boolean
  236. */
  237. public static function isDateTimeFormatCode($pFormatCode = '')
  238. {
  239. if (strtolower($pFormatCode) === strtolower(PHPExcel_Style_NumberFormat::FORMAT_GENERAL)) {
  240. // "General" contains an epoch letter 'e', so we trap for it explicitly here (case-insensitive check)
  241. return false;
  242. }
  243. if (preg_match('/[0#]E[+-]0/i', $pFormatCode)) {
  244. // Scientific format
  245. return false;
  246. }
  247. // Switch on formatcode
  248. switch ($pFormatCode) {
  249. // Explicitly defined date formats
  250. case PHPExcel_Style_NumberFormat::FORMAT_DATE_YYYYMMDD:
  251. case PHPExcel_Style_NumberFormat::FORMAT_DATE_YYYYMMDD2:
  252. case PHPExcel_Style_NumberFormat::FORMAT_DATE_DDMMYYYY:
  253. case PHPExcel_Style_NumberFormat::FORMAT_DATE_DMYSLASH:
  254. case PHPExcel_Style_NumberFormat::FORMAT_DATE_DMYMINUS:
  255. case PHPExcel_Style_NumberFormat::FORMAT_DATE_DMMINUS:
  256. case PHPExcel_Style_NumberFormat::FORMAT_DATE_MYMINUS:
  257. case PHPExcel_Style_NumberFormat::FORMAT_DATE_DATETIME:
  258. case PHPExcel_Style_NumberFormat::FORMAT_DATE_TIME1:
  259. case PHPExcel_Style_NumberFormat::FORMAT_DATE_TIME2:
  260. case PHPExcel_Style_NumberFormat::FORMAT_DATE_TIME3:
  261. case PHPExcel_Style_NumberFormat::FORMAT_DATE_TIME4:
  262. case PHPExcel_Style_NumberFormat::FORMAT_DATE_TIME5:
  263. case PHPExcel_Style_NumberFormat::FORMAT_DATE_TIME6:
  264. case PHPExcel_Style_NumberFormat::FORMAT_DATE_TIME7:
  265. case PHPExcel_Style_NumberFormat::FORMAT_DATE_TIME8:
  266. case PHPExcel_Style_NumberFormat::FORMAT_DATE_YYYYMMDDSLASH:
  267. case PHPExcel_Style_NumberFormat::FORMAT_DATE_XLSX14:
  268. case PHPExcel_Style_NumberFormat::FORMAT_DATE_XLSX15:
  269. case PHPExcel_Style_NumberFormat::FORMAT_DATE_XLSX16:
  270. case PHPExcel_Style_NumberFormat::FORMAT_DATE_XLSX17:
  271. case PHPExcel_Style_NumberFormat::FORMAT_DATE_XLSX22:
  272. return true;
  273. }
  274. // Typically number, currency or accounting (or occasionally fraction) formats
  275. if ((substr($pFormatCode, 0, 1) == '_') || (substr($pFormatCode, 0, 2) == '0 ')) {
  276. return false;
  277. }
  278. // Try checking for any of the date formatting characters that don't appear within square braces
  279. if (preg_match('/(^|\])[^\[]*['.self::$possibleDateFormatCharacters.']/i', $pFormatCode)) {
  280. // We might also have a format mask containing quoted strings...
  281. // we don't want to test for any of our characters within the quoted blocks
  282. if (strpos($pFormatCode, '"') !== false) {
  283. $segMatcher = false;
  284. foreach (explode('"', $pFormatCode) as $subVal) {
  285. // Only test in alternate array entries (the non-quoted blocks)
  286. if (($segMatcher = !$segMatcher) &&
  287. (preg_match('/(^|\])[^\[]*['.self::$possibleDateFormatCharacters.']/i', $subVal))) {
  288. return true;
  289. }
  290. }
  291. return false;
  292. }
  293. return true;
  294. }
  295. // No date...
  296. return false;
  297. }
  298. /**
  299. * Convert a date/time string to Excel time
  300. *
  301. * @param string $dateValue Examples: '2009-12-31', '2009-12-31 15:59', '2009-12-31 15:59:10'
  302. * @return float|FALSE Excel date/time serial value
  303. */
  304. public static function stringToExcel($dateValue = '')
  305. {
  306. if (strlen($dateValue) < 2) {
  307. return false;
  308. }
  309. if (!preg_match('/^(\d{1,4}[ \.\/\-][A-Z]{3,9}([ \.\/\-]\d{1,4})?|[A-Z]{3,9}[ \.\/\-]\d{1,4}([ \.\/\-]\d{1,4})?|\d{1,4}[ \.\/\-]\d{1,4}([ \.\/\-]\d{1,4})?)( \d{1,2}:\d{1,2}(:\d{1,2})?)?$/iu', $dateValue)) {
  310. return false;
  311. }
  312. $dateValueNew = PHPExcel_Calculation_DateTime::DATEVALUE($dateValue);
  313. if ($dateValueNew === PHPExcel_Calculation_Functions::VALUE()) {
  314. return false;
  315. }
  316. if (strpos($dateValue, ':') !== false) {
  317. $timeValue = PHPExcel_Calculation_DateTime::TIMEVALUE($dateValue);
  318. if ($timeValue === PHPExcel_Calculation_Functions::VALUE()) {
  319. return false;
  320. }
  321. $dateValueNew += $timeValue;
  322. }
  323. return $dateValueNew;
  324. }
  325. /**
  326. * Converts a month name (either a long or a short name) to a month number
  327. *
  328. * @param string $month Month name or abbreviation
  329. * @return integer|string Month number (1 - 12), or the original string argument if it isn't a valid month name
  330. */
  331. public static function monthStringToNumber($month)
  332. {
  333. $monthIndex = 1;
  334. foreach (self::$monthNames as $shortMonthName => $longMonthName) {
  335. if (($month === $longMonthName) || ($month === $shortMonthName)) {
  336. return $monthIndex;
  337. }
  338. ++$monthIndex;
  339. }
  340. return $month;
  341. }
  342. /**
  343. * Strips an ordinal froma numeric value
  344. *
  345. * @param string $day Day number with an ordinal
  346. * @return integer|string The integer value with any ordinal stripped, or the original string argument if it isn't a valid numeric
  347. */
  348. public static function dayStringToNumber($day)
  349. {
  350. $strippedDayValue = (str_replace(self::$numberSuffixes, '', $day));
  351. if (is_numeric($strippedDayValue)) {
  352. return (integer) $strippedDayValue;
  353. }
  354. return $day;
  355. }
  356. }