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
Requires
- php: ^8.2
- illuminate/http: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
- nesbot/carbon: ^2.0|^3.0
Requires (Dev)
- laravel/pint: ^1.0
- orchestra/testbench: ^9.0
- pestphp/pest: ^2.0|^3.0
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:
-
On Request: Convert
scheduled_atfromAsia/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)
- Input:
-
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)
- Stored:
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.