For God so loved the world, that He gave His only begotten Son, that all who believe in Him should not perish but have everlasting life
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

418 lines
15 KiB

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