Skip to content

Commit b3d957d

Browse files
authored
Merge pull request #9081 from kenjis/fix-Time-loses-microseconds
fix: Time loses microseconds
2 parents 72b8d96 + ed29b50 commit b3d957d

File tree

14 files changed

+261
-35
lines changed

14 files changed

+261
-35
lines changed

system/DataCaster/Cast/DatetimeCast.php

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,7 @@ public static function get(
4343
/**
4444
* @see https://www.php.net/manual/en/datetimeimmutable.createfromformat.php#datetimeimmutable.createfromformat.parameters
4545
*/
46-
$format = match ($params[0] ?? '') {
47-
'' => $helper->dateFormat['datetime'],
48-
'ms' => $helper->dateFormat['datetime-ms'],
49-
'us' => $helper->dateFormat['datetime-us'],
50-
default => throw new InvalidArgumentException('Invalid parameter: ' . $params[0]),
51-
};
46+
$format = self::getDateTimeFormat($params, $helper);
5247

5348
return Time::createFromFormat($format, $value);
5449
}
@@ -62,6 +57,29 @@ public static function set(
6257
self::invalidTypeValueError($value);
6358
}
6459

65-
return (string) $value;
60+
if (! $helper instanceof BaseConnection) {
61+
$message = 'The parameter $helper must be BaseConnection.';
62+
63+
throw new InvalidArgumentException($message);
64+
}
65+
66+
$format = self::getDateTimeFormat($params, $helper);
67+
68+
return $value->format($format);
69+
}
70+
71+
/**
72+
* Gets DateTime format from the DB connection.
73+
*
74+
* @param list<string> $params Additional param
75+
*/
76+
protected static function getDateTimeFormat(array $params, BaseConnection $db): string
77+
{
78+
return match ($params[0] ?? '') {
79+
'' => $db->dateFormat['datetime'],
80+
'ms' => $db->dateFormat['datetime-ms'],
81+
'us' => $db->dateFormat['datetime-us'],
82+
default => throw new InvalidArgumentException('Invalid parameter: ' . $params[0]),
83+
};
6684
}
6785
}

system/I18n/TimeTrait.php

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,10 @@ public function __construct(?string $time = null, $timezone = null, ?string $loc
8181
if ($time === '' && static::$testNow instanceof self) {
8282
if ($timezone !== null) {
8383
$testNow = static::$testNow->setTimezone($timezone);
84-
$time = $testNow->format('Y-m-d H:i:s');
84+
$time = $testNow->format('Y-m-d H:i:s.u');
8585
} else {
8686
$timezone = static::$testNow->getTimezone();
87-
$time = static::$testNow->format('Y-m-d H:i:s');
87+
$time = static::$testNow->format('Y-m-d H:i:s.u');
8888
}
8989
}
9090

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

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

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

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

289289
return new self($date, $timezone, $locale);
@@ -314,10 +314,11 @@ public static function instance(DateTime $dateTime, ?string $locale = null)
314314
*/
315315
public function toDateTime()
316316
{
317-
$dateTime = new DateTime('', $this->getTimezone());
318-
$dateTime->setTimestamp(parent::getTimestamp());
319-
320-
return $dateTime;
317+
return DateTime::createFromFormat(
318+
'Y-m-d H:i:s.u',
319+
$this->format('Y-m-d H:i:s.u'),
320+
$this->getTimezone()
321+
);
321322
}
322323

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

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

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

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

949950
/**
@@ -956,15 +957,15 @@ public function equals($testTime, ?string $timezone = null): bool
956957
public function sameAs($testTime, ?string $timezone = null): bool
957958
{
958959
if ($testTime instanceof DateTimeInterface) {
959-
$testTime = $testTime->format('Y-m-d H:i:s');
960+
$testTime = $testTime->format('Y-m-d H:i:s.u O');
960961
} elseif (is_string($testTime)) {
961962
$timezone = $timezone ?: $this->timezone;
962963
$timezone = $timezone instanceof DateTimeZone ? $timezone : new DateTimeZone($timezone);
963964
$testTime = new DateTime($testTime, $timezone);
964-
$testTime = $testTime->format('Y-m-d H:i:s');
965+
$testTime = $testTime->format('Y-m-d H:i:s.u O');
965966
}
966967

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

969970
return $testTime === $ourTime;
970971
}
@@ -979,10 +980,16 @@ public function sameAs($testTime, ?string $timezone = null): bool
979980
*/
980981
public function isBefore($testTime, ?string $timezone = null): bool
981982
{
982-
$testTime = $this->getUTCObject($testTime, $timezone)->getTimestamp();
983-
$ourTime = $this->getTimestamp();
983+
$testTime = $this->getUTCObject($testTime, $timezone);
984+
985+
$testTimestamp = $testTime->getTimestamp();
986+
$ourTimestamp = $this->getTimestamp();
987+
988+
if ($ourTimestamp === $testTimestamp) {
989+
return $this->format('u') < $testTime->format('u');
990+
}
984991

985-
return $ourTime < $testTime;
992+
return $ourTimestamp < $testTimestamp;
986993
}
987994

988995
/**
@@ -995,10 +1002,16 @@ public function isBefore($testTime, ?string $timezone = null): bool
9951002
*/
9961003
public function isAfter($testTime, ?string $timezone = null): bool
9971004
{
998-
$testTime = $this->getUTCObject($testTime, $timezone)->getTimestamp();
999-
$ourTime = $this->getTimestamp();
1005+
$testTime = $this->getUTCObject($testTime, $timezone);
1006+
1007+
$testTimestamp = $testTime->getTimestamp();
1008+
$ourTimestamp = $this->getTimestamp();
1009+
1010+
if ($ourTimestamp === $testTimestamp) {
1011+
return $this->format('u') > $testTime->format('u');
1012+
}
10001013

1001-
return $ourTime > $testTime;
1014+
return $ourTimestamp > $testTimestamp;
10021015
}
10031016

10041017
// --------------------------------------------------------------------

tests/system/DataConverter/DataConverterTest.php

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ public function testDateTimeConvertDataToDB(): void
368368
'id' => 'int',
369369
'date' => 'datetime',
370370
];
371-
$converter = $this->createDataConverter($types);
371+
$converter = $this->createDataConverter($types, [], db_connect());
372372

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

