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.
- Retrieving and Storing User's TimeZone.
- 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"> *</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.