feat: support version 7 (Unix Epoch time) UUIDs

This commit is contained in:
Ben Ramsey
2022-09-13 22:13:33 -05:00
parent e153b3420a
commit aa1e488afa
20 changed files with 784 additions and 42 deletions
+3 -6
View File
@@ -95,13 +95,10 @@
<code>$macs[]</code>
</MixedArrayAssignment>
</file>
<file src="src/Rfc4122/Fields.php">
<DeprecatedConstant occurrences="1">
<code>Uuid::UUID_TYPE_PEABODY</code>
</DeprecatedConstant>
</file>
<file src="src/Rfc4122/UuidBuilder.php">
<ImpureVariable occurrences="15">
<ImpureVariable occurrences="17">
<code>$this</code>
<code>$this</code>
<code>$this</code>
<code>$this</code>
<code>$this</code>
+93
View File
@@ -0,0 +1,93 @@
<?php
/**
* This file is part of the ramsey/uuid library
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
* @license http://opensource.org/licenses/MIT MIT
*/
declare(strict_types=1);
namespace Ramsey\Uuid\Converter\Time;
use Ramsey\Uuid\Converter\TimeConverterInterface;
use Ramsey\Uuid\Math\CalculatorInterface;
use Ramsey\Uuid\Math\RoundingMode;
use Ramsey\Uuid\Type\Hexadecimal;
use Ramsey\Uuid\Type\Integer as IntegerObject;
use Ramsey\Uuid\Type\Time;
use function explode;
use function str_pad;
use const STR_PAD_LEFT;
/**
* UnixTimeConverter converts Unix Epoch timestamps to/from hexadecimal values
* consisting of milliseconds elapsed since the Unix Epoch
*
* @psalm-immutable
*/
class UnixTimeConverter implements TimeConverterInterface
{
private const MILLISECONDS = 1000;
private CalculatorInterface $calculator;
public function __construct(CalculatorInterface $calculator)
{
$this->calculator = $calculator;
}
public function calculateTime(string $seconds, string $microseconds): Hexadecimal
{
$timestamp = new Time($seconds, $microseconds);
// Convert the seconds into milliseconds.
$sec = $this->calculator->multiply(
$timestamp->getSeconds(),
new IntegerObject(self::MILLISECONDS),
);
// Convert the microseconds into milliseconds; the scale is zero because
// we need to discard the fractional part.
$usec = $this->calculator->divide(
RoundingMode::DOWN, // Always round down to stay in the previous millisecond.
0,
$timestamp->getMicroseconds(),
new IntegerObject(self::MILLISECONDS),
);
/** @var IntegerObject $unixTime */
$unixTime = $this->calculator->add($sec, $usec);
$unixTimeHex = str_pad(
$this->calculator->toHexadecimal($unixTime)->toString(),
12,
'0',
STR_PAD_LEFT
);
return new Hexadecimal($unixTimeHex);
}
public function convertTime(Hexadecimal $uuidTimestamp): Time
{
$milliseconds = $this->calculator->toInteger($uuidTimestamp);
$unixTimestamp = $this->calculator->divide(
RoundingMode::HALF_UP,
6,
$milliseconds,
new IntegerObject(self::MILLISECONDS)
);
$split = explode('.', (string) $unixTimestamp, 2);
return new Time($split[0], $split[1] ?? '0');
}
}
+31 -1
View File
@@ -23,6 +23,7 @@ use Ramsey\Uuid\Converter\Number\GenericNumberConverter;
use Ramsey\Uuid\Converter\NumberConverterInterface;
use Ramsey\Uuid\Converter\Time\GenericTimeConverter;
use Ramsey\Uuid\Converter\Time\PhpTimeConverter;
use Ramsey\Uuid\Converter\Time\UnixTimeConverter;
use Ramsey\Uuid\Converter\TimeConverterInterface;
use Ramsey\Uuid\Generator\DceSecurityGenerator;
use Ramsey\Uuid\Generator\DceSecurityGeneratorInterface;
@@ -35,6 +36,7 @@ use Ramsey\Uuid\Generator\RandomGeneratorFactory;
use Ramsey\Uuid\Generator\RandomGeneratorInterface;
use Ramsey\Uuid\Generator\TimeGeneratorFactory;
use Ramsey\Uuid\Generator\TimeGeneratorInterface;
use Ramsey\Uuid\Generator\UnixTimeGenerator;
use Ramsey\Uuid\Guid\GuidBuilder;
use Ramsey\Uuid\Math\BrickMathCalculator;
use Ramsey\Uuid\Math\CalculatorInterface;
@@ -141,6 +143,8 @@ class FeatureSet
*/
private $calculator;
private TimeGeneratorInterface $unixTimeGenerator;
/**
* @param bool $useGuids True build UUIDs using the GuidStringCodec
* @param bool $force32Bit True to force the use of 32-bit functionality
@@ -164,15 +168,18 @@ class FeatureSet
$this->ignoreSystemNode = $ignoreSystemNode;
$this->enablePecl = $enablePecl;
$this->randomGenerator = $this->buildRandomGenerator();
$this->setCalculator(new BrickMathCalculator());
$this->builder = $this->buildUuidBuilder($useGuids);
$this->codec = $this->buildCodec($useGuids);
$this->nodeProvider = $this->buildNodeProvider();
$this->nameGenerator = $this->buildNameGenerator();
$this->randomGenerator = $this->buildRandomGenerator();
$this->setTimeProvider(new SystemTimeProvider());
$this->setDceSecurityProvider(new SystemDceSecurityProvider());
$this->validator = new GenericValidator();
assert($this->timeProvider !== null);
$this->unixTimeGenerator = $this->buildUnixTimeGenerator($this->timeProvider);
}
/**
@@ -255,6 +262,14 @@ class FeatureSet
return $this->timeGenerator;
}
/**
* Returns the Unix Epoch time generator configured for this environment
*/
public function getUnixTimeGenerator(): TimeGeneratorInterface
{
return $this->unixTimeGenerator;
}
/**
* Returns the validator configured for this environment
*/
@@ -396,6 +411,21 @@ class FeatureSet
))->getGenerator();
}
/**
* Returns a Unix Epoch time generator configured for this environment
*
* @param TimeProviderInterface $timeProvider The time provider to use with
* the time generator
*/
private function buildUnixTimeGenerator(TimeProviderInterface $timeProvider): TimeGeneratorInterface
{
return new UnixTimeGenerator(
new UnixTimeConverter(new BrickMathCalculator()),
$timeProvider,
$this->randomGenerator,
);
}
/**
* Returns a name generator configured for this environment
*/
+59
View File
@@ -0,0 +1,59 @@
<?php
/**
* This file is part of the ramsey/uuid library
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
* @license http://opensource.org/licenses/MIT MIT
*/
declare(strict_types=1);
namespace Ramsey\Uuid\Generator;
use Ramsey\Uuid\Converter\TimeConverterInterface;
use Ramsey\Uuid\Provider\TimeProviderInterface;
use function hex2bin;
/**
* UnixTimeGenerator generates bytes that combine a 48-bit timestamp in
* milliseconds since the Unix Epoch with 80 random bits
*/
class UnixTimeGenerator implements TimeGeneratorInterface
{
private RandomGeneratorInterface $randomGenerator;
private TimeConverterInterface $timeConverter;
private TimeProviderInterface $timeProvider;
public function __construct(
TimeConverterInterface $timeConverter,
TimeProviderInterface $timeProvider,
RandomGeneratorInterface $randomGenerator
) {
$this->timeConverter = $timeConverter;
$this->timeProvider = $timeProvider;
$this->randomGenerator = $randomGenerator;
}
/**
* @inheritDoc
*/
public function generate($node = null, ?int $clockSeq = null): string
{
// Generate 10 random bytes to append to the string returned, since our
// time bytes will consist of 6 bytes.
$random = $this->randomGenerator->generate(10);
$time = $this->timeProvider->getTime();
$unixTimeHex = $this->timeConverter->calculateTime(
$time->getSeconds()->toString(),
$time->getMicroseconds()->toString(),
);
return hex2bin($unixTimeHex->toString()) . $random;
}
}
+12 -1
View File
@@ -150,7 +150,7 @@ final class Fields implements FieldsInterface
);
break;
case Uuid::UUID_TYPE_PEABODY:
case Uuid::UUID_TYPE_REORDERED_TIME:
$timestamp = sprintf(
'%08s%04s%03x',
$this->getTimeLow()->toString(),
@@ -158,6 +158,17 @@ final class Fields implements FieldsInterface
hexdec($this->getTimeHiAndVersion()->toString()) & 0x0fff
);
break;
case Uuid::UUID_TYPE_UNIX_TIME:
// The Unix timestamp in version 7 UUIDs is a 48-bit number,
// but for consistency, we will return a 60-bit number, padded
// to the left with zeros.
$timestamp = sprintf(
'%011s%04s',
$this->getTimeLow()->toString(),
$this->getTimeMid()->toString(),
);
break;
default:
$timestamp = sprintf(
+22 -17
View File
@@ -17,10 +17,13 @@ namespace Ramsey\Uuid\Rfc4122;
use Ramsey\Uuid\Builder\UuidBuilderInterface;
use Ramsey\Uuid\Codec\CodecInterface;
use Ramsey\Uuid\Converter\NumberConverterInterface;
use Ramsey\Uuid\Converter\Time\UnixTimeConverter;
use Ramsey\Uuid\Converter\TimeConverterInterface;
use Ramsey\Uuid\Exception\UnableToBuildUuidException;
use Ramsey\Uuid\Exception\UnsupportedOperationException;
use Ramsey\Uuid\Math\BrickMathCalculator;
use Ramsey\Uuid\Rfc4122\UuidInterface as Rfc4122UuidInterface;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
use Throwable;
@@ -31,15 +34,9 @@ use Throwable;
*/
class UuidBuilder implements UuidBuilderInterface
{
/**
* @var NumberConverterInterface
*/
private $numberConverter;
/**
* @var TimeConverterInterface
*/
private $timeConverter;
private NumberConverterInterface $numberConverter;
private TimeConverterInterface $timeConverter;
private TimeConverterInterface $unixTimeConverter;
/**
* Constructs the DefaultUuidBuilder
@@ -47,14 +44,20 @@ 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 timestamps extracted from a UUID to Unix timestamps
* 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 extracted from version 7 UUIDs
* to Unix timestamps
*/
public function __construct(
NumberConverterInterface $numberConverter,
TimeConverterInterface $timeConverter
TimeConverterInterface $timeConverter,
?TimeConverterInterface $unixTimeConverter = null
) {
$this->numberConverter = $numberConverter;
$this->timeConverter = $timeConverter;
$this->unixTimeConverter = $unixTimeConverter ?? new UnixTimeConverter(new BrickMathCalculator());
}
/**
@@ -77,18 +80,20 @@ class UuidBuilder implements UuidBuilderInterface
}
switch ($fields->getVersion()) {
case 1:
case Uuid::UUID_TYPE_TIME:
return new UuidV1($fields, $this->numberConverter, $codec, $this->timeConverter);
case 2:
case Uuid::UUID_TYPE_DCE_SECURITY:
return new UuidV2($fields, $this->numberConverter, $codec, $this->timeConverter);
case 3:
case Uuid::UUID_TYPE_HASH_MD5:
return new UuidV3($fields, $this->numberConverter, $codec, $this->timeConverter);
case 4:
case Uuid::UUID_TYPE_RANDOM:
return new UuidV4($fields, $this->numberConverter, $codec, $this->timeConverter);
case 5:
case Uuid::UUID_TYPE_HASH_SHA1:
return new UuidV5($fields, $this->numberConverter, $codec, $this->timeConverter);
case 6:
case Uuid::UUID_TYPE_REORDERED_TIME:
return new UuidV6($fields, $this->numberConverter, $codec, $this->timeConverter);
case Uuid::UUID_TYPE_UNIX_TIME:
return new UuidV7($fields, $this->numberConverter, $codec, $this->unixTimeConverter);
}
throw new UnsupportedOperationException(
+60
View File
@@ -0,0 +1,60 @@
<?php
/**
* This file is part of the ramsey/uuid library
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
* @license http://opensource.org/licenses/MIT MIT
*/
declare(strict_types=1);
namespace Ramsey\Uuid\Rfc4122;
use Ramsey\Uuid\Codec\CodecInterface;
use Ramsey\Uuid\Converter\NumberConverterInterface;
use Ramsey\Uuid\Converter\TimeConverterInterface;
use Ramsey\Uuid\Exception\InvalidArgumentException;
use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface;
use Ramsey\Uuid\Uuid;
/**
* Gregorian time, or version 1, UUIDs include timestamp, clock sequence, and node
* values that are combined into a 128-bit unsigned integer
*
* @psalm-immutable
*/
final class UuidV7 extends Uuid implements UuidInterface
{
use TimeTrait;
/**
* Creates a version 7 (Unix Epoch time) UUID
*
* @param Rfc4122FieldsInterface $fields The fields from which to construct a UUID
* @param NumberConverterInterface $numberConverter The number converter to use
* for converting hex values to/from integers
* @param CodecInterface $codec The codec to use when encoding or decoding
* UUID strings
* @param TimeConverterInterface $timeConverter The time converter to use
* for converting timestamps extracted from a UUID to unix timestamps
*/
public function __construct(
Rfc4122FieldsInterface $fields,
NumberConverterInterface $numberConverter,
CodecInterface $codec,
TimeConverterInterface $timeConverter
) {
if ($fields->getVersion() !== Uuid::UUID_TYPE_UNIX_TIME) {
throw new InvalidArgumentException(
'Fields used to create a UuidV7 must represent a '
. 'version 7 (Unix Epoch time) UUID'
);
}
parent::__construct($fields, $numberConverter, $codec, $timeConverter);
}
}
+9 -6
View File
@@ -14,6 +14,8 @@ declare(strict_types=1);
namespace Ramsey\Uuid\Rfc4122;
use Ramsey\Uuid\Uuid;
/**
* Provides common functionality for handling the version, as defined by RFC 4122
*
@@ -43,12 +45,13 @@ trait VersionTrait
}
switch ($this->getVersion()) {
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case Uuid::UUID_TYPE_TIME:
case Uuid::UUID_TYPE_DCE_SECURITY:
case Uuid::UUID_TYPE_HASH_MD5:
case Uuid::UUID_TYPE_RANDOM:
case Uuid::UUID_TYPE_HASH_SHA1:
case Uuid::UUID_TYPE_REORDERED_TIME:
case Uuid::UUID_TYPE_UNIX_TIME:
return true;
}
+29
View File
@@ -18,6 +18,7 @@ use DateTimeInterface;
use Ramsey\Uuid\Codec\CodecInterface;
use Ramsey\Uuid\Converter\NumberConverterInterface;
use Ramsey\Uuid\Converter\TimeConverterInterface;
use Ramsey\Uuid\Exception\UnsupportedOperationException;
use Ramsey\Uuid\Fields\FieldsInterface;
use Ramsey\Uuid\Lazy\LazyUuidFromString;
use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface;
@@ -27,6 +28,7 @@ use ValueError;
use function assert;
use function bin2hex;
use function method_exists;
use function preg_match;
use function sprintf;
use function str_replace;
@@ -167,6 +169,13 @@ class Uuid implements UuidInterface
*/
public const UUID_TYPE_REORDERED_TIME = 6;
/**
* Version 7 (Unix Epoch time) UUID
*
* @link https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-04#section-5.2 UUID Version 7
*/
public const UUID_TYPE_UNIX_TIME = 7;
/**
* DCE Security principal domain
*
@@ -670,4 +679,24 @@ class Uuid implements UuidInterface
): UuidInterface {
return self::getFactory()->uuid6($node, $clockSeq);
}
/**
* Returns a version 7 (Unix Epoch time) UUID
*
* @return UuidInterface A UuidInterface instance that represents a
* version 7 UUID
*/
public static function uuid7(): UuidInterface
{
$factory = self::getFactory();
if (method_exists($factory, 'uuid7')) {
/** @var UuidInterface */
return $factory->uuid7();
}
throw new UnsupportedOperationException(
'The provided factory does not support the uuid7() method',
);
}
}
+21 -7
View File
@@ -98,6 +98,8 @@ class UuidFactory implements UuidFactoryInterface
/** @var bool whether the feature set was provided from outside, or we can operate under "default" assumptions */
private $isDefaultFeatureSet;
private TimeGeneratorInterface $unixTimeGenerator;
/**
* @param FeatureSet $features A set of available features in the current environment
*/
@@ -117,6 +119,7 @@ class UuidFactory implements UuidFactoryInterface
$this->timeGenerator = $features->getTimeGenerator();
$this->uuidBuilder = $features->getBuilder();
$this->validator = $features->getValidator();
$this->unixTimeGenerator = $features->getUnixTimeGenerator();
}
/**
@@ -342,7 +345,7 @@ class UuidFactory implements UuidFactoryInterface
$bytes = $timeGenerator->generate($nodeHex, $clockSeq);
return $this->uuidFromBytesAndVersion($bytes, 1);
return $this->uuidFromBytesAndVersion($bytes, Uuid::UUID_TYPE_TIME);
}
/**
@@ -352,7 +355,7 @@ class UuidFactory implements UuidFactoryInterface
{
$bytes = $this->timeGenerator->generate($node, $clockSeq);
return $this->uuidFromBytesAndVersion($bytes, 1);
return $this->uuidFromBytesAndVersion($bytes, Uuid::UUID_TYPE_TIME);
}
public function uuid2(
@@ -368,7 +371,7 @@ class UuidFactory implements UuidFactoryInterface
$clockSeq
);
return $this->uuidFromBytesAndVersion($bytes, 2);
return $this->uuidFromBytesAndVersion($bytes, Uuid::UUID_TYPE_DCE_SECURITY);
}
/**
@@ -377,14 +380,14 @@ class UuidFactory implements UuidFactoryInterface
*/
public function uuid3($ns, string $name): UuidInterface
{
return $this->uuidFromNsAndName($ns, $name, 3, 'md5');
return $this->uuidFromNsAndName($ns, $name, Uuid::UUID_TYPE_HASH_MD5, 'md5');
}
public function uuid4(): UuidInterface
{
$bytes = $this->randomGenerator->generate(16);
return $this->uuidFromBytesAndVersion($bytes, 4);
return $this->uuidFromBytesAndVersion($bytes, Uuid::UUID_TYPE_RANDOM);
}
/**
@@ -393,7 +396,7 @@ class UuidFactory implements UuidFactoryInterface
*/
public function uuid5($ns, string $name): UuidInterface
{
return $this->uuidFromNsAndName($ns, $name, 5, 'sha1');
return $this->uuidFromNsAndName($ns, $name, Uuid::UUID_TYPE_HASH_SHA1, 'sha1');
}
public function uuid6(?Hexadecimal $node = null, ?int $clockSeq = null): UuidInterface
@@ -412,7 +415,18 @@ class UuidFactory implements UuidFactoryInterface
$v6Bytes = hex2bin(substr($v6, 1, 12) . '0' . substr($v6, -3));
$v6Bytes .= substr($bytes, 8);
return $this->uuidFromBytesAndVersion($v6Bytes, 6);
return $this->uuidFromBytesAndVersion($v6Bytes, Uuid::UUID_TYPE_REORDERED_TIME);
}
/**
* Returns a version 7 (Unix Epoch time) UUID
*
* @return UuidInterface A UuidInterface instance that represents a
* version 7 UUID
*/
public function uuid7(): UuidInterface
{
return $this->uuidFromBytesAndVersion($this->unixTimeGenerator->generate(), Uuid::UUID_TYPE_UNIX_TIME);
}
/**
@@ -0,0 +1,198 @@
<?php
declare(strict_types=1);
namespace Ramsey\Uuid\Test\Converter\Time;
use Ramsey\Uuid\Converter\Time\UnixTimeConverter;
use Ramsey\Uuid\Math\BrickMathCalculator;
use Ramsey\Uuid\Test\TestCase;
use Ramsey\Uuid\Type\Hexadecimal;
class UnixTimeConverterTest extends TestCase
{
/**
* @dataProvider provideConvertTime
*/
public function testConvertTime(Hexadecimal $uuidTimestamp, string $unixTimestamp, string $microseconds): void
{
$calculator = new BrickMathCalculator();
$converter = new UnixTimeConverter($calculator);
$result = $converter->convertTime($uuidTimestamp);
$this->assertSame($unixTimestamp, $result->getSeconds()->toString());
$this->assertSame($microseconds, $result->getMicroseconds()->toString());
}
/**
* @return array<array{uuidTimestamp: Hexadecimal, unixTimestamp: string, microseconds: string}>
*/
public function provideConvertTime(): array
{
return [
[
'uuidTimestamp' => new Hexadecimal('017F22E279B0'),
'unixTimestamp' => '1645557742',
'microseconds' => '0',
],
[
'uuidTimestamp' => new Hexadecimal('01384fc480fb'),
'unixTimestamp' => '1341368074',
'microseconds' => '491000',
],
[
'uuidTimestamp' => new Hexadecimal('016f8ca10161'),
'unixTimestamp' => '1578612359',
'microseconds' => '521000',
],
[
'uuidTimestamp' => new Hexadecimal('5dbe85111a5f'),
'unixTimestamp' => '103072857659',
'microseconds' => '999000',
],
// This is the last possible time supported by v7 UUIDs.
// 10889-08-02 05:31:50.655 +00:00
[
'uuidTimestamp' => new Hexadecimal('ffffffffffff'),
'unixTimestamp' => '281474976710',
'microseconds' => '655000',
],
// This is the earliest possible date supported by v7 UUIDs.
// It is the Unix Epoch (big surprise!).
// 1970-01-01 00:00:00.0 +00:00
[
'uuidTimestamp' => new Hexadecimal('000000000000'),
'unixTimestamp' => '0',
'microseconds' => '0',
],
[
'uuidTimestamp' => new Hexadecimal('000000000001'),
'unixTimestamp' => '0',
'microseconds' => '1000',
],
[
'uuidTimestamp' => new Hexadecimal('00000000000f'),
'unixTimestamp' => '0',
'microseconds' => '15000',
],
[
'uuidTimestamp' => new Hexadecimal('000000000064'),
'unixTimestamp' => '0',
'microseconds' => '100000',
],
[
'uuidTimestamp' => new Hexadecimal('0000000003e7'),
'unixTimestamp' => '0',
'microseconds' => '999000',
],
[
'uuidTimestamp' => new Hexadecimal('0000000003e8'),
'unixTimestamp' => '1',
'microseconds' => '0',
],
[
'uuidTimestamp' => new Hexadecimal('0000000003e9'),
'unixTimestamp' => '1',
'microseconds' => '1000',
],
];
}
/**
* @dataProvider provideCalculateTime
*/
public function testCalculateTime(string $seconds, string $microseconds, string $expected): void
{
$calculator = new BrickMathCalculator();
$converter = new UnixTimeConverter($calculator);
$result = $converter->calculateTime($seconds, $microseconds);
$this->assertSame($expected, $result->toString());
}
/**
* @return array<array{seconds: string, microseconds: string, expected: string}>
*/
public function provideCalculateTime(): array
{
return [
[
'seconds' => '1645557742',
'microseconds' => '0',
'expected' => '017f22e279b0',
],
[
'seconds' => '1341368074',
'microseconds' => '491000',
'expected' => '01384fc480fb',
],
[
'seconds' => '1578612359',
'microseconds' => '521023',
'expected' => '016f8ca10161',
],
[
'seconds' => '103072857659',
'microseconds' => '999499',
'expected' => '5dbe85111a5f',
],
[
'seconds' => '103072857659',
'microseconds' => '999999',
'expected' => '5dbe85111a5f',
],
// This is the earliest possible date supported by v7 UUIDs.
// It is the Unix Epoch (big surprise!): 1970-01-01 00:00:00.0 +00:00
[
'seconds' => '0',
'microseconds' => '0',
'expected' => '000000000000',
],
// This is the last possible time supported by v7 UUIDs:
// 10889-08-02 05:31:50.655 +00:00
[
'seconds' => '281474976710',
'microseconds' => '655000',
'expected' => 'ffffffffffff',
],
[
'seconds' => '0',
'microseconds' => '1000',
'expected' => '000000000001',
],
[
'seconds' => '0',
'microseconds' => '15000',
'expected' => '00000000000f',
],
[
'seconds' => '0',
'microseconds' => '100000',
'expected' => '000000000064',
],
[
'seconds' => '0',
'microseconds' => '999000',
'expected' => '0000000003e7',
],
[
'seconds' => '1',
'microseconds' => '0',
'expected' => '0000000003e8',
],
[
'seconds' => '1',
'microseconds' => '1000',
'expected' => '0000000003e9',
],
];
}
}
+8
View File
@@ -10,6 +10,7 @@ use Ramsey\Uuid\Converter\TimeConverterInterface;
use Ramsey\Uuid\FeatureSet;
use Ramsey\Uuid\Generator\DefaultNameGenerator;
use Ramsey\Uuid\Generator\PeclUuidTimeGenerator;
use Ramsey\Uuid\Generator\UnixTimeGenerator;
use Ramsey\Uuid\Guid\GuidBuilder;
use Ramsey\Uuid\Math\BrickMathCalculator;
use Ramsey\Uuid\Provider\NodeProviderInterface;
@@ -77,4 +78,11 @@ class FeatureSetTest extends TestCase
$this->assertSame($nodeProvider, $featureSet->getNodeProvider());
}
public function testGetUnixTimeGenerator(): void
{
$featureSet = new FeatureSet();
$this->assertInstanceOf(UnixTimeGenerator::class, $featureSet->getUnixTimeGenerator());
}
}
+39
View File
@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Ramsey\Uuid\Test\Generator;
use Mockery;
use Mockery\MockInterface;
use Ramsey\Uuid\Converter\Time\UnixTimeConverter;
use Ramsey\Uuid\Generator\RandomGeneratorInterface;
use Ramsey\Uuid\Generator\UnixTimeGenerator;
use Ramsey\Uuid\Math\BrickMathCalculator;
use Ramsey\Uuid\Provider\TimeProviderInterface;
use Ramsey\Uuid\Test\TestCase;
use Ramsey\Uuid\Type\Time;
class UnixTimeGeneratorTest extends TestCase
{
public function testGenerate(): void
{
$unixTimeConverter = new UnixTimeConverter(new BrickMathCalculator());
/** @var TimeProviderInterface&MockInterface $timeProvider */
$timeProvider = Mockery::mock(TimeProviderInterface::class, [
'getTime' => new Time('1578612359', '521023'),
]);
/** @var RandomGeneratorInterface&MockInterface $randomGenerator */
$randomGenerator = Mockery::mock(RandomGeneratorInterface::class);
$randomGenerator->expects()->generate(10)->andReturns("\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00");
$unixTimeGenerator = new UnixTimeGenerator($unixTimeConverter, $timeProvider, $randomGenerator);
$this->assertSame(
"\x01\x6f\x8c\xa1\x01\x61\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00",
$unixTimeGenerator->generate(),
);
}
}
+6 -2
View File
@@ -89,9 +89,13 @@ class FieldsTest extends TestCase
// representations, which are never in GUID byte order.
return [
['b08c6fff7dc5e1018b210800200c9a66'],
['b08c6fff7dc5e1719b210800200c9a66'],
['b08c6fff7dc5e181ab210800200c9a66'],
['b08c6fff7dc5e191bb210800200c9a66'],
['b08c6fff7dc5e1a19b210800200c9a66'],
['b08c6fff7dc5e1b1ab210800200c9a66'],
['b08c6fff7dc5e1c1ab210800200c9a66'],
['b08c6fff7dc5e1d1ab210800200c9a66'],
['b08c6fff7dc5e1e1ab210800200c9a66'],
['b08c6fff7dc5e1f1ab210800200c9a66'],
];
}
+18 -2
View File
@@ -84,9 +84,13 @@ class FieldsTest extends TestCase
{
return [
['ff6f8cb0-c57d-01e1-8b21-0800200c9a66'],
['ff6f8cb0-c57d-71e1-9b21-0800200c9a66'],
['ff6f8cb0-c57d-81e1-ab21-0800200c9a66'],
['ff6f8cb0-c57d-91e1-bb21-0800200c9a66'],
['ff6f8cb0-c57d-a1e1-9b21-0800200c9a66'],
['ff6f8cb0-c57d-b1e1-ab21-0800200c9a66'],
['ff6f8cb0-c57d-c1e1-ab21-0800200c9a66'],
['ff6f8cb0-c57d-d1e1-ab21-0800200c9a66'],
['ff6f8cb0-c57d-e1e1-ab21-0800200c9a66'],
['ff6f8cb0-c57d-f1e1-ab21-0800200c9a66'],
];
}
@@ -198,6 +202,18 @@ class FieldsTest extends TestCase
['000001f5-5cde-21ea-8400-0242ac130003', 'getVariant', 2],
['000001f5-5cde-21ea-8400-0242ac130003', 'getVersion', 2],
['000001f5-5cde-21ea-8400-0242ac130003', 'isNil', false],
['018339f0-1b83-71e1-9b21-0800200c9a66', 'getClockSeq', '1b21'],
['018339f0-1b83-71e1-9b21-0800200c9a66', 'getClockSeqHiAndReserved', '9b'],
['018339f0-1b83-71e1-9b21-0800200c9a66', 'getClockSeqLow', '21'],
['018339f0-1b83-71e1-9b21-0800200c9a66', 'getNode', '0800200c9a66'],
['018339f0-1b83-71e1-9b21-0800200c9a66', 'getTimeHiAndVersion', '71e1'],
['018339f0-1b83-71e1-9b21-0800200c9a66', 'getTimeLow', '018339f0'],
['018339f0-1b83-71e1-9b21-0800200c9a66', 'getTimeMid', '1b83'],
['018339f0-1b83-71e1-9b21-0800200c9a66', 'getTimestamp', '000018339f01b83'],
['018339f0-1b83-71e1-9b21-0800200c9a66', 'getVariant', 2],
['018339f0-1b83-71e1-9b21-0800200c9a66', 'getVersion', 7],
['018339f0-1b83-71e1-9b21-0800200c9a66', 'isNil', false],
];
}
+7
View File
@@ -20,6 +20,7 @@ use Ramsey\Uuid\Rfc4122\UuidV3;
use Ramsey\Uuid\Rfc4122\UuidV4;
use Ramsey\Uuid\Rfc4122\UuidV5;
use Ramsey\Uuid\Rfc4122\UuidV6;
use Ramsey\Uuid\Rfc4122\UuidV7;
use Ramsey\Uuid\Test\TestCase;
use function hex2bin;
@@ -95,6 +96,12 @@ class UuidBuilderTest extends TestCase
'expectedClass' => NonstandardUuidV6::class,
'expectedVersion' => 6,
],
[
'uuid' => 'ff6f8cb0-c57d-71e1-9b21-0800200c9a66',
'expectedClass' => UuidV7::class,
'expectedVersion' => 7,
],
];
}
+132
View File
@@ -0,0 +1,132 @@
<?php
declare(strict_types=1);
namespace Ramsey\Uuid\Test\Rfc4122;
use DateTimeImmutable;
use Mockery;
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;
use Ramsey\Uuid\Rfc4122\UuidV7;
use Ramsey\Uuid\Test\TestCase;
use Ramsey\Uuid\Type\Hexadecimal;
use Ramsey\Uuid\Type\Time;
use Ramsey\Uuid\Uuid;
class UuidV7Test extends TestCase
{
/**
* @dataProvider provideTestVersions
*/
public function testConstructorThrowsExceptionWhenFieldsAreNotValidForType(int $version): void
{
$fields = Mockery::mock(FieldsInterface::class, [
'getVersion' => $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 UuidV7 must represent a '
. 'version 7 (Unix Epoch time) UUID'
);
new UuidV7($fields, $numberConverter, $codec, $timeConverter);
}
/**
* @return array<array{version: int}>
*/
public function provideTestVersions(): array
{
return [
['version' => 0],
['version' => 1],
['version' => 2],
['version' => 3],
['version' => 4],
['version' => 5],
['version' => 6],
['version' => 8],
['version' => 9],
];
}
/**
* @param non-empty-string $uuid
*
* @dataProvider provideUuidV7WithMicroseconds
*/
public function testGetDateTimeProperlyHandlesMicroseconds(string $uuid, string $expected): void
{
/** @var UuidV7 $object */
$object = Uuid::fromString($uuid);
$date = $object->getDateTime();
$this->assertInstanceOf(DateTimeImmutable::class, $date);
$this->assertSame($expected, $date->format('U.u'));
}
/**
* @return array<array{uuid: string, expected: numeric-string}>
*/
public function provideUuidV7WithMicroseconds(): array
{
return [
[
'uuid' => '00000000-0001-71b2-9669-00007ffffffe',
'expected' => '0.001000',
],
[
'uuid' => '00000000-000f-71b2-9669-00007ffffffe',
'expected' => '0.015000',
],
[
'uuid' => '00000000-0064-71b2-9669-00007ffffffe',
'expected' => '0.100000',
],
[
'uuid' => '00000000-03e7-71b2-9669-00007ffffffe',
'expected' => '0.999000',
],
[
'uuid' => '00000000-03e8-71b2-9669-00007ffffffe',
'expected' => '1.000000',
],
[
'uuid' => '00000000-03e9-71b2-9669-00007ffffffe',
'expected' => '1.001000',
],
];
}
public function testGetDateTimeThrowsException(): void
{
$fields = Mockery::mock(FieldsInterface::class, [
'getVersion' => 7,
'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 UuidV7($fields, $numberConverter, $codec, $timeConverter);
$this->expectException(DateTimeException::class);
$uuid->getDateTime();
}
}
+2
View File
@@ -67,6 +67,7 @@ class UuidFactoryTest extends TestCase
$randomGenerator = Mockery::mock(RandomGeneratorInterface::class);
$timeConverter = Mockery::mock(TimeConverterInterface::class);
$timeGenerator = Mockery::mock(TimeGeneratorInterface::class);
$unixTimeGenerator = Mockery::mock(TimeGeneratorInterface::class);
$nameGenerator = Mockery::mock(NameGeneratorInterface::class);
$dceSecurityGenerator = Mockery::mock(DceSecurityGeneratorInterface::class);
$numberConverter = Mockery::mock(NumberConverterInterface::class);
@@ -84,6 +85,7 @@ class UuidFactoryTest extends TestCase
'getNumberConverter' => $numberConverter,
'getBuilder' => $builder,
'getValidator' => $validator,
'getUnixTimeGenerator' => $unixTimeGenerator,
]);
$uuidFactory = new UuidFactory($featureSet);
+30
View File
@@ -8,6 +8,7 @@ use Brick\Math\BigDecimal;
use Brick\Math\RoundingMode;
use DateTimeInterface;
use Mockery;
use Mockery\MockInterface;
use PHPUnit\Framework\MockObject\MockObject;
use Ramsey\Uuid\Builder\DefaultUuidBuilder;
use Ramsey\Uuid\Codec\StringCodec;
@@ -33,6 +34,7 @@ use Ramsey\Uuid\Type\Hexadecimal;
use Ramsey\Uuid\Type\Time;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidFactory;
use Ramsey\Uuid\UuidFactoryInterface;
use Ramsey\Uuid\UuidInterface;
use Ramsey\Uuid\Validator\GenericValidator;
use Ramsey\Uuid\Validator\ValidatorInterface;
@@ -705,6 +707,27 @@ class UuidTest extends TestCase
$this->assertSame(6, $uuid->getVersion());
}
public function testUuid7(): void
{
$uuid = Uuid::uuid7();
$this->assertInstanceOf(DateTimeInterface::class, $uuid->getDateTime());
$this->assertSame(2, $uuid->getVariant());
$this->assertSame(7, $uuid->getVersion());
}
public function testUuid7ThrowsExceptionForUnsupportedFactory(): void
{
/** @var UuidFactoryInterface&MockInterface $factory */
$factory = Mockery::mock(UuidFactoryInterface::class);
Uuid::setFactory($factory);
$this->expectException(UnsupportedOperationException::class);
$this->expectExceptionMessage('The provided factory does not support the uuid7() method');
Uuid::uuid7();
}
/**
* Tests known version-3 UUIDs
*
@@ -1649,6 +1672,13 @@ class UuidTest extends TestCase
{
$uuid = Uuid::fromString('886313e1-3b8a-6372-9b90-0c9aee199e5d');
$this->assertSame($uuid->getVersion(), Uuid::UUID_TYPE_PEABODY);
$this->assertSame($uuid->getVersion(), Uuid::UUID_TYPE_REORDERED_TIME);
}
public function testUuidVersionConstantForVersion7(): void
{
$uuid = Uuid::fromString('886313e1-3b8a-7372-9b90-0c9aee199e5d');
$this->assertSame($uuid->getVersion(), Uuid::UUID_TYPE_UNIX_TIME);
}
public function testGetDateTimeThrowsExceptionWhenDateTimeCannotParseDate(): void
+5
View File
@@ -99,4 +99,9 @@ final class UuidGenerationBench
{
Uuid::uuid6($this->node, $this->clockSequence);
}
public function benchUuid7Generation(): void
{
Uuid::uuid7();
}
}