jeromejhipolito/laravel-timezone-middleware

Automatic timezone conversion middleware for Laravel APIs. Stores everything in UTC, converts request/response datetimes based on Accept-Timezone header.

Installs: 0

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/jeromejhipolito/laravel-timezone-middleware

v1.0.0 2026-02-02 02:30 UTC

This package is auto-updated.

Last update: 2026-02-02 02:33:32 UTC


README

Automatic timezone conversion middleware for Laravel APIs. Stores everything in UTC, converts request/response datetimes based on the Accept-Timezone header.

Features

  • Automatically converts incoming request datetime values from user's timezone to UTC
  • Automatically converts outgoing response datetime values from UTC to user's timezone
  • Configurable datetime patterns for detection
  • Exclude specific keys from conversion (e.g., birthdate)
  • Exclude specific routes from conversion (e.g., webhooks)
  • Supports nested arrays and JSON responses
  • Zero configuration needed - works out of the box

Requirements

  • PHP 8.2+
  • Laravel 11.0+ or 12.0+

Installation

composer require jeromejhipolito/laravel-timezone-middleware

The package will auto-register its service provider.

Configuration

Publish the configuration file:

php artisan vendor:publish --tag=timezone-config

This will create config/timezone.php:

return [
    // HTTP header name for timezone detection
    'header' => env('TIMEZONE_HEADER', 'Accept-Timezone'),

    // Default timezone when no header is provided
    'default' => env('TIMEZONE_DEFAULT', 'UTC'),

    // Storage timezone (always UTC for consistency)
    'storage' => 'UTC',

    // Patterns to detect datetime strings in requests
    'request_patterns' => [
        '/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?$/',
        '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/',
        '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/',
    ],

    // Patterns to detect datetime strings in responses
    'response_patterns' => [
        '/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{6})?Z?$/',
        '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/',
    ],

    // Output format for response datetimes
    'response_format' => 'Y-m-d\TH:i:s.u\Z',

    // Output format for request datetimes (stored in DB)
    'request_format' => 'Y-m-d H:i:s',

    // Keys to exclude from conversion
    'excluded_keys' => [
        'birthdate',
        'date_of_birth',
        'dob',
    ],

    // Route patterns to exclude from conversion
    'excluded_routes' => [
        // 'api/webhooks/*',
    ],
];

Usage

Register the Middleware

Add the middleware to your routes in bootstrap/app.php:

use JeromeJHipolito\TimezoneMiddleware\Middleware\TimezoneMiddleware;

->withMiddleware(function (Middleware $middleware) {
    $middleware->api(append: [
        TimezoneMiddleware::class,
    ]);
})

Or apply it to specific route groups:

use JeromeJHipolito\TimezoneMiddleware\Middleware\TimezoneMiddleware;

Route::middleware([TimezoneMiddleware::class])->group(function () {
    Route::get('/events', [EventController::class, 'index']);
    Route::post('/events', [EventController::class, 'store']);
});

Client-Side Usage

Clients should send their timezone in the Accept-Timezone header:

fetch('/api/events', {
    headers: {
        'Accept-Timezone': 'Asia/Manila',
        'Content-Type': 'application/json',
    },
    body: JSON.stringify({
        title: 'Meeting',
        scheduled_at: '2024-01-15 18:00:00', // User's local time
    }),
});

The middleware will:

  1. On Request: Convert scheduled_at from Asia/Manila (UTC+8) to UTC before it reaches your controller

    • Input: 2024-01-15 18:00:00 (Manila time)
    • Stored: 2024-01-15 10:00:00 (UTC)
  2. On Response: Convert all datetime values from UTC back to Asia/Manila

    • Stored: 2024-01-15 10:00:00 (UTC)
    • Response: 2024-01-15T18:00:00.000000Z (Manila time)

Excluding Fields

Some fields shouldn't be timezone-converted (like birthdates). Add them to the excluded_keys config:

'excluded_keys' => [
    'birthdate',
    'date_of_birth',
    'dob',
    'anniversary_date',
],

Excluding Routes

Webhook endpoints often need raw UTC timestamps. Exclude them:

'excluded_routes' => [
    'api/webhooks/*',
    'api/callbacks/*',
],

Getting the User's Timezone

You can access the detected timezone in your application:

use JeromeJHipolito\TimezoneMiddleware\Middleware\TimezoneMiddleware;

public function show(Request $request)
{
    $middleware = app(TimezoneMiddleware::class);
    $userTimezone = $middleware->getUserTimezone();

    // Use it for custom formatting, etc.
}

How It Works

┌─────────────────────────────────────────────────────────────────┐
│                         CLIENT                                   │
│  Timezone: Asia/Manila (UTC+8)                                  │
│  Sends: { "scheduled_at": "2024-01-15 18:00:00" }               │
│  Header: Accept-Timezone: Asia/Manila                           │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                    TIMEZONE MIDDLEWARE                          │
│  Detects: Asia/Manila from header                               │
│  Converts: 18:00 Manila → 10:00 UTC                             │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                      CONTROLLER                                  │
│  Receives: { "scheduled_at": "2024-01-15 10:00:00" }            │
│  Stores in DB as UTC                                            │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                      DATABASE                                    │
│  Stored: "2024-01-15 10:00:00" (UTC)                            │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                    TIMEZONE MIDDLEWARE                          │
│  Converts response: 10:00 UTC → 18:00 Manila                    │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                         CLIENT                                   │
│  Receives: { "scheduled_at": "2024-01-15T18:00:00.000000Z" }    │
│  Displays in user's local time                                  │
└─────────────────────────────────────────────────────────────────┘

Testing

composer test

License

The MIT License (MIT). Please see License File for more information.

Credits