Skip to content

Date fields shift by timezone offset on every resource save when PHP date.timezone is not UTC #2431

Closed

Description

@andonebz

Evolution CMS 3.5.7 and 3.3 (reproduced on both) / PHP 8.3 / date.timezone = Asia/Tokyo (default on most Japanese shared hosting: Xserver, Heteml, etc.)

Steps to reproduce:

Set date.timezone = "Asia/Tokyo" in php.ini
Edit any resource, set pub_date to 2026/09/01 10:00:00, save
Reopen the resource: field shows 2026/09/01 01:00:00 (−9h)
Save again without changes: value shifts another −9h (compounds on every save). unpub_date behaves identically; publishedon inherits the drift via its copy from pub_date. createdon/editedon are unaffected (no form round-trip).

Cause: Asymmetric timezone handling between the two converters in core/src/Core.php:

toTimeStamp() uses mktime() — interpreted in the runtime timezone (line ~4797)
toDateFormat() uses Carbon::createFromTimestamp($timestamp, 'UTC') — fixed to UTC (line ~4740)

Each open→save round-trip therefore subtracts the TZ offset. Not reproducible when date.timezone=UTC because both paths coincide.

Suggested fix: use gmmktime() in toTimeStamp() (consistent with the UTC-fixed display path), or pass date_default_timezone_get() to Carbon in toDateFormat().

Activity

  1. MiddleSokilAI commented on Aug 27, 2026

    @MiddleSokilAI
    Collaborator

    Thanks for the detailed report. I can reproduce the asymmetric conversion path from the code: toDateFormat() formats timestamps in UTC while toTimeStamp() currently uses the runtime timezone through mktime().

    I am taking this into work and will keep the fix focused on making the resource date round-trip stable across non-UTC PHP timezones.

  2. MiddleSokilAI commented on Aug 27, 2026

    @MiddleSokilAI
    Collaborator

    Repro

    Confirmed from the code path: toDateFormat() renders timestamps in UTC while toTimeStamp() used PHP runtime timezone parsing through mktime(). With Asia/Tokyo, that makes a manager date lose 9 hours on each open/save/open round trip.

    Root Cause

    The display and parse sides used different timezone bases: UTC for display, runtime PHP timezone for parse.

    Validation

    Opened PR #2435 with a focused fix and regression coverage.

    Validated locally with:

    • git diff --check
    • wsl php -l core/src/Core.php
    • wsl php -l core/tests/Unit/CoreTest.php
    • WSL smoke test confirming 2026/09/01 10:00:00 stays stable through two Asia/Tokyo conversions.

    Full Pest was not run locally because vendor/bin/pest is not present in the current local core/vendor.

    Risk

    Low. The change is scoped to the shared date parser and aligns it with the existing UTC display path.

  3. added 2 commits that reference this issue on Aug 27, 2026
  4. andonebz commented on Aug 27, 2026

    @andonebz
    Author

    Thank you for the incredibly fast turnaround — reported, root-caused, and merged within hours!

    Confirmed the fix on our production environment:

    • Evolution CMS 3.5.7 on Xserver (Japanese shared hosting), PHP 8.3, date.timezone = Asia/Tokyo
    • Applied the same change as PR fix(core): keep resource date round trips timezone-stable (#2431) #2435 (gmmktime() in toTimeStamp()) to our instance
    • Result: pub_date / unpub_date now survive repeated open→save round-trips with no drift. publishedon correctly inherits the stable value.

    One practical note for anyone reading this later: after the fix, stored timestamps are "wall-clock time carved as UTC", so any custom snippet reading these raw values should format them with gmdate() / Carbon(…, 'UTC') rather than date() — otherwise the display shifts by the runtime TZ offset on non-UTC servers (this also applies to date formatting in third-party snippets). Once we aligned our snippets to gmdate(), everything is consistent across UTC and Asia/Tokyo environments.

    Thanks again — this fixes a long-standing trap for every Evo user on Japanese hosting defaults.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions