feature #40008 [Uid] Replace getTime() with getDateTime() (fancyweb)

This PR was merged into the 5.3-dev branch.

Discussion
----------

[Uid] Replace getTime() with getDateTime()

| Q             | A
| ------------- | ---
| Branch?       | 5.x
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Tickets       | https://github.com/symfony/symfony/pull/39507#pullrequestreview-575491827
| License       | MIT
| Doc PR        | -

Getting a `\DateTimeImmutable` has two benefits: easier to use and no float precision problems.

There is however one drawback for UUIDs: we *technically* loose some precision in the output because datetimes do not handle nanoseconds (only microseconds) and uuid timestamps increment every 100 nanoseconds (0.1 microseconds). However, this is theoretical since the increment is only there to generate more entropy. Also, if an end user really want the precision, he can still do the conversion him/herself from the raw data. Finally, because of some rounding problems with floats even on 64b platforms, precision is *actually* won most of the time thanks to the datetime.

The idea is to also accept `\DateTimeInterface` as input in `UuidFactory` and `UlidFactory` (see https://github.com/symfony/symfony/pull/39507) btw.

The breaking change is allowed because the component is experimental.

Commits
-------

360c900acf [Uid] Replace getTime() with getDateTime()
This commit is contained in:
Nicolas Grekas 2021-01-27 22:07:25 +01:00
commit 1a78e057d5
8 changed files with 67 additions and 55 deletions

View File

@ -4,24 +4,24 @@ UPGRADE FROM 5.2 to 5.3
Asset
-----
* Deprecated `RemoteJsonManifestVersionStrategy`, use `JsonManifestVersionStrategy` instead.
* Deprecated `RemoteJsonManifestVersionStrategy`, use `JsonManifestVersionStrategy` instead
DomCrawler
----------
* Deprecated the `parents()` method, use `ancestors()` instead.
* Deprecated the `parents()` method, use `ancestors()` instead
Form
----
* Changed `$forms` parameter type of the `DataMapperInterface::mapDataToForms()` method from `iterable` to `\Traversable`.
* Changed `$forms` parameter type of the `DataMapperInterface::mapFormsToData()` method from `iterable` to `\Traversable`.
* Deprecated passing an array as the second argument of the `DataMapper::mapDataToForms()` method, pass `\Traversable` instead.
* Deprecated passing an array as the first argument of the `DataMapper::mapFormsToData()` method, pass `\Traversable` instead.
* Deprecated passing an array as the second argument of the `CheckboxListMapper::mapDataToForms()` method, pass `\Traversable` instead.
* Deprecated passing an array as the first argument of the `CheckboxListMapper::mapFormsToData()` method, pass `\Traversable` instead.
* Deprecated passing an array as the second argument of the `RadioListMapper::mapDataToForms()` method, pass `\Traversable` instead.
* Deprecated passing an array as the first argument of the `RadioListMapper::mapFormsToData()` method, pass `\Traversable` instead.
* Changed `$forms` parameter type of the `DataMapperInterface::mapDataToForms()` method from `iterable` to `\Traversable`
* Changed `$forms` parameter type of the `DataMapperInterface::mapFormsToData()` method from `iterable` to `\Traversable`
* Deprecated passing an array as the second argument of the `DataMapper::mapDataToForms()` method, pass `\Traversable` instead
* Deprecated passing an array as the first argument of the `DataMapper::mapFormsToData()` method, pass `\Traversable` instead
* Deprecated passing an array as the second argument of the `CheckboxListMapper::mapDataToForms()` method, pass `\Traversable` instead
* Deprecated passing an array as the first argument of the `CheckboxListMapper::mapFormsToData()` method, pass `\Traversable` instead
* Deprecated passing an array as the second argument of the `RadioListMapper::mapDataToForms()` method, pass `\Traversable` instead
* Deprecated passing an array as the first argument of the `RadioListMapper::mapFormsToData()` method, pass `\Traversable` instead
HttpFoundation
--------------
@ -36,29 +36,37 @@ HttpKernel
Messenger
---------
* Deprecated the `prefetch_count` parameter in the AMQP bridge, it has no effect and will be removed in Symfony 6.0.
* Deprecated the `prefetch_count` parameter in the AMQP bridge, it has no effect and will be removed in Symfony 6.0
Notifier
-------
--------
* Changed the return type of `AbstractTransportFactory::getEndpoint()` from `?string` to `string`
* Changed the signature of `Dsn::__construct()` to accept a single `string $dsn` argument
* Removed the `Dsn::fromString()` method
* Changed the return type of `Symfony\Component\Notifier\Transport\AbstractTransportFactory::getEndpoint()` from `?string` to `string`
PhpunitBridge
-------------
* Deprecated the `SetUpTearDownTrait` trait, use original methods with "void" return typehint.
* Deprecated the `SetUpTearDownTrait` trait, use original methods with "void" return typehint
PropertyInfo
------------
* Deprecated the `Type::getCollectionKeyType()` and `Type::getCollectionValueType()` methods, use `Type::getCollectionKeyTypes()` and `Type::getCollectionValueTypes()` instead.
* Deprecated the `Type::getCollectionKeyType()` and `Type::getCollectionValueType()` methods, use `Type::getCollectionKeyTypes()` and `Type::getCollectionValueTypes()` instead
Security
--------
* Deprecated voters that do not return a valid decision when calling the `vote` method.
* Deprecated voters that do not return a valid decision when calling the `vote` method
Serializer
----------
* Deprecated `ArrayDenormalizer::setSerializer()`, call `setDenormalizer()` instead.
* Deprecated `ArrayDenormalizer::setSerializer()`, call `setDenormalizer()` instead
Uid
---
* Replaced `UuidV1::getTime()`, `UuidV6::getTime()` and `Ulid::getTime()` by `UuidV1::getDateTime()`, `UuidV6::getDateTime()` and `Ulid::getDateTime()`

View File

@ -119,10 +119,10 @@ class BinaryUtil
/**
* @param string $time Count of 100-nanosecond intervals since the UUID epoch 1582-10-15 00:00:00 in hexadecimal
*/
public static function timeToFloat(string $time): float
public static function timeToDateTime(string $time): \DateTimeImmutable
{
if (\PHP_INT_SIZE >= 8) {
$time = hexdec($time) - self::TIME_OFFSET_INT;
$time = (string) (hexdec($time) - self::TIME_OFFSET_INT);
} else {
$time = str_pad(hex2bin($time), 8, "\0", \STR_PAD_LEFT);
@ -136,6 +136,10 @@ class BinaryUtil
}
}
return $time / 10000000;
if (9 > \strlen($time)) {
$time = '-' === $time[0] ? '-'.str_pad(substr($time, 1), 8, '0', \STR_PAD_LEFT) : str_pad($time, 8, '0', \STR_PAD_LEFT);
}
return \DateTimeImmutable::createFromFormat('U.u?', substr_replace($time, '.', -7, 0));
}
}

View File

@ -5,6 +5,7 @@ CHANGELOG
---
* Add `AbstractUid::fromBinary()`, `AbstractUid::fromBase58()`, `AbstractUid::fromBase32()` and `AbstractUid::fromRfc4122()`
* [BC BREAK] Replace `UuidV1::getTime()`, `UuidV6::getTime()` and `Ulid::getTime()` by `UuidV1::getDateTime()`, `UuidV6::getDateTime()` and `Ulid::getDateTime()`
5.2.0
-----

View File

@ -76,13 +76,18 @@ class UlidTest extends TestCase
/**
* @group time-sensitive
*/
public function testGetTime()
public function testGetDateTime()
{
$time = microtime(false);
$ulid = new Ulid();
$time = substr($time, 11).substr($time, 1, 4);
$this->assertSame((float) $time, $ulid->getTime());
$this->assertEquals(\DateTimeImmutable::createFromFormat('U.u', $time), $ulid->getDateTime());
$this->assertEquals(new \DateTimeImmutable('@0'), (new Ulid('000000000079KA1307SR9X4MV3'))->getDateTime());
$this->assertEquals(\DateTimeImmutable::createFromFormat('U.u', '0.001'), (new Ulid('000000000179KA1307SR9X4MV3'))->getDateTime());
$this->assertEquals(\DateTimeImmutable::createFromFormat('U.u', '281474976710.654'), (new Ulid('7ZZZZZZZZY79KA1307SR9X4MV3'))->getDateTime());
$this->assertEquals(\DateTimeImmutable::createFromFormat('U.u', '281474976710.655'), (new Ulid('7ZZZZZZZZZ79KA1307SR9X4MV3'))->getDateTime());
}
public function testIsValid()

View File

