diff --git a/psalm-baseline.xml b/psalm-baseline.xml index b5f529e..cf70c9a 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,5 +1,5 @@ - + DegradedUuid @@ -16,12 +16,6 @@ $this - - - Uuid::UUID_TYPE_TIME - Uuid::UUID_TYPE_TIME - - $this @@ -44,14 +38,6 @@ BigNumberTimeConverter - - - DegradedUuid - - - Uuid - - $calculator @@ -73,20 +59,6 @@ uuid_parse - - - Uuid::RESERVED_MICROSOFT - Uuid::RFC_4122 - - - - - Guid - - - parent::__construct($fields, $numberConverter, $codec, $timeConverter) - - $this @@ -95,24 +67,10 @@ - - new UuidFactory() - - - LazyUuidFromString - unserialize - - - Uuid - - - parent::__construct($fields, $numberConverter, $codec, $timeConverter) - - $this @@ -120,17 +78,6 @@ $this - - - Uuid::UUID_TYPE_PEABODY - - - UuidV6 - - - parent::__construct($fields, $numberConverter, $codec, $timeConverter) - - shell_exec('id -g') @@ -149,20 +96,10 @@ - - Uuid::RFC_4122 - Uuid::UUID_TYPE_DCE_SECURITY + Uuid::UUID_TYPE_PEABODY - - - NilUuid - - - Uuid - - $this @@ -182,68 +119,10 @@ $this - - - Uuid::UUID_TYPE_TIME - - - UuidV1 - - - parent::__construct($fields, $numberConverter, $codec, $timeConverter) - - - - - Uuid::UUID_TYPE_DCE_SECURITY - - - UuidV2 - - - parent::__construct($fields, $numberConverter, $codec, $timeConverter) - - - - - Uuid::UUID_TYPE_HASH_MD5 - - - UuidV3 - - - parent::__construct($fields, $numberConverter, $codec, $timeConverter) - - - - - Uuid::UUID_TYPE_RANDOM - - - UuidV4 - - - parent::__construct($fields, $numberConverter, $codec, $timeConverter) - - - - - Uuid::UUID_TYPE_HASH_SHA1 - - - UuidV5 - - - parent::__construct($fields, $numberConverter, $codec, $timeConverter) - - - - - Uuid::RESERVED_FUTURE - Uuid::RESERVED_MICROSOFT - Uuid::RESERVED_NCS - Uuid::RFC_4122 - + + + NonstandardUuidV6 + @@ -266,36 +145,6 @@ - - new UuidFactory() - new UuidFactory() - - - Uuid - - - fromDateTime - getValidator - self::getFactory() - self::getFactory() - self::getFactory() - self::getFactory() - self::getFactory() - self::getFactory() - self::getFactory() - self::getFactory() - self::getFactory() - self::getFactory() - self::getFactory() - self::getFactory() - self::getFactory() - uuid1 - uuid2 - uuid3 - uuid4 - uuid5 - uuid6 - DeprecatedUuidMethodsTrait @@ -312,13 +161,6 @@ - - FeatureSet - new FeatureSet() - - - UuidFactory - $this->codec $this->codec diff --git a/src/Lazy/LazyUuidFromString.php b/src/Lazy/LazyUuidFromString.php index 1ff1662..d2a169f 100644 --- a/src/Lazy/LazyUuidFromString.php +++ b/src/Lazy/LazyUuidFromString.php @@ -18,8 +18,8 @@ use DateTimeInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Exception\UnsupportedOperationException; use Ramsey\Uuid\Fields\FieldsInterface; -use Ramsey\Uuid\Nonstandard\UuidV6; use Ramsey\Uuid\Rfc4122\UuidV1; +use Ramsey\Uuid\Rfc4122\UuidV6; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Integer as IntegerObject; use Ramsey\Uuid\UuidFactory; diff --git a/src/Nonstandard/UuidV6.php b/src/Nonstandard/UuidV6.php index 05586b3..8ea22fd 100644 --- a/src/Nonstandard/UuidV6.php +++ b/src/Nonstandard/UuidV6.php @@ -14,39 +14,34 @@ declare(strict_types=1); namespace Ramsey\Uuid\Nonstandard; -use DateTimeImmutable; -use DateTimeInterface; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\TimeConverterInterface; -use Ramsey\Uuid\Exception\DateTimeException; use Ramsey\Uuid\Exception\InvalidArgumentException; -use Ramsey\Uuid\Lazy\LazyUuidFromString; use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface; +use Ramsey\Uuid\Rfc4122\TimeTrait; use Ramsey\Uuid\Rfc4122\UuidInterface; -use Ramsey\Uuid\Rfc4122\UuidV1; +use Ramsey\Uuid\Rfc4122\UuidV6ConverterTrait; use Ramsey\Uuid\Uuid; -use Throwable; - -use function hex2bin; -use function str_pad; -use function substr; - -use const STR_PAD_LEFT; /** - * Ordered-time, or version 6, UUIDs include timestamp, clock sequence, and node - * values that are combined into a 128-bit unsigned integer + * Reordered-time, or version 6, UUIDs include timestamp, clock sequence, and + * node values that are combined into a 128-bit unsigned integer + * + * @deprecated Use {@see \Ramsey\Uuid\Rfc4122\UuidV6} instead. * * @link https://github.com/uuid6/uuid6-ietf-draft UUID version 6 IETF draft * @link http://gh.peabody.io/uuidv6/ "Version 6" UUIDs * * @psalm-immutable */ -final class UuidV6 extends Uuid implements UuidInterface +class UuidV6 extends Uuid implements UuidInterface { + use TimeTrait; + use UuidV6ConverterTrait; + /** - * Creates a version 6 (time-based) UUID + * Creates a version 6 (reordered-time) UUID * * @param Rfc4122FieldsInterface $fields The fields from which to construct a UUID * @param NumberConverterInterface $numberConverter The number converter to use @@ -62,7 +57,7 @@ final class UuidV6 extends Uuid implements UuidInterface CodecInterface $codec, TimeConverterInterface $timeConverter ) { - if ($fields->getVersion() !== Uuid::UUID_TYPE_PEABODY) { + if ($fields->getVersion() !== Uuid::UUID_TYPE_REORDERED_TIME) { throw new InvalidArgumentException( 'Fields used to create a UuidV6 must represent a ' . 'version 6 (ordered-time) UUID' @@ -71,63 +66,4 @@ final class UuidV6 extends Uuid implements UuidInterface parent::__construct($fields, $numberConverter, $codec, $timeConverter); } - - /** - * Returns a DateTimeInterface object representing the timestamp associated - * with the UUID - * - * @return DateTimeImmutable A PHP DateTimeImmutable instance representing - * the timestamp of a version 6 UUID - */ - public function getDateTime(): DateTimeInterface - { - $time = $this->timeConverter->convertTime($this->fields->getTimestamp()); - - try { - return new DateTimeImmutable( - '@' - . $time->getSeconds()->toString() - . '.' - . str_pad($time->getMicroseconds()->toString(), 6, '0', STR_PAD_LEFT) - ); - } catch (Throwable $e) { - throw new DateTimeException($e->getMessage(), (int) $e->getCode(), $e); - } - } - - /** - * Converts this UUID into an instance of a version 1 UUID - */ - public function toUuidV1(): UuidV1 - { - $hex = $this->getHex()->toString(); - $hex = substr($hex, 7, 5) - . substr($hex, 13, 3) - . substr($hex, 3, 4) - . '1' . substr($hex, 0, 3) - . substr($hex, 16); - - /** @var LazyUuidFromString $uuid */ - $uuid = Uuid::fromBytes((string) hex2bin($hex)); - - return $uuid->toUuidV1(); - } - - /** - * Converts a version 1 UUID into an instance of a version 6 UUID - */ - public static function fromUuidV1(UuidV1 $uuidV1): UuidV6 - { - $hex = $uuidV1->getHex()->toString(); - $hex = substr($hex, 13, 3) - . substr($hex, 8, 4) - . substr($hex, 0, 5) - . '6' . substr($hex, 5, 3) - . substr($hex, 16); - - /** @var LazyUuidFromString $uuid */ - $uuid = Uuid::fromBytes((string) hex2bin($hex)); - - return $uuid->toUuidV6(); - } } diff --git a/src/Rfc4122/TimeTrait.php b/src/Rfc4122/TimeTrait.php new file mode 100644 index 0000000..5d939fa --- /dev/null +++ b/src/Rfc4122/TimeTrait.php @@ -0,0 +1,55 @@ + + * @license http://opensource.org/licenses/MIT MIT + */ + +declare(strict_types=1); + +namespace Ramsey\Uuid\Rfc4122; + +use DateTimeImmutable; +use DateTimeInterface; +use Ramsey\Uuid\Exception\DateTimeException; +use Throwable; + +use function str_pad; + +use const STR_PAD_LEFT; + +/** + * Provides common functionality for getting the time from a time-based UUID + * + * @psalm-immutable + */ +trait TimeTrait +{ + /** + * Returns a DateTimeInterface object representing the timestamp associated + * with the UUID + * + * @return DateTimeImmutable A PHP DateTimeImmutable instance representing + * the timestamp of a time-based UUID + */ + public function getDateTime(): DateTimeInterface + { + $time = $this->timeConverter->convertTime($this->fields->getTimestamp()); + + try { + return new DateTimeImmutable( + '@' + . $time->getSeconds()->toString() + . '.' + . str_pad($time->getMicroseconds()->toString(), 6, '0', STR_PAD_LEFT) + ); + } catch (Throwable $e) { + throw new DateTimeException($e->getMessage(), (int) $e->getCode(), $e); + } + } +} diff --git a/src/Rfc4122/UuidBuilder.php b/src/Rfc4122/UuidBuilder.php index 736931a..df7ab30 100644 --- a/src/Rfc4122/UuidBuilder.php +++ b/src/Rfc4122/UuidBuilder.php @@ -20,7 +20,6 @@ use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Exception\UnableToBuildUuidException; use Ramsey\Uuid\Exception\UnsupportedOperationException; -use Ramsey\Uuid\Nonstandard\UuidV6; use Ramsey\Uuid\Rfc4122\UuidInterface as Rfc4122UuidInterface; use Ramsey\Uuid\UuidInterface; use Throwable; diff --git a/src/Rfc4122/UuidV1.php b/src/Rfc4122/UuidV1.php index 764e42f..8a9cad2 100644 --- a/src/Rfc4122/UuidV1.php +++ b/src/Rfc4122/UuidV1.php @@ -14,20 +14,12 @@ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; -use DateTimeImmutable; -use DateTimeInterface; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\TimeConverterInterface; -use Ramsey\Uuid\Exception\DateTimeException; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface; use Ramsey\Uuid\Uuid; -use Throwable; - -use function str_pad; - -use const STR_PAD_LEFT; /** * Time-based, or version 1, UUIDs include timestamp, clock sequence, and node @@ -37,6 +29,8 @@ use const STR_PAD_LEFT; */ final class UuidV1 extends Uuid implements UuidInterface { + use TimeTrait; + /** * Creates a version 1 (time-based) UUID * @@ -63,30 +57,4 @@ final class UuidV1 extends Uuid implements UuidInterface parent::__construct($fields, $numberConverter, $codec, $timeConverter); } - - /** - * Returns a DateTimeInterface object representing the timestamp associated - * with the UUID - * - * The timestamp value is only meaningful in a time-based UUID, which - * has version type 1. - * - * @return DateTimeImmutable A PHP DateTimeImmutable instance representing - * the timestamp of a version 1 UUID - */ - public function getDateTime(): DateTimeInterface - { - $time = $this->timeConverter->convertTime($this->fields->getTimestamp()); - - try { - return new DateTimeImmutable( - '@' - . $time->getSeconds()->toString() - . '.' - . str_pad($time->getMicroseconds()->toString(), 6, '0', STR_PAD_LEFT) - ); - } catch (Throwable $e) { - throw new DateTimeException($e->getMessage(), (int) $e->getCode(), $e); - } - } } diff --git a/src/Rfc4122/UuidV2.php b/src/Rfc4122/UuidV2.php index 74906f0..c8ccbe4 100644 --- a/src/Rfc4122/UuidV2.php +++ b/src/Rfc4122/UuidV2.php @@ -14,28 +14,33 @@ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; -use DateTimeImmutable; -use DateTimeInterface; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\TimeConverterInterface; -use Ramsey\Uuid\Exception\DateTimeException; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface; use Ramsey\Uuid\Type\Integer as IntegerObject; use Ramsey\Uuid\Uuid; -use Throwable; use function hexdec; -use function str_pad; - -use const STR_PAD_LEFT; /** * DCE Security version, or version 2, UUIDs include local domain identifier, * local ID for the specified domain, and node values that are combined into a * 128-bit unsigned integer * + * It is important to note that a version 2 UUID suffers from some loss of + * fidelity of the timestamp, due to replacing the time_low field with the + * local identifier. When constructing the timestamp value for date + * purposes, we replace the local identifier bits with zeros. As a result, + * the timestamp can be off by a range of 0 to 429.4967295 seconds (or 7 + * minutes, 9 seconds, and 496730 microseconds). + * + * Astute observers might note this value directly corresponds to 2^32 - 1, + * or 0xffffffff. The local identifier is 32-bits, and we have set each of + * these bits to 0, so the maximum range of timestamp drift is 0x00000000 + * to 0xffffffff (counted in 100-nanosecond intervals). + * * @link https://publications.opengroup.org/c311 DCE 1.1: Authentication and Security Services * @link https://publications.opengroup.org/c706 DCE 1.1: Remote Procedure Call * @link https://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#tagcjh_08_02_01_01 DCE 1.1: Auth & Sec, ยง5.2.1.1 @@ -47,6 +52,8 @@ use const STR_PAD_LEFT; */ final class UuidV2 extends Uuid implements UuidInterface { + use TimeTrait; + /** * Creates a version 2 (DCE Security) UUID * @@ -74,41 +81,6 @@ final class UuidV2 extends Uuid implements UuidInterface parent::__construct($fields, $numberConverter, $codec, $timeConverter); } - /** - * Returns a DateTimeInterface object representing the timestamp associated - * with the UUID - * - * It is important to note that a version 2 UUID suffers from some loss of - * fidelity of the timestamp, due to replacing the time_low field with the - * local identifier. When constructing the timestamp value for date - * purposes, we replace the local identifier bits with zeros. As a result, - * the timestamp can be off by a range of 0 to 429.4967295 seconds (or 7 - * minutes, 9 seconds, and 496730 microseconds). - * - * Astute observers might note this value directly corresponds to 2^32 - 1, - * or 0xffffffff. The local identifier is 32-bits, and we have set each of - * these bits to 0, so the maximum range of timestamp drift is 0x00000000 - * to 0xffffffff (counted in 100-nanosecond intervals). - * - * @return DateTimeImmutable A PHP DateTimeImmutable instance representing - * the timestamp of a version 2 UUID - */ - public function getDateTime(): DateTimeInterface - { - $time = $this->timeConverter->convertTime($this->fields->getTimestamp()); - - try { - return new DateTimeImmutable( - '@' - . $time->getSeconds()->toString() - . '.' - . str_pad($time->getMicroseconds()->toString(), 6, '0', STR_PAD_LEFT) - ); - } catch (Throwable $e) { - throw new DateTimeException($e->getMessage(), (int) $e->getCode(), $e); - } - } - /** * Returns the local domain used to create this version 2 UUID */ diff --git a/src/Rfc4122/UuidV6.php b/src/Rfc4122/UuidV6.php new file mode 100644 index 0000000..8b8c4b7 --- /dev/null +++ b/src/Rfc4122/UuidV6.php @@ -0,0 +1,29 @@ + + * @license http://opensource.org/licenses/MIT MIT + */ + +declare(strict_types=1); + +namespace Ramsey\Uuid\Rfc4122; + +use Ramsey\Uuid\Nonstandard\UuidV6 as NonstandardUuidV6; + +/** + * Reordered-time, or version 6, UUIDs include timestamp, clock sequence, and + * node values that are combined into a 128-bit unsigned integer + * + * @link https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-04#section-5.1 UUID Version 6 + * + * @psalm-immutable + */ +final class UuidV6 extends NonstandardUuidV6 implements UuidInterface +{ +} diff --git a/src/Rfc4122/UuidV6ConverterTrait.php b/src/Rfc4122/UuidV6ConverterTrait.php new file mode 100644 index 0000000..cca0cff --- /dev/null +++ b/src/Rfc4122/UuidV6ConverterTrait.php @@ -0,0 +1,62 @@ + + * @license http://opensource.org/licenses/MIT MIT + */ + +declare(strict_types=1); + +namespace Ramsey\Uuid\Rfc4122; + +use Ramsey\Uuid\Lazy\LazyUuidFromString; +use Ramsey\Uuid\Uuid; + +/** + * Provides functionality to convert between UuidV1 and UuidV6 + * + * @psalm-immutable + */ +trait UuidV6ConverterTrait +{ + /** + * Converts this UUID into an instance of a version 1 UUID + */ + public function toUuidV1(): UuidV1 + { + $hex = $this->getHex()->toString(); + $hex = substr($hex, 7, 5) + . substr($hex, 13, 3) + . substr($hex, 3, 4) + . '1' . substr($hex, 0, 3) + . substr($hex, 16); + + /** @var LazyUuidFromString $uuid */ + $uuid = Uuid::fromBytes((string) hex2bin($hex)); + + return $uuid->toUuidV1(); + } + + /** + * Converts a version 1 UUID into an instance of a version 6 UUID + */ + public static function fromUuidV1(UuidV1 $uuidV1): UuidV6 + { + $hex = $uuidV1->getHex()->toString(); + $hex = substr($hex, 13, 3) + . substr($hex, 8, 4) + . substr($hex, 0, 5) + . '6' . substr($hex, 5, 3) + . substr($hex, 16); + + /** @var LazyUuidFromString $uuid */ + $uuid = Uuid::fromBytes((string) hex2bin($hex)); + + return $uuid->toUuidV6(); + } +} diff --git a/src/Uuid.php b/src/Uuid.php index 019cec8..3a7c8ec 100644 --- a/src/Uuid.php +++ b/src/Uuid.php @@ -156,19 +156,25 @@ class Uuid implements UuidInterface public const UUID_TYPE_HASH_SHA1 = 5; /** - * Version 6 (ordered-time) UUID + * Version 6 (reordered-time) UUID * * This is named `UUID_TYPE_PEABODY`, since the specification is still in * draft form, and the primary author/editor's name is Brad Peabody. * - * @deprecated The Ramsey\Uuid\Version enum will replace this constant in - * ramsey/uuid version 5. + * @deprecated Use {@see Uuid::UUID_TYPE_REORDERED_TIME} instead. * * @link https://github.com/uuid6/uuid6-ietf-draft UUID version 6 IETF draft * @link http://gh.peabody.io/uuidv6/ "Version 6" UUIDs */ public const UUID_TYPE_PEABODY = 6; + /** + * Version 6 (reordered-time) UUID + * + * @link https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-04#section-5.1 UUID Version 6 + */ + public const UUID_TYPE_REORDERED_TIME = 6; + /** * DCE Security principal domain * diff --git a/tests/Builder/FallbackBuilderTest.php b/tests/Builder/FallbackBuilderTest.php index 7fe2adb..a601ee6 100644 --- a/tests/Builder/FallbackBuilderTest.php +++ b/tests/Builder/FallbackBuilderTest.php @@ -18,10 +18,10 @@ use Ramsey\Uuid\Exception\UnableToBuildUuidException; use Ramsey\Uuid\Guid\GuidBuilder; use Ramsey\Uuid\Math\BrickMathCalculator; use Ramsey\Uuid\Nonstandard\UuidBuilder as NonstandardUuidBuilder; -use Ramsey\Uuid\Nonstandard\UuidV6; use Ramsey\Uuid\Rfc4122\UuidBuilder as Rfc4122UuidBuilder; use Ramsey\Uuid\Rfc4122\UuidV1; use Ramsey\Uuid\Rfc4122\UuidV2; +use Ramsey\Uuid\Rfc4122\UuidV6; use Ramsey\Uuid\Test\TestCase; class FallbackBuilderTest extends TestCase diff --git a/tests/Rfc4122/UuidBuilderTest.php b/tests/Rfc4122/UuidBuilderTest.php index 9e39067..e234fc3 100644 --- a/tests/Rfc4122/UuidBuilderTest.php +++ b/tests/Rfc4122/UuidBuilderTest.php @@ -10,7 +10,7 @@ use Ramsey\Uuid\Converter\Number\GenericNumberConverter; use Ramsey\Uuid\Converter\Time\GenericTimeConverter; use Ramsey\Uuid\Exception\UnableToBuildUuidException; use Ramsey\Uuid\Math\BrickMathCalculator; -use Ramsey\Uuid\Nonstandard\UuidV6; +use Ramsey\Uuid\Nonstandard\UuidV6 as NonstandardUuidV6; use Ramsey\Uuid\Rfc4122\Fields; use Ramsey\Uuid\Rfc4122\FieldsInterface; use Ramsey\Uuid\Rfc4122\UuidBuilder; @@ -19,6 +19,7 @@ use Ramsey\Uuid\Rfc4122\UuidV2; use Ramsey\Uuid\Rfc4122\UuidV3; use Ramsey\Uuid\Rfc4122\UuidV4; use Ramsey\Uuid\Rfc4122\UuidV5; +use Ramsey\Uuid\Rfc4122\UuidV6; use Ramsey\Uuid\Test\TestCase; use function hex2bin; @@ -86,6 +87,14 @@ class UuidBuilderTest extends TestCase 'expectedClass' => UuidV6::class, 'expectedVersion' => 6, ], + + // The same UUIDv6 will also be of the expected class type + // \Ramsey\Uuid\Nonstandard\UuidV6. + [ + 'uuid' => 'ff6f8cb0-c57d-61e1-9b21-0800200c9a66', + 'expectedClass' => NonstandardUuidV6::class, + 'expectedVersion' => 6, + ], ]; } diff --git a/tests/Rfc4122/UuidV6Test.php b/tests/Rfc4122/UuidV6Test.php new file mode 100644 index 0000000..1c72528 --- /dev/null +++ b/tests/Rfc4122/UuidV6Test.php @@ -0,0 +1,222 @@ + $version, + ]); + + $numberConverter = Mockery::mock(NumberConverterInterface::class); + $codec = Mockery::mock(CodecInterface::class); + $timeConverter = Mockery::mock(TimeConverterInterface::class); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage( + 'Fields used to create a UuidV6 must represent a ' + . 'version 6 (ordered-time) UUID' + ); + + new UuidV6($fields, $numberConverter, $codec, $timeConverter); + } + + /** + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingTraversableTypeHintSpecification + */ + public function provideTestVersions(): array + { + return [ + ['version' => 0], + ['version' => 1], + ['version' => 2], + ['version' => 3], + ['version' => 4], + ['version' => 5], + ['version' => 7], + ['version' => 8], + ['version' => 9], + ]; + } + + /** + * @param non-empty-string $uuid + * + * @dataProvider provideUuidV6WithOddMicroseconds + */ + public function testGetDateTimeProperlyHandlesLongMicroseconds(string $uuid, string $expected): void + { + /** @var UuidV6 $object */ + $object = Uuid::fromString($uuid); + + $date = $object->getDateTime(); + + $this->assertInstanceOf(DateTimeImmutable::class, $date); + $this->assertSame($expected, $date->format('U.u')); + } + + /** + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingTraversableTypeHintSpecification + */ + public function provideUuidV6WithOddMicroseconds(): array + { + return [ + [ + 'uuid' => '1b21dd21-4814-6000-9669-00007ffffffe', + 'expected' => '1.677722', + ], + [ + 'uuid' => '1b21dd21-3714-6000-9669-00007ffffffe', + 'expected' => '0.104858', + ], + [ + 'uuid' => '1b21dd21-3713-6000-9669-00007ffffffe', + 'expected' => '0.105267', + ], + [ + 'uuid' => '1b21dd21-2e8a-6980-8d4f-acde48001122', + 'expected' => '-1.000000', + ], + ]; + } + + /** + * @param non-empty-string $uuidv6 + * @param non-empty-string $uuidv1 + * + * @dataProvider provideUuidV1UuidV6Equivalents + */ + public function testToUuidV1(string $uuidv6, string $uuidv1): void + { + /** @var UuidV6 $uuid6 */ + $uuid6 = Uuid::fromString($uuidv6); + $uuid1 = $uuid6->toUuidV1(); + + $this->assertSame($uuidv6, $uuid6->toString()); + $this->assertSame($uuidv1, $uuid1->toString()); + + $this->assertSame( + $uuid6->getDateTime()->format('U.u'), + $uuid1->getDateTime()->format('U.u') + ); + } + + /** + * @param non-empty-string $uuidv6 + * @param non-empty-string $uuidv1 + * + * @dataProvider provideUuidV1UuidV6Equivalents + */ + public function testFromUuidV1(string $uuidv6, string $uuidv1): void + { + /** @var LazyUuidFromString $uuid */ + $uuid = Uuid::fromString($uuidv1); + $uuid1 = $uuid->toUuidV1(); + $uuid6 = UuidV6::fromUuidV1($uuid1); + + $this->assertSame($uuidv1, $uuid1->toString()); + $this->assertSame($uuidv6, $uuid6->toString()); + + $this->assertSame( + $uuid1->getDateTime()->format('U.u'), + $uuid6->getDateTime()->format('U.u') + ); + } + + /** + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingTraversableTypeHintSpecification + */ + public function provideUuidV1UuidV6Equivalents(): array + { + return [ + [ + 'uuidv6' => '1b21dd21-4814-6000-9669-00007ffffffe', + 'uuidv1' => '14814000-1dd2-11b2-9669-00007ffffffe', + ], + [ + 'uuidv6' => '1b21dd21-3714-6000-9669-00007ffffffe', + 'uuidv1' => '13714000-1dd2-11b2-9669-00007ffffffe', + ], + [ + 'uuidv6' => '1b21dd21-3713-6000-9669-00007ffffffe', + 'uuidv1' => '13713000-1dd2-11b2-9669-00007ffffffe', + ], + [ + 'uuidv6' => '1b21dd21-2e8a-6980-8d4f-acde48001122', + 'uuidv1' => '12e8a980-1dd2-11b2-8d4f-acde48001122', + ], + ]; + } + + public function testGetDateTimeThrowsException(): void + { + $fields = Mockery::mock(FieldsInterface::class, [ + 'getVersion' => 6, + 'getTimestamp' => new Hexadecimal('0'), + ]); + + $numberConverter = Mockery::mock(NumberConverterInterface::class); + $codec = Mockery::mock(CodecInterface::class); + + $timeConverter = Mockery::mock(TimeConverterInterface::class, [ + 'convertTime' => new Time('0', '1234567'), + ]); + + $uuid = new UuidV6($fields, $numberConverter, $codec, $timeConverter); + + $this->expectException(DateTimeException::class); + + $uuid->getDateTime(); + } + + /** + * @link https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-04#appendix-B.1 + */ + public function testUsingDraftPeabodyUuidV6TestVector(): void + { + $testVector = '1EC9414C-232A-6B00-B3C8-9E6BDECED846'; + + /** @var UuidV6 $uuidv6 */ + $uuidv6 = Uuid::fromString($testVector); + + $uuidv1 = $uuidv6->toUuidV1(); + + /** @var FieldsInterface $fields */ + $fields = $uuidv6->getFields(); + + $this->assertSame('1ec9414c', $fields->getTimeLow()->toString()); + $this->assertSame('232a', $fields->getTimeMid()->toString()); + $this->assertSame('6b00', $fields->getTimeHiAndVersion()->toString()); + $this->assertSame('b3', $fields->getClockSeqHiAndReserved()->toString()); + $this->assertSame('c8', $fields->getClockSeqLow()->toString()); + $this->assertSame('9e6bdeced846', $fields->getNode()->toString()); + $this->assertSame(1645557742, $uuidv6->getDateTime()->getTimestamp()); + + $this->assertSame( + 'c232ab00-9414-11ec-b3c8-9e6bdeced846', + $uuidv1->toString(), + ); + } +}