Good Practices: Dates, Time & Time zones in PHP

Coding (Php 7.x)

Sep 26, 2019

Your PHP script does not cope with dates and time? We all been there, here is how you solve it...
 
/img/blog/good-practices-date-time-time-zones-php.jpg

Introduction

How do you know when it’s time to sleep or wake up in the morning?


If you are a Web developer this question may be pretty simple,

 

If you start to feel asleep get a cup of coffee and go back on coding.

 

I have done it as well,

 

This may work but after a while, our productivity level will drop and this can lead to mistakes or even worse: result in bad code.

 

Our body, in order to prevent us to write down horrors that we would not understand the next morning or get fired by our project manager, has developed some extremely special cells in the back of our eyes called Photosensitive Retinal Ganglion Cells.

 

These cells are in charge of calculating the amount of luminosity we receive from the outside world and switch the state of our internal biological clock from awake to drowsy.

 

Unfortunately, our pc or MAC is not so lucky,

 

Yes, they have an internal clock but its efficiency is not even close to our and for a PHP developer that was a problem until a few years ago.

 

Now instead everything has changed!

 

Manage dates and times in PHP is still a pain but from PHP 5.2 on it has become easier and easier.

 

In this post, you are going to learn all about the classes that let you manage time on your PHP web application

 

The series


In this series, we are exploring what are the best practices a web developer must take care of when creating or managing PHP code.

 

  1. Sanitize, validate and escape
  2. Security and managing passwords
  3. Handling error and exceptions
  4. Dates and Time

 

Server Time Zone


One of the first task that has to be in your checklist when you start a new web application is to set the right time zone.

 

By declaring it you will avoid errors with the database and annoying warning messages that will appear.

 

There are two different ways to set the time zone.

 

The first and the one I advice is to declare the time zone in the php.ini file.

 

With the following command 

 

date.timezone = ‘Europe/London’

 

You can also declare the default time zone during runtime.

 

In this case you need to use the PHP command as below:


date_default_timezone_set("Europe/London");

 

Of course,  London it’s just an example you need to use the one you prefer.
 

In the PHP manual, you can find the complete list of supported timezones.

 

 

The DateTime class


In PHP 5.2 a little revolution began,

 

The DateTime class was created and dealing with dates was not an impossible task anymore.

 

It actually became very easy to work with, and the number of functionality was impressive.

 

Let’s get technical for a second then you’ll see some example.

 

The DateTime Class replace the built-in function date() and time().

 

Both functions are still available but it is preferred to use the class and work with the date object instead.

 

DateTime implements the DateTimeInterface.

 

This interface provides several constants and a few methods such as DateTime::diff() that return the difference between two dates, or DateTime::format() that returns a date in a given format.

 

To create a new DateTime object you need to pass a variable, in string format, with the date you want to create, the string will be then parsed and the object created.

 

You can use a lot of different formats to create an object, from UNIX, to pseudo-code, to American dates to plain English.

 

Have a look at these examples below:

 

$stringsOfDate = ['Next Monday', '', '1 January 2019', '+1 week'];
foreach($stringsOfDate as $stringOfDate) {
    $dateTime = new DateTime($stringOfDate);
    echo $dateTime->format(DateTime::RSS) . PHP_EOL;
}

 

In this snippet we created and output 4 DateTime objects, injecting parameters in 4 different formats.

 

The first string is written in plain English and will show the date of next Monday, the second parameter passed is an empty string, PHP will NOT return an error in this case, it will return the current timestamp, then we got a date written in European format, eventually some pseudo-code.

 

They will all work and be shown in an RSS format, which, for the record, look like this: 
 

D, d M Y H:i:s O

 

The constants 

 

The constant RSS in the snippet and the weird sequence of letters above are a representation of how the date is actually going to be displayed
Let’s start with the formatting code:

 

  • Y represent a full four-digit year (2019)
  • M its a two-digit month (03)
  • d day of the month, two-digit with leading zeros (06)    
  • D three letters textual day (Tue)
  • H 24h format hour with leading zeros (03)
  • i two-digits minutes with leading zeros (59)
  • s two-digits seconds with leading zeros (59)
  • O difference to GMT in hours
  • T time zone abbreviation (EST)

 

There are a lot more of these and you can find all of them in the official PHP manual.

 

