How to manage DateTime with Carbon in Laravel & PHP

Published September 10, 2021; last updated on October 1, 2021
How to manage DateTime with Carbon in Laravel & PHP

At some point, almost every Laravel application needs to work with dates and times. But managing dates and times is sometimes ridiculously complex, because of timezones. In which timezone do you store dates and times? How do you calculate differences between times?

Luckily, there’s Carbon: a simple PHP API extension for DateTime. That might sound very complex, but it really isn’t. Laravel uses Carbon by default, but the documentation barely talks about it. In this tutorial I’ll show you all the important ways how you can use Carbon to manage DateTime and timezones in Laravel.

What is Carbon?

Carbon is a PHP package that makes dealing with times and timezones much more fluent and readable. It deals with a lot of things, like:

  1. Creating datetimes
  2. Converting datetimes between different timezones
  3. Adding and subtracting times
  4. Calculating the differences between times
  5. Converting datetimes into something human-readable

First, what is exactly a datetime? Bluntly put, a datetime means a moment. It consists of three parts:

  1. Date
  2. Time
  3. Timezone (if not set, it uses the default; mostly UTC (+0)

These three characteristics allow you to point out just that exact moment. After you’ve defined your moments, you can start playing with it by converting it to other timezones and calculating differences.

You store a datetime (which is in fact an instance of the Carbon class) in a variable, and then you can use or modify those:

$now = now();

$trialExpires = $now->addDays(30);
  // Or
$trialExpires = now()->addDays(30);

How to use Carbon

For this tutorial I’ll assume you have a Laravel installation. If you want to use the Carbon:: helper in a file, import the Illuminate\Support\Carbon or Carbon\Carbon at the top of your file:

<?php

// namespace App\....

use Illuminate\Support\Carbon;

Getting specific dates or times with Carbon

These are a few examples of how to get specific dates and times with Carbon in Laravel:

Getting the current time with Carbon

This gets an instance of Carbon with the current time:

$now = now();
$today = today(); 

The now() helper is a method you’ll likely use the most frequently. There are also a few other helpers, but to use them, you must call them as a static method on the Carbon:: facade:

$now = Carbon::now();
$today = Carbon::today();
$tomorrow = Carbon::tomorrow('Europe/London');
$yesterday = Carbon::yesterday();

These methods all accept a timezone (if not, it will default to UTC) and it will set the time to midnight (00:00:00).

Please be aware that the two below examples are not the same, because the first example creates a datetime for tomorrow, midnight in the Europe/London timezone. Whereas the second example creates a datetime for tomorrow, midnight in the UTC (UTC+0) timezone and then converts it to the Europe/London timezone. I’ll explain timezones below.

$tomorrow = Carbon::now()->tomorrow('Europe/London');
$tomorrow = Carbon::now()->tomorrow()->tz('Europe/London');

Manually creating date/times with Carbon

There are also a few handy helpers that allow you to create dates and times manually with values you pass in yourself. See the examples below. Please note that when you pass in null somewhere, it will default to the current value of that. So passing in null for the month will take the current month. If the month is not relevant in a situation, I’d recommend to pass in 1 for January. That will save you some painful debugging time.

Carbon::createFromDate($year, $month, $day, $tz);
Carbon::createMidnightDate($year, $month, $day, $tz);
Carbon::createFromTime($hour, $minute, $second, $tz);
Carbon::createFromTimeString("$hour:$minute:$second", $tz);
Carbon::create($year, $month, $day, $hour, $minute, $second, $tz);

Managing timezones with Carbon

Managing timezones in Laravel and PHP is not very difficult, but it’s something you need to get used to. Here is the most basic rule of managing timezones in Laravel:

In the config/app.php you can set the default timezone for your application. By default this is set to UTC, but in theory you could change it. Please note that storing times in a timezone other than UTC+0 is highly discouraged.

Please always store your dates in UTC in the database and convert them only when you need it to the correct timezone.

What timezones can we use in Carbon and PHP?

What timezones can we choose from, you might ask? There is a PHP function called timezone_identifiers_list(), which returns an array of all the supported timezones in PHP.

$tzs = timezone_identifiers_list();

echo $tzs;

=> [
     "Africa/Abidjan",
     "Africa/Accra",
     "Africa/Addis_Ababa",
     "Africa/Algiers",
     "Africa/Asmara",
     "Africa/Bamako",
     "Africa/Bangui",
     "Africa/Banjul",
     "Africa/Bissau",
     "Africa/Blantyre",
     "Africa/Brazzaville",
     "Africa/Bujumbura",
     "Africa/Cairo",
     "Africa/Casablanca",
     "Africa/Ceuta",
     //

In theory, if your users are in different timezones, you would store all times and dates in UTC and convert them to the correct timezone when you display them.

How to get the current user’s timezone in Laravel

To simplify finding the current user timezone, there’s a package called jamesmills/laravel-timezone. This package creates a timezone column on the users table for the Eloquent User model. Each time a user logs in, or only the first time (what you wish), it will automatically detect the timezone and update the timezone column accordingly.

The package has a few handy helpers, like:

Timezone::convertToLocal($post->created_at, 'Y-m-d g:i', true);
Timezone::convertFromLocal($request->get('publish_at'));

I usually use this package to just fetch the user’s timezone on first login and then add an option to change the timezone in the user settings.

How do you add such a choose your timezone option in the settings? You could add a sort of dropdown with all the results from timezone_identifiers_list(); to each user’s settings page and let the user update the timezone themselves. Make sure to update the timezone column on the User model with the new timezone and save it.

Converting dates and times between different timezones

So, how do we convert dates and times to a different timezone? That is very simple. On each Carbon instance, you can chain (->) the tz() helper. This helper converts the time into the new timezone.

now()->tz('Europe/Amsterdam');

Please be aware that this does not change the moment. It purely changes the time to the correct

Or you can insert a timezone parameter everywhere where you see $tz parameter:

Carbon::create($year, $month, $day, $hour, $minute, $second, $tz);

But you need to be aware of the following, what I also said above. This is not the same:

$tomorrow = Carbon::now()->tomorrow('Europe/London');
$tomorrow = Carbon::now()->tomorrow()->tz('Europe/London');

You should see the second item as: ‘I have a certain date and time, which in fact describes a certain moment. I want to display that moment in a different timezone, so I do $moment->tz($tz). The moment stays exactly the same, but the timezone is different.

Perhaps you need to get a little used to it, but as soon as you master the right mindset, working with timezones in Laravel and PHP will not be complex anymore.

Convert English string into a date

Carbon also supports parsing an English string into a date:

$lastDayOfFeb2021 = Carbon::parse('last day of February 2021');   // 2021-02-28 00:00:00.0 UTC (+00:00)

$lastDayOfFeb2020 = Carbon::parse('last day of February 2020');   // 2020-02-29 00:00:00.0 UTC (+00:00)

This is really magical, but something I usually don’t prefer. To me this feels like you leave some room for ‘interpretation’ in your code, relying on Carbon understanding what you mean. And in case it doesn’t, your code will break.

But converting English strings into datetimes is not the only usage of the Carbon::parse() function. You can also use it to parse just anything. Got a timestamp in some ISO format you don’t know how t parse? Here’s Carbon::parse() to the rescue!

Testing if Carbon will always return the same date

You can test if Carbon will return an absolute date though, by asking Carbon ‘if you get this string, will you always return the exact same datetime or not?’ For example, saying last day of February 2020 will always return only 1 date. But saying last day of next month will return different dates, depending on the current date and month.

To test whether Carbon will always return a so-called absolute date/time string, you can use the Carbon::hasRelativeKeywords($string) function.

$string = 'first day of next month';
if (strtotime($string) === false) {
    echo "'$string' is not a valid date/time string.";
} elseif (Carbon::hasRelativeKeywords($string)) {
    echo "'$string' is a relative valid date/time string, it will returns different dates depending on the current date.";
} else {
    echo "'$string' is an absolute date/time string, it will always returns the same date.";
}

Testing this for each time that you use Carbon::parse() is quite a hassle and not really worth it in my opinion.

Adding or subtracting dates and times in Carbon

One of the most important features of Carbon is the ability to add or subtract times and dates from other times and dates. This is usually very simple and looks like this:

now()->addDays(3);
now()->subWeekDays(3);

For each method, there’s usually an addSomething() and a subSomething() method. Each method usually also has a addSomethings($howMany) and subSomethings($howMany) method.

The current time doesn’t matter, and neither does the timezone.

Adding years

First, one example of a single unit (days). You can add or subtract days with the following methods:

now()->addDay();
now()->addDays(30);
now()->subDay();
now()->subDays(30);

It doesn’t matter if the last period ends in a new month.

Adding or subtracting units

Checkout the examples about adding and subtracting Carbon datetimes below from the documentation. Note that there are many, many units available to choose from.

$dt = Carbon::create(2012, 1, 31, 0);

echo $dt->toDateTimeString();            // 2012-01-31 00:00:00

echo $dt->addCenturies(5);               // 2512-01-31 00:00:00
echo $dt->addCentury();                  // 2612-01-31 00:00:00
echo $dt->subCentury();                  // 2512-01-31 00:00:00
echo $dt->subCenturies(5);               // 2012-01-31 00:00:00

echo $dt->addYears(5);                   // 2017-01-31 00:00:00
echo $dt->addYear();                     // 2018-01-31 00:00:00
echo $dt->subYear();                     // 2017-01-31 00:00:00
echo $dt->subYears(5);                   // 2012-01-31 00:00:00

echo $dt->addQuarters(2);                // 2012-07-31 00:00:00
echo $dt->addQuarter();                  // 2012-10-31 00:00:00
echo $dt->subQuarter();                  // 2012-07-31 00:00:00
echo $dt->subQuarters(2);                // 2012-01-31 00:00:00

echo $dt->addMonths(60);                 // 2017-01-31 00:00:00
echo $dt->addMonth();                    // 2017-03-03 00:00:00 equivalent of $dt->month($dt->month + 1); so it wraps
echo $dt->subMonth();                    // 2017-02-03 00:00:00
echo $dt->subMonths(60);                 // 2012-02-03 00:00:00

echo $dt->addDays(29);                   // 2012-03-03 00:00:00
echo $dt->addDay();                      // 2012-03-04 00:00:00
echo $dt->subDay();                      // 2012-03-03 00:00:00
echo $dt->subDays(29);                   // 2012-02-03 00:00:00

echo $dt->addWeekdays(4);                // 2012-02-09 00:00:00
echo $dt->addWeekday();                  // 2012-02-10 00:00:00
echo $dt->subWeekday();                  // 2012-02-09 00:00:00
echo $dt->subWeekdays(4);                // 2012-02-03 00:00:00

echo $dt->addWeeks(3);                   // 2012-02-24 00:00:00
echo $dt->addWeek();                     // 2012-03-02 00:00:00
echo $dt->subWeek();                     // 2012-02-24 00:00:00
echo $dt->subWeeks(3);                   // 2012-02-03 00:00:00

echo $dt->addHours(24);                  // 2012-02-04 00:00:00
echo $dt->addHour();                     // 2012-02-04 01:00:00
echo $dt->subHour();                     // 2012-02-04 00:00:00
echo $dt->subHours(24);                  // 2012-02-03 00:00:00

echo $dt->addMinutes(61);                // 2012-02-03 01:01:00
echo $dt->addMinute();                   // 2012-02-03 01:02:00
echo $dt->subMinute();                   // 2012-02-03 01:01:00
echo $dt->subMinutes(61);                // 2012-02-03 00:00:00

echo $dt->addSeconds(61);                // 2012-02-03 00:01:01
echo $dt->addSecond();                   // 2012-02-03 00:01:02
echo $dt->subSecond();                   // 2012-02-03 00:01:01
echo $dt->subSeconds(61);                // 2012-02-03 00:00:00

echo $dt->addMilliseconds(61);           // 2012-02-03 00:00:00
echo $dt->addMillisecond();              // 2012-02-03 00:00:00
echo $dt->subMillisecond();              // 2012-02-03 00:00:00
echo $dt->subMillisecond(61);            // 2012-02-03 00:00:00

echo $dt->addMicroseconds(61);           // 2012-02-03 00:00:00
echo $dt->addMicrosecond();              // 2012-02-03 00:00:00
echo $dt->subMicrosecond();              // 2012-02-03 00:00:00
echo $dt->subMicroseconds(61);           // 2012-02-03 00:00:00

// and so on for any unit: millenium, century, decade, year, quarter, month, week, day, weekday,
// hour, minute, second, microsecond.

// Generic methods add/sub (or subtract alias) can take many different arguments:
echo $dt->add(61, 'seconds');                      // 2012-02-03 00:01:01
echo $dt->sub('1 day');                            // 2012-02-02 00:01:01
echo $dt->add(CarbonInterval::months(2));          // 2012-04-02 00:01:01
echo $dt->subtract(new DateInterval('PT1H'));      // 2012-04-01 23:01:01

Calculating the differences between two times

Calculating the differences between two dates is not difficult with Carbon. Carbon offers a large toolset of diff() methods.

The first method is called just diff() and it’s really easy. You take a date, chain ->diff($dateToCompare) on it and insert a second date as a parameter:

$date1 = now();
$date2 = now()->addDay();

$difference = $date1->diff($date2);
// This gives a DateInterval instance

There are also methods that give you the difference in days, hours or minutes. Checkout the following example from the docs:

$dtOttawa = Carbon::createMidnightDate(2021, 1, 1, 'America/Toronto');
$dtVancouver = Carbon::createMidnightDate(2021, 1, 1, 'America/Vancouver');
echo $dtOttawa->diffInHours($dtVancouver);                             // 3
echo $dtVancouver->diffInHours($dtOttawa);                             // 3

echo $dtOttawa->diffInHours($dtVancouver, false);                      // 3
echo $dtVancouver->diffInHours($dtOttawa, false);                      // -3

$dt = Carbon::createMidnightDate(2021, 1, 31);
echo $dt->diffInDays($dt->copy()->addMonth());                         // 31
echo $dt->diffInDays($dt->copy()->subMonth(), false);                  // -31

$dt = Carbon::createMidnightDate(2021, 4, 30);
echo $dt->diffInDays($dt->copy()->addMonth());                         // 30
echo $dt->diffInDays($dt->copy()->addWeek());                          // 7

$dt = Carbon::createMidnightDate(2021, 1, 1);
echo $dt->diffInMinutes($dt->copy()->addSeconds(59));                  // 0
echo $dt->diffInMinutes($dt->copy()->addSeconds(60));                  // 1
echo $dt->diffInMinutes($dt->copy()->addSeconds(119));                 // 1
echo $dt->diffInMinutes($dt->copy()->addSeconds(120));                 // 2

Be aware that the values returned here are truncated and rounded. This means that a difference of 2.6 hours will return 3.

To get better, more exact differences, use floatDiff():

echo Carbon::parse('06:01:23.252987')->floatDiffInSeconds('06:02:34.321450');    // 71.068463
echo Carbon::parse('06:01:23')->floatDiffInMinutes('06:02:34');                  // 1.1833333333333
echo Carbon::parse('06:01:23')->floatDiffInHours('06:02:34');                    // 0.019722222222222
// Those methods are absolute by default but can return negative value
// setting the second argument to false when start date is greater than end date
echo Carbon::parse('12:01:23')->floatDiffInHours('06:02:34', false);             // -5.9802777777778
echo Carbon::parse('2000-01-01 12:00')->floatDiffInDays('2000-02-11 06:00');     // 40.75
echo Carbon::parse('2000-01-01')->floatDiffInWeeks('2000-02-11');                // 5.8571428571429
echo Carbon::parse('2000-01-15')->floatDiffInMonths('2000-02-24');               // 1.3103448275862
// floatDiffInMonths count as many full months as possible from the start date
// (for instance 31 days if the start is in January), then consider the number
// of days in the months for ending chunks to reach the end date.
// So the following result (ending with 24 march is different from previous one with 24 february):
echo Carbon::parse('2000-02-15 12:00')->floatDiffInMonths('2000-03-24 06:00');   // 1.2822580645161
// floatDiffInYears apply the same logic (and so different results with leap years)
echo Carbon::parse('2000-02-15 12:00')->floatDiffInYears('2010-03-24 06:00');    // 10.100684931507

Displaying datetime differences for humans

Now here comes the real magic part: displaying datetime differences in a human-readable way. Forget about using all sorts of complex if conditions to display a timestamp, instead use Carbon!

There are roughly four possibilities when comparing dates:

  1. Comparing a moment in the past to now
    • 2 hours ago
    • 4 weeks ago
  2. Comparing a moment in the past to another moment in the past
    • 2 hours before
    • 4 weeks before
  3. Comparing a moment in the future to now
    • 2 hours from now
    • 4 weeks from now
  4. Comparing a moment in the future to another moment in the future
    • 2 hours after
    • 4 weeks after

How to display datetime differences for humans?

The syntax for displaying Carbon datetime differences is very easy. It goes like this:

$date->diffForHumans($dateToCompareWith);

The first $date variable is always needed, but the second variable $dateToCompareWith not. If you leave this variable empty, it will default to the current moment, now.

Comparing a moment in the past to now

now()->subDays(5)->diffForHumans();
// "5 days ago"

Note that now()->subDays(5) is the $date variable.

Comparing a moment in the past to another moment in the past

now()->subDays(5)->diffForHumans( now()->subDays(3) );
// "2 days before"

Comparing a moment in the future to now

now()->addDays(5)->diffForHumans();
// "4 days from now"

// This might seem weird, but in fact it is 4 days and 23 hours

Comparing a moment in the future to another moment in the future

now()->addDays(7)->diffForHumans( now()->addDays(5) );
// "1 day after"

// This might seem weird, but in fact it is 1 day and 23 hours

As a rule of thumb: the diffForHumans() compares whether the $date is before or after the $dateToCompareWith.

So if you compare two moments in the future or two moments in the past, you could also get a ‘before’ statement back:

now()->addDays(5)->diffForHumans( now()->addDays(7) );
// "2 days before"

There are a few additional Carbon methods you can use, but in my opinion these are more than enough for the average developer just starting out with Carbon.

Conclusion

As we’ve seen in this Carbon for beginners tutorial, mastering Carbon is absolutely not hard. It only requires a little understanding of how it works and what a moment exactly is. I’m sure you’ll fully master this as soon as you play with Carbon a bit more, so feel free to leave this browser tab open while coding 😉

If you’ve any questions or additions, feel free to leave a comment below! Or subscribe to my newsletter if this was interesting and if you want to receive more articles like this every now and then.

Stay up to date with all things Laravel, Tailwind, WordPress & PHP

Subscribe now to my e-mail newsletter and get my latest articles and project updates delivered directly to your inbox. Never miss an update.

Image Ralph J. Smit
Ralph is a designer gone developer. He happily lives in the Netherlands. His passion for good design drove him towards development, because he felt that no-code tools were too limiting. On this blog, Ralph writes the articles he would've wanted to have during his continual developer journey. → Follow on Twitter

Comments

Leave a reply

Your email address will not be published. Required fields are marked *

Close
Do you want more articles like this delivered straight to your inbox?

Subscribe now to my e-mail newsletter and get my latest articles and project updates delivered directly to your inbox. Never miss an update.