Skip to content

Commit

Permalink
Merge pull request #9081 from kenjis/fix-Time-loses-microseconds
Browse files Browse the repository at this point in the history
fix: Time loses microseconds
  • Loading branch information
kenjis authored Jul 31, 2024
2 parents 72b8d96 + ed29b50 commit b3d957d
Show file tree
Hide file tree
Showing 14 changed files with 261 additions and 35 deletions.
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

0 comments on commit b3d957d

Please sign in to comment.