У контейнері з PHP кілька незалежних годинників і налаштувань часового поясу:
| Рівень | Де задається | На що впливає |
|---|---|---|
| ОС контейнера | TZ, /etc/localtime, пакет tzdata |
date у shell, cron, логи системних утиліт |
| PHP | date.timezone у php.ini |
date(), new DateTime() без явного поясу |
| Laravel | config/app.php → timezone |
now(), Carbon, Eloquent-дати, планувальник |
| база даних | налаштування сервера, сесії | NOW(), CURRENT_TIMESTAMP, timestamptz |
Типові пастки:
- офіційні PHP-образи за замовчуванням - UTC, і
date.timezoneне задано; - Alpine без
tzdataвзагалі не знає назв поясів -TZ=Europe/Kyivмовчки ігнорується; TZне впливає на PHP напряму: PHP бере пояс зdate.timezone, а якщо його немає - з власного значення за замовчуванням (UTC). Деякі сервери (зокрема вбудований у FrankenPHP) не підхоплюютьTZдля PHP-дат;- Laravel перевизначає PHP: при старті він викликає
date_default_timezone_set(config('app.timezone')), тож у застосунку діє саме конфігурація Laravel, а cron-задачі ОС чи сторонні скрипти - живуть за іншими правилами.
Узгоджене налаштування:
RUN apk add --no-cache tzdata # для Alpine
ENV TZ=Europe/Kyiv
RUN echo "date.timezone=\${TZ}" > "$PHP_INI_DIR/conf.d/timezone.ini"
// config/app.php
'timezone' => env('APP_TIMEZONE', 'UTC'),
Яку політику обрати:
- зберігати в базі UTC (або
timestamptzу PostgreSQL) - класична рекомендація: без двозначностей при переході на літній час, без проблем з користувачами з різних поясів; - показувати в локальному поясі користувача чи сайту на рівні відображення;
- якщо застосунок свідомо працює в одному місцевому поясі (сайт для однієї країни), - той самий пояс на всіх рівнях: ОС, PHP, Laravel, планувальник. Розбіжність між рівнями - головне джерело зсуву на 2-3 години.
Планувальник: ->dailyAt('09:00') виконується за поясом app.timezone (чи schedule_timezone), а не ОС. Переходи на літній час можуть пропустити чи подвоїти задачі, заплановані на 3:00 ночі.
Перевірка в контейнері:
docker exec app date
docker exec app php -r 'echo date_default_timezone_get(), PHP_EOL;'
docker exec app php artisan tinker --execute 'echo now();'