Ralph J. Smit Laravel Software Engineer
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:
-
Creating datetimes
-
Converting datetimes between different timezones
-
Adding and subtracting times
-
Calculating the differences between times
-
Converting datetimes into something human-readable
First, what is exactly a datetime? Bluntly put, a datetime means a moment. It consists of three parts:
-
Date
-
Time
-
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:00echo $dt->addCentury(); // 2612-01-31 00:00:00echo $dt->subCentury(); // 2512-01-31 00:00:00echo $dt->subCenturies(5); // 2012-01-31 00:00:00 echo $dt->addYears(5); // 2017-01-31 00:00:00echo $dt->addYear(); // 2018-01-31 00:00:00echo $dt->subYear(); // 2017-01-31 00:00:00echo $dt->subYears(5); // 2012-01-31 00:00:00 echo $dt->addQuarters(2); // 2012-07-31 00:00:00echo $dt->addQuarter(); // 2012-10-31 00:00:00echo $dt->subQuarter(); // 2012-07-31 00:00:00echo $dt->subQuarters(2); // 2012-01-31 00:00:00 echo $dt->addMonths(60); // 2017-01-31 00:00:00echo $dt->addMonth(); // 2017-03-03 00:00:00 equivalent of $dt->month($dt->month + 1); so it wrapsecho $dt->subMonth(); // 2017-02-03 00:00:00echo $dt->subMonths(60); // 2012-02-03 00:00:00 echo $dt->addDays(29); // 2012-03-03 00:00:00echo $dt->addDay(); // 2012-03-04 00:00:00echo $dt->subDay(); // 2012-03-03 00:00:00echo $dt->subDays(29); // 2012-02-03 00:00:00 echo $dt->addWeekdays(4); // 2012-02-09 00:00:00echo $dt->addWeekday(); // 2012-02-10 00:00:00echo $dt->subWeekday(); // 2012-02-09 00:00:00echo $dt->subWeekdays(4); // 2012-02-03 00:00:00 echo $dt->addWeeks(3); // 2012-02-24 00:00:00echo $dt->addWeek(); // 2012-03-02 00:00:00echo $dt->subWeek(); // 2012-02-24 00:00:00echo $dt->subWeeks(3); // 2012-02-03 00:00:00 echo $dt->addHours(24); // 2012-02-04 00:00:00echo $dt->addHour(); // 2012-02-04 01:00:00echo $dt->subHour(); // 2012-02-04 00:00:00echo $dt->subHours(24); // 2012-02-03 00:00:00 echo $dt->addMinutes(61); // 2012-02-03 01:01:00echo $dt->addMinute(); // 2012-02-03 01:02:00echo $dt->subMinute(); // 2012-02-03 01:01:00echo $dt->subMinutes(61); // 2012-02-03 00:00:00 echo $dt->addSeconds(61); // 2012-02-03 00:01:01echo $dt->addSecond(); // 2012-02-03 00:01:02echo $dt->subSecond(); // 2012-02-03 00:01:01echo $dt->subSeconds(61); // 2012-02-03 00:00:00 echo $dt->addMilliseconds(61); // 2012-02-03 00:00:00echo $dt->addMillisecond(); // 2012-02-03 00:00:00echo $dt->subMillisecond(); // 2012-02-03 00:00:00echo $dt->subMillisecond(61); // 2012-02-03 00:00:00 echo $dt->addMicroseconds(61); // 2012-02-03 00:00:00echo $dt->addMicrosecond(); // 2012-02-03 00:00:00echo $dt->subMicrosecond(); // 2012-02-03 00:00:00echo $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:01echo $dt->sub('1 day'); // 2012-02-02 00:01:01echo $dt->add(CarbonInterval::months(2)); // 2012-04-02 00:01:01echo $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); // 3echo $dtVancouver->diffInHours($dtOttawa); // 3 echo $dtOttawa->diffInHours($dtVancouver, false); // 3echo $dtVancouver->diffInHours($dtOttawa, false); // -3 $dt = Carbon::createMidnightDate(2021, 1, 31);echo $dt->diffInDays($dt->copy()->addMonth()); // 31echo $dt->diffInDays($dt->copy()->subMonth(), false); // -31 $dt = Carbon::createMidnightDate(2021, 4, 30);echo $dt->diffInDays($dt->copy()->addMonth()); // 30echo $dt->diffInDays($dt->copy()->addWeek()); // 7 $dt = Carbon::createMidnightDate(2021, 1, 1);echo $dt->diffInMinutes($dt->copy()->addSeconds(59)); // 0echo $dt->diffInMinutes($dt->copy()->addSeconds(60)); // 1echo $dt->diffInMinutes($dt->copy()->addSeconds(119)); // 1echo $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.068463echo Carbon::parse('06:01:23')->floatDiffInMinutes('06:02:34'); // 1.1833333333333echo 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 dateecho Carbon::parse('12:01:23')->floatDiffInHours('06:02:34', false); // -5.9802777777778echo Carbon::parse('2000-01-01 12:00')->floatDiffInDays('2000-02-11 06:00'); // 40.75echo Carbon::parse('2000-01-01')->floatDiffInWeeks('2000-02-11'); // 5.8571428571429echo 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:
-
Comparing a moment in the past to now
-
2 hours ago
-
4 weeks ago
-
-
Comparing a moment in the past to another moment in the past
-
2 hours before
-
4 weeks before
-
-
Comparing a moment in the future to now
-
2 hours from now
-
4 weeks from now
-
-
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.
Published by Ralph J. Smit on in Laravel . Last updated on 11 March 2022 .