Suggesting Carbon with Composer – Date and Time the Right Way

Share this article

Carbon is a small library for date and time manipulation in PHP. It relies on and extends the core DateTime class, adding helpful methods for a significantly saner experience.

In this article, we’ll take a look at some basic usage examples, and then use it in a real project.

Date and time image

Introduction

Carbon is just a class which is designed to be used instead of DateTime. Due to extending DateTime, all DateTime methods are available to users of Carbon. Additionally, it implements a __toString method, allowing users to put it in place of string representations of date and time as well.

It can easily be installed with Composer:

composer require nesbot/carbon

Let’s see some example uses, as presented in their excellent documentation.

Example Uses

The easiest way to get started with Carbon is to just pass a human readable date string into its constructor, along with an optional timezone – if the timezone is omitted, the one set by the current PHP installation will be used.

$carbon = new Carbon('first day of next week');

It can also be instantiated from strings, timestamps, even other instances of DateTime or even Carbon. The instance can be copied with the copy() method, for efficient cloning.

From there, we have access to a smorgasbord of helper checkers and getters:

$carbon->isWeekend();
$carbon->isFuture();
$carbon->isLeapYear();

$carbon->year;
$carbon->month;

$carbon->daysInMonth;
$carbon->weekOfYear;

The package also exposes static methods for creating new instances quickly:

echo Carbon::now()->addYear()->diffForHumans();    // in 1 year

Even birthdays can be checked, as we can see by this example from the docs:

$born = Carbon::createFromDate(1987, 4, 23);
$noCake = Carbon::createFromDate(2014, 9, 26);
$yesCake = Carbon::createFromDate(2014, 4, 23);
$overTheHill = Carbon::now()->subYears(50);
var_dump($born->isBirthday($noCake));              // bool(false)
var_dump($born->isBirthday($yesCake));             // bool(true)
var_dump($overTheHill->isBirthday());              // bool(true) -> default compare it to today!

Localization

Localization is also supported, so that output can be given in any desired language installed on the machine powering the PHP app. Note that you do need to install the necessary locales for this to work – refer to your operating system’s documentation for details on how to do that.

To localize date and time strings, the standard PHP function setlocale can be used:

setlocale(LC_TIME, 'German');
echo $dt->formatLocalized('%A %d %B %Y');          // Mittwoch 21 Mai 1975

To localize the diffForHumans method which outputs a human-readable difference in time, the class offers its own setLocale method:

Carbon::setLocale('de');
echo Carbon::now()->addYear()->diffForHumans();    // in 1 Jahr

Interval

A CarbonInterval class is also provided, which is an extension of DateInterval. Self-descriptively, it holds interval values, just like the base class, but adds helper methods on top. As per examples:

echo CarbonInterval::year();                           // 1 year
echo CarbonInterval::months(3);                        // 3 months
echo CarbonInterval::days(3)->seconds(32);             // 3 days 32 seconds
echo CarbonInterval::weeks(3);                         // 3 weeks
echo CarbonInterval::days(23);                         // 3 weeks 2 days
echo CarbonInterval::create(2, 0, 5, 1, 1, 2, 7);      // 2 years 5 weeks 1 day 1 hour 2 minutes 7 seconds

Note that Carbon as a whole is exceptionally well documented – for a full reference of methods and usage examples, please see their docs.

Implementation

In this section, we’ll upgrade the Diffbot PHP Client to optionally support Carbon. The plan is as follows: if the user has the library installed, then the Article entity and Post entity will return Carbon instances instead of date strings from their getDate and getEstimatedDate methods. Otherwise, they’ll return strings as usual.

If you’d like to follow along, clone the client at this version.

Composer Suggests

The first step is to add the library to the suggests list in composer.json. The suggests list takes the same format as the require blocks, but instead of version constraints, we put full string messages on why that package is suggested.

"suggest": {
    "nesbot/carbon": "Turns the date and estimatedDate return values of Article and Post entity into Carbon entities."
  },

We can make sure we got the syntax right by running composer validate:

vagrant@homestead:~/Code/diffbot-php-client$ composer validate
./composer.json is valid

When a user installs the Diffbot PHP Client, they’ll see a recommendation to install Carbon.

Tests

Next, it’s time to update the tests to accommodate for this.

In tests/Entities/ArticleTest.php, we alter the dateProvider and testDate functions like so:

    public function dateProvider()
    {
        return [
            [
                'Articles/diffbot-sitepoint-basic.json',
                "Sun, 27 Jul 2014 00:00:00 GMT",
                2014
            ],
            [
                'Articles/diffbot-sitepoint-extended.json',
                "Sun, 27 Jul 2014 00:00:00 GMT",
                2014
            ],
            [
                'Articles/apple-watch-verge-basic.json',
                "Wed, 08 Apr 2015 00:00:00 GMT",
                2015
            ],
            [
                'Articles/apple-watch-verge-extended.json',
                "Wed, 08 Apr 2015 00:00:00 GMT",
                2015
            ]
        ];
    }

    /**
     * @param $file
     * @param $articles
     * @dataProvider dateProvider
     */
    public function testDate($file, $articles, $year)
    {
        $articles = (is_array($articles)) ? $articles : [$articles];
        /** @var Article $entity */
        foreach ($this->ei($file) as $i => $entity) {
            $this->assertEquals($articles[$i], $entity->getDate());
            if (class_exists('\Carbon\Carbon')) {
                $this->assertEquals($year, $entity->getDate()->year);
            }
        }
    }

