How to Manage User Timezone in Laravel?

By Hardik Savani December 3, 2024 Category : Laravel

In this post, I will show you how to manage dynamic user timezone in laravel application.

In this example, we will set up authentication using Laravel UI and add a "timezone" column to the users table so each user can have their own timezone. We will also create a posts table with columns for "title" and "body." A profile page will be added where users can update their timezone, and a posts page will show a list of posts and allow users to create new ones. The "created at" date and time for posts will be displayed according to the user's timezone. To manage timezones dynamically, we will create a trait. Let's get started!.

Step 1: Install Laravel 11

This step is not required; however, if you have not created the Laravel app, then you may go ahead and execute the below command:

composer create-project laravel/laravel example-app

Step 2: Create Auth using Scaffold

Now, in this step, we will create an auth scaffold command to generate login, register, and dashboard functionalities. So, run the following commands:

Laravel 11 UI Package:

composer require laravel/ui 

Generate Auth:

php artisan ui bootstrap --auth 

npm install

npm run build

Step 3: Create Migrations

Here, we will create posts and add timezone column to users table. so, let's run the following command:

php artisan make:migration add_timezone_column_users_table

php artisan make:migration create_posts_table

now, let's update the following migrations:

database/migrations/2024_06_18_140624_add_timezone_column_users_table.php

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('timezone')->nullable();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        //
    }
};

database/migrations/2024_06_18_140906_create_posts_table.php

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->text('body');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('posts');
    }
};

now, Let's run the migration command:

php artisan migrate

Step 4: Create HandlesTimezones Trait

Here, you need to create "Traits" folder in app and create HandlesTimezones.php file. so, let's do it:

app/Traits/HandlesTimezones.php

<?php

namespace App\Traits;

use Auth;
use Carbon\Carbon;

trait HandlesTimezones{

    /**
     * Write code on Method
     *
     * @return response()
     */
    public function getCreatedAtAttribute($value)
    {
        return $this->convertTimeZoneToUserTimezone($value);
    }

    /**
     * Write code on Method
     *
     * @return response()
     */
    public function getUpdatedAtAttribute($value)
    {
        return $this->convertTimeZoneToUserTimezone($value);
    }

    /**
     * Write code on Method
     *
     * @return response()
     */
    protected function convertTimeZoneToUserTimezone($value){
        $timezone = Auth::check() & Auth::user()->timezone ? Auth::user()->timezone : config("app.timezone");

        return Carbon::parse($value)->timezone($timezone)->format("Y-m-d H:i:s");
    }

}

Step 5: Create and Update Models

Here, we will create Post model using the following command. we also need to update User model here. we will write relationship and some model function for like and dislike.

php artisan make:model Post

now, update the model file with hasMany() relationship:

app/Models/Post.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use App\Traits\HandlesTimezones;

class Post extends Model
{   
    use HandlesTimezones;
    
    protected $fillable = ["title", "body"];
}

app/Models/User.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use HasFactory, Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name',
        'email',
        'password',
        'timezone'
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * Get the attributes that should be cast.
     *
     * @return array
     */
    protected function casts(): array
    {
        return [
            'email_verified_at' => 'datetime',
            'password' => 'hashed',
        ];
    }
}

Step 6: Create Routes

In this step, we need to create some routes for profile and posts. So open your "routes/web.php" file and add the following route.

routes/web.php

<?php

use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    return view('welcome');
});

Auth::routes();

Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home');

Route::get('/posts', [App\Http\Controllers\PostController::class, 'index']);
Route::post('/posts', [App\Http\Controllers\PostController::class, 'store'])->name("posts.store");

Route::get('/profile', [App\Http\Controllers\ProfileController::class, 'profile']);
Route::post('/profile', [App\Http\Controllers\ProfileController::class, 'profileUpdate'])->name("profile.update");

Step 7: Create Controller

Here, we will create new PostController and ProfileController with methods. So let's put the code below.

app/Http/Controllers/PostController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Post;

class PostController extends Controller
{
    /**
     * Write code on Method
     *
     * @return response()
     */
    public function index(Request $request)
    {
        $posts = Post::latest()->get();
        return view("posts", compact('posts'));
    }

    /**
     * Write code on Method
     *
     * @return response()
     */
    public function store(Request $request)
    {
        Post::create([
            "title" => $request->title,
            "body" => $request->body,
        ]);

        return back()->with('success', "post created.");
    }
}

app/Http/Controllers/ProfileController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class ProfileController extends Controller
{
    /**
     * Write code on Method
     *
     * @return response()
     */
    public function profile(Request $request)
    {
         $timezones = [
            'Asia/Kolkata' => 'Asia/Kolkata',
            'America/New_York' => 'America/New_York',
            'Europe/London' => 'Europe/London',
            'Asia/Tokyo' => 'Asia/Tokyo',
            'Australia/Sydney' => 'Australia/Sydney',
        ];
        return view("profile", compact('timezones'));
    }

    /**
     * Write code on Method
     *
     * @return response()
     */
    public function profileUpdate(Request $request)
    {
        $user = auth()->user();

        $user->name = $request->name;
        $user->email = $request->email;
        $user->timezone = $request->timezone;
        $user->save();

        return back()->with('success', "profile updated.");
    }
}

Step 8: Create and Update Blade Files

In this step, we will update app.blade.php file and create posts, profile blade file. so, let's update it.

