Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Time loses microseconds #9081

Merged
merged 15 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 25 additions & 7 deletions system/DataCaster/Cast/DatetimeCast.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,7 @@ public static function get(
/**
* @see https://www.php.net/manual/en/datetimeimmutable.createfromformat.php#datetimeimmutable.createfromformat.parameters
*/
$format = match ($params[0] ?? '') {
'' => $helper->dateFormat['datetime'],
'ms' => $helper->dateFormat['datetime-ms'],
'us' => $helper->dateFormat['datetime-us'],
default => throw new InvalidArgumentException('Invalid parameter: ' . $params[0]),
};
$format = self::getDateTimeFormat($params, $helper);

return Time::createFromFormat($format, $value);
}
Expand All @@ -62,6 +57,29 @@ public static function set(
self::invalidTypeValueError($value);
}

return (string) $value;
if (! $helper instanceof BaseConnection) {
$message = 'The parameter $helper must be BaseConnection.';

throw new InvalidArgumentException($message);
}

$format = self::getDateTimeFormat($params, $helper);

return $value->format($format);
}

/**
* Gets DateTime format from the DB connection.
*
* @param list<string> $params Additional param
*/
protected static function getDateTimeFormat(array $params, BaseConnection $db): string
{
return match ($params[0] ?? '') {
'' => $db->dateFormat['datetime'],
'ms' => $db->dateFormat['datetime-ms'],
'us' => $db->dateFormat['datetime-us'],
default => throw new InvalidArgumentException('Invalid parameter: ' . $params[0]),
};
}
}
55 changes: 34 additions & 21 deletions system/I18n/TimeTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,10 @@ public function __construct(?string $time = null, $timezone = null, ?string $loc
if ($time === '' && static::$testNow instanceof self) {
if ($timezone !== null) {
$testNow = static::$testNow->setTimezone($timezone);
$time = $testNow->format('Y-m-d H:i:s');
$time = $testNow->format('Y-m-d H:i:s.u');
} else {
$timezone = static::$testNow->getTimezone();
$time = static::$testNow->format('Y-m-d H:i:s');
$time = static::$testNow->format('Y-m-d H:i:s.u');
}
}

Expand All @@ -97,7 +97,7 @@ public function __construct(?string $time = null, $timezone = null, ?string $loc
if ($time !== '' && static::hasRelativeKeywords($time)) {
$instance = new DateTime('now', $this->timezone);
$instance->modify($time);
$time = $instance->format('Y-m-d H:i:s');
$time = $instance->format('Y-m-d H:i:s.u');
}

parent::__construct($time, $this->timezone);
Expand Down Expand Up @@ -253,7 +253,7 @@ public static function createFromFormat($format, $datetime, $timezone = null)
throw I18nException::forInvalidFormat($format);
}

return new self($date->format('Y-m-d H:i:s'), $timezone);
return new self($date->format('Y-m-d H:i:s.u'), $timezone);
}