Now that you understood how these letters works let’s have a look at the constants that are available in DateTimeInterface, thus in DateTime Class.

 

  • DateTimeInterface::ATOM Y-m-d\TH:i:sP
  • DateTimeInterface::COOKIE l, d-M-Y H:i:s T
  • DateTimeInterface::ISO8601 Y-m-d\TH:i:sO
  • DateTimeInterface::RFC822 D, d M y H:i:s O
  • DateTimeInterface::RFC850 l, d-M-y H:i:s T
  • DateTimeInterface::RFC1036 D, d M y H:i:s O
  • DateTimeInterface::RFC1123 D, d M Y H:i:s O
  • DateTimeInterface::RFC2822 D, d M Y H:i:s O
  • DateTimeInterface::RFC3339 Y-m-d\TH:i:sP
  • DateTimeInterface::RFC3339_EXTENDED Y-m-d\TH:i:s.vP
  • DateTimeInterface::RSS D, d M Y H:i:s O
  • DateTimeInterface::W3C Y-m-d\TH:i:sP

 

Date calculations


There are several ways you can do calculation while playing around with dates,

 

In this paragraph, I’ll show you the quick one and the proper one.

 

The quickest way you can do calculations is by using the modify() method of the DateTime class.

 

It needs a string as a parameter and returns the updated DateTime object

 

// Creating a new DateTime object using ‘now’ as date
$dateTime = new DateTime();

// adding one month to current date and output the result
$dateTime->modify('+1 month');

echo $dateTime->format(DateTime::COOKIE) . PHP_EOL;

 

Another method, and a proper way to do calculation according to expert web developers, it is by using DateInterval class together with DateTime add() and sub() methods.

 

In order to instantiate a DateInterval object, you need to know about interval_spec.

 

It stands for interval specification and it is just a weird string that requires a very specific format.

 

This string starts with the letter P, which stands for "Period" than a couple of pair of numbers are followed by period designators they are ordered from the biggest to the smallest.

 

Also, In case you want to specify the time you need to prefix it with the T letter, it stands for "Time".

 

It is confusing, isn’t it?

 

Let’s build a few together.

Let’s say we want to add 1 month, 2 weeks, 3 days to a DateTime object.

 

And in another example we want to remove 1 year, 1hour, 1 minute and 30 seconds to a DateTime object.

 

  1. We start adding the period ‘P’
  2. Then adding 1 month ‘P1M’
  3. Then adding 2 weeks ‘P1M2W’
  4. Then adding 3 days ‘P1M2W3D’

 

That is the first string we need, 

Let’s do the second with a time

 

  1. We start adding the period ‘P’
  2. Then adding 1 month ‘P1Y’
  3. Now we need the time, let’s add the T prefix ‘P1YT’
  4. Then adding 1 hour ‘P1YT1H’
  5. Then adding 1 minute ‘P1YT1H1M’
  6. Then adding 30 second ‘P1YT1H1M30S’

 

Now we have the two strings that we must use as parameter when creating the DateInterval objects

 

$dateTime = DateTime::createFromFormat('d-m-Y H:i:s', "27-09-2019 20:45:30");
// the current $datetime value is 27-09-2019 20:45:30


$intervalToAdd = new DateInterval('P1M2W3D');
$dateTime->add($intervalToAdd);
// adding $intervalToAdd to $dateTime the current $datetime value is 27-10-2019 23:45:30
echo $dateTime->format(DateTime::COOKIE) . PHP_EOL;
// this block of code will output: Wednesday, 30-Oct-2019 20:45:30 UTC

$intervalToSubtract = new DateInterval('P1YT1H1M30S');
$dateTime->sub($intervalToSubtract);
// subtracting $intervalToSubtract to $dateTime the current $datetime value is 27-10-2019 23:45:30
echo $dateTime->format(DateTime::COOKIE) . PHP_EOL;
// this block of code will output: Tuesday, 30-Oct-2018 19:44:00 UTC


Manual Calculations


What is the Unix epoch?


Sometimes you just want to do some calculation by yourself, without using all these fancy methods provided by the DateTime class.

 

PHP provides some functions that let you create dates and do not need you to invoke classes or even use OOP at all.

 

These PHP functions instead leverage the UNIX timestamp.

 

Unix timestamp is a number that keeps increasing and count the number of seconds passed since the UNIX epoch, which is 1 January 1970 00:00:00 GMT.

 

The advantage of using this method is that is time zone independent and really easy to implement into your code.

 

The two PHP functions are:


strtotime()

 

This function parses English textual date-time description and returns a Unix timestamp

 

You can use pretty much every sentence you preferer as a parameter and it will be accepted.

 

You don’t believe me?

 

Below there are just a few examples directly from the manual:

 

