SetTimezoneMiddleware
The SetTimezoneMiddleware automatically sets the application timezone based on user preferences, request parameters, or language settings, enabling accurate date and time handling for global applications.
Namespace
JobMetric\Language\Http\Middleware\SetTimezoneMiddleware
Overview
This middleware intelligently sets the application timezone, which is then used by helper functions like client_timezone(), tz_format(), and tz_carbon() to ensure all dates and times are displayed in the user's local timezone.
The middleware determines timezone from:
Accept-Timezoneheader (if present and valid)- Config fallback
config('app.timezone')(defaults to 'UTC' if invalid)
Why Use This Middleware?
Problem Without Middleware
Without this middleware, you would face:
- Incorrect Date Display: All dates shown in server timezone, confusing users
- Manual Timezone Handling: Need to convert timezones in every controller
- Inconsistent Experience: Different parts of app might use different timezones
- Complex Calculations: Manual timezone conversions everywhere
- User Confusion: Users see times that don't match their local time
- Scheduling Issues: Events and appointments shown in wrong timezone
Solution With Middleware
The SetTimezoneMiddleware solves all these problems:
- Automatic Detection: Sets timezone automatically based on user context
- Consistent Experience: All dates/times use the same timezone throughout request
- Helper Integration: Works seamlessly with timezone helper functions
- Language Integration: Uses timezone from language settings when available
- Session Persistence: Remembers user's timezone preference
- Zero Configuration: Works out of the box with sensible defaults
Usage
Basic Registration
Register in app/Http/Kernel.php:
protected $middlewareGroups = [
'web' => [
// ... other middleware
\JobMetric\Language\Http\Middleware\SetTimezoneMiddleware::class,
],
];
API Routes
For API routes, register in the api middleware group:
protected $middlewareGroups = [
'api' => [
// ... other middleware
\JobMetric\Language\Http\Middleware\SetTimezoneMiddleware::class,
],
];
Specific Routes
Apply to specific routes only:
Route::middleware([
\JobMetric\Language\Http\Middleware\SetTimezoneMiddleware::class
])->group(function () {
Route::get('/events', [EventController::class, 'index']);
Route::get('/appointments', [AppointmentController::class, 'index']);
});
How It Works
Priority System
The middleware follows this priority order:
-
Accept-Timezone Header (Highest Priority)
- Checks for
Accept-Timezoneheader in request - Example:
Accept-Timezone: America/New_York - Validates timezone identifier (IANA format)
- Useful for API clients and browser-based timezone detection
- Checks for
-
Config Fallback (Lowest Priority)
- Uses
config('app.timezone')as fallback - Defaults to 'UTC' if config value is invalid
- Ensures a valid timezone is always set
- Uses
Timezone Resolution
The middleware resolves timezone using this logic:
// Priority 1: Accept-Timezone header
$timezone = $request->header('Accept-Timezone');
if ($this->isValidTimezone($timezone)) {
// Use header value
} else {
// Priority 2: Config fallback
$timezone = config('app.timezone', 'UTC');
if (!$this->isValidTimezone($timezone)) {
$timezone = 'UTC'; // Final fallback
}
}
// Store for request lifetime
config(['app.client_timezone' => $timezone]);
$request->headers->set('Accept-Timezone', $timezone);
Important Notes:
- The middleware does NOT change
config('app.timezone')or PHP's default timezone - It stores the client timezone in
config('app.client_timezone')for the current request only - Helper functions like
client_timezone()read fromconfig('app.client_timezone')
Real-World Use Cases
Use Case 1: Global Event Management System
Scenario: An event management platform needs to show event times in each user's local timezone.
Problem Without Middleware:
// All events shown in server timezone (UTC)
$event = Event::find(1);
echo $event->starts_at; // "2024-01-15 14:00:00" (UTC)
// User in New York sees: 2:00 PM (but it's actually 9:00 AM their time!)
Solution With Middleware:
// Middleware automatically sets user's timezone
// User in New York: timezone = 'America/New_York'
// In controller
$event = Event::find(1);
// Helper functions use middleware-set timezone
echo tz_format($event->starts_at); // "2024-01-15 09:00:00" (New York time)
echo tz_carbon($event->starts_at)->format('g:i A'); // "9:00 AM"
Benefits:
- Users see event times in their local timezone
- No confusion about when events occur
- Better user experience and fewer missed events
Use Case 2: E-Commerce Order Tracking
Scenario: An e-commerce platform needs to show order timestamps in customer's timezone.
Solution:
// Middleware sets customer's timezone
// Customer in Tokyo: timezone = 'Asia/Tokyo'
// Order confirmation email
$order = Order::find(123);
// Dates automatically in customer's timezone
$data = [
'order_date' => tz_format($order->created_at), // Tokyo time
'estimated_delivery' => tz_format($order->estimated_delivery), // Tokyo time
'current_time' => tz_carbon(now())->format('Y-m-d H:i:s'), // Tokyo time
];
Mail::to($order->customer)->send(new OrderConfirmation($data));
Benefits:
- Customers see order times in their local timezone
- Delivery estimates make sense to customers
- Reduced support inquiries about "wrong" times
Use Case 3: Appointment Booking System
Scenario: A healthcare appointment system needs to show available slots in patient's timezone.
Solution:
// Patient in London: timezone = 'Europe/London'
// Doctor in New York: timezone = 'America/New_York'
// Show available slots in patient's timezone
$slots = AppointmentSlot::where('doctor_id', $doctorId)
->where('date', '>=', now())
->get()
->map(function ($slot) {
return [
'id' => $slot->id,
'date' => tz_format($slot->date), // Patient's timezone
'time' => tz_carbon($slot->date)->format('g:i A'), // Patient's timezone
'available' => $slot->isAvailable(),
];
});
return response()->json($slots);
Benefits:
- Patients see appointment times in their timezone
- No confusion about booking times
- Better booking experience
Use Case 4: Social Media Platform
Scenario: A social media platform needs to show post timestamps in user's timezone.
Solution:
// User in Dubai: timezone = 'Asia/Dubai'
// Timeline posts
$posts = Post::latest()->paginate(20);
return view('timeline', [
'posts' => $posts->map(function ($post) {
return [
'id' => $post->id,
'content' => $post->content,
'posted_at' => tz_carbon($post->created_at)->diffForHumans(), // "2 hours ago" (Dubai time)
'posted_time' => tz_format($post->created_at, 'g:i A'), // "3:45 PM" (Dubai time)
];
}),
]);
Benefits:
- Users see "2 hours ago" relative to their timezone
- Timestamps make sense to users
- Better engagement and user experience
Use Case 5: Analytics Dashboard
Scenario: An analytics dashboard needs to show metrics in user's timezone for accurate reporting.
Solution:
// Admin in Sydney: timezone = 'Australia/Sydney'
// Daily analytics
$startOfDay = tz_carbon(now())->startOfDay(); // Sydney midnight
$endOfDay = tz_carbon(now())->endOfDay(); // Sydney 11:59 PM
$metrics = [
'today_visits' => Visit::whereBetween('created_at', [$startOfDay, $endOfDay])->count(),
'today_orders' => Order::whereBetween('created_at', [$startOfDay, $endOfDay])->count(),
'today_revenue' => Order::whereBetween('created_at', [$startOfDay, $endOfDay])->sum('total'),
];
// All metrics are for "today" in admin's timezone
return view('analytics.dashboard', compact('metrics'));
Benefits:
- Reports show data for "today" in admin's timezone
- Accurate daily/weekly/monthly reports
- No confusion about time ranges
Integration with Helper Functions
The middleware works seamlessly with timezone helper functions:
client_timezone()
Get the current client timezone:
// Middleware sets timezone to 'America/New_York'
$timezone = client_timezone(); // 'America/New_York'
tz_format()
Format dates in client timezone:
$date = now(); // Server time: 2024-01-15 14:00:00 (UTC)
// Middleware sets timezone to 'America/New_York'
$formatted = tz_format($date); // "2024-01-15 09:00:00" (New York time)
$formatted = tz_format($date, 'Y-m-d H:i:s'); // Custom format
tz_carbon()
Get Carbon instance in client timezone:
$date = now(); // Server time
// Middleware sets timezone to 'Asia/Tokyo'
$carbon = tz_carbon($date); // Carbon instance in Tokyo timezone
echo $carbon->format('Y-m-d H:i:s'); // Tokyo time
echo $carbon->diffForHumans(); // Relative time in Tokyo timezone
Complete Setup Example
Step 1: Register Middleware
// app/Http/Kernel.php
protected $middlewareGroups = [
'web' => [
\Illuminate\Cookie\Middleware\EncryptCookies::class,
\Illuminate\Session\Middleware\StartSession::class,
\JobMetric\Language\Http\Middleware\SetLanguageMiddleware::class,
\JobMetric\Language\Http\Middleware\SetTimezoneMiddleware::class,
// ... other middleware
],
];
Step 2: Send Timezone in Requests
For web applications, you can send timezone via JavaScript:
// Detect user timezone
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
// Send with every request
fetch('/api/events', {
headers: {
'Accept-Timezone': timezone
}
});
Or set it in your frontend framework:
// Axios interceptor
axios.interceptors.request.use(config => {
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
config.headers['Accept-Timezone'] = timezone;
return config;
});
For API clients, include the header:
// API client example
$response = Http::withHeaders([
'Accept-Timezone' => 'America/New_York'
])->get('https://api.example.com/events');
Step 3: Use in Controllers
// app/Http/Controllers/EventController.php
public function index()
{
$events = Event::upcoming()
->get()
->map(function ($event) {
return [
'id' => $event->id,
'title' => $event->title,
'starts_at' => tz_format($event->starts_at),
'starts_at_formatted' => tz_carbon($event->starts_at)->format('l, F j, Y \a\t g:i A'),
'timezone' => client_timezone(),
];
});
return view('events.index', compact('events'));
}
Step 4: Custom Timezone Detection
You can extend the middleware behavior by creating a custom middleware that sets the Accept-Timezone header:
// app/Http/Middleware/DetectUserTimezone.php
class DetectUserTimezone
{
public function handle(Request $request, Closure $next)
{
// Check session first
if ($request->hasSession() && $request->session()->has('timezone')) {
$timezone = $request->session()->get('timezone');
}
// Check user profile
elseif (auth()->check() && auth()->user()->timezone) {
$timezone = auth()->user()->timezone;
}
// Check language timezone
elseif ($language = CurrentLanguage::resolve() && $language->timezone) {
$timezone = $language->timezone;
}
// Set header for SetTimezoneMiddleware to use
if (isset($timezone)) {
$request->headers->set('Accept-Timezone', $timezone);
}
return $next($request);
}
}
// Register before SetTimezoneMiddleware
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\DetectUserTimezone::class,
\JobMetric\Language\Http\Middleware\SetTimezoneMiddleware::class,
],
];
When to Use SetTimezoneMiddleware
Use this middleware when you need to:
- Automatic Timezone Detection: Automatically detect and set timezone from user context
- User Timezone: Support user-specific timezones for personalized experience
- Date Formatting: Format dates in user's timezone throughout the application
- Global Applications: Support users in different timezones seamlessly
- Event Management: Show event times in user's local timezone
- E-Commerce: Display order timestamps in customer's timezone
- Appointment Systems: Show available slots in user's timezone
- Social Media: Display post timestamps relative to user's timezone
- Analytics: Show reports in admin's timezone for accurate data
- Notifications: Send notifications with times in user's timezone
When NOT to Use
You might not need this middleware if:
- Your application only serves users in one timezone
- You handle timezone conversion manually in controllers
- You use a different timezone management strategy
- All dates are stored and displayed in UTC only
Best Practices
1. Always Store Dates in UTC
// Store in UTC
$event->starts_at = Carbon::parse($request->input('starts_at'), $userTimezone)->utc();
$event->save();
// Display in user timezone
echo tz_format($event->starts_at); // Automatically converted
2. Use Helper Functions
// Good: Use helper functions
$formatted = tz_format($date);
$carbon = tz_carbon($date);
// Bad: Manual timezone conversion
$formatted = Carbon::parse($date)->setTimezone($timezone)->format('Y-m-d H:i:s');
3. Set User Timezone on Registration
// When user registers, detect and set timezone
public function register(Request $request)
{
$user = User::create([...]);
// Detect timezone from request or use default
$timezone = $request->input('timezone') ?? config('app.timezone');
session(['timezone' => $timezone]);
$user->update(['timezone' => $timezone]);
}
4. Allow Timezone Override
// Provide timezone selector in user settings
<select name="timezone">
@foreach(timezone_identifiers_list() as $tz)
<option value="{{ $tz }}" {{ $tz === session('timezone') ? 'selected' : '' }}>
{{ $tz }}
</option>
@endforeach
</select>