/**
Expand Down Expand Up @@ -283,7 +283,7 @@ public static function createFromTimestamp(int $timestamp, $timezone = null, ?st
*/
public static function createFromInstance(DateTimeInterface $dateTime, ?string $locale = null)
{
$date = $dateTime->format('Y-m-d H:i:s');
$date = $dateTime->format('Y-m-d H:i:s.u');
$timezone = $dateTime->getTimezone();

return new self($date, $timezone, $locale);
Expand Down Expand Up @@ -314,10 +314,11 @@ public static function instance(DateTime $dateTime, ?string $locale = null)
*/
public function toDateTime()
{
$dateTime = new DateTime('', $this->getTimezone());
$dateTime->setTimestamp(parent::getTimestamp());

return $dateTime;
return DateTime::createFromFormat(
'Y-m-d H:i:s.u',
$this->format('Y-m-d H:i:s.u'),
$this->getTimezone()
);
}

// --------------------------------------------------------------------
Expand Down Expand Up @@ -348,7 +349,7 @@ public static function setTestNow($datetime = null, $timezone = null, ?string $l
if (is_string($datetime)) {
$datetime = new self($datetime, $timezone, $locale);
} elseif ($datetime instanceof DateTimeInterface && ! $datetime instanceof self) {
$datetime = new self($datetime->format('Y-m-d H:i:s'), $timezone);
$datetime = new self($datetime->format('Y-m-d H:i:s.u'), $timezone);
}

static::$testNow = $datetime;
Expand Down Expand Up @@ -941,9 +942,9 @@ public function equals($testTime, ?string $timezone = null): bool

$ourTime = $this->toDateTime()
->setTimezone(new DateTimeZone('UTC'))
->format('Y-m-d H:i:s');
->format('Y-m-d H:i:s.u');

return $testTime->format('Y-m-d H:i:s') === $ourTime;
return $testTime->format('Y-m-d H:i:s.u') === $ourTime;
}

/**
Expand All @@ -956,15 +957,15 @@ public function equals($testTime, ?string $timezone = null): bool
public function sameAs($testTime, ?string $timezone = null): bool
{
if ($testTime instanceof DateTimeInterface) {
$testTime = $testTime->format('Y-m-d H:i:s');
$testTime = $testTime->format('Y-m-d H:i:s.u O');
} elseif (is_string($testTime)) {
$timezone = $timezone ?: $this->timezone;
$timezone = $timezone instanceof DateTimeZone ? $timezone : new DateTimeZone($timezone);
$testTime = new DateTime($testTime, $timezone);
$testTime = $testTime->format('Y-m-d H:i:s');
$testTime = $testTime->format('Y-m-d H:i:s.u O');
}

$ourTime = $this->toDateTimeString();
$ourTime = $this->format('Y-m-d H:i:s.u O');

return $testTime === $ourTime;
}
Expand All @@ -979,10 +980,16 @@ public function sameAs($testTime, ?string $timezone = null): bool
*/
public function isBefore($testTime, ?string $timezone = null): bool
{
$testTime = $this->getUTCObject($testTime, $timezone)->getTimestamp();
$ourTime = $this->getTimestamp();
$testTime = $this->getUTCObject($testTime, $timezone);

$testTimestamp = $testTime->getTimestamp();
$ourTimestamp = $this->getTimestamp();

if ($ourTimestamp === $testTimestamp) {
return $this->format('u') < $testTime->format('u');
}

return $ourTime < $testTime;
return $ourTimestamp < $testTimestamp;
}

/**
Expand All @@ -995,10 +1002,16 @@ public function isBefore($testTime, ?string $timezone = null): bool
*/
public function isAfter($testTime, ?string $timezone = null): bool
{
$testTime = $this->getUTCObject($testTime, $timezone)->getTimestamp();
$ourTime = $this->getTimestamp();
$testTime = $this->getUTCObject($testTime, $timezone);

$testTimestamp = $testTime->getTimestamp();
$ourTimestamp = $this->getTimestamp();

if ($ourTimestamp === $testTimestamp) {
return $this->format('u') > $testTime->format('u');
}

return $ourTime > $testTime;
return $ourTimestamp > $testTimestamp;
}

// --------------------------------------------------------------------
Expand Down
23 changes: 20 additions & 3 deletions tests/system/DataConverter/DataConverterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ public function testDateTimeConvertDataToDB(): void
'id' => 'int',
'date' => 'datetime',
];
$converter = $this->createDataConverter($types);
$converter = $this->createDataConverter($types, [], db_connect());

$phpData = [
'id' => '1',
Expand All @@ -379,6 +379,23 @@ public function testDateTimeConvertDataToDB(): void
$this->assertSame('2023-11-18 14:18:18', $data['date']);
}

public function testDateTimeConvertDataToDBWithFormat(): void
{
$types = [
'id' => 'int',
'date' => 'datetime[us]',
];
$converter = $this->createDataConverter($types, [], db_connect());

$phpData = [
'id' => '1',
'date' => Time::parse('2009-02-15 00:00:01.123456'),
];
$data = $converter->toDataSource($phpData);

$this->assertSame('2009-02-15 00:00:01.123456', $data['date']);
}

