Displaying Dates Using User Selected Timezones in Laravel 4

Let me start by saying, you can safely stop worrying about user selected timezones if you are using Laravel 4+. I just spent a few hours trying to determine what was generally the most accepted method to store and display dates using a users own timezone. As it turns out, it is way too easy. So much so that I was finding it difficult to locate up to date Laravel 4 discussions on the topic.

So in case you are as thick as I am, here it is, all spelled out for you.

Important First Step: Set the timezone in app/config/app.php to your preferred timezone (or leave it at UTC if you want) keeping in mind that you will never change this, and you will never do something terrible like Config::set(‘app.timezone’, Auth::user()->timezone); anywhere in your application.

Using the following users table as an example, assume that we want to show foobazed_at to the user using their selected timezone:

CREATE TABLE `users` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(255) NOT NULL,
  `locale` varchar(255) NOT NULL DEFAULT 'en',
  `timezone` varchar(255) NOT NULL DEFAULT 'America/Toronto',
  `foobazed_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  `deleted_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`),
) ENGINE=InnoDB;

Laravel by default converts created_at, updated_at, and deleted_at columns to instances of Carbon, which, not knowing what Carbon was at the time didn’t mean a whole lot to me. Well, let me tell you… Carbon is awesome.

Instead of doing the following to display the created_at date (which will show the date in the default application timezone):

echo User::find(1)->created_at;

You can simply do this to get created_at in the users own preferred timezone:

echo User::find(1)->created_at->timezone(Auth::user()->timezone);

This is all well and good for the default columns, but what about our foobazed_at date? Easy. In your model (in this case the User model at app/models/User.php) add the following method:

public function getDates()
{
    return array('foobazed_at', static::CREATED_AT, static::UPDATED_AT, static::DELETED_AT);
}

That’s it! Any time you want to display foobazed_at you can simply do this:

echo User::find(1)->foobazed_at->timezone(Auth::user()->timezone);

I hope this is useful to someone else.

13 thoughts on “Displaying Dates Using User Selected Timezones in Laravel 4

  1. A good idea to use a timezone stored with the user, thanks for the tip. In the current version of Laravel you could just set the $dates member like “protected $dates = array(‘foobazed_at’);” instead of overwriting the getDates method.

  2. Thanks !!

    Wish I had come to the same conclusion and read your post earlier before being drawn in by all the documentation on using mutators or overiding the a methods in a base model.

    This is exactly what I need for a presentation level dates with out having to worry about changing the model or applicant time zone and breaking any calculation logic.

  3. Absolutely, as Martin points you… it’s even easier than overloading the getDates method you can also just specify “protected $dates = array(‘foobazed_at’);” directly in your model. I should update this post accordingly.

  4. Hi Matt. My approach would be different, and I’ll try to explain my thinking. No matter what the language or framework, I might prefer to deal with time as UTC on the server. On the client side, I might convert it to the appropriate timezone using the appropriate offset that I would derive by using “var offset = new Date().getTimezoneOffset();”

    When passing a date to the server, I would convert it to UTC. Therefore, the time on the server is standard always, and web clients take care of converting it to the appropriate time zone before displaying it.

    I might use the moment.js library to handle the conversions on the client – I think that would work.

    My biggest reason is that I do not want to depend on the time on the server being correct – if it is wrong, it is wrong for everyone. However, most clients will have the correct time – anybody who has the wrong time gets what they deserve. There has to be one time to rule them all and in the darkness bind them.

    Keeping track of timezones in the database seems more work than is necessary. I just want to deal with UTC, and I just want the client to take care of the rest. IMHO.

    Does that make sense? I may want to track where someone is, but I don’t think I would want to tie that to how I store my dates. Just my take on the problem.

    Having said that, Carbon is a beautiful library. I like the fluent setters. I checked out Zend a few years ago, and it just wasn’t as easy as I wanted it to be. But, Laravel looks like PHP heaven.

  5. Hi Jim,

    Thanks for the comments.

    Laravel does (by default) convert and store time in the database as UTC. You can change its’ default timezone in app/config/app.php if you want to.

    moment.js is an awesome Javascript library for displaying and “humanizing” dates, but personally I wouldn’t use it as the way to handle converting timezone data and relying on the clients time, especially not when Laravel comes packed with Carbon which automatically takes care of this at the model level. I definitely rely and depend on the server time being right, one of the server (admins?) responsibilities is to ensure the server is configured properly, this includes configuring an NTP server.

    Take care.

  6. Hi Matt:

    I worked on a project for the federal government where infrastructure and applications were transferred from Ottawa to New Brunswick, but the data was very time specific and tied to the fact that the servers were in Ottawa. Everything was an hour out when it was moved, and we had users all over Canada who relied on the time being right. It turns out that 4PM in Ottawa became 5PM – it was still stored as 4PM, but now it was 4PM in New Brunswick, if that makes sense.

    The database stored over 100,000 records a day, many of which related to filing deadlines. The data that had been stored in Ottawa was off when the server time was changed to New Brunswick time.

    The other factor was that we had no control of or assurances about the servers we were using – that was handled by a different team – a team of strangers, in fact.

    My comments probably relate more to that project than they do to Carbon itself. You took my back to a frustrating project!

    We wrote an SNTP client into the code, and made sure we stored everything as UTC. We did not depend on the server admins, nor did we want to depend on default timezones – the default was UTC no matter what. That was a .NET app with an Oracle back end.

    I have NEVER seen anything on the .NET side as lovely as Carbon or Momentjs.

    Cheers
    JC

  7. Great tip!
    Looks like you can do it other way too quite easily (example if user can select blog post publish time):

    $publish_date = Carbon::createFromFormat(‘Y-m-d H:i:s’, $publish_date, Auth::user()->timezone);
    $publish_date->setTimezone(‘UTC’);

  8. Hey this helped me find my solution, As I’m using laravel for web services I’m not rendering any template form here, I needed to add a custom attribute for created_at

    BaseModel.php (which is extended from all other models)
    public function getCreatedAtAttribute()
    {
    return (empty($this->created_at)) ? null : $this->created_at->timezone(Auth::user()->preferences->time_zone);
    }

    Within each model I added as parameter

    protected $appends = [‘….’, ‘…..’, ‘created_at’];

    This way all the clients using this web services gets the right time, worked like a charm

    Hope this help others

  9. “keeping in mind that … you will never do something terrible like Config::set(‘app.timezone’, Auth::user()->timezone); anywhere in your application.”

    Apologies if this is a dumb question – but why not? I’ve seen a bunch of other blogs recommending exactly this – does it cause problems?

  10. Hi Brendan,

    Happy to explain.

    Config::set(‘app.timezone’, Auth::user()->timezone);
    

    The reason I suggest that this is a horrible approach is that different users would be changing the timezone your application uses internally, which will result in timestamps being stored in the database using the timezone of the user who created / updated / deleted the record.

    The resulting dates & times would appear correctly for users in the same timezone, but would be wrong for anyone outside of that timezone.

    Consider:

    I create a new blog post at Saturday, November 15th, 2014 8:00AM EST (UTC -5). If we set the application configuration using Config::set() as being discussed, then the timestamp would be stored in the database as 2014-11-15 08:00:00.

    If you were in Los Angeles and happened to be read my post 5:30AM PST (UTC -8) it would appear to you like this was posted in the future since your timezone and the blog application’s timezone are both PST.

    I think the general acceptance is to leave app.timezone set to UTC… then use let the beautiful Carbon library do its thing.

  11. the method you describe works great, but I would have to apply it everywhere User->created_at is accessed. Looks like we can use Laravel’s Accessor/mutator capabilities:
    http://laravel.com/docs/5.1/eloquent-mutators#accessors-and-mutators

    so on our User model we add something like:
    public function getCreatedAtAttribute($value){
    return formatUtcToUserTime($value);
    }

    and this actually works great. if anyone is curious what my formatUtcToUserTime global function looks like:
    function formatUtcToUserTime($value){
    $datetimevalue = new DateTime($value, new DateTimeZone(Config::get(‘app.timezone’)));
    return $datetimevalue->setTimezone(new DateTimeZone(Auth::user()->timezone->identifier))->format(‘Y-m-d H:i:s’);
    }

    Your blog post here lead me down the right path though, thanks.

  12. Sir, thank you for this article. This article was seems very helpful. But I actually didn’t understand that what really foobazed_at is for?

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.