strtotime("now");
// 1569396041
strtotime("10 September 2000");
// 968544000
strtotime("+1 day");
// 1569482441
strtotime("+1 week");
// 1570000841
strtotime("+1 week 2 days 4 hours 2 seconds");
// 1570188043
strtotime("next Thursday");
// 1569456000
strtotime("last Monday");
// 1569196800

 

That is incredibly easy to use and you can now manipulate and interact with these integers as you like the most.

 

mktime()

 

The "make-time" function is a bit more complicated because of its arguments but not that much,

 

It returns a Unix timestamp in the form of a long integer, that corresponds to the arguments given. 

 

You do not need to insert all arguments, as a matter of fact, you do not need to insert any argument at all, since this function requires arguments to be inserted in a specific order PHP will consider the current moment for the ones that are left out.

 

Here there are a few examples

 

// mktime(h, i, s, m, d, y)

date('F jS, Y g:i:s a', mktime(0, 0, 0, 0, 0, 2013));
// November 30th, 2012 12:00:00 am
date('F jS, Y g:i:s a', mktime(1, 1, 1, 1, 1, 2013));
// January 1st, 2013 1:01:01 am
date("M-d-Y", mktime(0, 0, 0, 12, 32, 1997));
// Jan-01-1998 
date("M-d-Y", mktime(0, 0, 0, 13, 1, 1997));
// Jan-01-1998

 

If you paid attention you surely have noticed something weird in the result above,

 

The last two examples result in the first day of January 1998 even though we specify another date,

 

The reason for this is that if you pass a greater parameter than the value in a specific place allows, PHP is smart enough to understand it and reference to the next period.

 

In this case, there is no 32nd of December or 13th month in a year so PHP considers the value as the 1st day of January of the following year.

 

Isn’t that clever?

 

How to compare Dates?


Another great functionality of DateTime is to calculate the difference between two dates,

 

You can use the diff() function on a DateTime object and pass another DateTime object as a parameter and you will have a DateInterval class (see below) representing the period between the two object

 

That’s it with the talk let’s see some code.

 

$now = new DateTime();
$christmas = new DateTime('25 December');
if ($now > $christmas) {
  $christmas = new DateTime('25 December next year');
}
  
$interval = $christmas->diff($now);
  
echo "$interval->days days until Christmas";
// 90 days until Christmas


$interval is an object instance of the DateInterval class and its method days() return an integer that shows the days between now and Christmas day, 90 at the moment I am writing this sentence.

Better be fast with the presents!

 

The DateInterval class


We have just seen an example of how DateInterval is used in the previous section of this article, 

 

Now we’ll dive into this class by itself.

 

DateInterval can represent two lengths of time, a fixed one (eg: one week) or a relative (eg: yesterday).

 

The reason we use DateInterval is to modify DateTime instances.

 

We saw above how to use the add() and sub() methods to manipulate dates.

 

Dateinterval has a constructor method, 


if you do not know what a constructor method is you can read about it in the Object-Oriented Basic Series here

 

This construct requires an interval specification as we saw before, alternatively, you can use the function createFromDateString() that instead accepts a simple string

 

$objConstructed = new DateInterval('P1DT12H');
$objFromDateString = DateInterval::createFromDateString('1 day + 12 hours');
object(DateInterval)#1 (16) {
  ["y"]=>
  int(0)
  ["m"]=>
  int(0)
  ["d"]=>
  int(1)
  ["h"]=>
  int(12)
  ["i"]=>
  int(0)
  ["s"]=>
  int(0)
  ["f"]=>
  float(0)
  ["weekday"]=>
  int(0)
  ["weekday_behavior"]=>
  int(0)
  ["first_last_day_of"]=>
  int(0)
  ["invert"]=>
  int(0)
  ["days"]=>
  bool(false)
  ["special_type"]=>
  int(0)
  ["special_amount"]=>
  int(0)
  ["have_weekday_relative"]=>
  int(0)
  ["have_special_relative"]=>
  int(0)
}
// Both ways return an identical DateInterval object 

 

Note that DateInterval does not support split seconds (microseconds or milliseconds etc.)

 

The DateTimeZone class


If you have worked with clients from different countries you know that working in different time zones is very often a source of pain.

 

You save something on the database and the time stamp are just not the one you want,

 

You run a MySql query where the modified field has a specific period and it’s all screwed-up just because you are or the user is in a different time zone.

 

PHP has solved this problem is several ways, one of them is the user of DateTimeZone class,

 

It is very easy to create a DateTimeZone object, the only value that you need is a string that represents one of the supported timezone stings.

 

In my opinion,

 

the most common use of this type of object is when creating a new DateTime instance, the second argument of the construct, in fact, is an instance of the

 

