Zubair Mohsin

How date and time work in Laravel using `nesbot/carbon` package

From the About section of nesbot/carbon package on GitHub:

  • A simple PHP API extension for DateTime.

It is an extension of PHP's native DateTime object. It adds lots of cool stuff onto DateTimeand makes it easier to do date and time manipulations.

Let's see how Laravel uses this package to provide even more simpler abstraction such as:

$now = now(); // returns current date and time as Carbon instance

Code Dive

Let's start from definition of now() method. It can be found in Illuminate\Foundation\helpers.php file.

use Illuminate\Support\Facades\Date;

/**
 * Create a new Carbon instance for the current time.
 *
 * @param  \DateTimeZone|string|null  $tz
 * @return \Illuminate\Support\Carbon
 */
function now($tz = null)
{
    return Date::now($tz);
}

We can see that its calling now() method on Date facade.

Date facade

namespace Illuminate\Support\Facades;

use Illuminate\Support\DateFactory;

class Date extends Facade
{
    const DEFAULT_FACADE = DateFactory::class;

    /**
     * Get the registered name of the component.
     *
     * @return string
     *
     * @throws \RuntimeException
     */
    protected static function getFacadeAccessor()
    {
        return 'date';
    }

    /**
     * Resolve the facade root instance from the container.
     *
     * @param  string  $name
     * @return mixed
     */
    protected static function resolveFacadeInstance($name)
    {
        if (! isset(static::$resolvedInstance[$name]) && 
            ! isset(static::$app, static::$app[$name])) {

            $class = static::DEFAULT_FACADE;

            static::swap(new $class);
        }

        return parent::resolveFacadeInstance($name);
    }
}

We have a typical getFacadeAccessor method that returns a string (date). But there are few other things that we haven't seen before. const DEFAULT_FACADE and resolveFacadeInstance method.

Since this is not a module like Log or Scheduling etc, there is no separate ServiceProvider for this to perform service container binding. Therefore inside resolveFacadeInstance method it is swapping the underlying instance behind Date facade to DateFactory class.

We can also confirm this by using app() helper like below:

app('date'); // returns instance of `Illuminate\Support\DateFactory` class 

DateFactroy class

Below is a ripped off version of DateFactory class which helps us understand implementation detail behind now() helper method. This class has other stuff too, see full implementation here.

use Illuminate\Support\Carbon;

class DateFactory
{
    /**
     * The default class that will be used for all created dates.
     *
     * @var string
     */
    const DEFAULT_CLASS_NAME = Carbon::class;

    /**
     * The type (class) of dates that should be created.
     *
     * @var string
     */
    protected static $dateClass;

    /**
     * Handle dynamic calls to generate dates.
     *
     * @param  string  $method
     * @param  array  $parameters
     * @return mixed
     *
     * @throws \RuntimeException
     */
    public function __call($method, $parameters)
    {
        $defaultClassName = static::DEFAULT_CLASS_NAME;

        $dateClass = static::$dateClass ?: $defaultClassName;

        // Check if date can be created using public class method...
        if (
            method_exists($dateClass, $method) ||
            $dateClass::hasMacro($method)
        ) {
            return $dateClass::$method(...$parameters);
        }
    }
}

Since now() method does not exist on this class and we are also in object context from Date facade, therefore, __call() magic method will be triggered when we do Date::now().

Inside __call() method we can see that $dateClass is being set to Illuminate\Support\Carbon class. After that it checks if given method ( now() ) exists on that class or this method is a macro. Once decided, it calls that method on Illuminate\Support\Carbon class.

Illuminate\Support\Carbon

<?php

namespace Illuminate\Support;

use Carbon\Carbon as BaseCarbon;
use Carbon\CarbonImmutable as BaseCarbonImmutable;

class Carbon extends BaseCarbon
{
}

Here we can see that this class extends Carbon\Carbon class from nesbot/carbon package. It can be considered as wrapper class around Carbon\Carbon which allows you to do interesting stuff like adding Macros.

Finding now() method

With above findings we can safely assume that now() method should be on Carbon\Carbon class. But exactly where? Let's find out.

use Carbon\Traits\Date;

class Carbon extends DateTime implements CarbonInterface
{
    use Date;

    /**
     * Returns true if the current class/instance is mutable.
     *
     * @return bool
     */
    public static function isMutable()
    {
        return true;
    }
}

This class extends PHP's native DateTime class and also uses a Date trait. Since now() method does not exist on DateTime class, it must be inside the Date trait.

<?php

namespace Carbon\Traits;

use Carbon\Traits\Creator;

trait Date
{
    use Creator;
}

To my surprise, it was further inside a trait called Creator which is being used inside Date trait.

<?php

namespace Carbon\Traits;

trait Creator
{
    /**
     * Create a new Carbon instance.
     *
     * Please see the testing aids section (specifically static::setTestNow())
     * for more on the possibility of this constructor returning a test instance.
     *
     * @param DateTimeInterface|string|null $time
     * @param DateTimeZone|string|null      $tz
     *
     * @throws InvalidFormatException
     */
    public function __construct($time = null, $tz = null)
    {
        try {
            parent::__construct(
                $time ?: 'now',
                static::safeCreateDateTimeZone($tz) ?: null
            );
        } catch (Exception $exception) {
            throw new InvalidFormatException(
                $exception->getMessage(), 0, $exception
            );
        }
    }

    /**
     * Get a Carbon instance for the current date and time.
     *
     * @param DateTimeZone|string|null $tz
     *
     * @return static
     */
    public static function now($tz = null)
    {
        return new static(null, $tz);
    }
}

Here we can finally see the now() method's definition. It is returning a new Carbon\Carbon instance using static constructor approach. I included the __construct method here to highlight the parent::__construct being called on PHP's native DateTime class.

Therefore the About section of this package says extension of DateTime API.

Interesting facts about nesbot/carbon package in Laravel

  • This package was first added in Laravel v4.0 on 19 Apr, 2013 by Kapil Verma
  • v2 of carbon package was first supported in Laravel v5.8 and then support for v1 of the package was completely dropped in Laravel v6.0
  • Carbon\Carbon class wrapped by Illuminate\Support\Carbon class in Laravel v5.5 to make it Macroable. Pull Request can be found here.

I hope you enjoyed this post. Next, we will see how Laravel uses opis/closure package. You can follow me on Twitter or join my newsletter for more content.