Skip to content

Conversation

@stephanedelprat
Copy link

@stephanedelprat stephanedelprat commented Dec 26, 2025

Thank you for Timecop, it really makes testing as thorough as can be!

The Problem:
When Timecop was active and new DateTime('2026-01-30', new DateTimeZone('UTC')) (or DateTimeImmutable) was called with a date, the time was incorrectly interpreted as in the default timezone (e.g., Europe/Paris) and then converted to UTC, resulting in 2026-01-29 23:00:00 UTC instead of the correct 2026-01-30 00:00:00 UTC.

The Fix (in timecop.c):
Modified the get_formatted_mock_time() function to temporarily set the default timezone to the provided timezone before calling strtotime(), then restore the original default timezone afterwards. This ensures date strings without explicit time information are correctly interpreted in the specified timezone. The fix also handles edge cases where offset-based timezones (like +00:00) are used, skipping the timezone switch for those since date_default_timezone_set() doesn't accept them.

Files Changed:

  • timecop.c - Core fix in get_formatted_mock_time() function
  • tests/issue_timezone_datetime.phpt - New test case for this fix

Note : the problem still exists when using offset-based timezones. (+3:00 etc.)


Minimal PHP test to see the problem:

<?php

echo "Default timezone: ".date_default_timezone_get();
echo "\n";

foreach ([true, false] as $activated) {
    if ($activated) {
        \Timecop::travel(new \DateTime());
    } else {
        \Timecop::return();
    }
    echo "Timecop active: ".($activated ? 'yes' : 'no')."\n";

    $dtStart = new DateTimeImmutable('2026-01-30 00:00:00', new DateTimeZone('UTC'));
    $dtStart2 = new DateTime('2026-01-30', new DateTimeZone('UTC'));

    echo $dtStart->format('Y-m-d H:i:s T')." (timestamp: ".$dtStart->getTimestamp().")\n";
    echo $dtStart2->format('Y-m-d H:i:s T')." (timestamp: ".$dtStart->getTimestamp().")\n";

    echo "\n";
}

Output :

Default timezone: Europe/Paris
Timecop active: yes
2026-01-29 23:00:00 UTC (timestamp: 1769727600)
2026-01-29 23:00:00 UTC (timestamp: 1769727600)

Timecop active: no
2026-01-30 00:00:00 UTC (timestamp: 1769731200)
2026-01-30 00:00:00 UTC (timestamp: 1769731200)

@stephanedelprat stephanedelprat changed the title Fix contructors when TZ and no time Fix contructors when TZ is set Dec 26, 2025
@stephanedelprat stephanedelprat force-pushed the fix/constructor-without-time-with-tz branch from 04ad6a5 to f789044 Compare December 26, 2025 04:27
@stephanedelprat stephanedelprat force-pushed the fix/constructor-without-time-with-tz branch from f789044 to 79feeb7 Compare December 26, 2025 04:28
Copy link
Member

@cs278 cs278 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense to me - thanks!

@cs278 cs278 merged commit 4abc67c into kiddivouchers:master Jan 5, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants