Subscribe to PHP Freaks RSS

Working with dates in PHP

Print
by Daniel on Aug 19, 2009 2:31:45 AM - 99,754 views

Preface

There are many topics on the forums that go again, topics many people often are having trouble with. One of these problem areas are how to handle dates, convert them to a different format, timezone issues, etc. This tutorial will attempt to address many of the commonest problems related to date and time issues.

Storing date and time in a database

Before covering how you handle dates in PHP, I want to talk a little about how you should store dates in a database. Specifically I'm going to talk about MySQL because that is what I've got the most experience with. The other DBMS have also got facilities for working with date and time information, so you can check out the manual for these.

Many people "invent" their own way of storing dates in the database, and this inevitably gives them some trouble later on. They may for instance have a VARCHAR field type and store dates in a DD/MM/YYYY format. This is the wrong way to do it.

There are a number of different ways you can store dates properly in a database. One way is to have an integer field and store a Unix timestamp. I don't like this approach. It makes you unable to use MySQL's date and time functions, and there are the potential issues with the Y2K38 problem.

MySQL has a datatype called TIMESTAMP. This displays the date and time in an ISO 8601 format, i.e. from the most significant part (year) to the least significant part (second). An example could be 2009-08-18 9:17:21. The extra formatting isn't actually necessary when typing it in, so 2009081891721 would be the same thing.

There is also a datatype called DATETIME. First looking at it, it seems identical to the TIMESTAMP, but that is not the case. First of all, TIMESTAMP still have issues with the Y2K38 thing, so it can only store dates that can be represented with a 32-bit unsigned integer starting from the Unix epoch (1970-01-01 00:00:00). This gives an upper limit 2038-01-19 03:14:07. DATETIME does not have this problem; it can store dates from 1000-01-01 to 9999-12-31. Finally, there are some datatypes called DATE and TIME. These are for if you just need either the date or time part only.

My recommendation is to use either DATETIME, DATE or TIME depending on your needs. Actually, there is a YEAR type as well. Using MySQL's built-in datatypes for date and time storage allows you to use its date and time functions.

I'll not be using databases for the remainder of this tutorial. The examples will assume you already have some string containing some date.

Timezones

This is a very frequent problem. When using the date() function in PHP it is always using the server time for generating date and time stamps. Unless you're in a different timezone than your server, this is not a problem. I have got a VPS (Virtual Private Server) in England, but I live in Denmark. That is a time difference of one hour. How do I get PHP to display the right time for me?

Because I am the server admin, I can just change the server's clock (and that is actually what I've done). Most people cannot just do that, so we'll have to work out some alternative solutions to this problem.

If you've got access to php.ini (or can change its directives using an Apache .htaccess file) you can change the date.timezone directive (actually, this needs to be set to something if you don't want errors from PHP). Otherwise you can use the date_default_timezone_set() function. The manual has a list of valid timezones you can use for these two things. Because I am in Denmark, I would choose Europe/Copenhagen.

Let's try it out:

echo date('H:i:s');

My computer's local time is 09:34:08, so that is also the output. Now we'll try to change the timezone:

date_default_timezone_set('America/New_York');
echo date('H:i:s');

And we'll get 03:34:08. So it seems to be working (how fortunate!). This is a very good way of doing it.

What if we need multiple dates in different timezones, but on the same page? Well, that is quite simple. Consider the following example:

date_default_timezone_set('Europe/Copenhagen');
echo 'The time in Copenhagen is: ' . date('H:i:s') . PHP_EOL;

date_default_timezone_set('America/New_York');
echo 'The time in New York is: ' . date('H:i:s') . PHP_EOL;

date_default_timezone_set('Europe/Moscow');
echo 'The time in Moscow is: ' . date('H:i:s') . PHP_EOL;

The output of that would currently be:

The time in Copenhagen is: 09:45:56
The time in New York is: 03:45:56
The time in Moscow is: 11:45:56

What if you have users from multiple timezones? Still simple! Ask them what their timezone is and set it accordingly. The function timezone_identifiers_list() gives you an array of all the timezone identifiers, so you can use that to generate a drop down list they can choose from. You can also try out geolocating, but that is beyond the scope of this tutorial.

Finally, PHP has a function called gmdate(), which always formats the date in GMT. If you know the offset in seconds you can do like:

gmdate('H:i:s', time() + $offset);

, so you might do

gmdate('H:i:s', time() + 3600);

for an offset of one hour. However, this requires you to take DST into consideration. Not all countries observe DST, and some do it differently than others. My recommendation would be to use PHP's built-in support for timezones.

