DateTime factory components for PHP

0.6.1 2023-04-11 00:55 UTC

This package is auto-updated.

Last update: 2024-04-11 03:06:29 UTC


README

build codecov Scrutinizer Code Quality

Arp\DateTime

About

The library provides implementations of the PSR-20 Clock ClockInterface and a number of factories which abstract the creation of native PHP DateTime objects.

Installation

Installation via composer.

require alex-patterson-webdev/date-time ^0.6

Theory

By abstracting the creation of Date Time objects behind a simple collection of interfaces, we can allow developers to treat date time creation as a service. The Arp\DateTime\DateTimeFactory can be used as a replacement for any code that would normally require new \DateTime().

Consider an example UserService::updateLastLoginDate() method; designed to update a user's last login date with the current date and time.

class UserService
{
    public function updateLastLoginDate($user)
    {
        $user->setLastLoginDate(new \DateTime());
    }
}

This approach, while simple, would be difficult to assert a value for the 'current time' in a unit test. Alternatively, we could update this example to include the DateTimeFactory, which would abstract the creation of the \DateTime object.

class UserService
{
    private DateTimeFactoryInterface $dateTimeFactory;

    public function __construct(DateTimeFactoryInterface $dateTimeFactory)
    {
        $this->dateTimeFactory = $dateTimeFactory;
    }

    public function updateLastLoginDate($user)
    {
        $user->setLastLoginDate($this->dateTimeFactory->createDateTime());
    }
}

The approach has a number of notable benefits

  • The DateTimeFactoryInterface provides an abstraction for all \DateTime object creation.
  • Unit testing and asserting date time values becomes very easy as we can now mock the return value of $this->dateTimeFactory->createDateTime().
  • Rather than returning a boolean false when unable to create date objects, the factory classes will instead throw a DateTimeException.

DateTimeFactoryInterface

The DateTimeFactoryInterface exposes two public methods, createDateTime() and createFromFormat(). The method signatures are similar to the PHP \DateTime methods.

interface DateTimeFactoryInterface
{
    /**
     * @throws DateTimeFactoryException
     */
    public function createDateTime(?string $spec = null, $timeZone = null): \DateTimeInterface;

    /**
     * @throws DateTimeFactoryException
     */
    public function createFromFormat(string $format, string $spec, $timeZone = null): \DateTimeInterface;
}

The createDateTime() method can replace uses of \DateTime::__construct. The createFromFormat() method can replace uses of \DateTime::createFromFormat().

There are however a number of differences to consider.

  • The methods of the interface are defined as non-static and require a factory instance to invoke them.
  • A DateTimeFactoryException will be thrown if the \DateTime instance cannot be created.
  • The $spec parameter of createDateTime() accepts null. Passing null is equivalent to using the current date and time, i.e. now.
  • The $timeZone can be either a string or \DateTimeZone instance. If a supported DateTimeZone string is provided, the \DateTimeZone instance will be created internally; otherwise a DateTimeFactoryException will be thrown.

Implementations

The package provides two default implementations of the DateTimeFactoryInterface.

  • DateTimeFactory can be used to create \DateTime instances.
  • DateTimeImmutableFactory can be used to create \DateTimeImmutible instances

Because both classes implement the DateTimeFactoryInterface, they can be used in the same way.

$dateTimeFactory = new \Arp\DateTime\DateTimeFactory();
$dateTimeImmutableFactory = new \Arp\DateTime\DateTimeImmutableFactory();

try {
    /** @var \DateTime $dateTime **/
    $dateTime = $dateTimeFactory->createDateTime();

    /** @var \DateTimeImmutable $dateTimeImmutable **/
    $dateTimeImmutable = $dateTimeImmutableFactory->createDateTime();
} catch (\DateTimeFactoryException $e) {
    // if the date creation fails
}

DateTimeZoneFactory

\DateTimeZone instances can be created using any class that implements Arp\DateTime\DateTimeZoneFactoryInterface.

/*
 * @throws DateTimeZoneFactoryException
 */
public function createDateTimeZone(string $spec): \DateTimeZone;

The default implementation of the interface is Arp\DateTime\DateTimeZoneFactory.

$dateTimeZoneFactory = new \Arp\DateTime\DateTimeZoneFactory();

try { 
    /** @var \DateTimeZone $dateTimeZone **/
    $dateTimeZone = $dateTimeZoneFactory->createDateTimeZone('UTC');
} catch (\DateTimeZoneFactoryException $e) {
    // The \DateTimeZone() could not be created
}

PSR-20 ClockInterface

Using the PSR standards provides implementing projects interoperability between other packages by creating a standard way of accessing the current time.

The package provides a number of implementations of the PSR-20 Clock interface.

  • Arp\DateTime\Psr\Clock A default implementation of Psr\Clock\ClockInterface where a desired DateTimeZone can be provided.
  • Arp\DateTime\Psr\SystemClock An implementation of Psr\Clock\ClockInterface that will always provide the current time using the configured system timezone.
  • Arp\DateTime\Psr\FixedClock An implementation of Psr\Clock\ClockInterface that will always return a fixed \DateTimeImmutable; which can be useful in testing.

Example usages

use Arp\DateTime\DateTimeImmutableFactory;
use Arp\DateTime\Psr\SystemClock;

// Fetch the current time using the UTC timezone
$clock = new Clock(new DateTimeImmutableFactory(), 'UTC');
$utcTime = $clock->now();

// Fetch the current time using the systems configured timezone
$clock = new SystemClock(new DateTimeImmutableFactory());
$systemTime = $clock->now();

// Calls to FixedClock::now() will always return the same time provided in the constructor 
$clock = new FixedClock(new \DateTimeImmutable());
$fixedTime = $clock->now();

Unit tests

Unit tests can be executed using PHPUnit from the application root directory.

php vendor/bin/phpunit