eslam-reda-div/filament-timezone-detector 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

eslam-reda-div/filament-timezone-detector

Composer 安装命令:

composer require eslam-reda-div/filament-timezone-detector

包简介

A Filament v5 plugin that automatically detects user timezone from the browser and provides comprehensive helpers, facades, macros, and middleware for seamless timezone conversion across your entire application.

README 文档

README

Latest Version on Packagist Total Downloads License

Automatically detects the user's browser timezone and provides Facade, Helpers, Carbon macros, Filament table/form macros, and an Eloquent trait for seamless timezone conversion.

Store in UTC. Display in the user's local time. Automatically.

Features

  • Auto browser detection — JavaScript detects IANA timezone via Intl.DateTimeFormat with 10+ fallback methods
  • Livewire v4 / SPA compatible — uses Livewire.interceptRequest(), wire:navigate, fetch/XHR interceptors, cross-tab BroadcastChannel sync
  • Middleware — captures timezone from header, cookie, query param, or form input → stores in session
  • One-liner macrosTextColumn::toUserTimezone(), TextEntry::toUserTimezone(), DateTimePicker::fromUserTimezone()
  • Carbon macros$date->toUserTimezone(), ->toSystemTimezone(), ->formatInUserTimezone()
  • Facade & helpersTimezoneDetector::forDisplay(), to_user_timezone(), user_now(), etc.
  • Eloquent traitInteractsWithTimezone for per-attribute conversion
  • Fully configurable — toggle every feature on/off, customize header/cookie/session names

Requirements

  • PHP >= 8.2, Laravel >= 11.0, Filament >= 5.0

Installation

composer require eslam-reda-div/filament-timezone-detector

Register the plugin in your panel provider:

use EslamRedaDiv\TimezoneDetector\TimezoneDetectorPlugin;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->plugin(TimezoneDetectorPlugin::make());
}

Optionally publish the config:

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

That's it. The plugin auto-loads JS detection, registers middleware, Carbon macros, and Filament macros.

How It Works

Browser (JS)                          Server (Middleware)                Your Code
─────────────                         ──────────────────                ─────────
Intl.DateTimeFormat() detects    →    Reads X-Timezone header      →   Facade / Helper / Carbon macro
timezone and sends it via:            (or cookie / query param)        converts any datetime between
• Livewire interceptRequest           Stores in session('user_tz')     user ↔ system timezone
• fetch / XHR / Axios interceptors
• Cookie + hidden form inputs
• BroadcastChannel (cross-tab)

Recommended: Store all datetimes in UTC (config/app.php → 'timezone' => 'UTC'). The plugin handles display conversion automatically.

Usage

Filament Table Columns

TextColumn::make('created_at')->dateTime()->toUserTimezone(),
TextColumn::make('published_at')->dateTime('M d, Y h:i A')->toUserTimezone(),
TextColumn::make('updated_at')->since()->toUserTimezone(),

Filament Infolist Entries

TextEntry::make('created_at')->dateTime()->toUserTimezone(),
TextEntry::make('published_at')->dateTime('M d, Y h:i A')->toUserTimezone(),
TextEntry::make('updated_at')->since()->toUserTimezone(),

Filament Form Fields

DateTimePicker::make('starts_at')->toUserTimezone(),    // display + save in user TZ
DateTimePicker::make('event_time')->fromUserTimezone(),  // alias, clearer intent
DatePicker::make('event_date')->toUserTimezone(),

Facade

use EslamRedaDiv\TimezoneDetector\Facades\TimezoneDetector;

// Info
TimezoneDetector::getUserTimezone();       // "America/New_York"
TimezoneDetector::getSystemTimezone();     // "UTC"
TimezoneDetector::getOffsetFromSystem();   // -4.0
TimezoneDetector::getUserUtcOffset();      // "-04:00"

// Convert to user TZ (for display)
TimezoneDetector::toUserTimezone($model->created_at);                    // Carbon
TimezoneDetector::toUserTimezone('2025-06-15 14:00:00', 'M d, Y h:i A'); // "Jun 15, 2025 10:00 AM"
TimezoneDetector::forDisplay($model->created_at);                        // formatted string
TimezoneDetector::diffForHumans($model->created_at);                     // "2 hours ago"

// Convert to system TZ (for storage)
TimezoneDetector::toSystemTimezone($request->starts_at);   // Carbon in UTC
TimezoneDetector::forStorage($request->input('event_time')); // alias
TimezoneDetector::fromUserInput('06/15/2025 10:00 AM', 'm/d/Y h:i A'); // parse + convert