@ -60,7 +60,7 @@ class UuidTest extends TestCase
$uuid = new UuidV1(self::A_UUID_V1);
$this->assertSame(1583245966.746458, $uuid->getTime());
$this->assertEquals(\DateTimeImmutable::createFromFormat('U.u', '1583245966.746458'), $uuid->getDateTime());
$this->assertSame('3499710062d0', $uuid->getNode());
}
@ -95,7 +95,7 @@ class UuidTest extends TestCase
$uuid = new UuidV6(substr_replace(self::A_UUID_V1, '6', 14, 1));
$this->assertSame(85916308548.27832, $uuid->getTime());
$this->assertEquals(\DateTimeImmutable::createFromFormat('U.u', '85916308548.278321'), $uuid->getDateTime());
$this->assertSame('3499710062d0', $uuid->getNode());
}
@ -308,12 +308,14 @@ class UuidTest extends TestCase
$this->assertInstanceOf(CustomUuid::class, CustomUuid::fromString(self::A_UUID_V4));
}
public function testGetTime()
public function testGetDateTime()
{
$this->assertSame(103072857660.6847, ((new UuidV1('ffffffff-ffff-1fff-a456-426655440000'))->getTime()));
$this->assertSame(0.0000001, ((new UuidV1('13814001-1dd2-11b2-a456-426655440000'))->getTime()));
$this->assertSame(0.0, (new UuidV1('13814000-1dd2-11b2-a456-426655440000'))->getTime());
$this->assertSame(-0.0000001, (new UuidV1('13813fff-1dd2-11b2-a456-426655440000'))->getTime());
$this->assertSame(-12219292800.0, ((new UuidV1('00000000-0000-1000-a456-426655440000'))->getTime()));
$this->assertEquals(\DateTimeImmutable::createFromFormat('U.u', '103072857660.684697'), ((new UuidV1('ffffffff-ffff-1fff-a456-426655440000'))->getDateTime()));
$this->assertEquals(\DateTimeImmutable::createFromFormat('U.u', '0.000001'), ((new UuidV1('1381400a-1dd2-11b2-a456-426655440000'))->getDateTime()));
$this->assertEquals(new \DateTimeImmutable('@0'), (new UuidV1('13814001-1dd2-11b2-a456-426655440000'))->getDateTime());
$this->assertEquals(new \DateTimeImmutable('@0'), (new UuidV1('13814000-1dd2-11b2-a456-426655440000'))->getDateTime());
$this->assertEquals(new \DateTimeImmutable('@0'), (new UuidV1('13813fff-1dd2-11b2-a456-426655440000'))->getDateTime());
$this->assertEquals(\DateTimeImmutable::createFromFormat('U.u', '-0.000001'), ((new UuidV1('13813ff6-1dd2-11b2-a456-426655440000'))->getDateTime()));
$this->assertEquals(new \DateTimeImmutable('@-12219292800'), ((new UuidV1('00000000-0000-1000-a456-426655440000'))->getDateTime()));
}
}

View File

@ -104,24 +104,26 @@ class Ulid extends AbstractUid
return $this->uid;
}
/**
* @return float Seconds since the Unix epoch 1970-01-01 00:00:00
*/
public function getTime(): float
public function getDateTime(): \DateTimeImmutable
{
$time = strtr(substr($this->uid, 0, 10), 'ABCDEFGHJKMNPQRSTVWXYZ', 'abcdefghijklmnopqrstuv');
if (\PHP_INT_SIZE >= 8) {
return hexdec(base_convert($time, 32, 16)) / 1000;
}
$time = (string) hexdec(base_convert($time, 32, 16));
} else {
$time = sprintf('%02s%05s%05s',
base_convert(substr($time, 0, 2), 32, 16),
base_convert(substr($time, 2, 4), 32, 16),
base_convert(substr($time, 6, 4), 32, 16)
);
$time = BinaryUtil::toBase(hex2bin($time), BinaryUtil::BASE10);
}
return BinaryUtil::toBase(hex2bin($time), BinaryUtil::BASE10) / 1000;
if (4 > \strlen($time)) {
$time = str_pad($time, 4, '0', \STR_PAD_LEFT);
}
return \DateTimeImmutable::createFromFormat('U.u', substr_replace($time, '.', -3, 0));
}
private static function generate(): string

View File

@ -31,14 +31,9 @@ class UuidV1 extends Uuid
}
}
/**
* @return float Seconds since the Unix epoch 1970-01-01 00:00:00
*/
public function getTime(): float
public function getDateTime(): \DateTimeImmutable
{
$time = '0'.substr($this->uid, 15, 3).substr($this->uid, 9, 4).substr($this->uid, 0, 8);
return BinaryUtil::timeToFloat($time);
return BinaryUtil::timeToDateTime('0'.substr($this->uid, 15, 3).substr($this->uid, 9, 4).substr($this->uid, 0, 8));
}
public function getNode(): string

View File

@ -50,14 +50,9 @@ class UuidV6 extends Uuid
}
}
/**
* @return float Seconds since the Unix epoch 1970-01-01 00:00:00
*/
public function getTime(): float
public function getDateTime(): \DateTimeImmutable
{
$time = '0'.substr($this->uid, 0, 8).substr($this->uid, 9, 4).substr($this->uid, 15, 3);
return BinaryUtil::timeToFloat($time);
return BinaryUtil::timeToDateTime('0'.substr($this->uid, 0, 8).substr($this->uid, 9, 4).substr($this->uid, 15, 3));
}
public function getNode(): string