DateTimeZone Class, it is not mandatory but it’s more than advised to add it, especially in multilingual web applications.

 

$timezone = new DateTimeZone('Europe/London');
$datetime = new DateTime('2019-10-01', $timezone);
object(DateTime){
  ["date"]=>
  string(26) "2019-10-01 00:00:00.000000"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(13) "Europe/London"
}

$datetime->setTimezone(new DateTimeZone('Asia/Dhaka'));
object(DateTime){
  ["date"]=>
  string(26) "2019-10-01 05:00:00.000000"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(10) "Asia/Dhaka"
}


Personally to don’t go mad when I know users from abroad are going to connect to one of the websites I developed.

 

I set the timezone as my server’s time zone, so every data will be consistent.

 

I can just change the dates using one of the functions in this article if I need to.

 

The DatePeriod class


Did you ever forget the birthday of your parents, even worse the anniversary with your girlfriend?

 

Don’t worry, PHP got you covered.

 

DatePeriod is the PHP class in charge of dealing with recurring events and dates,

 

There are 3 different ways to create a DatePeriod class,

 

The first method is passing an ISO 8601 string, which is a string that describes an interval of period.
 

You can create an ISO sting online here

 

You need to use the second method when you know the total number of iteration you want your code to do.

 

The construct requires 3 arguments and a fourth is not mandatory

 

The arguments are the date you want to start the iteration, the interval of time that you want to use to iterate among the dates, the number of iteration you want to perform before the finish, and lastly, you can type the flag DatePeriod::EXCLUDE_START_DATE if you want to start from your second iteration

 

The third method is very similar to the second with the only difference that instead of defining the number of cycles you want to do you define the end date.

 

Here are the examples of each of these three methods

 

echo "Constructor using Iso";
$iso = 'R8/2019-10-31T00:00:00Z/P7D';  
$periodISO = new DatePeriod($iso);
foreach ($periodISO as $date) {
    echo $date->format('Y-m-d');
}

echo "Constructor using defined recurrences";
$start = new DateTime('2019-10-31');
$interval = new DateInterval('P7D');
$recurrences = 8;  
$periodRecurrences = new DatePeriod($start, $interval, $recurrences);
foreach ($periodRecurrences as $date) {
    echo $date->format('Y-m-d');
}
 
echo "Constructor using end date";
$start = new DateTime('2019-10-31');
$interval = new DateInterval('P7D');
$end = new DateTime('2019-12-31');
$periodEnd = new DatePeriod($start, $interval, $end);
foreach ($periodEnd as $date) {
    echo $date->format('Y-m-d');
}



nesbot/carbon


If you use Date and Times in your PHP application and you know how to use Composer you gotta use Brian Nesbitt component Carbon.

 

It is very easy to use, it has very detailed documentation and a lot of functionality that improve the DateTime class over every almost aspect.

 

I have no more to say about it,

 

Just look at the snippet below

 

$howOldAmI = Carbon::createFromDate(1975, 5, 21)->age;

Carbon::now()->subMinutes(2)->diffForHumans(); // '2 minutes ago'

if (Carbon::now()->isWeekend()) { echo 'Party!';}

$date = Carbon::now()->locale('it_IT');
echo $date->locale();            // it_IT
echo $date->diffForHumans();     // 1 secondo fa
echo $date->monthName;           // settembre
echo $date->isoFormat('LLLL');   // giovedì 26 settembre 2019 09:28

 

That is pretty amazing.

 

Here is the full documentation of Nesbot Carbon

 


Conclusion


As you find out in this article the computer in which our web application run do not have amazing cells at the back of their CPU that automatically switch depending on the hour our visitor log in or the time zone he is in.

 

There is also to consider that with no doubt that managing dates, times and time zones on your web applications are a daunting task.

 

However,

 

we all are now working (hopefully) with PHP 7 and later versions of this programming language so there are no excuses not to implement the features you just read here.

 

These good practices are going to actually make your job much easier and your software more scalable and reliable.

 
 
If you like this content and you are hungry for some more join the Facebook's community in which we share info and news just like this one!

Other posts that might interest you

Coding (Php 7.x) Sep 19, 2019

Good Practices: handling error and exceptions in php

See details
Coding (Php 7.x) Oct 4, 2019

Good Practices: use stream in PHP

See details
Coding (Php 7.x) Oct 11, 2019

How‌ ‌to‌ ‌become‌ the ‌best‌ ‌programmer‌ ‌in‌ ‌the‌ ‌world‌ (My journey)

See details
Get my free books' review to improve your skill now!
I don't want to improve