August 25, 2015 15:45

Here is a simple way to manage storing and displaying dates and times in Laravel in the user's timezone.

First of all, we are going to store all dates and times in UTC. In Laravel, make sure that the timezone is set to UTC in config/app.php

Second, you'll need a way to let users specify their timezone. I'll leave that up to the reader for now. You can either do this through a settings page, or auto-detect it with JavaScript. I prefer to store timezones as an hourly offset from UTC, e.g. -5 for Central time, -4 for Eastern.

Finally, we'll have a base model that all of our Eloquent models extend. If you already have one you can just add this function to it. Save this into app/Model.php

namespace App;

use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model as Eloquent;

class Model extends Eloquent
{
    /**
     * Display timestamps in user's timezone
     */
    protected function asDateTime($value)
    {
        if ($value instanceof Carbon) {
            return $value;
        }

        $me = \Auth::user();
        $tz = $me->timezone ?: \Cookie::get('tz');

        $value = parent::asDateTime($value);

        return $value->addHours($tz);
    }
}

Make sure to update all of your existing models to extend the new model class instead of the Eloquent model.

Eloquent has an asDateTime function which converts MySQL timestamps to Carbon entities. It calls this function both when setting and getting timestamps. So first we check if the $value is already a Carbon instance - if so, we return it without any adjustments, because when setting a timestamp we do NOT want to apply the timezone adjustment. **

The next two lines are simply retrieving the user's timezone from one of two sources, an attribute or a cookie. If you are setting the cookie through JavaScript, make sure it is added as an exception to the EncryptCookies middleware. Again, this value in my case is an hour offset e.g. -5. Finally we get the Carbon instance that Eloquent returns and add the hour offset.

Now all dates and times will be stored in UTC but displayed in the user's timezone!

Two caveats that I've discovered so far. First, when setting timestamps, to make sure to set them as Carbon instances. (You don't need to worry about the created_at, updated_at, or deleted_at timestamps, just any others that you use). For example, do this:

$post->scheduled_at = Carbon::now();

Not these:

$post->scheduled_at = \DB::raw('now');
$post->scheduled_at = date('Y-m-d H:i:s');

Otherwise your timestamps will not be stored in UTC.

The more annoying caveat is that for certain Carbon methods, you need to undo the hour adjustment in order for them to work correctly. The two that I've discovered so far are age and diffForHumans, because both of those methods compare the given timestamp to the current timestamp (in UTC). So if your user's timezone is -5 hours, and you ask for $date->diffForHumans() on a brand new timestamp, it'll falsely report that the date is 5 hours ago. I personally added a helper method undo_tz to wrap around those methods. For example:

echo undo_tz($post->created_at)->diffForHumans();

And the method:

function undo_tz(Carbon $date)
{
    $me = \Auth::user();
    $tz = $me->timezone ?: \Cookie::get('tz');
    return $date->subHours($tz);
}

Laravel, PHP

August 22, 2015 21:28

I've recently discovered how great collections are in Laravel.

No more array_summing or looping. Collections are easy to extend. First add this method to your model:

public function newCollection(array $models = [])
{
    return new TransactionCollection($models);
}

Then create the TransactionCollection class and extend it as you wish. For example:

class TransactionCollection extends Illuminate\Database\Eloquent\Collection
{
    public function total()
    {
        return $this->sum('amount');
    }
}

Now you can easily display the total of all transactions with $transactions->total()

Laravel, PHP

August 9, 2014 06:08

If you know me, you know that I'm a little OCD. It was killing me that my gmail and iPhone contacts were out of sync. So here's what I did to fix that:

  1. First turn on "Contacts" for iCloud in my phone settings
  2. Now all of my phone contacts are on iCloud
  3. Log in to icloud.com, and export all contacts as vCard file
  4. Go to Gmail and import the vCard file
  5. This creates a new group, "Imported on [Date]" with all of the phone contacts
  6. Find and merge duplicates
  7. Go through other groups and remove contacts from the "Imported on [Date]" group
  8. Now the only contacts left in the "Imported on [Date]" group are the new ones that were on the phone and not in gmail
  9. Organize and clean up those contacts, and delete the "Imported on [Date]" group
  10. Now Gmail has ALL of my contacts, organized in the way I want
  11. Go to the iPhone settings > Mail, Contacts, Calendars. Add a new Google account and enable only "Contacts" for this account
  12. Go to Mail, Contacts, Calendars, and select this Google account as the Default account for Contacts.
  13. Disable "Contacts" for iCloud.
  14. Now any new contacts you create on your phone will be automatically copied to Gmail
  15. The last step is to ensure that any new contacts created in Gmail will also be automatically copied to your phone. In Mail, Contacts, Calendar > Fetch New Data, scroll to the bottom and select "Every 15 Minutes" for Fetch.
  16. Don't forget to backup your phone so that you don't have to go back and redo all of this.