382+
public function testDateTimeConvertDataToDBWithFormat(): void
383+
{
384+
$types = [
385+
'id' => 'int',
386+
'date' => 'datetime[us]',
387+
];
388+
$converter = $this->createDataConverter($types, [], db_connect());
389+
390+
$phpData = [
391+
'id' => '1',
392+
'date' => Time::parse('2009-02-15 00:00:01.123456'),
393+
];
394+
$data = $converter->toDataSource($phpData);
395+
396+
$this->assertSame('2009-02-15 00:00:01.123456', $data['date']);
397+
}
398+
382399
public function testTimestampConvertDataFromDB(): void
383400
{
384401
$types = [
@@ -603,7 +620,7 @@ public function testExtract(): void
603620
'created_at' => 'datetime',
604621
'updated_at' => 'datetime',
605622
];
606-
$converter = $this->createDataConverter($types);
623+
$converter = $this->createDataConverter($types, [], db_connect());
607624

608625
$phpData = [
609626
'id' => 1,
@@ -635,7 +652,7 @@ public function testExtractWithExtractMethod(): void
635652
'created_at' => 'datetime',
636653
'updated_at' => 'datetime',
637654
];
638-
$converter = $this->createDataConverter($types, [], null, 'toRawArray');
655+
$converter = $this->createDataConverter($types, [], db_connect(), 'toRawArray');
639656

640657
$phpData = [
641658
'id' => 1,

tests/system/I18n/TimeTest.php

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public function testNewTimeNow(): void
6161
'en_US',
6262
IntlDateFormatter::SHORT,
6363
IntlDateFormatter::SHORT,
64-
'America/Chicago', // Default for CodeIgniter
64+
'America/Chicago',
6565
IntlDateFormatter::GREGORIAN,
6666
'yyyy-MM-dd HH:mm:ss'
6767
);
@@ -76,7 +76,7 @@ public function testTimeWithTimezone(): void
7676
'en_US',
7777
IntlDateFormatter::SHORT,
7878
IntlDateFormatter::SHORT,
79-
'Europe/London', // Default for CodeIgniter
79+
'Europe/London',
8080
IntlDateFormatter::GREGORIAN,
8181
'yyyy-MM-dd HH:mm:ss'
8282
);
@@ -92,7 +92,7 @@ public function testTimeWithTimezoneAndLocale(): void
9292
'fr_FR',
9393
IntlDateFormatter::SHORT,
9494
IntlDateFormatter::SHORT,
95-
'Europe/London', // Default for CodeIgniter
95+
'Europe/London',
9696
IntlDateFormatter::GREGORIAN,
9797
'yyyy-MM-dd HH:mm:ss'
9898
);
@@ -234,6 +234,13 @@ public function testCreateFromFormat(): void
234234
$this->assertCloseEnoughString(date('2017-01-15 H:i:s', $now->getTimestamp()), $time->toDateTimeString());
235235
}
236236