What happened here?

First, we added year values into the provider as the third argument to be passed into testDate. Then, during iteration and assertion, we first check if the class is loaded / exists, and if so, we test for one of its getters (->year).

We have to check if the class exists before testing, because Carbon is optional in the Diffbot SDK – it’s just a suggestion, so we mustn’t fail if it isn’t there.

We repeat the process for estimatedDateProvider and estimatedDate at the bottom of the ArticleTest class:

    public function estimatedDateProvider()
    {
        return [
            ['Articles/15-11-07/diffbot-sitepoint-basic.json', 'Sun, 27 Jul 2014 00:00:00 GMT', 2014],
        ];
    }

    /**
     * @dataProvider estimatedDateProvider
     * @param $file
     * @param $value1
     */
    public function testEstimatedDate($file, $value1, $value2)
    {
        $value1 = (is_array($value1)) ? $value1 : [$value1];
        /** @var Article $entity */
        foreach ($this->ei($file) as $i => $entity) {
            $this->assertEquals($value1[$i], $entity->getEstimatedDate());
            if (class_exists('\Carbon\Carbon')) {
                $this->assertEquals($value2, $entity->getDate()->year);
            }
        }
    }

Next, let’s update the PostTest class. That one only has the getDate method, but involves a bit more typing because a Discussion almost always returns multiple posts.

    {
        return [
            [
                'Discussions/15-05-01/sp_discourse_php7_recap.json',
                [
                    "Wed, 29 Apr 2015 16:00:00 GMT",
                    "Thu, 30 Apr 2015 01:13:00 GMT",
                    "Thu, 30 Apr 2015 05:55:00 GMT",
                    "Thu, 30 Apr 2015 06:57:00 GMT",
                    "Thu, 30 Apr 2015 07:51:00 GMT",
                    "Thu, 30 Apr 2015 09:29:00 GMT",
                    "Thu, 30 Apr 2015 10:26:00 GMT",
                    "Thu, 30 Apr 2015 10:40:00 GMT",
                    "Thu, 30 Apr 2015 11:06:00 GMT",
                    "Thu, 30 Apr 2015 11:29:00 GMT",
                    "Thu, 30 Apr 2015 14:33:00 GMT",
                    "Thu, 30 Apr 2015 15:48:00 GMT",
                    "Thu, 30 Apr 2015 16:17:00 GMT",
                    "Thu, 30 Apr 2015 16:51:00 GMT",
                    "Thu, 30 Apr 2015 17:02:00 GMT",
                    "Fri, 01 May 2015 08:00:00 GMT",
                ],
                [
                    2015,
                    2015,
                    2015,
                    2015,
                    2015,
                    2015,
                    2015,
                    2015,
                    2015,
                    2015,
                    2015,
                    2015,
                    2015,
                    2015,
                    2015,
                    2015,
                ]
            ]
        ];
    }

    /**
     * @param $file
     * @param $posts
     * @dataProvider dateProvider
     */
    public function testDate($file, $posts, $years)
    {
        /** @var Discussion $entity */
        foreach ($this->ei($file) as $entity) {
            /** @var Post $post */
            foreach ($entity->getPosts() as $i => $post) {
                $this->assertEquals($posts[$i], $post->getDate());
                if (class_exists('\Carbon\Carbon')) {
                    $this->assertEquals($years[$i], $post->getDate()->year);
                }

            }
        }
    }

If we now attempt to run these tests, they will succeed. If we install Carbon into the project with:

composer require nesbot/carbon --dev

… the tests will fail.

Implementation

It’s time to change the actual entities now.

Diffbot returns dates in the following format: Sun, 27 Jul 2014 00:00:00 GMT.

In order to maintain backwards compatibility, we need to set Carbon to produce the same output when used as a string (via __toString), so that everyone who used the date values as output directly can still do so. This is done with the static Carbon::setToStringFormat($format); method.

Adding the following to the constructor in src/Entity/Article.php will accomplish this:

if (class_exists('\Carbon\Carbon')) {
    $format = 'D, d M o H:i:s e';
    \Carbon\Carbon::setToStringFormat($format);
}

The same must be added to the src/Entity/Post.php file, though that one still doesn’t override the base class’ constructor and needs to do that first. The final version of Post‘s construct method is:

public function __construct(array $data)
{
    if (class_exists('\Carbon\Carbon')) {
        $format = 'D, d M o H:i:s e';
        \Carbon\Carbon::setToStringFormat($format);
    }

    parent::__construct($data);
}

Now that we’ve got Carbon activated and defaulting to a format we want, it’s time to upgrade our getters.

In both Article and Post, the getDate method should now look like this:

public function getDate()
{
    return (class_exists('\Carbon\Carbon')) ?
        new \Carbon\Carbon($this->data['date'], 'GMT') :
        $this->data['date'];
}