Converting from one format to another

"I have got a date in DD/MM/YYYY but I need it in MM-DD-YYYY. Help!"

How do we help this person? Well, let's analyze the problem a bit. Each date consists of three components: the day, the month and the year. So, essentially what we need to do is reorder it and use hyphens instead of slashes. There are a few ways we can accomplish this. Let's first try splitting it up. We've got a function called explode() for that.

$oldDate = '18/08/2009'; // DD/MM/YYYY

$parts = explode('/', $oldDate);

/**
 * Now we have:
 *   $parts[0]: the day
 *   $parts[1]: the month
 *   $parts[2]: the year
 * We could also have done:
 *   list($day, $month, $year) = explode('/', $oldDate);
 */

$newDate = "{$parts[1]}-{$parts[0]}-{$parts[2]}"; // MM-DD-YYYY

echo $newDate; // 08-18-2009

This seems to work pretty nice. We can also use something called regular expressions. This will essentially be a one-liner:

$oldDate = '18/08/2009';
$newDate = preg_replace('#^(\d{2})/(\d{2})/(\d{4})$#', '$2-$1-$3', $oldDate);

I am not going to explain regular expressions in this tutorial. You may check out this regex tutorial for that.

Any other ways? Yes, there is! As it usually happens to be with programming, there are many ways to do just one thing. Assuming we have already split up our date in three variables $day, $month and $year we can use a function called mktime() for generating a Unix timestamp and then use date() to format it in this way:

$timestamp = mktime(0, 0, 0, $month, $day, $year);
echo date('m-d-Y', $timestamp);

Yet another way is using strtotime() to convert a formatted string into a Unix timestamp. EXCEPT, strtotime() does not recognize DD/MM/YYYY as a valid date format, so it returns false. There are also problems with ambiguity. Consider the string 07-08-2009. Is that the 7th of August, or is it the 8th of July? It will be the former.

Date validation

If you take a date as input (for instance a birthday), it would be quite useful knowing if it's actually a valid date. There are two conditions that must be met before a date can be considered valid:

  1. It must conform to a particular format (we might only want YYYY-MM-DD for instance).
  2. The date must actually exist (2009-02-29 doesn't exist, but 2008-02-29 does).

The first condition is rather simple, and we can do that using regular expressions. The second one is a bit more involved. We will have to take the length of a month into consideration. Maybe it's a leap year, maybe it's not? Fortunately, we needn't worry about that; PHP has a function called checkdate() that, uhm... checks a date. So, regex and checkdate() is what we need. A little example:

<?php
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['date'])) {
	if (preg_match('#^(\d{4})-(\d{2})-(\d{2})$#', $_POST['date'], $matches) && checkdate($matches[2], $matches[3], $matches[1])) {
		echo '<p>The date is <strong>valid</strong>!</p>';
	}
	else {
		echo '<p>The date is <strong>invalid</strong>!</p>';
	}
	
	echo '<hr>';
}
?>

<form action="<?php echo $_SERVER['PHP_SELF'] ?>" method="post">
	<label for="date">Enter an ISO 8601 date:</label>
	<input type="text" name="date" id="date"<?php if (!empty($_POST['date'])): ?> value="<?php echo htmlentities($_POST['date']) ?>"<?php endif ?>>
	<button type="submit">Check validity</button>
</form>

This example should be fairly straightforward. First we check the format of the string using preg_match() where we also extract the month, day and year in backreferences. preg_match() returns true if the string matches the pattern. Because logical and is left to right associative, the preg_match() is evaluated first, and the checkdate() is only evaluated if the first operand is true (if the left operand was false, the AND expression couldn't possibly return true, so evaluating the right operand would be redundant, hence the reason why it isn't).

If you have made separate fields for day, month and year it's even simpler and we can skip checking the format.

You might setup additional conditions that must be met before you consider a date valid. You might ask for a date interval meaning that the first must be earlier than the last. You might only want dates in the past or in the future, or they might have to be within a particular preset interval. The easiest way of doing this would be to convert the dates to Unix timestamps. From hereon it's rudimentary integer comparison. I'll leave that as an exercise to you. A safer bet might (recall Y2K38) be to use DateTime::diff(). For that, see this comment in the manual.

Conclusion and further reading

As you have hopefully seen. Dates aren't really that difficult to work with. PHP's built-in functions makes it very easy to validate and format all kinds of dates. It really does all the heavy lifting for us. Therefore, it shouldn't come as a surprise that the recommended further reading for this topic is the date reference section in the manual. PHP also has native support for other calendar types such as the Jewish calendar. I even think someone wrote a book solely about PHP and dates. You can go Google it (or Bing it).

