/lib/spout/src/Spout/Reader/XLSX/Helper/DateFormatHelper.php

https://github.com/markn86/moodle · PHP · 123 lines · 67 code · 17 blank · 39 comment · 3 complexity · 8343109e66b820a5ac6ed487be8769c3 MD5 · raw file

  1. <?php
  2. namespace Box\Spout\Reader\XLSX\Helper;
  3. /**
  4. * Class DateFormatHelper
  5. * This class provides helper functions to format Excel dates
  6. */
  7. class DateFormatHelper
  8. {
  9. const KEY_GENERAL = 'general';
  10. const KEY_HOUR_12 = '12h';
  11. const KEY_HOUR_24 = '24h';
  12. /**
  13. * This map is used to replace Excel format characters by their PHP equivalent.
  14. * Keys should be ordered from longest to smallest.
  15. *
  16. * @var array Mapping between Excel format characters and PHP format characters
  17. */
  18. private static $excelDateFormatToPHPDateFormatMapping = [
  19. self::KEY_GENERAL => [
  20. // Time
  21. 'am/pm' => 'A', // Uppercase Ante meridiem and Post meridiem
  22. ':mm' => ':i', // Minutes with leading zeros - if preceded by a ":" (otherwise month)
  23. 'mm:' => 'i:', // Minutes with leading zeros - if followed by a ":" (otherwise month)
  24. 'ss' => 's', // Seconds, with leading zeros
  25. '.s' => '', // Ignore (fractional seconds format does not exist in PHP)
  26. // Date
  27. 'e' => 'Y', // Full numeric representation of a year, 4 digits
  28. 'yyyy' => 'Y', // Full numeric representation of a year, 4 digits
  29. 'yy' => 'y', // Two digit representation of a year
  30. 'mmmmm' => 'M', // Short textual representation of a month, three letters ("mmmmm" should only contain the 1st letter...)
  31. 'mmmm' => 'F', // Full textual representation of a month
  32. 'mmm' => 'M', // Short textual representation of a month, three letters
  33. 'mm' => 'm', // Numeric representation of a month, with leading zeros
  34. 'm' => 'n', // Numeric representation of a month, without leading zeros
  35. 'dddd' => 'l', // Full textual representation of the day of the week
  36. 'ddd' => 'D', // Textual representation of a day, three letters
  37. 'dd' => 'd', // Day of the month, 2 digits with leading zeros
  38. 'd' => 'j', // Day of the month without leading zeros
  39. ],
  40. self::KEY_HOUR_12 => [
  41. 'hh' => 'h', // 12-hour format of an hour without leading zeros
  42. 'h' => 'g', // 12-hour format of an hour without leading zeros
  43. ],
  44. self::KEY_HOUR_24 => [
  45. 'hh' => 'H', // 24-hour hours with leading zero
  46. 'h' => 'G', // 24-hour format of an hour without leading zeros
  47. ],
  48. ];
  49. /**
  50. * Converts the given Excel date format to a format understandable by the PHP date function.
  51. *
  52. * @param string $excelDateFormat Excel date format
  53. * @return string PHP date format (as defined here: http://php.net/manual/en/function.date.php)
  54. */
  55. public static function toPHPDateFormat($excelDateFormat)
  56. {
  57. // Remove brackets potentially present at the beginning of the format string
  58. // and text portion of the format at the end of it (starting with ";")
  59. // See §18.8.31 of ECMA-376 for more detail.
  60. $dateFormat = \preg_replace('/^(?:\[\$[^\]]+?\])?([^;]*).*/', '$1', $excelDateFormat);
  61. // Double quotes are used to escape characters that must not be interpreted.
  62. // For instance, ["Day " dd] should result in "Day 13" and we should not try to interpret "D", "a", "y"
  63. // By exploding the format string using double quote as a delimiter, we can get all parts
  64. // that must be transformed (even indexes) and all parts that must not be (odd indexes).
  65. $dateFormatParts = \explode('"', $dateFormat);
  66. foreach ($dateFormatParts as $partIndex => $dateFormatPart) {
  67. // do not look at odd indexes
  68. if ($partIndex % 2 === 1) {
  69. continue;
  70. }
  71. // Make sure all characters are lowercase, as the mapping table is using lowercase characters
  72. $transformedPart = \strtolower($dateFormatPart);
  73. // Remove escapes related to non-format characters
  74. $transformedPart = \str_replace('\\', '', $transformedPart);
  75. // Apply general transformation first...
  76. $transformedPart = \strtr($transformedPart, self::$excelDateFormatToPHPDateFormatMapping[self::KEY_GENERAL]);
  77. // ... then apply hour transformation, for 12-hour or 24-hour format
  78. if (self::has12HourFormatMarker($dateFormatPart)) {
  79. $transformedPart = \strtr($transformedPart, self::$excelDateFormatToPHPDateFormatMapping[self::KEY_HOUR_12]);
  80. } else {
  81. $transformedPart = \strtr($transformedPart, self::$excelDateFormatToPHPDateFormatMapping[self::KEY_HOUR_24]);
  82. }
  83. // overwrite the parts array with the new transformed part
  84. $dateFormatParts[$partIndex] = $transformedPart;
  85. }
  86. // Merge all transformed parts back together
  87. $phpDateFormat = \implode('"', $dateFormatParts);
  88. // Finally, to have the date format compatible with the DateTime::format() function, we need to escape
  89. // all characters that are inside double quotes (and double quotes must be removed).
  90. // For instance, ["Day " dd] should become [\D\a\y\ dd]
  91. $phpDateFormat = \preg_replace_callback('/"(.+?)"/', function ($matches) {
  92. $stringToEscape = $matches[1];
  93. $letters = \preg_split('//u', $stringToEscape, -1, PREG_SPLIT_NO_EMPTY);
  94. return '\\' . \implode('\\', $letters);
  95. }, $phpDateFormat);
  96. return $phpDateFormat;
  97. }
  98. /**
  99. * @param string $excelDateFormat Date format as defined by Excel
  100. * @return bool Whether the given date format has the 12-hour format marker
  101. */
  102. private static function has12HourFormatMarker($excelDateFormat)
  103. {
  104. return (\stripos($excelDateFormat, 'am/pm') !== false);
  105. }
  106. }