datetimeformat.class.inc.php 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. <?php
  2. // Copyright (C) 2016 Combodo SARL
  3. //
  4. // This file is part of iTop.
  5. //
  6. // iTop is free software; you can redistribute it and/or modify
  7. // it under the terms of the GNU Affero General Public License as published by
  8. // the Free Software Foundation, either version 3 of the License, or
  9. // (at your option) any later version.
  10. //
  11. // iTop is distributed in the hope that it will be useful,
  12. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. // GNU Affero General Public License for more details.
  15. //
  16. // You should have received a copy of the GNU Affero General Public License
  17. // along with iTop. If not, see <http://www.gnu.org/licenses/>
  18. /**
  19. * Helper class to generate Date & Time formatting strings in the various conventions
  20. * from the PHP DateTime::createFromFormat convention.
  21. *
  22. * Example:
  23. *
  24. * $oFormat = new DateTimeFormat('m/d/Y H:i');
  25. * $oFormat->ToExcel();
  26. * >> 'MM/dd/YYYY HH:mm'
  27. *
  28. * @author Denis Flaven <denis.flaven@combodo.com>
  29. *
  30. */
  31. class DateTimeFormat
  32. {
  33. protected $sPHPFormat;
  34. /**
  35. * Constructs the DateTimeFormat object
  36. * @param string $sPHPFormat A format string using the PHP 'DateTime::createFromFormat' convention
  37. */
  38. public function __construct($sPHPFormat)
  39. {
  40. $this->sPHPFormat = $sPHPFormat;
  41. }
  42. /**
  43. * Return the mapping table for converting between various conventions for date/time formats
  44. */
  45. protected static function GetFormatMapping()
  46. {
  47. return array(
  48. // Days
  49. 'd' => array('regexpr' => '(0[1-9]|[1-2][0-9]||3[0-1])', 'datepicker' => 'dd', 'excel' => 'dd', 'moment' => 'DD'), // Day of the month: 2 digits (with leading zero)
  50. 'j' => array('regexpr' => '([1-9]|[1-2][0-9]||3[0-1])', 'datepicker' => 'd', 'excel' => '%d', 'moment' => 'D'), // Day of the month: 1 or 2 digits (without leading zero)
  51. // Months
  52. 'm' => array('regexpr' => '(0[1-9]|1[0-2])', 'datepicker' => 'mm', 'excel' => 'MM', 'moment' => 'MM' ), // Month on 2 digits i.e. 01-12
  53. 'n' => array('regexpr' => '([1-9]|1[0-2])', 'datepicker' => 'm', 'excel' => '%M', 'moment' => 'M'), // Month on 1 or 2 digits 1-12
  54. // Years
  55. 'Y' => array('regexpr' => '([0-9]{4})', 'datepicker' => 'yy', 'excel' => 'YYYY', 'moment' => 'YYYY'), // Year on 4 digits
  56. 'y' => array('regexpr' => '([0-9]{2})', 'datepicker' => 'y', 'excel' => 'YY', 'moment' => 'YY'), // Year on 2 digits
  57. // Hours
  58. 'H' => array('regexpr' => '([0-1][0-9]|2[0-3])', 'datepicker' => 'HH', 'excel' => 'HH', 'moment' => 'HH'), // Hour 00..23
  59. 'h' => array('regexpr' => '(0[1-9]|1[0-2])', 'datepicker' => 'hh', 'excel' => 'hh', 'moment' => 'hh'), // Hour 01..12
  60. 'G' => array('regexpr' => '([1-9]|[1[0-9]|2[0-3])', 'datepicker' => 'H', 'excel' => '%H', 'moment' => 'H'), // Hour 0..23
  61. 'g' => array('regexpr' => '([1-9]|1[0-2])', 'datepicker' => 'h', 'excel' => '%h', 'moment' => 'h'), // Hour 1..12
  62. 'a' => array('regexpr' => '(am|pm)', 'datepicker' => 'tt', 'excel' => 'am/pm', 'moment' => 'a'),
  63. 'A' => array('regexpr' => '(AM|PM)', 'datepicker' => 'TT', 'excel' => 'AM/PM', 'moment' => 'A'),
  64. // Minutes
  65. 'i' => array('regexpr' => '([0-5][0-9])', 'datepicker' => 'mm', 'excel' => 'mm', 'moment' => 'mm'),
  66. // Seconds
  67. 's' => array('regexpr' => '([0-5][0-9])', 'datepicker' => 'ss', 'excel' => 'ss', 'moment' => 'ss'),
  68. );
  69. }
  70. /**
  71. * Transform the PHP format into the specified format, taking care of escaping the litteral characters
  72. * using the supplied escaping expression
  73. * @param string $sOutputFormatCode THe target format code: regexpr|datepicker|excel|moment
  74. * @param string $sEscapePattern The replacement string for escaping characters in the output string. %s is the source char.
  75. * @param string $bEscapeAll True to systematically escape all litteral characters
  76. * @param array $sSpecialChars A string containing the only characters to escape in the output
  77. * @return string The string in the requested format
  78. */
  79. protected function Transform($sOutputFormatCode, $sEscapePattern, $bEscapeAll = false, $sSpecialChars = '')
  80. {
  81. $aMappings = static::GetFormatMapping();
  82. $sResult = '';
  83. $bEscaping = false;
  84. for($i=0; $i < strlen($this->sPHPFormat); $i++)
  85. {
  86. if (($this->sPHPFormat[$i] == '\\'))
  87. {
  88. $bEscaping = true;
  89. continue;
  90. }
  91. if ($bEscaping)
  92. {
  93. if (($sSpecialChars === '') || (strpos($sSpecialChars, $this->sPHPFormat[$i]) !== false))
  94. {
  95. $sResult .= sprintf($sEscapePattern, $this->sPHPFormat[$i]);
  96. }
  97. else
  98. {
  99. $sResult .= $this->sPHPFormat[$i];
  100. }
  101. $bEscaping = false;
  102. }
  103. else if(array_key_exists($this->sPHPFormat[$i], $aMappings))
  104. {
  105. // Not a litteral value, must be replaced by its regular expression pattern
  106. $sResult .= $aMappings[$this->sPHPFormat[$i]][$sOutputFormatCode];
  107. }
  108. else
  109. {
  110. if ($bEscapeAll || (strpos($sSpecialChars, $this->sPHPFormat[$i]) !== false))
  111. {
  112. $sResult .= sprintf($sEscapePattern, $this->sPHPFormat[$i]);
  113. }
  114. else
  115. {
  116. // Normal char with no special meaning, no need to escape it
  117. $sResult .= $this->sPHPFormat[$i];
  118. }
  119. }
  120. }
  121. return $sResult;
  122. }
  123. /**
  124. * Format a date into the supplied format string
  125. * @param mixed $date An int, string, DateTime object or null !!
  126. * @throws Exception
  127. * @return string The formatted date
  128. */
  129. public function Format($date)
  130. {
  131. if ($date == null)
  132. {
  133. $sDate = '';
  134. }
  135. else if (($date === '0000-00-00') || ($date === '0000-00-00 00:00:00'))
  136. {
  137. $sDate = '';
  138. }
  139. else if ($date instanceof DateTime)
  140. {
  141. // Parameter is a DateTime
  142. $sDate = $date->format($this->sPHPFormat);
  143. }
  144. else if (is_int($date))
  145. {
  146. // Parameter is a Unix timestamp
  147. $oDate = new DateTime();
  148. $oDate->setTimestamp($date);
  149. $sDate = $oDate->format($this->sPHPFormat);
  150. }
  151. else if (is_string($date))
  152. {
  153. $oDate = new DateTime($date);
  154. $sDate = $oDate->format($this->sPHPFormat);
  155. }
  156. else
  157. {
  158. throw new Exception(__CLASS__."::Format: Unexpected date value: ".print_r($date, true));
  159. }
  160. return $sDate;
  161. }
  162. /**
  163. * Parse a date in the supplied format and return the date as a string in the internal format
  164. * @param string $sDate The string to parse
  165. * @param string $sFormat The format, in PHP createFromFormat convention
  166. * @throws Exception
  167. * @return DateTime|null
  168. */
  169. public function Parse($sDate)
  170. {
  171. if (($sDate == null) || ($sDate == '0000-00-00 00:00:00') || ($sDate == '0000-00-00'))
  172. {
  173. return null;
  174. }
  175. else
  176. {
  177. $sFormat = preg_replace('/\\?/', '', $this->sPHPFormat); // replace escaped characters by a wildcard for parsing
  178. $oDate = DateTime::createFromFormat($this->sPHPFormat, $sDate);
  179. if ($oDate === false)
  180. {
  181. throw new Exception(__CLASS__."::Parse: Unable to parse the date: '$sDate' using the format: '{$this->sPHPFormat}'");
  182. }
  183. return $oDate;
  184. }
  185. }
  186. /**
  187. * Get the date or datetime format string in the jQuery UI date picker format
  188. * @return string The format string using the date picker convention
  189. */
  190. public function ToDatePicker()
  191. {
  192. return $this->Transform('datepicker', "'%s'");
  193. }
  194. /**
  195. * Get a date or datetime format string in the Excel format
  196. * @param string $sFormat
  197. * @return string The format string using the Excel convention
  198. */
  199. public function ToExcel($sFormat = null)
  200. {
  201. return $this->Transform('datepicker', "%s");
  202. }
  203. /**
  204. * Get a date or datetime format string in the moment.js format
  205. * @param string $sFormat
  206. * @return string The format string using the moment.js convention
  207. */
  208. public function ToMomentJS($sFormat = null)
  209. {
  210. return $this->Transform('moment', "[%s]", true /* escape all */);
  211. }
  212. /**
  213. * Get a placeholder text for a date or datetime format string
  214. * @param string $sFormat
  215. * @return string The placeholder text (localized)
  216. */
  217. public function ToPlaceholder($sFormat = null)
  218. {
  219. $sFormat = ($sFormat == null) ? static::GetFormat() : $sFormat;
  220. $aMappings = static::GetFormatMapping();
  221. $sResult = '';
  222. $bEscaping = false;
  223. for($i=0; $i < strlen($sFormat); $i++)
  224. {
  225. if (($sFormat[$i] == '\\'))
  226. {
  227. $bEscaping = true;
  228. continue;
  229. }
  230. if ($bEscaping)
  231. {
  232. $sResult .= $sFormat[$i]; // No need to escape characters in the placeholder
  233. $bEscaping = false;
  234. }
  235. else if(array_key_exists($sFormat[$i], $aMappings))
  236. {
  237. // Not a litteral value, must be replaced by Dict equivalent
  238. $sResult .= Dict::S('Core:DateTime:Placeholder_'.$sFormat[$i]);
  239. }
  240. else
  241. {
  242. // Normal char with no special meaning
  243. $sResult .= $sFormat[$i];
  244. }
  245. }
  246. return $sResult;
  247. }
  248. /**
  249. * Produces the Date format string by extracting only the date part of the date and time format string
  250. * @return string
  251. */
  252. public function ToDateFormat()
  253. {
  254. $aDatePlaceholders = array('Y', 'y', 'd', 'j', 'm', 'n');
  255. $iStart = 999;
  256. $iEnd = 0;
  257. foreach($aDatePlaceholders as $sChar)
  258. {
  259. $iPos = strpos($this->sPHPFormat, $sChar);
  260. if ($iPos !== false)
  261. {
  262. if (($iPos > 0) && ($aDatePlaceholders[$iPos-1] == '\\'))
  263. {
  264. // The placeholder is actually escaped, it's a litteral character, ignore it
  265. continue;
  266. }
  267. $iStart = min($iStart, $iPos);
  268. $iEnd = max($iEnd, $iPos);
  269. }
  270. }
  271. $sFormat = substr($this->sPHPFormat, $iStart, $iEnd - $iStart + 1);
  272. return $sFormat;
  273. }
  274. /**
  275. * Get the regular expression to (approximately) validate a date/time for the current format
  276. * The validation does not take into account the number of days in a month (i.e. June 31st will pass, as well as Feb 30th!)
  277. * @return string The regular expression in PCRE syntax
  278. */
  279. public function ToRegExpr()
  280. {
  281. return '^'.$this->Transform('regexpr', "\\%s", false /* escape all */, '.?*$^()[]/:').'$';
  282. }
  283. }