237+
public function testCreateFromFormatWithMicroseconds(): void
238+
{
239+
$time = Time::createFromFormat('Y-m-d H:i:s.u', '2024-07-09 09:13:34.654321');
240+
241+
$this->assertSame('2024-07-09 09:13:34.654321', $time->format('Y-m-d H:i:s.u'));
242+
}
243+
237244
public function testCreateFromFormatWithTimezoneString(): void
238245
{
239246
$time = Time::createFromFormat('F j, Y', 'January 15, 2017', 'Europe/London');
@@ -875,13 +882,21 @@ public function testEqualWithString(): void
875882
$this->assertTrue($time1->equals('January 11, 2017 03:50:00', 'Europe/London'));
876883
}
877884

878-
public function testEqualWithStringAndNotimezone(): void
885+
public function testEqualWithStringAndNoTimezone(): void
879886
{
880887
$time1 = Time::parse('January 10, 2017 21:50:00', 'America/Chicago');
881888

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

892+
public function testEqualWithDifferentMicroseconds(): void
893+
{
894+
$time1 = new Time('2024-01-01 12:00:00.654321');
895+
$time2 = new Time('2024-01-01 12:00:00');
896+
897+
$this->assertFalse($time1->equals($time2));
898+
}
899+
885900
public function testSameSuccess(): void
886901
{
887902
$time1 = Time::parse('January 10, 2017 21:50:00', 'America/Chicago');
@@ -921,6 +936,24 @@ public function testBefore(): void
921936
$this->assertFalse($time2->isBefore($time1));
922937
}
923938

939+
public function testBeforeSameTime(): void
940+
{
941+
$time1 = new Time('2024-01-01 12:00:00.000000');
942+
$time2 = new Time('2024-01-01 12:00:00.000000');
943+
944+
$this->assertFalse($time1->isBefore($time2));
945+
$this->assertFalse($time2->isBefore($time1));
946+
}
947+
948+
public function testBeforeWithMicroseconds(): void
949+
{
950+
$time1 = new Time('2024-01-01 12:00:00.000000');
951+
$time2 = new Time('2024-01-01 12:00:00.654321');
952+
953+
$this->assertTrue($time1->isBefore($time2));
954+
$this->assertFalse($time2->isBefore($time1));
955+
}
956+
924957
public function testAfter(): void
925958
{
926959
$time1 = Time::parse('January 10, 2017 21:50:00', 'America/Chicago');
@@ -930,6 +963,24 @@ public function testAfter(): void
930963
$this->assertTrue($time2->isAfter($time1));
931964
}
932965

966+
public function testAfterSameTime(): void
967+
{
968+
$time1 = new Time('2024-01-01 12:00:00.000000');
969+
$time2 = new Time('2024-01-01 12:00:00.000000');
970+
971+
$this->assertFalse($time1->isAfter($time2));
972+
$this->assertFalse($time2->isAfter($time1));
973+
}
974+
975+
public function testAfterWithMicroseconds(): void
976+
{
977+
$time1 = new Time('2024-01-01 12:00:00.654321');
978+
$time2 = new Time('2024-01-01 12:00:00.000000');
979+
980+
$this->assertTrue($time1->isAfter($time2));
981+
$this->assertFalse($time2->isAfter($time1));
982+
}
983+
933984
public function testHumanizeYearsSingle(): void
934985
{
935986
Time::setTestNow('March 10, 2017', 'America/Chicago');

user_guide_src/source/changelogs/v4.6.0.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ executed twice, an exception will be thrown. See
5959

6060
.. _v460-interface-changes:
6161

62+
Time with Microseconds
63+
----------------------
64+
65+
Fixed bugs that some methods in ``Time`` to lose microseconds have been fixed.
66+
See :ref:`Upgrading Guide <upgrade-460-time-keeps-microseconds>` for details.
67+
6268
Interface Changes
6369
=================
6470

0 commit comments

Comments
 (0)