public function testTimestampConvertDataFromDB(): void
{
$types = [
Expand Down Expand Up @@ -603,7 +620,7 @@ public function testExtract(): void
'created_at' => 'datetime',
'updated_at' => 'datetime',
];
$converter = $this->createDataConverter($types);
$converter = $this->createDataConverter($types, [], db_connect());

$phpData = [
'id' => 1,
Expand Down Expand Up @@ -635,7 +652,7 @@ public function testExtractWithExtractMethod(): void
'created_at' => 'datetime',
'updated_at' => 'datetime',
];
$converter = $this->createDataConverter($types, [], null, 'toRawArray');
$converter = $this->createDataConverter($types, [], db_connect(), 'toRawArray');

$phpData = [
'id' => 1,
Expand Down
59 changes: 55 additions & 4 deletions tests/system/I18n/TimeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public function testNewTimeNow(): void
'en_US',
IntlDateFormatter::SHORT,
IntlDateFormatter::SHORT,
'America/Chicago', // Default for CodeIgniter
'America/Chicago',
IntlDateFormatter::GREGORIAN,
'yyyy-MM-dd HH:mm:ss'
);
Expand All @@ -76,7 +76,7 @@ public function testTimeWithTimezone(): void
'en_US',
IntlDateFormatter::SHORT,
IntlDateFormatter::SHORT,
'Europe/London', // Default for CodeIgniter
'Europe/London',
IntlDateFormatter::GREGORIAN,
'yyyy-MM-dd HH:mm:ss'
);
Expand All @@ -92,7 +92,7 @@ public function testTimeWithTimezoneAndLocale(): void
'fr_FR',
IntlDateFormatter::SHORT,
IntlDateFormatter::SHORT,
'Europe/London', // Default for CodeIgniter
'Europe/London',
IntlDateFormatter::GREGORIAN,
'yyyy-MM-dd HH:mm:ss'
);
Expand Down Expand Up @@ -234,6 +234,13 @@ public function testCreateFromFormat(): void
$this->assertCloseEnoughString(date('2017-01-15 H:i:s', $now->getTimestamp()), $time->toDateTimeString());
}

public function testCreateFromFormatWithMicroseconds(): void
{
$time = Time::createFromFormat('Y-m-d H:i:s.u', '2024-07-09 09:13:34.654321');

$this->assertSame('2024-07-09 09:13:34.654321', $time->format('Y-m-d H:i:s.u'));
}

public function testCreateFromFormatWithTimezoneString(): void
{
$time = Time::createFromFormat('F j, Y', 'January 15, 2017', 'Europe/London');
Expand Down Expand Up @@ -875,13 +882,21 @@ public function testEqualWithString(): void
$this->assertTrue($time1->equals('January 11, 2017 03:50:00', 'Europe/London'));
}

public function testEqualWithStringAndNotimezone(): void
public function testEqualWithStringAndNoTimezone(): void
{
$time1 = Time::parse('January 10, 2017 21:50:00', 'America/Chicago');

$this->assertTrue($time1->equals('January 10, 2017 21:50:00'));
}

public function testEqualWithDifferentMicroseconds(): void
{
$time1 = new Time('2024-01-01 12:00:00.654321');
$time2 = new Time('2024-01-01 12:00:00');

$this->assertFalse($time1->equals($time2));
}

public function testSameSuccess(): void
{
$time1 = Time::parse('January 10, 2017 21:50:00', 'America/Chicago');
Expand Down Expand Up @@ -921,6 +936,24 @@ public function testBefore(): void
$this->assertFalse($time2->isBefore($time1));
}

public function testBeforeSameTime(): void
{
$time1 = new Time('2024-01-01 12:00:00.000000');
$time2 = new Time('2024-01-01 12:00:00.000000');

$this->assertFalse($time1->isBefore($time2));
$this->assertFalse($time2->isBefore($time1));
}

public function testBeforeWithMicroseconds(): void
{
$time1 = new Time('2024-01-01 12:00:00.000000');
$time2 = new Time('2024-01-01 12:00:00.654321');

$this->assertTrue($time1->isBefore($time2));
$this->assertFalse($time2->isBefore($time1));
}

public function testAfter(): void
{
$time1 = Time::parse('January 10, 2017 21:50:00', 'America/Chicago');
Expand All @@ -930,6 +963,24 @@ public function testAfter(): void
$this->assertTrue($time2->isAfter($time1));
}

public function testAfterSameTime(): void
{
$time1 = new Time('2024-01-01 12:00:00.000000');
$time2 = new Time('2024-01-01 12:00:00.000000');

$this->assertFalse($time1->isAfter($time2));
$this->assertFalse($time2->isAfter($time1));
}

public function testAfterWithMicroseconds(): void
{
$time1 = new Time('2024-01-01 12:00:00.654321');
$time2 = new Time('2024-01-01 12:00:00.000000');

$this->assertTrue($time1->isAfter($time2));
$this->assertFalse($time2->isAfter($time1));
}

public function testHumanizeYearsSingle(): void
{
Time::setTestNow('March 10, 2017', 'America/Chicago');
Expand Down
6 changes: 6 additions & 0 deletions user_guide_src/source/changelogs/v4.6.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ executed twice, an exception will be thrown. See

.. _v460-interface-changes:

Time with Microseconds
----------------------

Fixed bugs that some methods in ``Time`` to lose microseconds have been fixed.
See :ref:`Upgrading Guide <upgrade-460-time-keeps-microseconds>` for details.

Interface Changes
=================

Expand Down
Loading
Loading