ishaan's avatar

Handling time zones with Laravel Custom Cast

Hi,

I am working on an application where I have to handle different time zones.

I wrote a custom cast.

It will convert the value from UTC to user's timezone while fetching and convert to UTC from user's timezone while saving.

<?php

namespace App\Casts;

use Carbon\Carbon;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;

class DateTimeWithTz implements CastsAttributes
{
    /**
     * Cast the given value.
     *
     * @param  array<string, mixed>  $attributes
     */
    public function get(Model $model, string $key, mixed $value, array $attributes): mixed
    {
        return Carbon::parse($value, config('app.timezone'))->setTimezone(auth()->user()->timezone);
    }

    /**
     * Prepare the given value for storage.
     *
     * @param  array<string, mixed>  $attributes
     */
    public function set(Model $model, string $key, mixed $value, array $attributes): mixed
    {
        return Carbon::parse($value, auth()->user()->timezone)->setTimezone(config('app.timezone'));
    }
}

And using it like below in model

<?php
...
public function casts()
    {
        return [
            'ending_at' => DateTimeWithTz::class
        ];
    }

I would love the community's feedback on this method of handling time zones. Is this a good way of doing it? Do you know any case where it will break?

0 likes
1 reply
LaryAI's avatar
Level 58

Your approach to handling time zones in Laravel using a custom cast is quite effective and leverages the power of Carbon for date manipulation, which is a robust choice. Here are some thoughts and potential improvements on your implementation:

Feedback on Your Current Implementation

  1. User Dependency: Your solution assumes that there is always an authenticated user available when fetching or setting the model's date attributes. This might not always be the case, such as during a console command or a queued job where auth()->user() might be null.

  2. Error Handling: There is no error handling if auth()->user() is null. This could lead to errors when trying to access the timezone property on a non-object.

  3. Performance Considerations: Each time a date attribute is accessed or mutated, it involves timezone conversion which might be slightly performance intensive if done frequently.

Suggested Improvements

  1. Handling Null Users: You should add a check to ensure there is an authenticated user before trying to access the user's timezone. If no user is authenticated, you could fall back to a default timezone.

  2. Caching User Timezone: If your application involves frequent fetching and setting of date attributes, consider caching the user's timezone to avoid repeated database hits or complex computations.

  3. Configuration Fallback: Provide a default timezone in your application configuration to handle cases where the user's timezone is not available.

Here's an improved version of your custom cast class:

<?php

namespace App\Casts;

use Carbon\Carbon;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;

class DateTimeWithTz implements CastsAttributes
{
    /**
     * Cast the given value.
     *
     * @param  array<string, mixed>  $attributes
     */
    public function get(Model $model, string $key, mixed $value, array $attributes): mixed
    {
        $userTimezone = auth()->user() ? auth()->user()->timezone : config('app.fallback_timezone', 'UTC');
        return Carbon::parse($value, config('app.timezone'))->setTimezone($userTimezone);
    }

    /**
     * Prepare the given value for storage.
     *
     * @param  array<string, mixed>  $attributes
     */
    public function set(Model $model, string $key, mixed $value, array $attributes): mixed
    {
        $userTimezone = auth()->user() ? auth()->user()->timezone : config('app.fallback_timezone', 'UTC');
        return Carbon::parse($value, $userTimezone)->setTimezone(config('app.timezone'));
    }
}

Additional Considerations

  • Testing: Ensure to write comprehensive tests to cover various scenarios including cases where the user is not authenticated.
  • User Timezone Storage: Make sure the timezone stored in the user's profile is always valid. Consider using a standardized list of timezones.

This approach should make your application more robust and handle edge cases more gracefully.

Please or to participate in this conversation.