Laravel's default timestamps are stored in UTC, and if you working with an application in which users who are in a different timezone, they might see a timestamp in the application which does not match the timezone that they are in.

To fix this, you need to have the user's timezone stored in the database and also convert the user-related models into the appropriate timezone from UTC.

We'll cover this tutorial in two parts.

  1. Retrieving and Storing User's TimeZone.
  2. Converting UTC timestamps to User's TimeZone.

Let's start with the first part.

#1 Retrieving And Storing User's TimeZone.

We'll be using a javascript plugin in the background to get the user's timezone, and we can store the timezone in the user's table either at the time of Registration or Sign-In. We'll cover both the ways

To store the user's timezone in the database, we'll need an additional column in the user's table, Let's first add that.

Add column "timezone" in users table database

We have to add a new column "timezone "in users table. for this add a new field in

database/migrations/2014_10_12_000000_create_users_table.php file using the following code

      public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('name');
            $table->string('timezone');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

Run the following artisan command, to drop all the tables and rerun the migrations

php artisan migrate:fresh

 

Store timezone during Registration

Let's now get to the part where we will retrieve the user's timezone during the registration process.

Include the following script in at the end of  resources/views/auth/register.blade.php


<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.20.1/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.14/moment-timezone-with-data-2012-2022.min.js"></script>
<script>
        $( document ).ready(function() {
            $('#timezone').val(moment.tz.guess())
        });        
    </script>

With this script, we are using moment-timezone plugin to retrieve the user's timezone and then we are putting it in a hidden field inside the registration form.

Add timezone as a hidden field in the registration form

 <form method="POST" action="{{ route('register') }}">
                        @csrf
                        <input type="hidden" name="timezone" id="timezone">

Now when you open the registration page (/register) and click on inspect.

You will find the user's timezone like this

Here as we can see timezone as "Asia/Calcutta".

 

Now next part is to get this timezone after registration and store it in the database

Get timezone from post request on registration

We are making use of the registered method, which gets called every time a new user is registered. For this, we have to add the following line of codes in app/Http/Controllers/Auth/RegisterController.php file


    protected function registered(Request $request, $user)
    {
        // set timezone
        if ($timezone = $this->getTimezone($request))
         {                    
            $user->timezone = $timezone;
            $user->save();
        }
    }

    protected function getTimezone(Request $request)
    {
        if ($timezone = $request->get('timezone')) {   
            return $timezone;
        }
    }

we are good to go. when the user will click on register with all required fields, the timezone will be stored in the database.

 

Store timezone during Sign-In

The same process can be done during login as well, for this, you need to include the same set of scripts at the end of the login.blade.php and then include a hidden field in the login form.

   
<form method="POST" action="{{ route('login') }}">
   @csrf
<input type="hidden" name="timezone" id="timezone">

once you have the timezone in the login form, you can now make use of the Authenticated method in the LoginController to store the timezone in the user's table in database.


 public function authenticated(Request $request, $user)
    {

        if($user->timezone == '' || $user->timezone == null )
        {
            // set timezone
            $timezone =  $this->getTimezone($request);
            $user->timezone = $timezone;
            $user->save();
        }
    }
 protected function getTimezone(Request $request)
    {
        if ($timezone = $request->get('timezone')) {
            return $timezone;
        }
    }

Now when the user will log in, if he does not already have a timezone in the database, it will be added.

Now we are done with looking at ways to retrieve and store the user's timezone, We will now see how we can make use of this timezone to convert the UTC timestamps to user-specific timezone.

#2 Convert UTC to user's timezone:

By default Laravel's timestamps which includes created_at , updated_at fields in the Model tables are stored in UTC timezone and this is specified in the file config/app.php

'timezone' => 'UTC',

We now have to convert is explicitly in the user's timezone using the stored "timezone" field.

We will make use of Laravel's mutator for this task so that every time a field that contains the timestamp is demanded from the database first it will go through the mutator for conversion.

For example, I have converted created_at field every time before showing to user like following:


public function getCreatedAtAttribute($value) 
     {      
        $timezone = optional(auth()->user())->timezone ?? config('app.timezone');
        return Carbon::parse($value)->timezone($timezone);
     }

If the user's timezone is present in database it will be converted into that timezone or it will be in UTC as the app's default timezone is "UTC".

Bonus

We have covered two ways in which we are using javascript to detect user's timezone and then we are storing it in the database, what if you want your user's to manually select a timezone from the list of timezone, or if he wants to update the timezone.

Allow Users to Select / Change TimeZone :

There is one more way, where the user can explicitly select the valid timezone. The timezone list provided by PHP.
For this, we have to provide a drop-down list to the user. where the user will select the timezone and that timezone will be stored in the users table in the database.

Add timezone list code in blade file :

<div class="form-group">
<label>Timezone:<span class="red">&nbsp;*</span></label>
<select name="timezone" id="timezone" class="form-control">
@foreach (timezone_identifiers_list() as $timezone)
        <option value="{{ $timezone }}"{{ $timezone == old('timezone') ? ' selected' : '' }}>{{ $timezone }}</option>
@endforeach
</select>
</div>

we can get this timezone field from "request" to store in the database and for later to convert from UTC to user's timezone.


$user= Auth::user();
$user->timezone = $request->timezone;
$user->save();

That's all about working with User Timezone in Laravel.

Comments