mirror of
https://github.com/ramsey/uuid.git
synced 2026-06-14 15:56:48 +03:00
Add getDateTime() to UuidV2
This commit is contained in:
+40
-11
@@ -123,20 +123,49 @@ final class Fields implements FieldsInterface
|
||||
return new Hexadecimal(bin2hex(substr($this->bytes, 4, 2)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full 60-bit timestamp, without the version
|
||||
*
|
||||
* For version 2 UUIDs, the time_low field is the local identifier and
|
||||
* should not be returned as part of the time. For this reason, we set the
|
||||
* bottom 32 bits of the timestamp to 0's. As a result, there is some loss
|
||||
* of fidelity of the timestamp, for version 2 UUIDs. The timestamp can be
|
||||
* off by a range of 0 to 429.4967295 seconds (or 7 minutes, 9 seconds, and
|
||||
* 496730 microseconds).
|
||||
*
|
||||
* For version 6 UUIDs, the timestamp order is reversed from the typical RFC
|
||||
* 4122 order (the time bits are in the correct bit order, so that it is
|
||||
* monotonically increasing). In returning the timestamp value, we put the
|
||||
* bits in the order: time_low + time_mid + time_hi.
|
||||
*/
|
||||
public function getTimestamp(): Hexadecimal
|
||||
{
|
||||
$timestamp = sprintf(
|
||||
'%03x%04s%08s',
|
||||
hexdec($this->getTimeHiAndVersion()->toString()) & 0x0fff,
|
||||
$this->getTimeMid()->toString(),
|
||||
$this->getTimeLow()->toString()
|
||||
);
|
||||
switch ($this->getVersion()) {
|
||||
case Uuid::UUID_TYPE_DCE_SECURITY:
|
||||
$timestamp = sprintf(
|
||||
'%03x%04s%08s',
|
||||
hexdec($this->getTimeHiAndVersion()->toString()) & 0x0fff,
|
||||
$this->getTimeMid()->toString(),
|
||||
''
|
||||
);
|
||||
|
||||
// Put the timestamp into the correct order, if this is a v6 UUID.
|
||||
if ($this->getVersion() === Uuid::UUID_TYPE_PEABODY) {
|
||||
$timestamp = substr($timestamp, 7)
|
||||
. substr($timestamp, 3, 4)
|
||||
. substr($timestamp, 0, 3);
|
||||
break;
|
||||
case Uuid::UUID_TYPE_PEABODY:
|
||||
$timestamp = sprintf(
|
||||
'%08s%04s%03x',
|
||||
$this->getTimeLow()->toString(),
|
||||
$this->getTimeMid()->toString(),
|
||||
hexdec($this->getTimeHiAndVersion()->toString()) & 0x0fff
|
||||
);
|
||||
|
||||
break;
|
||||
default:
|
||||
$timestamp = sprintf(
|
||||
'%03x%04s%08s',
|
||||
hexdec($this->getTimeHiAndVersion()->toString()) & 0x0fff,
|
||||
$this->getTimeMid()->toString(),
|
||||
$this->getTimeLow()->toString()
|
||||
);
|
||||
}
|
||||
|
||||
return new Hexadecimal($timestamp);
|
||||
|
||||
@@ -14,15 +14,22 @@ 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,
|
||||
@@ -65,6 +72,41 @@ 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, 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
|
||||
*/
|
||||
|
||||
@@ -185,6 +185,25 @@ class UuidFactory implements UuidFactoryInterface
|
||||
$this->timeGenerator = $generator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the DCE Security generator used by this factory
|
||||
*/
|
||||
public function getDceSecurityGenerator(): DceSecurityGeneratorInterface
|
||||
{
|
||||
return $this->dceSecurityGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the DCE Security generator to use for this factory
|
||||
*
|
||||
* @param DceSecurityGeneratorInterface $generator A generator to generate
|
||||
* binary data, based on a local domain and local identifier
|
||||
*/
|
||||
public function setDceSecurityGenerator(DceSecurityGeneratorInterface $generator): void
|
||||
{
|
||||
$this->dceSecurityGenerator = $generator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number converter used by this factory
|
||||
*/
|
||||
|
||||
@@ -187,6 +187,18 @@ class FieldsTest extends TestCase
|
||||
['00000000-0000-0000-0000-000000000000', 'getVariant', 0],
|
||||
['00000000-0000-0000-0000-000000000000', 'getVersion', null],
|
||||
['00000000-0000-0000-0000-000000000000', 'isNil', true],
|
||||
|
||||
['000001f5-5cde-21ea-8400-0242ac130003', 'getClockSeq', '0400'],
|
||||
['000001f5-5cde-21ea-8400-0242ac130003', 'getClockSeqHiAndReserved', '84'],
|
||||
['000001f5-5cde-21ea-8400-0242ac130003', 'getClockSeqLow', '00'],
|
||||
['000001f5-5cde-21ea-8400-0242ac130003', 'getNode', '0242ac130003'],
|
||||
['000001f5-5cde-21ea-8400-0242ac130003', 'getTimeHiAndVersion', '21ea'],
|
||||
['000001f5-5cde-21ea-8400-0242ac130003', 'getTimeLow', '000001f5'],
|
||||
['000001f5-5cde-21ea-8400-0242ac130003', 'getTimeMid', '5cde'],
|
||||
['000001f5-5cde-21ea-8400-0242ac130003', 'getTimestamp', '1ea5cde00000000'],
|
||||
['000001f5-5cde-21ea-8400-0242ac130003', 'getVariant', 2],
|
||||
['000001f5-5cde-21ea-8400-0242ac130003', 'getVersion', 2],
|
||||
['000001f5-5cde-21ea-8400-0242ac130003', 'isNil', false],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -4,16 +4,28 @@ declare(strict_types=1);
|
||||
|
||||
namespace Ramsey\Uuid\Test\Rfc4122;
|
||||
|
||||
use DateTimeInterface;
|
||||
use Mockery;
|
||||
use Ramsey\Uuid\Codec\CodecInterface;
|
||||
use Ramsey\Uuid\Converter\Number\GenericNumberConverter;
|
||||
use Ramsey\Uuid\Converter\NumberConverterInterface;
|
||||
use Ramsey\Uuid\Converter\Time\GenericTimeConverter;
|
||||
use Ramsey\Uuid\Converter\TimeConverterInterface;
|
||||
use Ramsey\Uuid\Exception\InvalidArgumentException;
|
||||
use Ramsey\Uuid\Generator\DceSecurityGenerator;
|
||||
use Ramsey\Uuid\Generator\DefaultTimeGenerator;
|
||||
use Ramsey\Uuid\Math\BrickMathCalculator;
|
||||
use Ramsey\Uuid\Provider\Dce\SystemDceSecurityProvider;
|
||||
use Ramsey\Uuid\Provider\Node\StaticNodeProvider;
|
||||
use Ramsey\Uuid\Provider\Time\FixedTimeProvider;
|
||||
use Ramsey\Uuid\Rfc4122\FieldsInterface;
|
||||
use Ramsey\Uuid\Rfc4122\UuidV2;
|
||||
use Ramsey\Uuid\Test\TestCase;
|
||||
use Ramsey\Uuid\Type\Hexadecimal;
|
||||
use Ramsey\Uuid\Type\Integer;
|
||||
use Ramsey\Uuid\Type\Time;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Ramsey\Uuid\UuidFactory;
|
||||
|
||||
class UuidV2Test extends TestCase
|
||||
{
|
||||
@@ -63,17 +75,40 @@ class UuidV2Test extends TestCase
|
||||
public function testGetLocalDomainAndIdentifier(
|
||||
int $domain,
|
||||
Integer $identifier,
|
||||
Time $time,
|
||||
int $expectedDomain,
|
||||
string $expectedDomainName,
|
||||
string $expectedIdentifier
|
||||
string $expectedIdentifier,
|
||||
string $expectedTimestamp,
|
||||
string $expectedTime
|
||||
): void {
|
||||
$calculator = new BrickMathCalculator();
|
||||
$genericConverter = new GenericTimeConverter($calculator);
|
||||
$numberConverter = new GenericNumberConverter($calculator);
|
||||
$nodeProvider = new StaticNodeProvider(new Hexadecimal('1234567890ab'));
|
||||
$timeProvider = new FixedTimeProvider($time);
|
||||
$timeGenerator = new DefaultTimeGenerator($nodeProvider, $genericConverter, $timeProvider);
|
||||
$dceProvider = new SystemDceSecurityProvider();
|
||||
$dceGenerator = new DceSecurityGenerator($numberConverter, $timeGenerator, $dceProvider);
|
||||
|
||||
$factory = new UuidFactory();
|
||||
$factory->setTimeGenerator($timeGenerator);
|
||||
$factory->setDceSecurityGenerator($dceGenerator);
|
||||
|
||||
/** @var UuidV2 $uuid */
|
||||
$uuid = Uuid::uuid2($domain, $identifier);
|
||||
$uuid = $factory->uuid2($domain, $identifier);
|
||||
|
||||
/** @var FieldsInterface $fields */
|
||||
$fields = $uuid->getFields();
|
||||
|
||||
$this->assertSame($expectedDomain, $uuid->getLocalDomain());
|
||||
$this->assertSame($expectedDomainName, $uuid->getLocalDomainName());
|
||||
$this->assertInstanceOf(Integer::class, $uuid->getLocalIdentifier());
|
||||
$this->assertSame($expectedIdentifier, $uuid->getLocalIdentifier()->toString());
|
||||
$this->assertSame($expectedTimestamp, $fields->getTimestamp()->toString());
|
||||
$this->assertInstanceOf(DateTimeInterface::class, $uuid->getDateTime());
|
||||
$this->assertSame($expectedTime, $uuid->getDateTime()->format('U.u'));
|
||||
$this->assertSame('1334567890ab', $fields->getNode()->toString());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,37 +120,88 @@ class UuidV2Test extends TestCase
|
||||
[
|
||||
'domain' => Uuid::DCE_DOMAIN_PERSON,
|
||||
'identifier' => new Integer('12345678'),
|
||||
'time' => new Time(0, 0),
|
||||
'expectedDomain' => 0,
|
||||
'expectedDomainName' => 'person',
|
||||
'expectedIdentifier' => '12345678',
|
||||
'expectedTimestamp' => '1b21dd200000000',
|
||||
'expectedTime' => '-32.723763',
|
||||
],
|
||||
[
|
||||
'domain' => Uuid::DCE_DOMAIN_GROUP,
|
||||
'identifier' => new Integer('87654321'),
|
||||
'time' => new Time(0, 0),
|
||||
'expectedDomain' => 1,
|
||||
'expectedDomainName' => 'group',
|
||||
'expectedIdentifier' => '87654321',
|
||||
'expectedTimestamp' => '1b21dd200000000',
|
||||
'expectedTime' => '-32.723763',
|
||||
],
|
||||
[
|
||||
'domain' => Uuid::DCE_DOMAIN_ORG,
|
||||
'identifier' => new Integer('1'),
|
||||
'time' => new Time(0, 0),
|
||||
'expectedDomain' => 2,
|
||||
'expectedDomainName' => 'org',
|
||||
'expectedIdentifier' => '1',
|
||||
'expectedTimestamp' => '1b21dd200000000',
|
||||
'expectedTime' => '-32.723763',
|
||||
],
|
||||
[
|
||||
'domain' => Uuid::DCE_DOMAIN_PERSON,
|
||||
'identifier' => new Integer('0'),
|
||||
'time' => new Time(1583208664, 444109),
|
||||
'expectedDomain' => 0,
|
||||
'expectedDomainName' => 'person',
|
||||
'expectedIdentifier' => '0',
|
||||
'expectedTimestamp' => '1ea5d0500000000',
|
||||
'expectedTime' => '1583208664.444109',
|
||||
],
|
||||
[
|
||||
'domain' => Uuid::DCE_DOMAIN_PERSON,
|
||||
'identifier' => new Integer('2147483647'),
|
||||
'time' => new Time(1583208879, 500000),
|
||||
'expectedDomain' => 0,
|
||||
'expectedDomainName' => 'person',
|
||||
'expectedIdentifier' => '2147483647',
|
||||
// This time is the same as in the previous test because of the
|
||||
// loss of precision by setting the lowest 32 bits to zeros.
|
||||
'expectedTimestamp' => '1ea5d0500000000',
|
||||
'expectedTime' => '1583208664.444109',
|
||||
],
|
||||
[
|
||||
'domain' => Uuid::DCE_DOMAIN_PERSON,
|
||||
'identifier' => new Integer('4294967295'),
|
||||
'time' => new Time(1583208879, 500000),
|
||||
'expectedDomain' => 0,
|
||||
'expectedDomainName' => 'person',
|
||||
'expectedIdentifier' => '4294967295',
|
||||
// This time is the same as in the previous test because of the
|
||||
// loss of precision by setting the lowest 32 bits to zeros.
|
||||
'expectedTimestamp' => '1ea5d0500000000',
|
||||
'expectedTime' => '1583208664.444109',
|
||||
],
|
||||
[
|
||||
'domain' => Uuid::DCE_DOMAIN_PERSON,
|
||||
'identifier' => new Integer('4294967295'),
|
||||
'time' => new Time(1583209093, 940838),
|
||||
'expectedDomain' => 0,
|
||||
'expectedDomainName' => 'person',
|
||||
'expectedIdentifier' => '4294967295',
|
||||
// This time is the same as in the previous test because of the
|
||||
// loss of precision by setting the lowest 32 bits to zeros.
|
||||
'expectedTimestamp' => '1ea5d0500000000',
|
||||
'expectedTime' => '1583208664.444109',
|
||||
],
|
||||
[
|
||||
'domain' => Uuid::DCE_DOMAIN_PERSON,
|
||||
'identifier' => new Integer('4294967295'),
|
||||
'time' => new Time(1583209093, 940839),
|
||||
'expectedDomain' => 0,
|
||||
'expectedDomainName' => 'person',
|
||||
'expectedIdentifier' => '4294967295',
|
||||
'expectedTimestamp' => '1ea5d0600000000',
|
||||
'expectedTime' => '1583209093.940838',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user