Rebuilding Wardrobe: Week 4

In this weeks Rebuilding Wardrobe series I started working on the list of posts. That sounds easy right? Grab all the posts from the database, loop them, and print them in table rows. But in our crazy world of web development it’s never that easy. One of the fields that should be included is the published date and that means I have to account for timezones. Yuck!

Since Wardrobe is going to be a downloadable app I can’t guarantee the end users server will have the proper timezone, and ideally dates should be stored in UTC. With them stored as UTC it prevents having to worry about offsets, daylight savings, and everything else.

There are a few ways of handling the conversion to local time and of course Carbon, the date time parser included in Laravel, has support for it. However, then I have to allow users select their timezone, and if they are in a part of the world that observes daylight savings they will need to edit the setting twice a year.

The second solution is to use JavaScript and that has the advantage of using the browsers timezone. However, it’s not all roses, I may eventually want to show graphs on the admin dashboard and to get a list of all posts posted in a time frame would need to be handled on the backend. I’m pretty confident I can work around this by fetching the graphs via ajax and passing in the browsers timezone. I haven’t experimented with this yet so I could be harder than I am thinking. None the less I think this is the best solution for Wardrobe.

Lets get started by first creating the Blade views and then work on the JavaScript.

Create the table

I’m a big fan of having minimal view files and break out as much as possible. To show you an example here is my controller and views:

public function index()

{

   $posts = $this->posts->allPaged();

   return view('wardrobe::admin.post.grid', compact('posts'));

}

Then my admin.post.grid view includes:

@extends('wardrobe::admin.layout')



@section('content')

    <table class="grid grid__posts">

        <tr>

            <th>Title</th>

            <th>Status</th>

            <th>Published</th>

            <th></th>

        </tr>

        @foreach ($posts as $post)

            @include('wardrobe::admin.post.item')

        @endforeach

    </table>
[email protected]

As you can see I’m just including the post.item which will have each row. One thing to note is the @include accepts an array as a second parameter. This is great for creating shared included views but I found that in this situation it causes the dates to loose the Carbon object, as they are cast to a string. So I can’t display them as I need with that.

My post.item looks like this:

<tr>

    <td class="grid__posts--title">{{ $post->title }}</td>

    <td>{{ $post->active == 1 ? "Active" : "Draft" }}</td>

    <td>

        <time datetime="{{ $post->publish_date->toISO8601String() }}">

            {{ $post->publish_date->toFormattedDateString() }}

        </time>

    </td>

    <td></td>

</tr>

I used the html time tag and I plan to use it throughout the app for any dates and times that need conversion.

With this displaying now it’s time to setup the JavaScript to handle the actual conversion.

JavaScript Local Timezone

So far I haven’t needed to use any JavaScript and before I can, all the infrastructure needs to be setup. I’m going to skip covering that part as I only did enough to really get going with Gulp. If you are interested in this then you can see the gulpfile.js.

To handle timezones Moment.js is great as it includes everything you typically need. I pulled it in along with jQuery via Bower then in the Gulp task just concat these two with my own main.js file.

With this all setup, to put the dates into the proper timezone all I need is the following:

$(function() {

    // Time conversion

    $("time").each(function(){

        var $el = $(this);

        var date = $el.attr("datetime");

        $el.text(moment(date).format("lll"));

    });

});

If you are not aware all this does is find all the “time” elements, loop them, convert to moment() and finally displays the parsed version. If you remember from the view I set the datetime attribute to “$post->publish_date->toISO8601String()” which sets a timezone like this: “2014-09-17T01:28:09+0000”. With this format it tells moment it’s in UTC so it knows to convert.

If this app was going to be bigger I would also add a data-* attribute for the format so this could be used for full dates or times. For example something like this:

<time datetime="{{ $post->publish_date->toISO8601String() }}" data-format="time">
<time datetime="{{ $post->publish_date->toISO8601String() }}" data-format="datetime">

Then:

// Time conversion
$("time").each(function(){
    var $el = $(this);
    var format = ($(this).data("type") == 'time') ? 'LT' : 'lll';

    var date = $el.attr("datetime");

    $el.text(moment(date).format(format));

});

Wrap Up

You can see all the code for this in the week 4 branch. Unfortunately, I pushed up a lot in a single commit so it might be a little hard to follow.

If you enjoyed this post be sure and check out the rest of this series:

As always if you have any questions or advice post a comment. I’m happy to listen and glad to respond.

0 Replies to “Rebuilding Wardrobe: Week 4”

  1. I was reminded on twitter that Laravel Blade also has the @each attribute.

    That would allow the grid view to be simplified into:

    Title
    Status
    Published

    @each('wardrobe::admin.post.item', $posts, 'post')

    I’ll adjust this in the code later.

  2. Hi Eric,

    Was reading about the parts on graphs needing timezone support. I’m using Rickshaw graphs (http://code.shutterstock.com/rickshaw/), and if you pass in UTC, with a little tweak it will automatically convert it using the browser’s settings.

    I chose Rickshaw because it is excellent at time-based graphs and saves a lot of work. I’ll be happy to share the necessary tweak, if you end up using it. 🙂

    1. Nice, I haven’t heard of that library.

      One issue that is stuck in my head is lets say I want a line graph of the number of posts each day for the past week. So that first day could be weird because of the overlap between the user timezone and UTC. In effect forcing it to span 8 days instead of the 7.

      Does that make sense?

      1. Was thinking about this some more, and probably the best approach would be to pass in the timezone to the MySQL query and use CONVERT_TZ() then group by weekday. But you’re right, it sou;don’t be as easy a s using the browser settings.

  3. I worry about the extensibility of a setup like this. Often plugins will add custom columns to your post list – for example WP-SEO adds SEO relevant columns. Perhaps I want to see the featured image thumbnail assigned to my posts and so on.

  4. From the article – “There are a few ways of handling the conversion to local time and of course Carbon, the date time parser included in Laravel, has support for it. However, then I have to allow users select their timezone, and if they are in a part of the world that observes daylight savings they will need to edit the setting twice a year.” – Sorry, you don’t have to change the setting twice a year for daylight savings, you’ve missed the point of the timezone library within PHP…

    New records should go into the database as UTC (easy)

    If a user stores “Europe/London” as their preference in the database then whenever Carbon converts a UTC value it will interpret the correct offset for daylight savings for that time of year as they are stored as “transitions” internally within PHP – http://php.net/manual/en/datetimezone.gettransitions.php

    It’s simply a matter of making a UTC Carbon instance from the database and then setting it to the user’s timezone (either the author or logged in user as you see fit).

    You can also do the reverse – set a user picked date as a localised Carbon instance, change the timezone to UTC (which will add/remove the right number of hours) and then you have a UTC value.

    Hope that clarifies it a bit 🙂

    1. I think I wasn’t clear on this in my post, but I wanted to go the JS route so I wouldn’t need the user to select their time zone. That way it’s less settings they need to worry about. It’s strictly for an easier user experience.

      1. That’s fine, I understand that. It was just that the paragraph gave the impression you have to change your timezone every time there’s a daylight savings change if you go down the server-side route, which is not the case.

Leave a Reply