// Between any two TZs
TimezoneDetector::convertTimezone($datetime, 'UTC', 'Asia/Tokyo', 'H:i');

// Current time
TimezoneDetector::userNow();    // Carbon in user TZ
TimezoneDetector::systemNow();  // Carbon in system TZ

Helper Functions

user_timezone();                                        // "America/New_York"
system_timezone();                                      // "UTC"
to_user_timezone($model->created_at, 'M d, Y h:i A');  // formatted in user TZ
to_system_timezone($userInput);                         // Carbon in UTC
convert_timezone($datetime, 'UTC', 'Asia/Tokyo');       // between any TZs
user_now();                                             // Carbon in user TZ
format_user_datetime($model->created_at);               // display string
diff_for_humans_user($model->created_at);               // "2 hours ago"

Carbon Macros

$date->toUserTimezone();                       // Carbon in user TZ
$date->toSystemTimezone();                     // Carbon in system TZ
$date->formatInUserTimezone('M d, Y h:i A');   // formatted string
$date->diffForHumansInUserTimezone();          // "2 hours ago"

// Chaining with Eloquent
$model->created_at->toUserTimezone()->format('H:i');
$model->deadline->toUserTimezone()->isPast();

Eloquent Model Trait

use EslamRedaDiv\TimezoneDetector\Concerns\InteractsWithTimezone;

class Event extends Model
{
    use InteractsWithTimezone;
    protected $casts = ['starts_at' => 'datetime', 'ends_at' => 'datetime'];
}

$event->toUserTimezone('starts_at');                          // Carbon in user TZ
$event->getDatetimeForUser('starts_at', 'd/m/Y H:i');        // formatted string
$event->setDatetimeFromUser('starts_at', $input);             // converts to UTC and sets
$event->diffForHumansInUserTimezone('starts_at');             // "in 3 days"
$event->convertAttributeTimezone('starts_at', 'Asia/Tokyo');  // any TZ

Blade Views

{{ to_user_timezone($post->created_at, 'M d, Y h:i A') }}
{{ $post->created_at->formatInUserTimezone('M d, Y h:i A') }}
{{ diff_for_humans_user($post->published_at) }}
Your timezone: {{ user_timezone() }}

Controllers

// Store: convert user input → UTC
$event->starts_at = TimezoneDetector::toSystemTimezone($request->starts_at);

// Display: convert UTC → user TZ
$display = TimezoneDetector::forDisplay($event->starts_at);

API Resources

'starts_at_local' => TimezoneDetector::toUserTimezone($this->starts_at)?->toISOString(),
'starts_at_display' => TimezoneDetector::forDisplay($this->starts_at),

Queued Jobs

The session is unavailable in jobs. Pass the timezone explicitly:

$userTz = user_timezone();
SendReminder::dispatch($event, $userTz);

// In the job:
$localTime = convert_timezone($this->event->starts_at, system_timezone(), $this->userTimezone, 'M d, Y h:i A');

Using Outside Filament Panels

Register the middleware manually and include the JS:

// bootstrap/app.php (Laravel 11+)
use EslamRedaDiv\TimezoneDetector\Http\Middleware\DetectUserTimezone;

->withMiddleware(function (Middleware $middleware) {
    $middleware->web(append: [DetectUserTimezone::class]);
})
{{-- Option 1: Use FilamentAsset (recommended, no publishing needed) --}}
<script src="{{ \Filament\Support\Facades\FilamentAsset::getScriptSrc('timezone-detector', 'eslam-reda-div/filament-timezone-detector') }}"></script>

{{-- Option 2: Publish and use from public/ --}}
<script src="{{ asset('vendor/eslam-reda-div/filament-timezone-detector/timezone-detector.js') }}"></script>

For option 2, publish first: php artisan filament:assets

Configuration

// config/timezone-detector.php
return [
    'system_timezone'       => config('app.timezone', 'UTC'),
    'fallback_timezone'     => config('app.timezone', 'UTC'),
    'header_name'           => env('TIMEZONE_DETECTOR_HEADER', 'X-Timezone'),
    'cookie_name'           => env('TIMEZONE_DETECTOR_COOKIE', 'user_timezone'),
    'session_key'           => env('TIMEZONE_DETECTOR_SESSION_KEY', 'user_timezone'),
    'query_param'           => env('TIMEZONE_DETECTOR_QUERY_PARAM', 'timezone'),
    'auto_middleware'        => env('TIMEZONE_DETECTOR_AUTO_MIDDLEWARE', true),
    'register_carbon_macros' => env('TIMEZONE_DETECTOR_CARBON_MACROS', true),
    'register_column_macros' => env('TIMEZONE_DETECTOR_COLUMN_MACROS', true),
    'register_field_macros'  => env('TIMEZONE_DETECTOR_FIELD_MACROS', true),
];

