diff --git a/psalm-baseline.xml b/psalm-baseline.xml
index cf70c9a..96f8617 100644
--- a/psalm-baseline.xml
+++ b/psalm-baseline.xml
@@ -95,13 +95,10 @@
$macs[]
-
-
- Uuid::UUID_TYPE_PEABODY
-
-
-
+
+ $this
+ $this
$this
$this
$this
diff --git a/src/Converter/Time/UnixTimeConverter.php b/src/Converter/Time/UnixTimeConverter.php
new file mode 100644
index 0000000..d94233f
--- /dev/null
+++ b/src/Converter/Time/UnixTimeConverter.php
@@ -0,0 +1,93 @@
+
+ * @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');
+ }
+}
diff --git a/src/FeatureSet.php b/src/FeatureSet.php
index 819f99a..482a8de 100644
--- a/src/FeatureSet.php
+++ b/src/FeatureSet.php
@@ -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
*/
diff --git a/src/Generator/UnixTimeGenerator.php b/src/Generator/UnixTimeGenerator.php
new file mode 100644
index 0000000..74914dd
--- /dev/null
+++ b/src/Generator/UnixTimeGenerator.php
@@ -0,0 +1,59 @@
+
+ * @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;
+ }
+}
diff --git a/src/Rfc4122/Fields.php b/src/Rfc4122/Fields.php
index 2ccc20b..6103cdc 100644
--- a/src/Rfc4122/Fields.php
+++ b/src/Rfc4122/Fields.php
@@ -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(
diff --git a/src/Rfc4122/UuidBuilder.php b/src/Rfc4122/UuidBuilder.php
index df7ab30..10eded9 100644
--- a/src/Rfc4122/UuidBuilder.php
+++ b/src/Rfc4122/UuidBuilder.php
@@ -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(
diff --git a/src/Rfc4122/UuidV7.php b/src/Rfc4122/UuidV7.php
new file mode 100644
index 0000000..90c2471
--- /dev/null
+++ b/src/Rfc4122/UuidV7.php
@@ -0,0 +1,60 @@
+
+ * @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);
+ }
+}
diff --git a/src/Rfc4122/VersionTrait.php b/src/Rfc4122/VersionTrait.php
index cee55fb..b65ca7b 100644
--- a/src/Rfc4122/VersionTrait.php
+++ b/src/Rfc4122/VersionTrait.php
@@ -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;
}
diff --git a/src/Uuid.php b/src/Uuid.php
index deaa58e..ee04da8 100644
--- a/src/Uuid.php
+++ b/src/Uuid.php
@@ -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',
+ );
+ }
}
diff --git a/src/UuidFactory.php b/src/UuidFactory.php
index 6f2cea0..de3707c 100644
--- a/src/UuidFactory.php
+++ b/src/UuidFactory.php
@@ -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);
}
/**
diff --git a/tests/Converter/Time/UnixTimeConverterTest.php b/tests/Converter/Time/UnixTimeConverterTest.php
new file mode 100644
index 0000000..3a22f8a
--- /dev/null
+++ b/tests/Converter/Time/UnixTimeConverterTest.php
@@ -0,0 +1,198 @@
+convertTime($uuidTimestamp);
+
+ $this->assertSame($unixTimestamp, $result->getSeconds()->toString());
+ $this->assertSame($microseconds, $result->getMicroseconds()->toString());
+ }
+
+ /**
+ * @return array
+ */
+ 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
+ */
+ 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',
+ ],
+ ];
+ }
+}
diff --git a/tests/FeatureSetTest.php b/tests/FeatureSetTest.php
index 407322d..62b8269 100644
--- a/tests/FeatureSetTest.php
+++ b/tests/FeatureSetTest.php
@@ -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());
+ }
}
diff --git a/tests/Generator/UnixTimeGeneratorTest.php b/tests/Generator/UnixTimeGeneratorTest.php
new file mode 100644
index 0000000..1b7a93d
--- /dev/null
+++ b/tests/Generator/UnixTimeGeneratorTest.php
@@ -0,0 +1,39 @@
+ 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(),
+ );
+ }
+}
diff --git a/tests/Guid/FieldsTest.php b/tests/Guid/FieldsTest.php
index 5c363db..d0f0689 100644
--- a/tests/Guid/FieldsTest.php
+++ b/tests/Guid/FieldsTest.php
@@ -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'],
];
}
diff --git a/tests/Rfc4122/FieldsTest.php b/tests/Rfc4122/FieldsTest.php
index b34a3d3..97f7497 100644
--- a/tests/Rfc4122/FieldsTest.php
+++ b/tests/Rfc4122/FieldsTest.php
@@ -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],
];
}
diff --git a/tests/Rfc4122/UuidBuilderTest.php b/tests/Rfc4122/UuidBuilderTest.php
index e234fc3..bf8a816 100644
--- a/tests/Rfc4122/UuidBuilderTest.php
+++ b/tests/Rfc4122/UuidBuilderTest.php
@@ -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,
+ ],
];
}
diff --git a/tests/Rfc4122/UuidV7Test.php b/tests/Rfc4122/UuidV7Test.php
new file mode 100644
index 0000000..fd3e5a4
--- /dev/null
+++ b/tests/Rfc4122/UuidV7Test.php
@@ -0,0 +1,132 @@
+ $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
+ */
+ 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
+ */
+ 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();
+ }
+}
diff --git a/tests/UuidFactoryTest.php b/tests/UuidFactoryTest.php
index 0102b18..19ec914 100644
--- a/tests/UuidFactoryTest.php
+++ b/tests/UuidFactoryTest.php
@@ -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);
diff --git a/tests/UuidTest.php b/tests/UuidTest.php
index c2313b7..4be8c72 100644
--- a/tests/UuidTest.php
+++ b/tests/UuidTest.php
@@ -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
diff --git a/tests/benchmark/UuidGenerationBench.php b/tests/benchmark/UuidGenerationBench.php
index c547a9f..c3838e1 100644
--- a/tests/benchmark/UuidGenerationBench.php
+++ b/tests/benchmark/UuidGenerationBench.php
@@ -99,4 +99,9 @@ final class UuidGenerationBench
{
Uuid::uuid6($this->node, $this->clockSequence);
}
+
+ public function benchUuid7Generation(): void
+ {
+ Uuid::uuid7();
+ }
}