frankenphp容器中date()返回utc而非北京时间,因alpine镜像未预装tzdata且php未配置时区;需构建时安装tzdata、设置/etc/localtime与/etc/timezone,并写入date.timezone=asia/shanghai到php配置;mysql连接需每次执行set time_zone='+08:00';server.php顶部须设时区以统一cli与http行为。

FrankenPHP容器里date()返回UTC,不是北京时间
FrankenPHP 默认用 Alpine 基础镜像,系统时区是 UTC,date()、DateTime 全按 UTC 解析——哪怕宿主机是 Asia/Shanghai,容器里也看不到。这不是 FrankenPHP 本身的问题,而是 Alpine 镜像没预装时区数据 + PHP 没加载时区配置。
必须在构建镜像阶段就写死时区,不能靠运行时 date_default_timezone_set() 补救:它只影响后续调用,但 FrankenPHP 启动时可能已执行过日志初始化、路由解析等依赖时间的操作,这部分仍走 UTC。
- Alpine 镜像要先装
tzdata:RUN apk add --no-cache tzdata - 再设软链和环境变量:
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo "Asia/Shanghai" > /etc/timezone - 同时给 PHP 加配置:
RUN echo "date.timezone = Asia/Shanghai" > /etc/php8/conf.d/timezone.ini(注意路径,Alpine 的 PHP 版本前缀可能是php8或php9,进容器执行php --ini确认) - 验证命令:
php -r "echo date_default_timezone_get();"必须输出Asia/Shanghai,不是UTC或空
MySQL 连接层的 SET time_zone 在 FrankenPHP 里被忽略
FrankenPHP 用 SAPI 直接跑 PHP,不经过 Apache/Nginx 的 request 生命周期,PDO 或 mysqli 初始化后不会自动执行初始化 SQL。如果你在 config/database.php 里写了 PDO::MYSQL_ATTR_INIT_COMMAND,它只在 Laravel 的 DB 工厂创建连接时生效;而 FrankenPHP 的长连接复用模型下,连接可能跨请求存活,SET time_zone 只跑一次,后续请求若切换了用户时区或连接池混用,就会失效。
- 不要只依赖框架配置,要在每次获取连接后显式执行:
$pdo->exec("SET time_zone = '+08:00'"); - 如果用 Doctrine 或 Eloquent,检查是否启用了连接事件监听,在
postConnect钩子中补上时区设置 - 避免用
Asia/Shanghai字符串值——部分 MySQL 版本对 IANA 时区名支持不稳定,'+08:00'更可靠 -
SELECT @@session.time_zone必须返回+08:00,不是SYSTEM或UTC
FrankenPHP 的 server.php 入口没设时区,导致 CLI 和 HTTP 请求行为不一致
FrankenPHP 的 server.php 是常驻进程入口,但很多人把它当普通脚本用,只在 Web 请求逻辑里加 date_default_timezone_set()。结果 CLI 命令(如 php artisan schedule:run)跑定时任务时,时区还是 UTC,和页面上看到的时间对不上。
- 必须在
server.php文件最顶部(<?php后第一行)就调用:date_default_timezone_set('Asia/Shanghai'); - 别用
ini_set('date.timezone', ...)——它不触发 PHP 内部时区缓存重载,date_default_timezone_set()才是唯一保证生效的方式 - 如果项目用了
symfony/runtime,确保Runtime::enable()前已完成时区设置 - 验证方式:在
server.php里加一行error_log('TZ: ' . date_default_timezone_get());,看错误日志是否输出Asia/Shanghai
Docker Compose 启动 FrankenPHP 时,TZ 环境变量不起作用
Alpine 镜像不读 TZ 环境变量来改系统时区,只影响部分 shell 命令(如 date),但 PHP 和 MySQL 完全无视它。你写 TZ=Asia/Shanghai 在 docker-compose.yml 里,date 命令显示对了,但 php -r "echo date();" 还是 UTC。
- 删掉
environment: TZ=...,它在这里纯属干扰项 - 改用挂载方式:
volumes: - /etc/localtime:/etc/localtime:ro - /etc/timezone:/etc/timezone:ro,但仅限宿主机是 Linux;Windows/macOS 宿主不适用 - 最稳方案:回到 Dockerfile,用
RUN ln -sf ...+echo > /etc/timezone固化,不依赖任何外部输入 - 别信“Alpine 支持 TZ”的二手说法——musl libc 的
setenv("TZ", ...)不会触发 glibc 风格的时区重载,PHP 底层用的是 musl 的localtime_r,它只认/etc/localtime文件
FrankenPHP 的时区问题本质是三层脱节:Alpine 系统没时区文件、PHP 没加载 timezone 配置、MySQL 连接没同步 session 时区。三个地方少一个,时间就可能错位,而且错得不规律——比如页面显示对了,日志时间却早 8 小时,或者定时任务在凌晨 0 点跑,实际却是 UTC 凌晨 0 点(北京时间上午 8 点)。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











