Courses

Laravel User Timezones Project: Convert, Display, Send Notifications

User Timezone in Registration

This lesson will be about adding a timezone field to the User Model and Registration process:


Setting Laravel Defaults

Laravel has a default timezone set in config/app.php:

'timezone' => 'UTC',

We have to ensure it's still set to UTC as we will be mainly working with UTC dates and times. The idea is that our Database data is always in UTC and we can easily convert it to any other timezone.


Adding Timezone to Users Table

To start with this, we need a new migration:

php artisan make:migration add_timezone_to_users_table

Migration

public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->string('timezone')->default('UTC');
});
}

This simply adds a new field called timezone to our users table with a default value of UTC.

Then we need to add this field to our User Model:

app/Models/User.php

// ...
 
protected $fillable = [
// ...
'timezone',
];

Adding Timezone to User Registration

Now that our database supports timezone saving, we need a way to set it. We will do this during registration, and we will use the timezone_identifiers_list() function to get a list of all timezones, and a custom function to attempt to guess user's timezone based on IP address.

Here's the updated Controller of Laravel Breeze.

app/Http/Controllers/Auth/RegisteredUserController.php

 
class RegisteredUserController extends Controller
{
public function create(): View
{
$timezones = timezone_identifiers_list();
 
return view('auth.register', compact('timezones'));
}
 
public function store(Request $request): RedirectResponse
{
$request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:'.User::class],
'password' => ['required', 'confirmed', Rules\Password::defaults()],
'timezone' => ['required', Rule::in(array_flip(timezone_identifiers_list()))]
]);
 
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
'timezone' => timezone_identifiers_list()[$request->input('timezone', 'UTC')]
]);
 
event(new Registered($user));
 
Auth::login($user);
 
return redirect(RouteServiceProvider::HOME);
}
}

With these modifications, we've added:

  • List of timezones to the registration view
  • Validation for the timezone field
  • Saving the timezone to the database

Next, we have to display the field in the registration form:

resources/views/auth/register.blade.php

{{-- ... --}}
 
<!-- Confirm Password -->
<div class="mt-4">
 
{{-- ... --}}
 
</div>
 
<!-- Timezone Select -->
<div class="mt-4">
<x-input-label for="timezone" :value="__('Timezone')"/>
<x-select-input id="timezone" class="block mt-1 w-full" name="timezone" :options="$timezones"
:selected="old('timezone')"
required/>
<x-input-error :messages="$errors->get('timezone')" class="mt-2"/>
</div>
 
{{-- ... --}}

And since we've added the select-input component, we need to create it.

php artisan make:component SelectInput --view

resources/views/components/select-input.blade.php

@props(['disabled' => false, 'options' => [], 'selected' => null, 'default' => null])
 
<select {{ $disabled ? 'disabled' : '' }} {!! $attributes->merge(['class' => 'border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm']) !!}>
<option value="">Select</option>
@foreach($options as $key => $value)
<option value="{{ $key }}" @selected($selected === $key || $default === $value)>{{ $value }}</option>
@endforeach
</select>

Fixing Tests

Lastly, we should update our Tests from Laravel Breeze to make sure they still pass:

tests/Feature/Auth/RegistrationTest.php

// ...
public function test_new_users_can_register(): void
{
$response = $this->post('/register', [
'name' => 'Test User',
'email' => 'test@example.com',
'password' => 'password',
'password_confirmation' => 'password',
// Take the first timezone from the list
'timezone' => array_keys(timezone_identifiers_list())[0]
]);
 
$this->assertAuthenticated();
$response->assertRedirect(RouteServiceProvider::HOME);
}
// ...

That's it! Loading the registration form we should see that the timezone field is now available:

And it's saved in the database:

Next, we'll add automatic pre-selection of the timezone, you can choose from two options:

  • Calling remote 3rd-party API
  • Using local JavaScript with no external calls

Automatically Filling Timezone Using 3rd-Party API

To add 3rd party API, we have to modify our User Model:

app/Models/User.php

// ...
 
public static function guessUserTimezoneUsingAPI($ip)
{
$ip = Http::get('https://ipecho.net/'. $ip .'/json');
if ($ip->json('timezone')) {
return $ip->json('timezone');
}
return null;
}

Then we can use it in our Controller:

app/Http/Controllers/Auth/RegisteredUserController.php

use Illuminate\Http\Request;
 
// ...
 
public function create(Request $request): View
{
$timezones = timezone_identifiers_list();
$guessedTimezone = User::guessUserTimezoneUsingAPI($request->ip());
 
return view('auth.register', compact('timezones', 'guessedTimezone'));
}
 
And lastly, we need to modify our View to pre-select the timezone:
 
**resources/views/auth/register.blade.php**
```blade
<x-select-input id="timezone" class="block mt-1 w-full" name="timezone" :options="$timezones"
:selected="old('timezone')"
:default="$guessedTimezone" required/>

That's it! Now, when loading the page, we'll see that the timezone is set to our current one:

Code in Repository


Automatically Filling Timezone Using JavaScript

Another option to guess the user's timezone is to use JavaScript INTL system. To do that we'll modify our Blade View:

resources/views/auth/register.blade.php

{{-- ... --}}
 
</form>
 
<script>
setSelectedValue(document.getElementById('timezone'), Intl.DateTimeFormat().resolvedOptions().timeZone);
 
function setSelectedValue(selectObj, valueToSet) {
for (var i = 0; i < selectObj.options.length; i++) {
if (selectObj.options[i].text == valueToSet) {
selectObj.options[i].selected = true;
return;
}
}
}
</script>
</x-guest-layout>

And that's it! When loading the page, we'll see that the timezone is set to our current one:

We didn't have to use any 3rd party API or a package to do this. Another option is to use Moment JS.

Code in Repository

avatar

you seame to be missing somthing Like what to put in the bookings Database Table ! I can find it in the repository but I dont see where you tell us how to set it up.

avatar

We are not focusing on the bookings itself here (especially in this lesson). But in any case, if you look at next lesson - you will see the two fields we use and a screenshot from database schema. It was too simple to add here :)

avatar

'https://ipecho.net/'. $ip .'/json' - not available anymore :(