working-with-datetime.rst 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. Working with DateTime Instances
  2. ===============================
  3. There are many nitty gritty details when working with PHPs DateTime instances. You have know their inner
  4. workings pretty well not to make mistakes with date handling. This cookbook entry holds several
  5. interesting pieces of information on how to work with PHP DateTime instances in Doctrine 2.
  6. DateTime changes are detected by Reference
  7. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  8. When calling ``EntityManager#flush()`` Doctrine computes the changesets of all the currently managed entities
  9. and saves the differences to the database. In case of object properties (@Column(type="datetime") or @Column(type="object"))
  10. these comparisons are always made **BY REFERENCE**. That means the following change will **NOT** be saved into the database:
  11. .. code-block:: php
  12. <?php
  13. /** @Entity */
  14. class Article
  15. {
  16. /** @Column(type="datetime") */
  17. private $updated;
  18. public function setUpdated()
  19. {
  20. // will NOT be saved in the database
  21. $this->updated->modify("now");
  22. }
  23. }
  24. The way to go would be:
  25. .. code-block:: php
  26. <?php
  27. class Article
  28. {
  29. public function setUpdated()
  30. {
  31. // WILL be saved in the database
  32. $this->updated = new \DateTime("now");
  33. }
  34. }
  35. Default Timezone Gotcha
  36. ~~~~~~~~~~~~~~~~~~~~~~~
  37. By default Doctrine assumes that you are working with a default timezone. Each DateTime instance that
  38. is created by Doctrine will be assigned the timezone that is currently the default, either through
  39. the ``date.timezone`` ini setting or by calling ``date_default_timezone_set()``.
  40. This is very important to handle correctly if your application runs on different serves or is moved from one to another server
  41. (with different timezone settings). You have to make sure that the timezone is the correct one
  42. on all this systems.
  43. Handling different Timezones with the DateTime Type
  44. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  45. If you first come across the requirement to save different you are still optimistic to manage this mess,
  46. however let me crush your expectations fast. There is not a single database out there (supported by Doctrine 2)
  47. that supports timezones correctly. Correctly here means that you can cover all the use-cases that
  48. can come up with timezones. If you don't believe me you should read up on `Storing DateTime
  49. in Databases <http://derickrethans.nl/storing-date-time-in-database.html>`_.
  50. The problem is simple. Not a single database vendor saves the timezone, only the differences to UTC.
  51. However with frequent daylight saving and political timezone changes you can have a UTC offset that moves
  52. in different offset directions depending on the real location.
  53. The solution for this dilemma is simple. Don't use timezones with DateTime and Doctrine 2. However there is a workaround
  54. that even allows correct date-time handling with timezones:
  55. 1. Always convert any DateTime instance to UTC.
  56. 2. Only set Timezones for displaying purposes
  57. 3. Save the Timezone in the Entity for persistence.
  58. Say we have an application for an international postal company and employees insert events regarding postal-package
  59. around the world, in their current timezones. To determine the exact time an event occurred means to save both
  60. the UTC time at the time of the booking and the timezone the event happened in.
  61. .. code-block:: php
  62. <?php
  63. namespace DoctrineExtensions\DBAL\Types;
  64. use Doctrine\DBAL\Platforms\AbstractPlatform;
  65. use Doctrine\DBAL\Types\ConversionException;
  66. class UTCDateTimeType extends DateTimeType
  67. {
  68. static private $utc = null;
  69. public function convertToDatabaseValue($value, AbstractPlatform $platform)
  70. {
  71. if ($value === null) {
  72. return null;
  73. }
  74. return $value->format($platform->getDateTimeFormatString(),
  75. (self::$utc) ? self::$utc : (self::$utc = new \DateTimeZone('UTC'))
  76. );
  77. }
  78. public function convertToPHPValue($value, AbstractPlatform $platform)
  79. {
  80. if ($value === null) {
  81. return null;
  82. }
  83. $val = \DateTime::createFromFormat(
  84. $platform->getDateTimeFormatString(),
  85. $value,
  86. (self::$utc) ? self::$utc : (self::$utc = new \DateTimeZone('UTC'))
  87. );
  88. if (!$val) {
  89. throw ConversionException::conversionFailed($value, $this->getName());
  90. }
  91. return $val;
  92. }
  93. }
  94. This database type makes sure that every DateTime instance is always saved in UTC, relative
  95. to the current timezone that the passed DateTime instance has. To be able to transform these values
  96. back into their real timezone you have to save the timezone in a separate field of the entity
  97. requiring timezoned datetimes:
  98. .. code-block:: php
  99. <?php
  100. namespace Shipping;
  101. /**
  102. * @Entity
  103. */
  104. class Event
  105. {
  106. /** @Column(type="datetime") */
  107. private $created;
  108. /** @Column(type="string") */
  109. private $timezone;
  110. /**
  111. * @var bool
  112. */
  113. private $localized = false;
  114. public function __construct(\DateTime $createDate)
  115. {
  116. $this->localized = true;
  117. $this->created = $createDate;
  118. $this->timezone = $createDate->getTimeZone()->getName();
  119. }
  120. public function getCreated()
  121. {
  122. if (!$this->localized) {
  123. $this->created->setTimeZone(new \DateTimeZone($this->timezone));
  124. }
  125. return $this->created;
  126. }
  127. }
  128. This snippet makes use of the previously discussed "changeset by reference only" property of
  129. objects. That means a new DateTime will only be used during updating if the reference
  130. changes between retrieval and flush operation. This means we can easily go and modify
  131. the instance by setting the previous local timezone.