petrknap / zoned-datetime-persistence
Timezone aware date-time persistence
Fund package maintenance!
Other
Installs: 1
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 1
pkg:composer/petrknap/zoned-datetime-persistence
Requires
- php: >=8.1
Requires (Dev)
- doctrine/orm: ^3.0
- nunomaduro/phpinsights: ^2.11
- petrknap/shorts: ^3.0
- phpstan/phpstan: ^1.12
- phpunit/phpunit: ^10.5
- squizlabs/php_codesniffer: ^3.7
Conflicts
- doctrine/orm: <3|>=4
This package is auto-updated.
Last update: 2025-11-06 22:42:43 UTC
README
Many data storage systems (like MySQL) do not natively support storing timezone information alongside date-time values. This limitation introduces ambiguity when handling zoned date-times — particularly in applications operating across multiple timezones or even within a single timezone that observes multiple offsets (e.g. due to daylight saving time).
This package addresses the issue by providing tools that treat zoned date-time as a pair consisting of:
- the local date-time value, and
- a companion value that explicitly captures the corresponding timezone information.
Local date-time with UTC companion
The most effective approach is to store the local date-time together with its UTC counterpart. This dual representation enables seamless manipulation of date-time values directly within storage system. The local date-time is ideal for grouping and filtering based on user or business context, while the UTC value ensures consistent and accurate sorting across timezones.
How to use it
There is built-in support for
the Jakarta Persistence API (see Note.java),
the Doctrine ORM (see Note.php),
and, of course, it can be integrated manually into any project, giving you full flexibility to adapt it to your specific needs.
namespace PetrKnap\ZonedDateTimePersistence; $em = DoctrineTest::prepareEntityManager(); # persist entity $em->persist(new Some\Note( createdAt: new \DateTime('2025-10-30 23:52'), content: 'Doctrine is supported', )); $em->flush(); # insert data manually (static call) $now = new \DateTime('2025-10-26 02:45', new \DateTimeZone('CEST')); $em->getConnection()->insert('notes', [ 'created_at__local' => $now->format('Y-m-d H:i:s'), 'created_at__utc' => ZonedDateTimePersistence::computeUtcCompanion($now)->format('Y-m-d H:i:s'), 'content' => 'We still have summer time', ]); # insert data manually (object instance) $now = new LocalDateTimeWithUtcCompanion(new \DateTime('2025-10-26 02:15', new \DateTimeZone('CET'))); $em->getConnection()->insert('notes', [ 'created_at__local' => $now->getLocalDateTime('Y-m-d H:i:s'), 'created_at__utc' => $now->getUtcCompanion('Y-m-d H:i:s'), 'content' => 'Now we have winter time', ]); # select entities $notes = $em->createQueryBuilder() ->select('note') ->from(Some\Note::class, 'note') ->where('note.createdAt.local BETWEEN :from AND :to') ->orderBy('note.createdAt.utc') ->getQuery() ->execute(['from' => '2025-10-26 00:00', 'to' => '2025-10-26 23:59']); foreach($notes as $note) { echo $note->getCreatedAt()->format('Y-m-d H:i T') . ': '. $note->getContent() . PHP_EOL; }
You can support this project via donation.
The project is licensed under the terms of the LGPL-3.0-or-later.