mirror of
https://github.com/ramsey/uuid.git
synced 2026-06-14 15:56:48 +03:00
Add fromDateTime() to create version 1 UUIDs from DateTime instances
Fixes #28
This commit is contained in:
@@ -10,8 +10,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
||||
|
||||
### Added
|
||||
|
||||
* Add `Uuid::fromDateTime()` to create version 1 UUIDs from instances of
|
||||
`\DateTimeInterface`.
|
||||
|
||||
### Changed
|
||||
|
||||
* Add `fromDateTime()` method to `UuidFactoryInterface`.
|
||||
|
||||
### Deprecated
|
||||
|
||||
### Removed
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<?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\Exception;
|
||||
|
||||
use RuntimeException as PhpRuntimeException;
|
||||
|
||||
/**
|
||||
* Thrown to indicate that the source of time encountered an error
|
||||
*/
|
||||
class TimeSourceException extends PhpRuntimeException
|
||||
{
|
||||
}
|
||||
@@ -214,6 +214,14 @@ class FeatureSet
|
||||
return $this->randomGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time converter configured for this environment
|
||||
*/
|
||||
public function getTimeConverter(): TimeConverterInterface
|
||||
{
|
||||
return $this->timeConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time generator configured for this environment
|
||||
*/
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace Ramsey\Uuid\Generator;
|
||||
use Ramsey\Uuid\Converter\TimeConverterInterface;
|
||||
use Ramsey\Uuid\Exception\InvalidArgumentException;
|
||||
use Ramsey\Uuid\Exception\RandomSourceException;
|
||||
use Ramsey\Uuid\Exception\TimeSourceException;
|
||||
use Ramsey\Uuid\Provider\NodeProviderInterface;
|
||||
use Ramsey\Uuid\Provider\TimeProviderInterface;
|
||||
use Throwable;
|
||||
@@ -75,12 +76,23 @@ class DefaultTimeGenerator implements TimeGeneratorInterface
|
||||
}
|
||||
}
|
||||
|
||||
$time = $this->timeProvider->getTime();
|
||||
|
||||
$uuidTime = $this->timeConverter->calculateTime(
|
||||
$this->timeProvider->getTime()->getSeconds()->toString(),
|
||||
$this->timeProvider->getTime()->getMicroSeconds()->toString()
|
||||
$time->getSeconds()->toString(),
|
||||
$time->getMicroSeconds()->toString()
|
||||
);
|
||||
|
||||
$timeBytes = (string) hex2bin(str_pad($uuidTime->toString(), 16, '0', STR_PAD_LEFT));
|
||||
$timeHex = str_pad($uuidTime->toString(), 16, '0', STR_PAD_LEFT);
|
||||
|
||||
if (strlen($timeHex) !== 16) {
|
||||
throw new TimeSourceException(sprintf(
|
||||
'The generated time of \'%s\' is larger than expected',
|
||||
$timeHex
|
||||
));
|
||||
}
|
||||
|
||||
$timeBytes = (string) hex2bin($timeHex);
|
||||
|
||||
return $timeBytes[4] . $timeBytes[5] . $timeBytes[6] . $timeBytes[7]
|
||||
. $timeBytes[2] . $timeBytes[3]
|
||||
|
||||
@@ -14,6 +14,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Ramsey\Uuid;
|
||||
|
||||
use DateTimeInterface;
|
||||
use Ramsey\Uuid\Codec\CodecInterface;
|
||||
use Ramsey\Uuid\Converter\NumberConverterInterface;
|
||||
use Ramsey\Uuid\Converter\TimeConverterInterface;
|
||||
@@ -368,6 +369,27 @@ class Uuid implements UuidInterface
|
||||
return self::getFactory()->fromString($uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a UUID from a DateTimeInterface instance
|
||||
*
|
||||
* @param DateTimeInterface $dateTime The date and time
|
||||
* @param Hexadecimal|null $node A 48-bit number representing the hardware
|
||||
* address
|
||||
* @param int|null $clockSeq A 14-bit number used to help avoid duplicates
|
||||
* that could arise when the clock is set backwards in time or if the
|
||||
* node ID changes
|
||||
*
|
||||
* @return UuidInterface A UuidInterface instance that represents a
|
||||
* version 1 UUID created from a DateTimeInterface instance
|
||||
*/
|
||||
public static function fromDateTime(
|
||||
DateTimeInterface $dateTime,
|
||||
?Hexadecimal $node = null,
|
||||
?int $clockSeq = null
|
||||
): UuidInterface {
|
||||
return self::getFactory()->fromDateTime($dateTime, $node, $clockSeq);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a UUID from a 128-bit integer string
|
||||
*
|
||||
|
||||
@@ -14,15 +14,20 @@ declare(strict_types=1);
|
||||
|
||||
namespace Ramsey\Uuid;
|
||||
|
||||
use DateTimeInterface;
|
||||
use Ramsey\Uuid\Builder\UuidBuilderInterface;
|
||||
use Ramsey\Uuid\Codec\CodecInterface;
|
||||
use Ramsey\Uuid\Converter\NumberConverterInterface;
|
||||
use Ramsey\Uuid\Converter\TimeConverterInterface;
|
||||
use Ramsey\Uuid\Generator\DceSecurityGeneratorInterface;
|
||||
use Ramsey\Uuid\Generator\DefaultTimeGenerator;
|
||||
use Ramsey\Uuid\Generator\RandomGeneratorInterface;
|
||||
use Ramsey\Uuid\Generator\TimeGeneratorInterface;
|
||||
use Ramsey\Uuid\Provider\NodeProviderInterface;
|
||||
use Ramsey\Uuid\Provider\Time\FixedTimeProvider;
|
||||
use Ramsey\Uuid\Type\Hexadecimal;
|
||||
use Ramsey\Uuid\Type\IntegerValue;
|
||||
use Ramsey\Uuid\Type\Time;
|
||||
use Ramsey\Uuid\Validator\ValidatorInterface;
|
||||
|
||||
class UuidFactory implements UuidFactoryInterface
|
||||
@@ -52,6 +57,11 @@ class UuidFactory implements UuidFactoryInterface
|
||||
*/
|
||||
private $randomGenerator;
|
||||
|
||||
/**
|
||||
* @var TimeConverterInterface
|
||||
*/
|
||||
private $timeConverter;
|
||||
|
||||
/**
|
||||
* @var TimeGeneratorInterface
|
||||
*/
|
||||
@@ -79,6 +89,7 @@ class UuidFactory implements UuidFactoryInterface
|
||||
$this->nodeProvider = $features->getNodeProvider();
|
||||
$this->numberConverter = $features->getNumberConverter();
|
||||
$this->randomGenerator = $features->getRandomGenerator();
|
||||
$this->timeConverter = $features->getTimeConverter();
|
||||
$this->timeGenerator = $features->getTimeGenerator();
|
||||
$this->uuidBuilder = $features->getBuilder();
|
||||
$this->validator = $features->getValidator();
|
||||
@@ -234,6 +245,28 @@ class UuidFactory implements UuidFactoryInterface
|
||||
return $this->fromString($hex);
|
||||
}
|
||||
|
||||
public function fromDateTime(
|
||||
DateTimeInterface $dateTime,
|
||||
?Hexadecimal $node = null,
|
||||
?int $clockSeq = null
|
||||
): UuidInterface {
|
||||
$timeProvider = new FixedTimeProvider(
|
||||
new Time($dateTime->getTimestamp(), $dateTime->format('u'))
|
||||
);
|
||||
|
||||
$timeGenerator = new DefaultTimeGenerator(
|
||||
$this->nodeProvider,
|
||||
$this->timeConverter,
|
||||
$timeProvider
|
||||
);
|
||||
|
||||
$nodeHex = $node ? $node->toString() : null;
|
||||
|
||||
$bytes = $timeGenerator->generate($nodeHex, $clockSeq);
|
||||
|
||||
return $this->uuidFromBytesAndVersion($bytes, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
@@ -14,6 +14,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Ramsey\Uuid;
|
||||
|
||||
use DateTimeInterface;
|
||||
use Ramsey\Uuid\Type\Hexadecimal;
|
||||
use Ramsey\Uuid\Type\IntegerValue;
|
||||
use Ramsey\Uuid\Validator\ValidatorInterface;
|
||||
@@ -139,4 +140,23 @@ interface UuidFactoryInterface
|
||||
* @psalm-pure
|
||||
*/
|
||||
public function fromInteger(string $integer): UuidInterface;
|
||||
|
||||
/**
|
||||
* Creates a UUID from a DateTimeInterface instance
|
||||
*
|
||||
* @param DateTimeInterface $dateTime The date and time
|
||||
* @param Hexadecimal|null $node A 48-bit number representing the hardware
|
||||
* address
|
||||
* @param int|null $clockSeq A 14-bit number used to help avoid duplicates
|
||||
* that could arise when the clock is set backwards in time or if the
|
||||
* node ID changes
|
||||
*
|
||||
* @return UuidInterface A UuidInterface instance that represents a
|
||||
* version 1 UUID created from a DateTimeInterface instance
|
||||
*/
|
||||
public function fromDateTime(
|
||||
DateTimeInterface $dateTime,
|
||||
?Hexadecimal $node = null,
|
||||
?int $clockSeq = null
|
||||
): UuidInterface;
|
||||
}
|
||||
|
||||
@@ -54,13 +54,28 @@ class GenericTimeConverterTest extends TestCase
|
||||
'expected' => '0000000000000000',
|
||||
],
|
||||
|
||||
// This is the last possible time supported by v1 UUIDs:
|
||||
// This is the last possible time supported by the GenericTimeConverter:
|
||||
// 60038-03-11 05:36:10.955161
|
||||
// When a UUID is created from this time, however, the highest 4 bits
|
||||
// are replaced with the version (1), so we lose fidelity and cannot
|
||||
// accurately decompose the date from the UUID.
|
||||
[
|
||||
'seconds' => '1832455114570',
|
||||
'microseconds' => '955161',
|
||||
'expected' => 'fffffffffffffffa',
|
||||
],
|
||||
|
||||
// This is technically the last possible time supported by v1 UUIDs:
|
||||
// 5236-03-31 21:21:00.684697
|
||||
// All dates above this will lose fidelity, since the highest 4 bits
|
||||
// are replaced with the UUID version (1). As a result, we cannot
|
||||
// accurately decompose the date from UUIDs created from dates
|
||||
// greater than this one.
|
||||
[
|
||||
'seconds' => '103072857660',
|
||||
'microseconds' => '684697',
|
||||
'expected' => '0ffffffffffffffa',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace Ramsey\Uuid\Test;
|
||||
|
||||
use Mockery;
|
||||
use Ramsey\Uuid\Builder\FallbackBuilder;
|
||||
use Ramsey\Uuid\Converter\TimeConverterInterface;
|
||||
use Ramsey\Uuid\FeatureSet;
|
||||
use Ramsey\Uuid\Guid\GuidBuilder;
|
||||
use Ramsey\Uuid\Validator\ValidatorInterface;
|
||||
@@ -35,4 +36,11 @@ class FeatureSetTest extends TestCase
|
||||
|
||||
$this->assertSame($validator, $featureSet->getValidator());
|
||||
}
|
||||
|
||||
public function testGetTimeConverter(): void
|
||||
{
|
||||
$featureSet = new FeatureSet();
|
||||
|
||||
$this->assertInstanceOf(TimeConverterInterface::class, $featureSet->getTimeConverter());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,11 @@ use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Ramsey\Uuid\BinaryUtils;
|
||||
use Ramsey\Uuid\Converter\TimeConverterInterface;
|
||||
use Ramsey\Uuid\Exception\RandomSourceException;
|
||||
use Ramsey\Uuid\Exception\TimeSourceException;
|
||||
use Ramsey\Uuid\FeatureSet;
|
||||
use Ramsey\Uuid\Generator\DefaultTimeGenerator;
|
||||
use Ramsey\Uuid\Provider\NodeProviderInterface;
|
||||
use Ramsey\Uuid\Provider\Time\FixedTimeProvider;
|
||||
use Ramsey\Uuid\Provider\TimeProviderInterface;
|
||||
use Ramsey\Uuid\Test\TestCase;
|
||||
use Ramsey\Uuid\Type\Hexadecimal;
|
||||
@@ -189,4 +192,22 @@ class DefaultTimeGeneratorTest extends TestCase
|
||||
|
||||
$defaultTimeGenerator->generate($this->nodeId);
|
||||
}
|
||||
|
||||
public function testDefaultTimeGeneratorThrowsExceptionForLargeGeneratedValue(): void
|
||||
{
|
||||
$timeProvider = new FixedTimeProvider(new Time('1832455114570', '955162'));
|
||||
$featureSet = new FeatureSet();
|
||||
$timeGenerator = new DefaultTimeGenerator(
|
||||
$featureSet->getNodeProvider(),
|
||||
$featureSet->getTimeConverter(),
|
||||
$timeProvider
|
||||
);
|
||||
|
||||
$this->expectException(TimeSourceException::class);
|
||||
$this->expectExceptionMessage(
|
||||
'The generated time of \'10000000000000004\' is larger than expected'
|
||||
);
|
||||
|
||||
$timeGenerator->generate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,16 +4,22 @@ declare(strict_types=1);
|
||||
|
||||
namespace Ramsey\Uuid\Test;
|
||||
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
use Mockery;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Ramsey\Uuid\Builder\UuidBuilderInterface;
|
||||
use Ramsey\Uuid\Codec\CodecInterface;
|
||||
use Ramsey\Uuid\Converter\NumberConverterInterface;
|
||||
use Ramsey\Uuid\Converter\TimeConverterInterface;
|
||||
use Ramsey\Uuid\FeatureSet;
|
||||
use Ramsey\Uuid\Generator\DceSecurityGeneratorInterface;
|
||||
use Ramsey\Uuid\Generator\RandomGeneratorInterface;
|
||||
use Ramsey\Uuid\Generator\TimeGeneratorInterface;
|
||||
use Ramsey\Uuid\Provider\NodeProviderInterface;
|
||||
use Ramsey\Uuid\Rfc4122\UuidV1;
|
||||
use Ramsey\Uuid\Type\Hexadecimal;
|
||||
use Ramsey\Uuid\UuidFactory;
|
||||
use Ramsey\Uuid\Validator\ValidatorInterface;
|
||||
|
||||
@@ -55,6 +61,7 @@ class UuidFactoryTest extends TestCase
|
||||
$codec = Mockery::mock(CodecInterface::class);
|
||||
$nodeProvider = Mockery::mock(NodeProviderInterface::class);
|
||||
$randomGenerator = Mockery::mock(RandomGeneratorInterface::class);
|
||||
$timeConverter = Mockery::mock(TimeConverterInterface::class);
|
||||
$timeGenerator = Mockery::mock(TimeGeneratorInterface::class);
|
||||
$dceSecurityGenerator = Mockery::mock(DceSecurityGeneratorInterface::class);
|
||||
$numberConverter = Mockery::mock(NumberConverterInterface::class);
|
||||
@@ -65,6 +72,7 @@ class UuidFactoryTest extends TestCase
|
||||
'getCodec' => $codec,
|
||||
'getNodeProvider' => $nodeProvider,
|
||||
'getRandomGenerator' => $randomGenerator,
|
||||
'getTimeConverter' => $timeConverter,
|
||||
'getTimeGenerator' => $timeGenerator,
|
||||
'getDceSecurityGenerator' => $dceSecurityGenerator,
|
||||
'getNumberConverter' => $numberConverter,
|
||||
@@ -122,4 +130,75 @@ class UuidFactoryTest extends TestCase
|
||||
$uuidFactory->setUuidBuilder($uuidBuilder);
|
||||
$this->assertEquals($uuidBuilder, $uuidFactory->getUuidBuilder());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideDateTime
|
||||
*/
|
||||
public function testFromDateTime(
|
||||
DateTimeInterface $dateTime,
|
||||
?Hexadecimal $node,
|
||||
?int $clockSeq,
|
||||
string $expectedUuidFormat,
|
||||
string $expectedTime
|
||||
): void {
|
||||
$factory = new UuidFactory();
|
||||
|
||||
/** @var UuidV1 $uuid */
|
||||
$uuid = $factory->fromDateTime($dateTime, $node, $clockSeq);
|
||||
|
||||
$this->assertInstanceOf(UuidV1::class, $uuid);
|
||||
$this->assertStringMatchesFormat($expectedUuidFormat, $uuid->toString());
|
||||
$this->assertSame($expectedTime, $uuid->getDateTime()->format('U.u'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingTraversableTypeHintSpecification
|
||||
*/
|
||||
public function provideDateTime(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
new DateTimeImmutable('2012-07-04 02:14:34.491000'),
|
||||
null,
|
||||
null,
|
||||
'ff6f8cb0-c57d-11e1-%s',
|
||||
'1341368074.491000',
|
||||
],
|
||||
[
|
||||
new DateTimeImmutable('1582-10-16 16:34:04'),
|
||||
new Hexadecimal('0800200c9a66'),
|
||||
15137,
|
||||
'0901e600-0154-1000-%cb21-0800200c9a66',
|
||||
'-12219146756.000000',
|
||||
],
|
||||
[
|
||||
new DateTime('5236-03-31 21:20:59.999999'),
|
||||
new Hexadecimal('00007ffffffe'),
|
||||
1641,
|
||||
'ff9785f6-ffff-1fff-%c669-00007ffffffe',
|
||||
'103072857659.999999',
|
||||
],
|
||||
[
|
||||
new DateTime('1582-10-15 00:00:00'),
|
||||
new Hexadecimal('00007ffffffe'),
|
||||
1641,
|
||||
'00000000-0000-1000-%c669-00007ffffffe',
|
||||
'-12219292800.000000',
|
||||
],
|
||||
[
|
||||
new DateTimeImmutable('@103072857660.684697'),
|
||||
new Hexadecimal('0'),
|
||||
0,
|
||||
'fffffffa-ffff-1fff-%c000-000000000000',
|
||||
'103072857660.684697',
|
||||
],
|
||||
[
|
||||
new DateTimeImmutable('5236-03-31 21:21:00.684697'),
|
||||
null,
|
||||
null,
|
||||
'fffffffa-ffff-1fff-%s',
|
||||
'103072857660.684697',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ use Ramsey\Uuid\Rfc4122\UuidV2;
|
||||
use Ramsey\Uuid\Rfc4122\UuidV3;
|
||||
use Ramsey\Uuid\Rfc4122\UuidV4;
|
||||
use Ramsey\Uuid\Rfc4122\UuidV5;
|
||||
use Ramsey\Uuid\Type\Hexadecimal;
|
||||
use Ramsey\Uuid\Type\Time;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Ramsey\Uuid\UuidFactory;
|
||||
@@ -961,6 +962,17 @@ class UuidTest extends TestCase
|
||||
$this->assertTrue($uuid->equals($fromIntegerUuid));
|
||||
}
|
||||
|
||||
public function testFromDateTime(): void
|
||||
{
|
||||
/** @var UuidV1 $uuid */
|
||||
$uuid = Uuid::fromString('ff6f8cb0-c57d-11e1-8b21-0800200c9a66');
|
||||
$dateTime = $uuid->getDateTime();
|
||||
|
||||
$fromDateTimeUuid = Uuid::fromDateTime($dateTime, new Hexadecimal('0800200c9a66'), 2849);
|
||||
|
||||
$this->assertTrue($uuid->equals($fromDateTimeUuid));
|
||||
}
|
||||
|
||||
/**
|
||||
* This test ensures that Ramsey\Uuid passes the same test cases
|
||||
* as the Python UUID library.
|
||||
|
||||
@@ -70,7 +70,8 @@
|
||||
</TypeDoesNotContainType>
|
||||
</file>
|
||||
<file src="src/Uuid.php">
|
||||
<ImpureMethodCall occurrences="4">
|
||||
<ImpureMethodCall occurrences="5">
|
||||
<code>getFactory</code>
|
||||
<code>getFactory</code>
|
||||
<code>getFactory</code>
|
||||
<code>getFactory</code>
|
||||
|
||||
Reference in New Issue
Block a user