resources/views/layouts/app.blade.php

<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">

    <title>{{ config('app.name', 'Laravel') }}</title>

    <!-- Fonts -->
    <link rel="dns-prefetch" href="//fonts.bunny.net">
    <link href="https://fonts.bunny.net/css?family=Nunito" rel="stylesheet">

    <!-- Scripts -->
    @vite(['resources/sass/app.scss', 'resources/js/app.js'])
    
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" />
    @yield('script')
</head>
<body>
    <div id="app">
        <nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm">
            <div class="container">
                <a class="navbar-brand" href="{{ url('/') }}">
                    Laravel Send Realtime Notification using Reverb
                </a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
                    <span class="navbar-toggler-icon"></span>
                </button>

                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <!-- Left Side Of Navbar -->
                    <ul class="navbar-nav me-auto">

                    </ul>

                    <!-- Right Side Of Navbar -->
                    <ul class="navbar-nav ms-auto">
                        <!-- Authentication Links -->
                        @guest
                            @if (Route::has('login'))
                                <li class="nav-item">
                                    <a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
                                </li>
                            @endif

                            @if (Route::has('register'))
                                <li class="nav-item">
                                    <a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a>
                                </li>
                            @endif
                        @else
                            <li class="nav-item">
                                <a class="nav-link" href="/posts">Posts</a>
                            </li>
                            <li class="nav-item">
                                <a class="nav-link" href="/profile">Profile</a>
                            </li>
                            <li class="nav-item dropdown">
                                <a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
                                    {{ Auth::user()->name }}
                                </a>

                                <div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
                                    <a class="dropdown-item" href="{{ route('logout') }}"
                                       onclick="event.preventDefault();
                                                     document.getElementById('logout-form').submit();">
                                        {{ __('Logout') }}
                                    </a>

                                    <form id="logout-form" action="{{ route('logout') }}" method="POST" class="d-none">
                                        @csrf
                                    </form>
                                </div>
                            </li>
                        @endguest
                    </ul>
                </div>
            </div>
        </nav>

        <main class="py-4">
            @yield('content')
        </main>
    </div>
</body>

</html>

resources/views/posts.blade.php

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-12">
            <div class="card">
                <div class="card-header">{{ __('Posts') }}</div>

                <div class="card-body">

                    <form method="POST" action="{{ route('posts.store') }}">
                        @csrf
                    
                        @session('success')
                            <div class="alert alert-success">
                                {{ $value }}
                            </div>
                        @endsession
                    
                        <div class="mt-2">
                            <label>Title:</label>
                            <input type="text" name="title" class="form-control">
                            @error('title')
                                <span class="text-danger">{{ $message }}</span>
                            @enderror
                        </div>

                        <div class="mt-2">
                            <label>Body:</label>
                            <textarea class="form-control" name="body"></textarea>
                            @error('body')
                                <span class="text-danger">{{ $message }}</span>
                            @enderror
                        </div>
                    
                        <div class="mt-2">
                            <button class="btn btn-success">Submit</button>
                        </div>
                    </form>

                    <table class="table table-striped table-bordered mt-3">
                        <thead>
                            <tr>
                                <th>ID</th>
                                <th>Title</th>
                                <th>Body</th>
                                <th>Creation Date</th>
                            </tr>
                        </thead>
                        <tbody>
                            @foreach($posts as $post)
                            <tr>
                                <td>{{ $post->id }}</td>
                                <td>{{ $post->title }}</td>
                                <td>{{ $post->body }}</td>
                                <td>{{ $post->created_at }}</td>
                            </tr>
                            @endforeach
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

resources/views/profile.blade.php

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-12">
            <div class="card">
                <div class="card-header">{{ __('Profile') }}</div>

                <div class="card-body">

                    <form method="POST" action="{{ route('profile.update') }}">
                        @csrf
                    
                        @session('success')
                            <div class="alert alert-success">
                                {{ $value }}
                            </div>
                        @endsession
                    
                        <div class="mt-2">
                            <label>Name:</label>
                            <input type="text" name="name" class="form-control" value="{{ auth()->user()->name }}">
                            @error('name')
                                <span class="text-danger">{{ $message }}</span>
                            @enderror
                        </div>

                        <div class="mt-2">
                            <label>Email:</label>
                            <input type="email" name="email" class="form-control" value="{{ auth()->user()->email }}">
                            @error('email')
                                <span class="text-danger">{{ $message }}</span>
                            @enderror
                        </div>

                        <div class="mt-2">
                            <label>Timezone:</label>
                            <select class="form-control" name="timezone">
                                @foreach($timezones as $key => $label)
                                    <option value="{{ $key }}" {{ auth()->user()->timezone == $key ? 'selected' : '' }}>
                                        {{ $label }}
                                    </option>
                                @endforeach
                            </select>
                            @error('email')
                                <span class="text-danger">{{ $message }}</span>
                            @enderror
                        </div>
                    
                        <div class="mt-2">
                            <button class="btn btn-success">Submit</button>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

Run Laravel App:

All the required steps have been done, now you have to type the given below command and hit enter to run the Laravel app:

php artisan serve

Now, Go to your web browser, type the given URL and view the app output:

http://localhost:8000/

Now, you can register new user and go to profile page.

select timezone and see the timezone from posts list page.

[VIDEO]

I hope it can help you...

Tags :
Shares