fix: max UUID should be variant 7, nil UUID should be variant 0

This commit is contained in:
Ben Ramsey
2025-05-31 20:07:13 -05:00
parent fe83f84089
commit b21cb6d655
7 changed files with 25 additions and 26 deletions
+2
View File
@@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
* Hexadecimal is never an empty string; fixed in [#593](https://github.com/ramsey/uuid/pull/593).
* Update call to `str_getcsv()` to avoid deprecation notice in PHP 8.4; fixed in [#590](https://github.com/ramsey/uuid/pull/590).
* Update docblocks for `Uuid::fromBytes()`, `Uuid::fromString()`, `Uuid::fromDateTime()`, `Uuid::fromHexadecimal()`, and `Uuid::fromInteger()` to note that each can throw `InvalidArgumentException`, addressing PHPStan errors occurring at call sites; fixed in [#552](https://github.com/ramsey/uuid/pull/552).
* `getVariant()` for `MaxUuid` now correctly returns `Uuid::RESERVED_FUTURE`, as specified in [RFC 9562, section 5.10](https://www.rfc-editor.org/rfc/rfc9562#section-5.10).
* `getVariant()` for `NilUuid` now correctly returns `Uuid::RESERVED_NCS`, as specified in [RFC 9562, section 5.9](https://www.rfc-editor.org/rfc/rfc9562#section-5.9).
### Deprecated
+2 -2
View File
@@ -42,13 +42,13 @@ class UuidBuilder implements UuidBuilderInterface
* @param NumberConverterInterface $numberConverter The number converter to use when constructing the Uuid
* @param TimeConverterInterface $timeConverter The time converter to use for converting Gregorian time extracted
* from version 1, 2, and 6 UUIDs to Unix timestamps
* @param TimeConverterInterface|null $unixTimeConverter The time converter to use for converter Unix Epoch time
* @param TimeConverterInterface | null $unixTimeConverter The time converter to use for converter Unix Epoch time
* extracted from version 7 UUIDs to Unix timestamps
*/
public function __construct(
private NumberConverterInterface $numberConverter,
private TimeConverterInterface $timeConverter,
?TimeConverterInterface $unixTimeConverter = null
?TimeConverterInterface $unixTimeConverter = null,
) {
$this->unixTimeConverter = $unixTimeConverter ?? new UnixTimeConverter(new BrickMathCalculator());
}
+12 -2
View File
@@ -58,8 +58,18 @@ trait VariantTrait
throw new InvalidBytesException('Invalid number of bytes');
}
if ($this->isMax() || $this->isNil()) {
return Uuid::RFC_4122;
// According to RFC 9562, sections {@link https://www.rfc-editor.org/rfc/rfc9562#section-4.1 4.1} and
// {@link https://www.rfc-editor.org/rfc/rfc9562#section-5.10 5.10}, the Max UUID falls within the range
// of the future variant.
if ($this->isMax()) {
return Uuid::RESERVED_FUTURE;
}
// According to RFC 9562, sections {@link https://www.rfc-editor.org/rfc/rfc9562#section-4.1 4.1} and
// {@link https://www.rfc-editor.org/rfc/rfc9562#section-5.9 5.9}, the Nil UUID falls within the range
// of the Apollo NCS variant.
if ($this->isNil()) {
return Uuid::RESERVED_NCS;
}
/** @var int[] $parts */
+2 -2
View File
@@ -380,7 +380,7 @@ class ExpectedBehaviorTest extends TestCase
public function provideFromStringInteger()
{
return [
['00000000-0000-0000-0000-000000000000', null, 2, '0'],
['00000000-0000-0000-0000-000000000000', null, 0, '0'],
['ff6f8cb0-c57d-11e1-8b21-0800200c9a66', 1, 2, '339532337419071774304650190139318639206'],
['ff6f8cb0-c57d-11e1-9b21-0800200c9a66', 1, 2, '339532337419071774305803111643925486182'],
['ff6f8cb0-c57d-11e1-ab21-0800200c9a66', 1, 2, '339532337419071774306956033148532333158'],
@@ -413,7 +413,7 @@ class ExpectedBehaviorTest extends TestCase
['ff6f8cb0-c57d-01e1-db21-0800200c9a66', null, 6, '339532337419071698752551071748029454950'],
['ff6f8cb0-c57d-01e1-eb21-0800200c9a66', null, 7, '339532337419071698753703993252636301926'],
['ff6f8cb0-c57d-01e1-fb21-0800200c9a66', null, 7, '339532337419071698754856914757243148902'],
['ffffffff-ffff-ffff-ffff-ffffffffffff', null, 2, '340282366920938463463374607431768211455'],
['ffffffff-ffff-ffff-ffff-ffffffffffff', null, 7, '340282366920938463463374607431768211455'],
];
}
+2 -2
View File
@@ -201,7 +201,7 @@ class FieldsTest extends TestCase
['00000000000000000000000000000000', 'getTimeLow', '00000000'],
['00000000000000000000000000000000', 'getTimeMid', '0000'],
['00000000000000000000000000000000', 'getTimestamp', '000000000000000'],
['00000000000000000000000000000000', 'getVariant', 2],
['00000000000000000000000000000000', 'getVariant', 0],
['00000000000000000000000000000000', 'getVersion', null],
['00000000000000000000000000000000', 'isNil', true],
['00000000000000000000000000000000', 'isMax', false],
@@ -215,7 +215,7 @@ class FieldsTest extends TestCase
['ffffffffffffffffffffffffffffffff', 'getTimeLow', 'ffffffff'],
['ffffffffffffffffffffffffffffffff', 'getTimeMid', 'ffff'],
['ffffffffffffffffffffffffffffffff', 'getTimestamp', 'fffffffffffffff'],
['ffffffffffffffffffffffffffffffff', 'getVariant', 2],
['ffffffffffffffffffffffffffffffff', 'getVariant', 7],
['ffffffffffffffffffffffffffffffff', 'getVersion', null],
['ffffffffffffffffffffffffffffffff', 'isNil', false],
['ffffffffffffffffffffffffffffffff', 'isMax', true],
+2 -2
View File
@@ -201,7 +201,7 @@ class FieldsTest extends TestCase
['00000000-0000-0000-0000-000000000000', 'getTimeLow', '00000000'],
['00000000-0000-0000-0000-000000000000', 'getTimeMid', '0000'],
['00000000-0000-0000-0000-000000000000', 'getTimestamp', '000000000000000'],
['00000000-0000-0000-0000-000000000000', 'getVariant', 2],
['00000000-0000-0000-0000-000000000000', 'getVariant', 0],
['00000000-0000-0000-0000-000000000000', 'getVersion', null],
['00000000-0000-0000-0000-000000000000', 'isNil', true],
['00000000-0000-0000-0000-000000000000', 'isMax', false],
@@ -214,7 +214,7 @@ class FieldsTest extends TestCase
['ffffffff-ffff-ffff-ffff-ffffffffffff', 'getTimeLow', 'ffffffff'],
['ffffffff-ffff-ffff-ffff-ffffffffffff', 'getTimeMid', 'ffff'],
['ffffffff-ffff-ffff-ffff-ffffffffffff', 'getTimestamp', 'fffffffffffffff'],
['ffffffff-ffff-ffff-ffff-ffffffffffff', 'getVariant', 2],
['ffffffff-ffff-ffff-ffff-ffffffffffff', 'getVariant', 7],
['ffffffff-ffff-ffff-ffff-ffffffffffff', 'getVersion', null],
['ffffffff-ffff-ffff-ffff-ffffffffffff', 'isNil', false],
['ffffffff-ffff-ffff-ffff-ffffffffffff', 'isMax', true],
+3 -16
View File
@@ -1436,13 +1436,7 @@ class UuidTest extends TestCase
'urn' => 'urn:uuid:00000000-0000-0000-0000-000000000000',
'time' => '0',
'clock_seq' => '0000',
// This is a departure from the Python tests. The Python tests
// are technically "correct" because all bits are set to zero,
// so it stands to reason that the variant is also zero, but
// that leads to this being considered a "Reserved NCS" variant,
// and that is not the case. RFC 4122 defines this special UUID,
// so it is an RFC 4122 variant.
'variant' => Uuid::RFC_4122,
'variant' => Uuid::RESERVED_NCS,
'version' => null,
],
[
@@ -1701,20 +1695,13 @@ class UuidTest extends TestCase
],
'urn' => 'urn:uuid:ffffffff-ffff-ffff-ffff-ffffffffffff',
'time' => 'fffffffffffffff',
// This is a departure from the Python tests. The Python tests
// are technically "correct" because all bits are set to one,
// which ends up calculating the variant as 7, or "Reserved
// Future," but that is not the case, and now that max UUIDs
// are defined as a special type, within the RFC 4122 variant
// rules, we also consider it an RFC 4122 variant.
//
// Similarly, Python's tests think the clock sequence should be
// Python's tests think the clock sequence should be
// 0x3fff because of the bit shifting performed on this field.
// However, since all the bits in this UUID are defined as being
// set to one, we will consider the clock sequence as 0xffff,
// which all bits set to one.
'clock_seq' => 'ffff',
'variant' => Uuid::RFC_4122,
'variant' => Uuid::RESERVED_FUTURE,
'version' => null,
],
];