DateTimePlus.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732
  1. <?php
  2. namespace Drupal\Component\Datetime;
  3. use Drupal\Component\Utility\ToStringTrait;
  4. /**
  5. * Wraps DateTime().
  6. *
  7. * This class wraps the PHP DateTime class with more flexible initialization
  8. * parameters, allowing a date to be created from an existing date object,
  9. * a timestamp, a string with an unknown format, a string with a known
  10. * format, or an array of date parts. It also adds an errors array
  11. * and a __toString() method to the date object.
  12. *
  13. * This class is less lenient than the DateTime class. It changes
  14. * the default behavior for handling date values like '2011-00-00'.
  15. * The DateTime class would convert that value to '2010-11-30' and report
  16. * a warning but not an error. This extension treats that as an error.
  17. *
  18. * As with the DateTime class, a date object may be created even if it has
  19. * errors. It has an errors array attached to it that explains what the
  20. * errors are. This is less disruptive than allowing datetime exceptions
  21. * to abort processing. The calling script can decide what to do about
  22. * errors using hasErrors() and getErrors().
  23. *
  24. * @method $this add(\DateInterval $interval)
  25. * @method static array getLastErrors()
  26. * @method $this modify(string $modify)
  27. * @method $this setDate(int $year, int $month, int $day)
  28. * @method $this setISODate(int $year, int $week, int $day = 1)
  29. * @method $this setTime(int $hour, int $minute, int $second = 0, int $microseconds = 0)
  30. * @method $this setTimestamp(int $unixtimestamp)
  31. * @method $this setTimezone(\DateTimeZone $timezone)
  32. * @method $this sub(\DateInterval $interval)
  33. * @method int getOffset()
  34. * @method int getTimestamp()
  35. * @method \DateTimeZone getTimezone()
  36. */
  37. class DateTimePlus {
  38. use ToStringTrait;
  39. const FORMAT = 'Y-m-d H:i:s';
  40. /**
  41. * A RFC7231 Compliant date.
  42. *
  43. * @see http://tools.ietf.org/html/rfc7231#section-7.1.1.1
  44. *
  45. * Example: Sun, 06 Nov 1994 08:49:37 GMT
  46. */
  47. const RFC7231 = 'D, d M Y H:i:s \G\M\T';
  48. /**
  49. * An array of possible date parts.
  50. */
  51. protected static $dateParts = [
  52. 'year',
  53. 'month',
  54. 'day',
  55. 'hour',
  56. 'minute',
  57. 'second',
  58. ];
  59. /**
  60. * The value of the time value passed to the constructor.
  61. *
  62. * @var string
  63. */
  64. protected $inputTimeRaw = '';
  65. /**
  66. * The prepared time, without timezone, for this date.
  67. *
  68. * @var string
  69. */
  70. protected $inputTimeAdjusted = '';
  71. /**
  72. * The value of the timezone passed to the constructor.
  73. *
  74. * @var string
  75. */
  76. protected $inputTimeZoneRaw = '';
  77. /**
  78. * The prepared timezone object used to construct this date.
  79. *
  80. * @var string
  81. */
  82. protected $inputTimeZoneAdjusted = '';
  83. /**
  84. * The value of the format passed to the constructor.
  85. *
  86. * @var string
  87. */
  88. protected $inputFormatRaw = '';
  89. /**
  90. * The prepared format, if provided.
  91. *
  92. * @var string
  93. */
  94. protected $inputFormatAdjusted = '';
  95. /**
  96. * The value of the language code passed to the constructor.
  97. */
  98. protected $langcode = NULL;
  99. /**
  100. * An array of errors encountered when creating this date.
  101. */
  102. protected $errors = [];
  103. /**
  104. * The DateTime object.
  105. *
  106. * @var \DateTime
  107. */
  108. protected $dateTimeObject = NULL;
  109. /**
  110. * Creates a date object from an input date object.
  111. *
  112. * @param \DateTime $datetime
  113. * A DateTime object.
  114. * @param array $settings
  115. * (optional) A keyed array for settings, suitable for passing on to
  116. * __construct().
  117. *
  118. * @return static
  119. * A new DateTimePlus object.
  120. */
  121. public static function createFromDateTime(\DateTime $datetime, $settings = []) {
  122. return new static($datetime->format(static::FORMAT), $datetime->getTimezone(), $settings);
  123. }
  124. /**
  125. * Creates a date object from an array of date parts.
  126. *
  127. * Converts the input value into an ISO date, forcing a full ISO
  128. * date even if some values are missing.
  129. *
  130. * @param array $date_parts
  131. * An array of date parts, like ('year' => 2014, 'month' => 4).
  132. * @param mixed $timezone
  133. * (optional) \DateTimeZone object, time zone string or NULL. NULL uses the
  134. * default system time zone. Defaults to NULL.
  135. * @param array $settings
  136. * (optional) A keyed array for settings, suitable for passing on to
  137. * __construct().
  138. *
  139. * @return static
  140. * A new DateTimePlus object.
  141. *
  142. * @throws \InvalidArgumentException
  143. * If the array date values or value combination is not correct.
  144. */
  145. public static function createFromArray(array $date_parts, $timezone = NULL, $settings = []) {
  146. $date_parts = static::prepareArray($date_parts, TRUE);
  147. if (static::checkArray($date_parts)) {
  148. // Even with validation, we can end up with a value that the
  149. // DateTime class won't handle, like a year outside the range
  150. // of -9999 to 9999, which will pass checkdate() but
  151. // fail to construct a date object.
  152. $iso_date = static::arrayToISO($date_parts);
  153. return new static($iso_date, $timezone, $settings);
  154. }
  155. else {
  156. throw new \InvalidArgumentException('The array contains invalid values.');
  157. }
  158. }
  159. /**
  160. * Creates a date object from timestamp input.
  161. *
  162. * The timezone of a timestamp is always UTC. The timezone for a
  163. * timestamp indicates the timezone used by the format() method.
  164. *
  165. * @param int $timestamp
  166. * A UNIX timestamp.
  167. * @param mixed $timezone
  168. * (optional) \DateTimeZone object, time zone string or NULL. See
  169. * __construct() for more details.
  170. * @param array $settings
  171. * (optional) A keyed array for settings, suitable for passing on to
  172. * __construct().
  173. *
  174. * @return static
  175. * A new DateTimePlus object.
  176. *
  177. * @throws \InvalidArgumentException
  178. * If the timestamp is not numeric.
  179. */
  180. public static function createFromTimestamp($timestamp, $timezone = NULL, $settings = []) {
  181. if (!is_numeric($timestamp)) {
  182. throw new \InvalidArgumentException('The timestamp must be numeric.');
  183. }
  184. $datetime = new static('', $timezone, $settings);
  185. $datetime->setTimestamp($timestamp);
  186. return $datetime;
  187. }
  188. /**
  189. * Creates a date object from an input format.
  190. *
  191. * @param string $format
  192. * PHP date() type format for parsing the input. This is recommended
  193. * to use things like negative years, which php's parser fails on, or
  194. * any other specialized input with a known format. If provided the
  195. * date will be created using the createFromFormat() method.
  196. * @see http://php.net/manual/datetime.createfromformat.php
  197. * @param string $time
  198. * String representing the time.
  199. * @param mixed $timezone
  200. * (optional) \DateTimeZone object, time zone string or NULL. See
  201. * __construct() for more details.
  202. * @param array $settings
  203. * (optional) A keyed array for settings, suitable for passing on to
  204. * __construct(). Supports an additional key:
  205. * - validate_format: (optional) Boolean choice to validate the
  206. * created date using the input format. The format used in
  207. * createFromFormat() allows slightly different values than format().
  208. * Using an input format that works in both functions makes it
  209. * possible to a validation step to confirm that the date created
  210. * from a format string exactly matches the input. This option
  211. * indicates the format can be used for validation. Defaults to TRUE.
  212. *
  213. * @return static
  214. * A new DateTimePlus object.
  215. *
  216. * @throws \InvalidArgumentException
  217. * If the a date cannot be created from the given format.
  218. * @throws \UnexpectedValueException
  219. * If the created date does not match the input value.
  220. */
  221. public static function createFromFormat($format, $time, $timezone = NULL, $settings = []) {
  222. if (!isset($settings['validate_format'])) {
  223. $settings['validate_format'] = TRUE;
  224. }
  225. // Tries to create a date from the format and use it if possible.
  226. // A regular try/catch won't work right here, if the value is
  227. // invalid it doesn't return an exception.
  228. $datetimeplus = new static('', $timezone, $settings);
  229. $date = \DateTime::createFromFormat($format, $time, $datetimeplus->getTimezone());
  230. if (!$date instanceof \DateTime) {
  231. throw new \InvalidArgumentException('The date cannot be created from a format.');
  232. }
  233. else {
  234. // Functions that parse date is forgiving, it might create a date that
  235. // is not exactly a match for the provided value, so test for that by
  236. // re-creating the date/time formatted string and comparing it to the input. For
  237. // instance, an input value of '11' using a format of Y (4 digits) gets
  238. // created as '0011' instead of '2011'.
  239. if ($date instanceof DateTimePlus) {
  240. $test_time = $date->format($format, $settings);
  241. }
  242. elseif ($date instanceof \DateTime) {
  243. $test_time = $date->format($format);
  244. }
  245. $datetimeplus->setTimestamp($date->getTimestamp());
  246. $datetimeplus->setTimezone($date->getTimezone());
  247. if ($settings['validate_format'] && $test_time != $time) {
  248. throw new \UnexpectedValueException('The created date does not match the input value.');
  249. }
  250. }
  251. return $datetimeplus;
  252. }
  253. /**
  254. * Constructs a date object set to a requested date and timezone.
  255. *
  256. * @param string $time
  257. * (optional) A date/time string. Defaults to 'now'.
  258. * @param mixed $timezone
  259. * (optional) \DateTimeZone object, time zone string or NULL. NULL uses the
  260. * default system time zone. Defaults to NULL. Note that the $timezone
  261. * parameter and the current timezone are ignored when the $time parameter
  262. * either is a UNIX timestamp (e.g. @946684800) or specifies a timezone
  263. * (e.g. 2010-01-28T15:00:00+02:00).
  264. * @see http://php.net/manual/datetime.construct.php
  265. * @param array $settings
  266. * (optional) Keyed array of settings. Defaults to empty array.
  267. * - langcode: (optional) String two letter language code used to control
  268. * the result of the format(). Defaults to NULL.
  269. * - debug: (optional) Boolean choice to leave debug values in the
  270. * date object for debugging purposes. Defaults to FALSE.
  271. */
  272. public function __construct($time = 'now', $timezone = NULL, $settings = []) {
  273. // Unpack settings.
  274. $this->langcode = !empty($settings['langcode']) ? $settings['langcode'] : NULL;
  275. // Massage the input values as necessary.
  276. $prepared_time = $this->prepareTime($time);
  277. $prepared_timezone = $this->prepareTimezone($timezone);
  278. try {
  279. $this->errors = [];
  280. if (!empty($prepared_time)) {
  281. $test = date_parse($prepared_time);
  282. if (!empty($test['errors'])) {
  283. $this->errors = $test['errors'];
  284. }
  285. }
  286. if (empty($this->errors)) {
  287. $this->dateTimeObject = new \DateTime($prepared_time, $prepared_timezone);
  288. }
  289. }
  290. catch (\Exception $e) {
  291. $this->errors[] = $e->getMessage();
  292. }
  293. // Clean up the error messages.
  294. $this->checkErrors();
  295. }
  296. /**
  297. * Renders the timezone name.
  298. *
  299. * @return string
  300. */
  301. public function render() {
  302. return $this->format(static::FORMAT) . ' ' . $this->getTimeZone()->getName();
  303. }
  304. /**
  305. * Implements the magic __call method.
  306. *
  307. * Passes through all unknown calls onto the DateTime object.
  308. *
  309. * @param string $method
  310. * The method to call on the decorated object.
  311. * @param array $args
  312. * Call arguments.
  313. *
  314. * @return mixed
  315. * The return value from the method on the decorated object. If the proxied
  316. * method call returns a DateTime object, then return the original
  317. * DateTimePlus object, which allows function chaining to work properly.
  318. * Otherwise, the value from the proxied method call is returned.
  319. *
  320. * @throws \Exception
  321. * Thrown when the DateTime object is not set.
  322. * @throws \BadMethodCallException
  323. * Thrown when there is no corresponding method on the DateTime object to
  324. * call.
  325. */
  326. public function __call($method, array $args) {
  327. // @todo consider using assert() as per https://www.drupal.org/node/2451793.
  328. if (!isset($this->dateTimeObject)) {
  329. throw new \Exception('DateTime object not set.');
  330. }
  331. if (!method_exists($this->dateTimeObject, $method)) {
  332. throw new \BadMethodCallException(sprintf('Call to undefined method %s::%s()', get_class($this), $method));
  333. }
  334. $result = call_user_func_array([$this->dateTimeObject, $method], $args);
  335. return $result === $this->dateTimeObject ? $this : $result;
  336. }
  337. /**
  338. * Returns the difference between two DateTimePlus objects.
  339. *
  340. * @param \Drupal\Component\Datetime\DateTimePlus|\DateTime $datetime2
  341. * The date to compare to.
  342. * @param bool $absolute
  343. * Should the interval be forced to be positive?
  344. *
  345. * @return \DateInterval
  346. * A DateInterval object representing the difference between the two dates.
  347. *
  348. * @throws \BadMethodCallException
  349. * If the input isn't a DateTime or DateTimePlus object.
  350. */
  351. public function diff($datetime2, $absolute = FALSE) {
  352. if ($datetime2 instanceof DateTimePlus) {
  353. $datetime2 = $datetime2->dateTimeObject;
  354. }
  355. if (!($datetime2 instanceof \DateTime)) {
  356. throw new \BadMethodCallException(sprintf('Method %s expects parameter 1 to be a \DateTime or \Drupal\Component\Datetime\DateTimePlus object', __METHOD__));
  357. }
  358. return $this->dateTimeObject->diff($datetime2, $absolute);
  359. }
  360. /**
  361. * Implements the magic __callStatic method.
  362. *
  363. * Passes through all unknown static calls onto the DateTime object.
  364. */
  365. public static function __callStatic($method, $args) {
  366. if (!method_exists('\DateTime', $method)) {
  367. throw new \BadMethodCallException(sprintf('Call to undefined method %s::%s()', get_called_class(), $method));
  368. }
  369. return call_user_func_array(['\DateTime', $method], $args);
  370. }
  371. /**
  372. * Implements the magic __clone method.
  373. *
  374. * Deep-clones the DateTime object we're wrapping.
  375. */
  376. public function __clone() {
  377. $this->dateTimeObject = clone($this->dateTimeObject);
  378. }
  379. /**
  380. * Prepares the input time value.
  381. *
  382. * Changes the input value before trying to use it, if necessary.
  383. * Can be overridden to handle special cases.
  384. *
  385. * @param mixed $time
  386. * An input value, which could be a timestamp, a string,
  387. * or an array of date parts.
  388. *
  389. * @return mixed
  390. * The massaged time.
  391. */
  392. protected function prepareTime($time) {
  393. return $time;
  394. }
  395. /**
  396. * Prepares the input timezone value.
  397. *
  398. * Changes the timezone before trying to use it, if necessary.
  399. * Most importantly, makes sure there is a valid timezone
  400. * object before moving further.
  401. *
  402. * @param mixed $timezone
  403. * Either a timezone name or a timezone object or NULL.
  404. *
  405. * @return \DateTimeZone
  406. * The massaged time zone.
  407. */
  408. protected function prepareTimezone($timezone) {
  409. // If the input timezone is a valid timezone object, use it.
  410. if ($timezone instanceof \DateTimezone) {
  411. $timezone_adjusted = $timezone;
  412. }
  413. // Allow string timezone input, and create a timezone from it.
  414. elseif (!empty($timezone) && is_string($timezone)) {
  415. $timezone_adjusted = new \DateTimeZone($timezone);
  416. }
  417. // Default to the system timezone when not explicitly provided.
  418. // If the system timezone is missing, use 'UTC'.
  419. if (empty($timezone_adjusted) || !$timezone_adjusted instanceof \DateTimezone) {
  420. $system_timezone = date_default_timezone_get();
  421. $timezone_name = !empty($system_timezone) ? $system_timezone : 'UTC';
  422. $timezone_adjusted = new \DateTimeZone($timezone_name);
  423. }
  424. // We are finally certain that we have a usable timezone.
  425. return $timezone_adjusted;
  426. }
  427. /**
  428. * Prepares the input format value.
  429. *
  430. * Changes the input format before trying to use it, if necessary.
  431. * Can be overridden to handle special cases.
  432. *
  433. * @param string $format
  434. * A PHP format string.
  435. *
  436. * @return string
  437. * The massaged PHP format string.
  438. */
  439. protected function prepareFormat($format) {
  440. return $format;
  441. }
  442. /**
  443. * Examines getLastErrors() to see what errors to report.
  444. *
  445. * Two kinds of errors are important: anything that DateTime
  446. * considers an error, and also a warning that the date was invalid.
  447. * PHP creates a valid date from invalid data with only a warning,
  448. * 2011-02-30 becomes 2011-03-03, for instance, but we don't want that.
  449. *
  450. * @see http://php.net/manual/time.getlasterrors.php
  451. */
  452. public function checkErrors() {
  453. $errors = \DateTime::getLastErrors();
  454. if (!empty($errors['errors'])) {
  455. $this->errors = array_merge($this->errors, $errors['errors']);
  456. }
  457. // Most warnings are messages that the date could not be parsed
  458. // which causes it to be altered. For validation purposes, a warning
  459. // as bad as an error, because it means the constructed date does
  460. // not match the input value.
  461. if (!empty($errors['warnings'])) {
  462. $this->errors[] = 'The date is invalid.';
  463. }
  464. $this->errors = array_values(array_unique($this->errors));
  465. }
  466. /**
  467. * Detects if there were errors in the processing of this date.
  468. *
  469. * @return bool
  470. * TRUE if there were errors in the processing of this date, FALSE
  471. * otherwise.
  472. */
  473. public function hasErrors() {
  474. return (boolean) count($this->errors);
  475. }
  476. /**
  477. * Gets error messages.
  478. *
  479. * Public function to return the error messages.
  480. *
  481. * @return array
  482. * An array of errors encountered when creating this date.
  483. */
  484. public function getErrors() {
  485. return $this->errors;
  486. }
  487. /**
  488. * Creates an ISO date from an array of values.
  489. *
  490. * @param array $array
  491. * An array of date values keyed by date part.
  492. * @param bool $force_valid_date
  493. * (optional) Whether to force a full date by filling in missing
  494. * values. Defaults to FALSE.
  495. *
  496. * @return string
  497. * The date as an ISO string.
  498. */
  499. public static function arrayToISO($array, $force_valid_date = FALSE) {
  500. $array = static::prepareArray($array, $force_valid_date);
  501. $input_time = '';
  502. if ($array['year'] !== '') {
  503. $input_time = static::datePad(intval($array['year']), 4);
  504. if ($force_valid_date || $array['month'] !== '') {
  505. $input_time .= '-' . static::datePad(intval($array['month']));
  506. if ($force_valid_date || $array['day'] !== '') {
  507. $input_time .= '-' . static::datePad(intval($array['day']));
  508. }
  509. }
  510. }
  511. if ($array['hour'] !== '') {
  512. $input_time .= $input_time ? 'T' : '';
  513. $input_time .= static::datePad(intval($array['hour']));
  514. if ($force_valid_date || $array['minute'] !== '') {
  515. $input_time .= ':' . static::datePad(intval($array['minute']));
  516. if ($force_valid_date || $array['second'] !== '') {
  517. $input_time .= ':' . static::datePad(intval($array['second']));
  518. }
  519. }
  520. }
  521. return $input_time;
  522. }
  523. /**
  524. * Creates a complete array from a possibly incomplete array of date parts.
  525. *
  526. * @param array $array
  527. * An array of date values keyed by date part.
  528. * @param bool $force_valid_date
  529. * (optional) Whether to force a valid date by filling in missing
  530. * values with valid values or just to use empty values instead.
  531. * Defaults to FALSE.
  532. *
  533. * @return array
  534. * A complete array of date parts.
  535. */
  536. public static function prepareArray($array, $force_valid_date = FALSE) {
  537. if ($force_valid_date) {
  538. $now = new \DateTime();
  539. $array += [
  540. 'year' => $now->format('Y'),
  541. 'month' => 1,
  542. 'day' => 1,
  543. 'hour' => 0,
  544. 'minute' => 0,
  545. 'second' => 0,
  546. ];
  547. }
  548. else {
  549. $array += [
  550. 'year' => '',
  551. 'month' => '',
  552. 'day' => '',
  553. 'hour' => '',
  554. 'minute' => '',
  555. 'second' => '',
  556. ];
  557. }
  558. return $array;
  559. }
  560. /**
  561. * Checks that arrays of date parts will create a valid date.
  562. *
  563. * Checks that an array of date parts has a year, month, and day,
  564. * and that those values create a valid date. If time is provided,
  565. * verifies that the time values are valid. Sort of an
  566. * equivalent to checkdate().
  567. *
  568. * @param array $array
  569. * An array of datetime values keyed by date part.
  570. *
  571. * @return bool
  572. * TRUE if the datetime parts contain valid values, otherwise FALSE.
  573. */
  574. public static function checkArray($array) {
  575. $valid_date = FALSE;
  576. $valid_time = TRUE;
  577. // Check for a valid date using checkdate(). Only values that
  578. // meet that test are valid. An empty value, either a string or a 0, is not
  579. // a valid value.
  580. if (!empty($array['year']) && !empty($array['month']) && !empty($array['day'])) {
  581. $valid_date = checkdate($array['month'], $array['day'], $array['year']);
  582. }
  583. // Testing for valid time is reversed. Missing time is OK,
  584. // but incorrect values are not.
  585. foreach (['hour', 'minute', 'second'] as $key) {
  586. if (array_key_exists($key, $array)) {
  587. $value = $array[$key];
  588. switch ($key) {
  589. case 'hour':
  590. if (!preg_match('/^([1-2][0-3]|[01]?[0-9])$/', $value)) {
  591. $valid_time = FALSE;
  592. }
  593. break;
  594. case 'minute':
  595. case 'second':
  596. default:
  597. if (!preg_match('/^([0-5][0-9]|[0-9])$/', $value)) {
  598. $valid_time = FALSE;
  599. }
  600. break;
  601. }
  602. }
  603. }
  604. return $valid_date && $valid_time;
  605. }
  606. /**
  607. * Pads date parts with zeros.
  608. *
  609. * Helper function for a task that is often required when working with dates.
  610. *
  611. * @param int $value
  612. * The value to pad.
  613. * @param int $size
  614. * (optional) Size expected, usually 2 or 4. Defaults to 2.
  615. *
  616. * @return string
  617. * The padded value.
  618. */
  619. public static function datePad($value, $size = 2) {
  620. return sprintf("%0" . $size . "d", $value);
  621. }
  622. /**
  623. * Formats the date for display.
  624. *
  625. * @param string $format
  626. * Format accepted by date().
  627. * @param array $settings
  628. * - timezone: (optional) String timezone name. Defaults to the timezone
  629. * of the date object.
  630. *
  631. * @return string|null
  632. * The formatted value of the date or NULL if there were construction
  633. * errors.
  634. */
  635. public function format($format, $settings = []) {
  636. // If there were construction errors, we can't format the date.
  637. if ($this->hasErrors()) {
  638. return;
  639. }
  640. // Format the date and catch errors.
  641. try {
  642. // Clone the date/time object so we can change the time zone without
  643. // disturbing the value stored in the object.
  644. $dateTimeObject = clone $this->dateTimeObject;
  645. if (isset($settings['timezone'])) {
  646. $dateTimeObject->setTimezone(new \DateTimeZone($settings['timezone']));
  647. }
  648. $value = $dateTimeObject->format($format);
  649. }
  650. catch (\Exception $e) {
  651. $this->errors[] = $e->getMessage();
  652. }
  653. return $value;
  654. }
  655. /**
  656. * Sets the default time for an object built from date-only data.
  657. *
  658. * The default time for a date without time can be anything, so long as it is
  659. * consistently applied. If we use noon, dates in most timezones will have the
  660. * same value for in both the local timezone and UTC.
  661. */
  662. public function setDefaultDateTime() {
  663. $this->dateTimeObject->setTime(12, 0, 0);
  664. }
  665. /**
  666. * Gets a clone of the proxied PHP \DateTime object wrapped by this class.
  667. *
  668. * @return \DateTime
  669. * A clone of the wrapped PHP \DateTime object.
  670. */
  671. public function getPhpDateTime() {
  672. return clone $this->dateTimeObject;
  673. }
  674. }