Plugin Options (Per-Panel)

->plugin(TimezoneDetectorPlugin::make())                            // default
->plugin(TimezoneDetectorPlugin::make()->withoutMiddleware())       // manual middleware
->plugin(TimezoneDetectorPlugin::make()->autoMiddleware(false))     // same as above

API Reference

Facade / Core Methods

Method Returns Description
getUserTimezone() string User's IANA timezone
getSystemTimezone() string System/database timezone
toUserTimezone($dt, $format?, $fromTz?) Carbon|string|null System → user TZ
toSystemTimezone($dt, $format?, $toTz?) Carbon|string|null User → system TZ
convertTimezone($dt, $from, $to, $format?) Carbon|string|null Any → any TZ
userNow() / systemNow() Carbon Current time in user/system TZ
forDisplay($dt, $format?) string|null Shorthand display format
forStorage($dt, $format?) Carbon|string|null Shorthand for toSystemTimezone
fromUserInput($dt, $format?) Carbon Parse user input → system TZ
diffForHumans($dt) string|null "2 hours ago" in user TZ
getOffsetFromSystem() float Offset in hours (e.g., -4.0)
getUserUtcOffset() string UTC offset (e.g., "-04:00")
isValidTimezone($tz) bool Validate IANA timezone
getAvailableTimezones() array All IANA identifiers

Carbon Macros

Method Returns Description
->toUserTimezone() Carbon Copy in user TZ
->toSystemTimezone() Carbon Copy in system TZ
->formatInUserTimezone($format?) string Format in user TZ
->diffForHumansInUserTimezone() string Diff for humans in user TZ

Filament Macros

Target Method
TextColumn ->toUserTimezone()
TextEntry ->toUserTimezone()
DateTimePicker ->toUserTimezone() / ->fromUserTimezone()
DatePicker ->toUserTimezone()

Model Trait (InteractsWithTimezone)

Method Returns
->toUserTimezone('attr') Carbon|null
->toSystemTimezone('attr') Carbon|null
->getDatetimeForUser('attr', $format?) string|null
->setDatetimeFromUser('attr', $value, $format?) static
->diffForHumansInUserTimezone('attr') string|null
->convertAttributeTimezone('attr', $tz, $format?) Carbon|string|null

Helper Functions

Function Returns
user_timezone() string
system_timezone() string
to_user_timezone($dt, $format?, $fromTz?) Carbon|string|null
to_system_timezone($dt, $format?, $toTz?) Carbon|string|null
convert_timezone($dt, $from, $to, $format?) Carbon|string|null
user_now() Carbon
format_user_datetime($dt, $format?) string|null
diff_for_humans_user($dt) string|null

FAQ

Q: UTC shows on first page load? Expected — JS hasn't run yet. After the first request, the real timezone is detected and used for all subsequent requests. The fallback_timezone config covers this initial load.

Q: Works with Filament SPA mode / wire:navigate? Yes. The JS listens to livewire:navigate and livewire:navigated events and injects X-Timezone via Livewire.interceptRequest(), fetch/XHR interceptors, and cookies.

Q: Works outside Filament panels? Yes. See Using Outside Filament Panels.

Q: Timezone in queued jobs? The session is unavailable in jobs. Pass it explicitly when dispatching. See Queued Jobs.

Q: Does this change PHP's global timezone? No. It never calls date_default_timezone_set(). Only converts individual values when you call the conversion methods.

Q: Livewire v4 compatible? Yes. The JS uses Livewire.interceptRequest() (the new v4 API) with a fallback to Livewire.hook('request') for backward compatibility.

Testing

composer test

Credits

License

MIT — see LICENSE.md

eslam-reda-div/filament-timezone-detector 适用场景与选型建议

eslam-reda-div/filament-timezone-detector 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 2.15k 次下载、GitHub Stars 达 2, 最近一次更新时间为 2026 年 03 月 04 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「laravel」 「timezone」 「detector」 「filament」 「filament-plugin」 「timezone-converter」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 eslam-reda-div/filament-timezone-detector 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 eslam-reda-div/filament-timezone-detector 我们能提供哪些服务?
定制开发 / 二次开发

基于 eslam-reda-div/filament-timezone-detector 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 2.15k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 2
  • 点击次数: 33
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 2
  • Watchers: 1
  • Forks: 2
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-03-04