PHP 8.1+Laravel 9:如何通过国家和地区查询时区?
嘿,这个问题我之前帮不少Laravel开发者解决过——确实,旧的GeoIP扩展停更后,想用国家/地区代码直接查时区,而GeoIP2又只支持IP查询,确实有点卡壳。不过针对你的Laravel 9 + PHP 8.1环境,有几个靠谱的解决方案,你可以根据自己的业务场景选:
方案1:基于MaxMind GeoIP2离线数据库自定义查询
MaxMind的GeoIP2数据库(免费版也可用)其实包含了时区信息,我们可以绕开“只能通过IP查询”的限制,自己实现一套通过国家/地区代码查询时区的逻辑。
步骤:
- 安装依赖:通过Composer安装MaxMind的官方包:
composer require maxmind-db/reader geoip2/geoip2 - 下载离线数据库:去MaxMind官网免费注册后,下载
GeoIP2-City.mmdb(免费版的GeoIP2-Country只有国家级时区,城市版能覆盖到地区级),把它放到Laravel的storage/app/geoip/目录下(如果目录不存在就新建)。 - 编写查询服务:在Laravel里创建一个服务类,读取数据库并匹配国家/地区代码。这里有个小技巧:第一次运行时可以把所有国家/地区-时区的映射提取出来存到Redis或者数据库表中,后续查询直接读缓存,避免每次遍历数据库的性能损耗。
示例服务类代码:
<?php namespace App\Services; use MaxMind\Db\Reader; use Illuminate\Support\Facades\Cache; class TimezoneLookupService { protected $reader; protected $cacheKey = 'geoip_timezone_map'; public function __construct() { $this->reader = new Reader(storage_path('app/geoip/GeoIP2-City.mmdb')); } public function getTimezoneByCountryAndRegion(string $countryCode, string $regionCode = null): ?string { // 先尝试从缓存读取映射表 $timezoneMap = Cache::get($this->cacheKey); if (!$timezoneMap) { // 缓存不存在,构建映射表 $timezoneMap = []; $records = $this->reader->getIterator(); foreach ($records as $record) { $countryCode = $record['country']['iso_code'] ?? null; $regionCode = $record['subdivisions'][0]['iso_code'] ?? null; $timezone = $record['location']['time_zone'] ?? null; if ($countryCode && $timezone) { $key = $regionCode ? "{$countryCode}-{$regionCode}" : $countryCode; $timezoneMap[strtoupper($key)] = $timezone; } } // 缓存映射表,有效期设为30天(MaxMind数据库一般每月更新一次) Cache::put($this->cacheKey, $timezoneMap, now()->addDays(30)); } // 构造查询键 $queryKey = $regionCode ? strtoupper("{$countryCode}-{$regionCode}") : strtoupper($countryCode); return $timezoneMap[$queryKey] ?? null; } }
之后你可以在Laravel的服务容器中注册这个服务,或者直接实例化调用,非常方便。
方案2:维护预定义的时区映射数组
如果你的业务只需要覆盖少数常用国家/地区,那直接维护一个硬编码的映射数组是最简单的方案,没有任何依赖,速度也最快。
示例代码:
<?php // 可以把这个数组放到配置文件里,比如config/timezones.php return [ 'country_region_map' => [ 'US-CA' => 'America/Los_Angeles', 'US-NY' => 'America/New_York', 'GB-ENG' => 'Europe/London', 'CN-BJ' => 'Asia/Shanghai', // 按需添加更多映射 ], ]; // 查询函数可以封装成辅助函数或者服务 function getTimezoneFromMap(string $countryCode, ?string $regionCode = null): ?string { $map = config('timezones.country_region_map'); $key = $regionCode ? strtoupper("{$countryCode}-{$regionCode}") : strtoupper($countryCode); return $map[$key] ?? null; }
缺点是需要手动维护映射,当有新的地区或者时区规则变更时,你得自己更新数组,但胜在简单直接。
方案3:调用第三方时区API
如果不想维护本地数据,可以用第三方API来查询,比如TimeZoneDB的API,它支持通过国家代码和地区代码返回时区。不过要注意免费版有请求次数限制,而且依赖网络请求,存在一定延迟。
示例代码(用Laravel的Http客户端):
<?php use Illuminate\Support\Facades\Http; function getTimezoneViaApi(string $countryCode, ?string $regionCode = null): ?string { $response = Http::get('http://api.timezonedb.com/v2.1/get-time-zone', [ 'key' => env('TIMEZONE_DB_API_KEY'), // 把API密钥存在.env里 'country' => strtoupper($countryCode), 'region' => $regionCode ? strtoupper($regionCode) : '', 'format' => 'json', ]); if ($response->successful() && $response->json('status') === 'OK') { return $response->json('zoneName'); } return null; }
这个方案适合查询频率不高,或者对实时性要求不高的场景。
额外小贴士
不管用哪个方案拿到时区后,你可以直接结合Laravel的Carbon来处理日期时间:
$timezone = app(TimezoneLookupService::class)->getTimezoneByCountryAndRegion('US', 'CA'); $localTime = \Carbon\Carbon::now($timezone);
内容的提问来源于stack exchange,提问作者alireza alizade
相关产品推荐
相关产品推荐