All done! Now my existing contacts are synced on both accounts, and any create, update, or delete actions are synced both ways.

Other

August 1, 2013 17:19

This weekend I took a trip to Tadoussac, Québec.

I arrived at night, and knew that this would be a great opportunity to see the sky without light pollution. I didn't want to wander too far from the road, since I had no idea where I was or what the terrain was like. But wow, even so, the sky was breathtaking! I've never seen so many stars in my life! I also saw two bats hunting moths, making high pitched squeaks. That was when I really thought "this is what I've been missing out on for all these years without traveling."

The next morning I was shocked to discover that the great black expanse I had been looking at the night before was not a forest, but the bay! I can't tell you how many times I had my mind blown on this trip.

I went down to the bay and got suited up for a boat ride. It was a little cloudy, grey, and drizzling, so everybody donned rain suits and split up into three Zodiac boats. The Zodiacs are smallish, fit 12-24 people, and are pretty low to the water. We then went out to the bay.

I was afraid we'd only catch a few glimpses of whales, but from a distance we could already see a lot of them spraying, and I knew immediately that I was wrong. We saw a few minkes, 3 fin whales (the second largest whale) who came up frequently but only showed us their backs. The most incredible were the humpbacks, who showed us their tails a few times. It was just incredible how massive and yet graceful and beautiful they were. A couple seals floated around, and on the way to the fjord we saw a pod of belugas, two of which got very close to the boat.

The water was mostly very choppy, but in one area of the bay it was completely still. Not a single wave, and you could see the ripples from the rain drops. It was so freaky and cool.

In the fjord we stopped close to some rocks where two seals were hanging out. They were really cute. The rocks were also covered in kelp and algae. Then the guide showed us a waterfall pouring from the top of massive 300+ foot cliffs. He said it swells after the winter when the ice melts, but it was impressive nonetheless. I think I lucked out getting on this boat because the captain seemed to give us a longer and more interesting tour than the others.

When we got back, I got lunch, and then looked at the tourist map. I noticed that instead of taking the street, I could take a "path" through the forest. On the map it was a straight line so I assumed it'd be a nice nature walk, but nothing particularly interesting.

I was so wrong. It turned out to be a mountain trail, with lots of wooden staircases, twists, and turns, and it took me to the top of a mountain, probably half the height of Mont Royal, but with an even better view. The view of the bay and lake was breathtaking. And since the map gave absolutely no indication of how nice this trail was, I only saw two other people.

I took a panorama of the bay, but then noticed some storm clouds, and it started to thunder. It was pretty terrifying being on top of a mountain with nothing but my camera, feeling the wind pick up and seeing the clouds approach very quickly. But I made it home before it started to rain.

I was planning to stay another night, but the forecast said it'd be rainy, so I decided to take the bus that had just arrived. On the way home, close to Québec City, it was drizzling on the left over the river, but the sun was shining on the right. And there was a beeeautiful rainbow stretching across the river. I could see both ends of it. A perfect end to this life changing trip.

News, Travel

July 16, 2013 04:55

On my connecting flight from Toronto, one of my favorite artists of all time Stevie Wonder was sitting in the row in front of me across the aisle. There was a period in college where his songs were literally the only music I listened to.

I learned that he was the closing act for the Festival d'été de Québec the following night. I would have paid the full $76 price for a 2-week pass just to see him but my kind hosts found me a ticket and I got to see him perform live for free! His voice is incredible and seems as strong as it was in the decades-old recordings I have. My favorite was "I Just Called to Say I Love You", he also played "My Cherie Amour", "Superstition", and many others of course.

Before the concert, my hosts also took me for a little walking tour of Vieux-Québec. C'est très charmant! It's so beautiful and clean, and feels very European. The view of the Fleuve Saint-Laurent was breath-taking, especially at sunset. I really love this city; it may have replaced Boston as my favorite. I'll definitely come back again.

For now I am stuck in Montréal, which so far reminds me very much of Chicago... dirty and crowded. I'll give it a chance though.

Travel