Comments

Gareth Evans Aug 19, 2009 5:47:23 AM

In your first example of 'Converting from one format to another' you explode the date and re-build it with dashes. If you know the format of the date coming in;

$oldDate = '18/08/2009'; // DD/MM/YYYY

Would a simple str_replace() be good enough;

<?php
$oldDate = '18/08/2009';
$newDate = str_replace("/", "-", $oldDate);
echo $newDate; // 08-18-2009
Daniel Aug 19, 2009 6:37:23 AM

Not quite. Note that I am also switching the day and month around. I.e. from DD/MM/YYYY to MM-DD-YYYY.

Either way, the point of that particular example was to show that a date really just consists of a series of individual components that you can extract and reuse.

Gareth Evans Aug 19, 2009 6:39:37 AM

Fair point, also I didn't notice the day and month's position switch... my bad.

Chris Young Aug 25, 2009 9:47:04 PM

Referring to "multiple users, multiple timezones":

I am doing something relevant to this blog, trying to display data based on users in different timezones. The concept is basically "who is online during which hours". Taking your suggestions above, it seems a good way to store this data would be to convert their input into GMT? This way when I choose to display the data, I can localize it to the timezone its being viewed in. It seems that the conversion wouldn't be too difficult using gmdate() back and forth.

Johnain Oct 12, 2009 3:45:54 AM

As always with dates, you never find what it is you are looking for. All I want to do is add one day to a date. The date is stored as yyyy-mm-dd, but for some reason this seems well beyond a simple one-line solution or even a simple function.

maikuru Oct 15, 2009 10:31:55 AM

the simplest way to do that is to just create a unix timestamp from your original date and then add 86400 (24 hours) to the timestamp. once you do that you just use date("date format", updatedTimeStamp) to get the date.

another solution would be to explode the date and then increment the day. if check date fails then you need to set the day to 1 and increment the month. if that fails then set month to 1 and increment the year.

it's simple enough, though there is no 1 line solution I can think of.

adaniels Dec 3, 2009 11:30:24 AM

This is a quite dirty approach to dates. Instead simply use the DateTime class: http://www.php.net/manual/en/class.datetime.php

<?php
$oldDate = '18/08/2009';
$date = DateTime::createFromFormat('d/m/Y');
echo $date->format('m-d-Y'); // 08-18-2009
?>

The class can do any type of conversion and work with timezones.

Daniel Dec 5, 2009 7:41:16 PM

I think you missed the point of this article. I'm not here to teach people about things in the PHP library. If that was my goal, I might as well just create one all-encompassing tutorial called "RTFM".

This article is about how you might choose to approach a problem, how to analyze the problem. If you read the tutorial, you will see that I mentioned DateTime several times and that I recommended it for further reading. If you are thinking about the copy-paste coders, then these are not my target audience.

Thanks for your input regardless.

Eric Rosebrock Feb 20, 2010 3:08:05 PM

Jeesh Daniel, Nice tutorial! Bravo! Bravo!

Anggie Jatrasmara Apr 16, 2010 9:38:21 PM

date_default_timezone_set('America/New_York');
echo date('H:i:s');

I just know that there is date_default_timezone_set

nice tutorial, keep post ;D

jwmstudios Oct 21, 2010 9:02:27 AM

very helpful

zkagenotora Dec 29, 2010 7:23:23 PM

nice post!

I found a lot of people expecting to write "date()" without parameter to have a current date.

Jeremy Smith Jan 18, 2011 4:42:10 AM

That's a really nice comment, but I am wanting to track how often my blog is accessed (a custom blog of my own, that I have designed), how would I say for instance make the user part of this timezone (that of my server I mean)?

Might be missing something here so I do apologise in advance but would be good to know.

Thanks,
Jeremy.

Jeremy Smith Jan 18, 2011 4:47:57 AM

Sorry just ignore my last post.

But ace tutorial, thanks for uploading!

Jeremy.

Sushant Danekar Aug 4, 2011 8:34:40 AM

This is nice article and very useful too

BlueBoden Nov 10, 2011 10:27:04 PM

I used to use unix timestamps for everything, but now i lean more towards using a readable date, and sometimes maybe, converting that to unix when i need to easily compare dates.

It just seems easier to do things this way, since you don't have to think much about how accurate your dates are.

Add Comment

Login or register to post a comment.