This lesson will be about adding a timezone field to the User
Model and Registration process:
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.
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',];
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:
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>
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:
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
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