Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 310 and 311] [Versions 39 and 311]

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