If Carbon exists, make a new instance from the post’s date in the GMT timezone (the timezone Diffbot always returns), otherwise, return the date string as before.

Finally, we need to change getEstimatedDate in Article:

public function getEstimatedDate()
{
    $date = $this->getOrDefault('estimatedDate', $this->getDate());

    return (class_exists('\Carbon\Carbon')) ?
        new \Carbon\Carbon($date, 'GMT') :
        $date;
}

Same thing, only this one first defaults to getDate if the estimatedDate could not be determined.

Running the tests should now show everything passing:

vagrant@homestead:~/Code/diffbot-php-client$ phpunit
PHPUnit 5.0.8 by Sebastian Bergmann and contributors.

Runtime:       PHP 5.6.10-1+deb.sury.org~trusty+1 with Xdebug 2.3.2
Configuration: /home/vagrant/Code/diffbot-php-client/phpunit.xml.dist

...............................................................  63 / 352 ( 17%)
............................................................... 126 / 352 ( 35%)
............S.................................................. 189 / 352 ( 53%)
............................................................... 252 / 352 ( 71%)
............................................................... 315 / 352 ( 89%)
.....................................                           352 / 352 (100%)

Time: 49.39 seconds, Memory: 21.00Mb

Success! We can now commit, push, and publish a new release!

Conclusion

We looked at Carbon, an extension of DateTime which adds helpful methods to the core class and make it much more pleasant to use. We saw how easy it is to implement in a project, and how it can replace pure string outputs and timestamps by means of different internal string formats.

Are you using Carbon in your projects? What do you like or dislike about it? Leave your thoughts and comments below, and if you liked this post, don’t forget to hit that thumbs up button!

Frequently Asked Questions about Carbon with Composer, Date and Time

Why should I use Carbon with Composer for date and time management in PHP?

Carbon is a simple PHP API extension for DateTime. It provides a more user-friendly and intuitive interface for handling date and time in PHP. It includes several helpful methods and constants to make working with dates and times easier. Using Carbon with Composer allows you to manage dependencies and ensure that your project has the correct versions of necessary packages, including Carbon.

How do I install Carbon with Composer?

To install Carbon with Composer, you need to run the command composer require nesbot/carbon. This command tells Composer to download the Carbon package and add it as a dependency in your project. Once the installation is complete, you can use Carbon in your PHP scripts by including the autoloader with require 'vendor/autoload.php';.

What are the benefits of using Carbon over native PHP DateTime functions?

Carbon provides several advantages over native PHP DateTime functions. It offers a more intuitive and fluent interface, making it easier to work with dates and times. It also includes several additional methods for common tasks, such as formatting dates, calculating differences between dates, and manipulating dates.

How do I format dates using Carbon?

Carbon provides several methods for formatting dates. For example, you can use the format method to format a date according to a specified format. You can also use methods like toDateString, toDateTimeString, and toDayDateTimeString to format dates in common formats.

How do I calculate the difference between two dates using Carbon?

Carbon provides several methods for calculating the difference between two dates. The diff method returns a DateInterval object representing the difference between two dates. You can also use methods like diffInDays, diffInHours, and diffInMinutes to calculate the difference in specific units.

How do I manipulate dates using Carbon?

Carbon provides several methods for manipulating dates. For example, you can use the addDays, subDays, addHours, and subHours methods to add or subtract days or hours from a date. You can also use the startOfDay, endOfDay, startOfMonth, and endOfMonth methods to set a date to the start or end of a day or month.

How do I compare dates using Carbon?

Carbon provides several methods for comparing dates. The equalTo, notEqualTo, greaterThan, greaterThanOrEqualTo, lessThan, and lessThanOrEqualTo methods allow you to compare two dates and determine their relative order.

How do I convert a Carbon instance to a DateTime instance?

You can convert a Carbon instance to a DateTime instance using the toDateTime method. This method returns a DateTime instance representing the same point in time as the Carbon instance.

How do I handle timezones with Carbon?

Carbon provides several methods for handling timezones. The timezone method allows you to set the timezone for a Carbon instance. You can also use the tz method as a shorthand for this. The shiftTimezone method allows you to shift a date to a different timezone, adjusting the time accordingly.

How do I handle localization with Carbon?

Carbon provides several methods for handling localization. The locale method allows you to set the locale for a Carbon instance, which affects the output of methods like formatLocalized. You can also use the setLocale method to set the locale globally for all Carbon instances.

Bruno SkvorcBruno Skvorc
View Author

Bruno is a blockchain developer and technical educator at the Web3 Foundation, the foundation that's building the next generation of the free people's internet. He runs two newsletters you should subscribe to if you're interested in Web3.0: Dot Leap covers ecosystem and tech development of Web3, and NFT Review covers the evolution of the non-fungible token (digital collectibles) ecosystem inside this emerging new web. His current passion project is RMRK.app, the most advanced NFT system in the world, which allows NFTs to own other NFTs, NFTs to react to emotion, NFTs to be governed democratically, and NFTs to be multiple things at once.

BrunoScarbondatesdatetimeDiffbotOOPHPPHP
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week