Courses

Laravel User Timezones Project: Convert, Display, Send Notifications

Project Setup & Bookings CRUD

For this project, we will be creating a simple process for bookings: meaning booking an appointment, a calendar event, or whatever else. And our goal with the timezones is to save/process the booking data according to the user's timezone.

Since this course is not about creating CRUD, I will just quickly run through it as a "setup".

First, we install Laravel with Breeze Starter kit:

laravel new timezones
cd timezones
composer require laravel/breeze --dev
php artisan breeze:install blade

Then, we generate the Model/Migration/Controller for bookings:

php artisan make:model Booking -mcrR

And then fill in all the CRUD for listing/creating/editing your own bookings for each user.

Full code is available in (GitHub Repository)[https://github.com/LaravelDaily/laravel-timezones-course]

The main files for Bookings CRUD:

  • app/Models/Booking.php - Our main Booking Model
  • app/Http/Controllers/BookingController.php - Our main Controller with all CRUD methods
  • app/Http/Requests/StoreBookingRequest.php - Store Request
  • app/Http/Requests/UpdateBookingRequest.php - Update Request
  • resources/views/bookings - Blade Views for index, create, and edit

So you would understand better, here's the Controller code.

app/Http/Controllers/BookingController.php:

use App\Http\Requests\StoreBookingRequest;
use App\Http\Requests\UpdateBookingRequest;
use App\Models\Booking;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
 
class BookingController extends Controller
{
public function index()
{
$bookings = Booking::query()
->with(['user'])
->get();
 
return view('bookings.index', compact('bookings'));
}
 
public function create()
{
return view('bookings.create');
}
 
public function store(StoreBookingRequest $request): RedirectResponse
{
$request->user()->bookings()->create($request->validated());
 
return redirect()->route('booking.index');
}
 
public function edit(Booking $booking)
{
return view('bookings.edit', compact('booking'));
}
 
public function update(UpdateBookingRequest $request, Booking $booking): RedirectResponse
{
$booking->update($request->validated());
 
return redirect()->route('booking.index');
}
 
public function destroy(Request $request, Booking $booking): RedirectResponse
{
abort_unless($booking->user_id === $request->user()->id, 404);
 
$booking->delete();
 
return redirect()->route('booking.index');
}
}

That's it! This will be our starting point for the whole course. From here, we can add timezone-related functionality.

avatar

In Booking migrations, as I check, we are using datetTime data type for start and end column, Schema::create('bookings', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->constrained(); $table->dateTime('start'); $table->dateTime('end'); $table->timestamps(); }); why don't we use timestamp data type, from what I know, timestamp data type will also store the offset value from UTC, so it will be easy to convert one timezone to another timezone.

avatar

This is because we are strictly working with UTC all the time and we want Carbon to handle the offset, not the database itself. If we start storing in users timezone on database level - then we will run into an issue where we will always add the timezone to already timezone specific date. For example:

User input 2020-02-01 03:00:00 - UTC + 3 Database saves 2020-02-01 00:00:00 - UTC

But with timestamps we will save 2020-02-01 03:00:00 - UTC + 3 to the database and on display we will add another +3 (hours), making it 2020-02-01 06:00:00 - UTC + 3 which is now incorrect :)

It's better to always work with UTC timezones on the database level, then do conversions on the system itself, once you know exactly what timezone you need to convert to. Just imagine if you allow 10 users see their booking times - they can't be in local timezone. They must be in their own individual one