From 80a7be00b3d5bbba849d582d5de41cc9cc6fafc5 Mon Sep 17 00:00:00 2001 From: Ben Ramsey Date: Tue, 21 Jan 2020 23:28:10 -0600 Subject: [PATCH] Return Hexadecimal type from TimeConverterInterface::calculateTime() --- CHANGELOG.md | 4 ++ src/Converter/Time/BigNumberTimeConverter.php | 2 +- src/Converter/Time/GenericTimeConverter.php | 8 +--- src/Converter/Time/PhpTimeConverter.php | 9 +--- src/Converter/TimeConverterInterface.php | 9 ++-- src/Generator/DefaultTimeGenerator.php | 25 ++++------ .../Time/BigNumberTimeConverterTest.php | 11 ++--- .../Time/GenericTimeConverterTest.php | 36 +++------------ tests/Converter/Time/PhpTimeConverterTest.php | 46 +++++-------------- tests/ExpectedBehaviorTest.php | 7 +-- tests/Generator/DefaultTimeGeneratorTest.php | 5 +- 11 files changed, 51 insertions(+), 111 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3809377..9ff33b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -95,6 +95,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. string. * `Generator\DefaultTimeGenerator` no longer adds the variant and version bits to the bytes it returns. These must be applied to the bytes afterwards. +* `Converter/TimeConverterInterface::calculateTime()` now returns + `Type\Hexadecimal` instead of `array`. The value is the full UUID timestamp + value (count of 100-nanosecond intervals since the Gregorian calendar epoch) + in hexadecimal format. * Change methods in converter interfaces to accept and return string values instead of `mixed`; this simplifies the interface and makes it consistent: * `NumberConverterInterface::fromHex(string $hex): string` diff --git a/src/Converter/Time/BigNumberTimeConverter.php b/src/Converter/Time/BigNumberTimeConverter.php index eb28650..97e60c4 100644 --- a/src/Converter/Time/BigNumberTimeConverter.php +++ b/src/Converter/Time/BigNumberTimeConverter.php @@ -41,7 +41,7 @@ class BigNumberTimeConverter implements TimeConverterInterface * @inheritDoc * @psalm-pure */ - public function calculateTime(string $seconds, string $microSeconds): array + public function calculateTime(string $seconds, string $microSeconds): Hexadecimal { return $this->converter->calculateTime($seconds, $microSeconds); } diff --git a/src/Converter/Time/GenericTimeConverter.php b/src/Converter/Time/GenericTimeConverter.php index fbe2846..bc506eb 100644 --- a/src/Converter/Time/GenericTimeConverter.php +++ b/src/Converter/Time/GenericTimeConverter.php @@ -41,7 +41,7 @@ class GenericTimeConverter implements TimeConverterInterface * @inheritDoc * @psalm-pure */ - public function calculateTime(string $seconds, string $microSeconds): array + public function calculateTime(string $seconds, string $microSeconds): Hexadecimal { $timestamp = new Time($seconds, $microSeconds); @@ -69,11 +69,7 @@ class GenericTimeConverter implements TimeConverterInterface STR_PAD_LEFT ); - return [ - 'low' => substr($uuidTimeHex, 8), - 'mid' => substr($uuidTimeHex, 4, 4), - 'hi' => substr($uuidTimeHex, 0, 4), - ]; + return new Hexadecimal($uuidTimeHex); } /** diff --git a/src/Converter/Time/PhpTimeConverter.php b/src/Converter/Time/PhpTimeConverter.php index f054c1b..8f9a11a 100644 --- a/src/Converter/Time/PhpTimeConverter.php +++ b/src/Converter/Time/PhpTimeConverter.php @@ -64,7 +64,7 @@ class PhpTimeConverter implements TimeConverterInterface * @inheritDoc * @psalm-pure */ - public function calculateTime(string $seconds, string $microSeconds): array + public function calculateTime(string $seconds, string $microSeconds): Hexadecimal { $seconds = new IntegerValue($seconds); $microSeconds = new IntegerValue($microSeconds); @@ -88,12 +88,7 @@ class PhpTimeConverter implements TimeConverterInterface ); } - /** @psalm-suppress MixedArgument */ - return [ - 'low' => sprintf('%08x', $uuidTime & 0xffffffff), - 'mid' => sprintf('%04x', ($uuidTime >> 32) & 0xffff), - 'hi' => sprintf('%04x', ($uuidTime >> 48) & 0x0fff), - ]; + return new Hexadecimal(str_pad(dechex($uuidTime), 16, '0', STR_PAD_LEFT)); } /** diff --git a/src/Converter/TimeConverterInterface.php b/src/Converter/TimeConverterInterface.php index de4785a..e0d763d 100644 --- a/src/Converter/TimeConverterInterface.php +++ b/src/Converter/TimeConverterInterface.php @@ -24,8 +24,9 @@ use Ramsey\Uuid\Type\Time; interface TimeConverterInterface { /** - * Uses the provided seconds and micro-seconds to calculate the time_low, - * time_mid, and time_high fields used by RFC 4122 version 1 UUIDs + * Uses the provided seconds and micro-seconds to calculate the count of + * 100-nanosecond intervals since UTC 00:00:00.00, 15 October 1582, for + * RFC 4122 variant UUIDs * * @link http://tools.ietf.org/html/rfc4122#section-4.2.2 RFC 4122, ยง 4.2.2: Generation Details * @@ -34,11 +35,11 @@ interface TimeConverterInterface * @param string $microSeconds A string representation of the micro-seconds * associated with the time to calculate * - * @return string[] An array guaranteed to contain `low`, `mid`, and `hi` keys + * @return Hexadecimal The full UUID timestamp as a Hexadecimal value * * @psalm-pure */ - public function calculateTime(string $seconds, string $microSeconds): array; + public function calculateTime(string $seconds, string $microSeconds): Hexadecimal; /** * Converts a timestamp extracted from a UUID to a Unix timestamp diff --git a/src/Generator/DefaultTimeGenerator.php b/src/Generator/DefaultTimeGenerator.php index 8ea3606..4fdf662 100644 --- a/src/Generator/DefaultTimeGenerator.php +++ b/src/Generator/DefaultTimeGenerator.php @@ -75,25 +75,18 @@ class DefaultTimeGenerator implements TimeGeneratorInterface } } - // Create a 60-bit time value as a count of 100-nanosecond intervals - // since 00:00:00.00, 15 October 1582. $uuidTime = $this->timeConverter->calculateTime( $this->timeProvider->getTime()->getSeconds()->toString(), $this->timeProvider->getTime()->getMicroSeconds()->toString() ); - $hex = vsprintf( - '%08s%04s%04s%04s%012s', - [ - $uuidTime['low'] ?? 0, - $uuidTime['mid'] ?? 0, - $uuidTime['hi'] ?? 0, - sprintf('%04x', $clockSeq), - $node, - ] - ); + $timeBytes = (string) hex2bin(str_pad($uuidTime->toString(), 16, '0', STR_PAD_LEFT)); - return (string) hex2bin($hex); + return $timeBytes[4] . $timeBytes[5] . $timeBytes[6] . $timeBytes[7] + . $timeBytes[2] . $timeBytes[3] + . $timeBytes[0] . $timeBytes[1] + . pack('n*', $clockSeq) + . $node; } /** @@ -102,7 +95,7 @@ class DefaultTimeGenerator implements TimeGeneratorInterface * * @param string|int|null $node A node value that may be used to override the node provider * - * @return string Hexadecimal representation of the node ID + * @return string 6-byte binary string representation of the node * * @throws InvalidArgumentException */ @@ -114,13 +107,13 @@ class DefaultTimeGenerator implements TimeGeneratorInterface // Convert the node to hex, if it is still an integer. if (is_int($node)) { - $node = sprintf('%012x', $node); + $node = dechex($node); } if (!ctype_xdigit((string) $node) || strlen((string) $node) > 12) { throw new InvalidArgumentException('Invalid node value'); } - return strtolower(sprintf('%012s', (string) $node)); + return (string) hex2bin(str_pad((string) $node, 12, '0', STR_PAD_LEFT)); } } diff --git a/tests/Converter/Time/BigNumberTimeConverterTest.php b/tests/Converter/Time/BigNumberTimeConverterTest.php index 963f6e7..cd6f871 100644 --- a/tests/Converter/Time/BigNumberTimeConverterTest.php +++ b/tests/Converter/Time/BigNumberTimeConverterTest.php @@ -26,16 +26,15 @@ class BigNumberTimeConverterTest extends TestCase $maskMid = BigInteger::fromBase('ffff', 16); $maskHi = BigInteger::fromBase('0fff', 16); - $expectedArray = [ - 'low' => sprintf('%08s', $calculatedTime->and($maskLow)->toBase(16)), - 'mid' => sprintf('%04s', $calculatedTime->shiftedRight(32)->and($maskMid)->toBase(16)), - 'hi' => sprintf('%04s', $calculatedTime->shiftedRight(48)->and($maskHi)->toBase(16)), - ]; + $expected = sprintf('%04s', $calculatedTime->shiftedRight(48)->and($maskHi)->toBase(16)); + $expected .= sprintf('%04s', $calculatedTime->shiftedRight(32)->and($maskMid)->toBase(16)); + $expected .= sprintf('%08s', $calculatedTime->and($maskLow)->toBase(16)); $converter = new BigNumberTimeConverter(); $returned = $converter->calculateTime((string) $seconds, (string) $microSeconds); - $this->assertSame($expectedArray, $returned); + $this->assertInstanceOf(Hexadecimal::class, $returned); + $this->assertSame($expected, $returned->toString()); } public function testConvertTime(): void diff --git a/tests/Converter/Time/GenericTimeConverterTest.php b/tests/Converter/Time/GenericTimeConverterTest.php index ccb94e3..61fcd33 100644 --- a/tests/Converter/Time/GenericTimeConverterTest.php +++ b/tests/Converter/Time/GenericTimeConverterTest.php @@ -12,18 +12,16 @@ use Ramsey\Uuid\Type\Hexadecimal; class GenericTimeConverterTest extends TestCase { /** - * @param string[] $expected - * * @dataProvider provideCalculateTime */ - public function testCalculateTime(string $seconds, string $microSeconds, array $expected): void + public function testCalculateTime(string $seconds, string $microSeconds, string $expected): void { $calculator = new BrickMathCalculator(); $converter = new GenericTimeConverter($calculator); $result = $converter->calculateTime($seconds, $microSeconds); - $this->assertSame($expected, $result); + $this->assertSame($expected, $result->toString()); } /** @@ -35,29 +33,17 @@ class GenericTimeConverterTest extends TestCase [ 'seconds' => '-12219146756', 'microSeconds' => '0', - 'expected' => [ - 'low' => '0901e600', - 'mid' => '0154', - 'hi' => '0000', - ], + 'expected' => '000001540901e600', ], [ 'seconds' => '103072857659', 'microseconds' => '999999', - 'expected' => [ - 'low' => 'ff9785f6', - 'mid' => 'ffff', - 'hi' => '0fff', - ], + 'expected' => '0fffffffff9785f6', ], [ 'seconds' => '1578612359', 'microseconds' => '521023', - 'expected' => [ - 'low' => '64c71df6', - 'mid' => '3337', - 'hi' => '01ea', - ], + 'expected' => '01ea333764c71df6', ], // This is the earliest possible date supported by v1 UUIDs: @@ -65,11 +51,7 @@ class GenericTimeConverterTest extends TestCase [ 'seconds' => '-12219292800', 'microSeconds' => '0', - 'expected' => [ - 'low' => '00000000', - 'mid' => '0000', - 'hi' => '0000', - ], + 'expected' => '0000000000000000', ], // This is the last possible time supported by v1 UUIDs: @@ -77,11 +59,7 @@ class GenericTimeConverterTest extends TestCase [ 'seconds' => '1832455114570', 'microseconds' => '955161', - 'expected' => [ - 'low' => 'fffffffa', - 'mid' => 'ffff', - 'hi' => 'ffff', - ], + 'expected' => 'fffffffffffffffa', ], ]; } diff --git a/tests/Converter/Time/PhpTimeConverterTest.php b/tests/Converter/Time/PhpTimeConverterTest.php index 638636e..0bc09aa 100644 --- a/tests/Converter/Time/PhpTimeConverterTest.php +++ b/tests/Converter/Time/PhpTimeConverterTest.php @@ -29,16 +29,14 @@ class PhpTimeConverterTest extends TestCase $maskMid = BigInteger::fromBase('ffff', 16); $maskHi = BigInteger::fromBase('0fff', 16); - $expectedArray = [ - 'low' => sprintf('%08s', $calculatedTime->and($maskLow)->toBase(16)), - 'mid' => sprintf('%04s', $calculatedTime->shiftedRight(32)->and($maskMid)->toBase(16)), - 'hi' => sprintf('%04s', $calculatedTime->shiftedRight(48)->and($maskHi)->toBase(16)), - ]; + $expected = sprintf('%04s', $calculatedTime->shiftedRight(48)->and($maskHi)->toBase(16)); + $expected .= sprintf('%04s', $calculatedTime->shiftedRight(32)->and($maskMid)->toBase(16)); + $expected .= sprintf('%08s', $calculatedTime->and($maskLow)->toBase(16)); $converter = new PhpTimeConverter(); $returned = $converter->calculateTime((string) $seconds, (string) $microSeconds); - $this->assertSame($expectedArray, $returned); + $this->assertSame($expected, $returned->toString()); } public function testCalculateTimeThrowsExceptionWhenSecondsIsNotOnlyDigits(): void @@ -134,11 +132,9 @@ class PhpTimeConverterTest extends TestCase } /** - * @param string[] $expected - * * @dataProvider provideCalculateTime */ - public function testCalculateTime(string $seconds, string $microSeconds, array $expected): void + public function testCalculateTime(string $seconds, string $microSeconds, string $expected): void { $calculator = new BrickMathCalculator(); $fallbackConverter = new GenericTimeConverter($calculator); @@ -146,7 +142,7 @@ class PhpTimeConverterTest extends TestCase $result = $converter->calculateTime($seconds, $microSeconds); - $this->assertSame($expected, $result); + $this->assertSame($expected, $result->toString()); } /** @@ -158,29 +154,17 @@ class PhpTimeConverterTest extends TestCase [ 'seconds' => '-12219146756', 'microSeconds' => '0', - 'expected' => [ - 'low' => '0901e600', - 'mid' => '0154', - 'hi' => '0000', - ], + 'expected' => '000001540901e600', ], [ 'seconds' => '103072857659', 'microseconds' => '999999', - 'expected' => [ - 'low' => 'ff9785f6', - 'mid' => 'ffff', - 'hi' => '0fff', - ], + 'expected' => '0fffffffff9785f6', ], [ 'seconds' => '1578612359', 'microseconds' => '521023', - 'expected' => [ - 'low' => '64c71df6', - 'mid' => '3337', - 'hi' => '01ea', - ], + 'expected' => '01ea333764c71df6', ], // This is the earliest possible date supported by v1 UUIDs: @@ -188,11 +172,7 @@ class PhpTimeConverterTest extends TestCase [ 'seconds' => '-12219292800', 'microSeconds' => '0', - 'expected' => [ - 'low' => '00000000', - 'mid' => '0000', - 'hi' => '0000', - ], + 'expected' => '0000000000000000', ], // This is the last possible time supported by v1 UUIDs: @@ -200,11 +180,7 @@ class PhpTimeConverterTest extends TestCase [ 'seconds' => '1832455114570', 'microseconds' => '955161', - 'expected' => [ - 'low' => 'fffffffa', - 'mid' => 'ffff', - 'hi' => 'ffff', - ], + 'expected' => 'fffffffffffffffa', ], ]; } diff --git a/tests/ExpectedBehaviorTest.php b/tests/ExpectedBehaviorTest.php index 2772ea9..d50344b 100644 --- a/tests/ExpectedBehaviorTest.php +++ b/tests/ExpectedBehaviorTest.php @@ -13,6 +13,7 @@ use Ramsey\Uuid\DegradedUuid; use Ramsey\Uuid\Generator\CombGenerator; use Ramsey\Uuid\Generator\DefaultTimeGenerator; use Ramsey\Uuid\Math\BrickMathCalculator; +use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Time; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidFactory; @@ -532,11 +533,7 @@ class ExpectedBehaviorTest extends TestCase $timeConverter ->shouldReceive('calculateTime') ->andReturnUsing(function ($seconds, $microSeconds) { - return [ - 'low' => dechex($seconds), - 'mid' => dechex($microSeconds), - 'hi' => 'abcd', - ]; + return new Hexadecimal('abcd' . dechex($microSeconds) . dechex($seconds)); }); $timeProvider = \Mockery::mock('Ramsey\Uuid\Provider\TimeProviderInterface', [ diff --git a/tests/Generator/DefaultTimeGeneratorTest.php b/tests/Generator/DefaultTimeGeneratorTest.php index d240d05..8d8cf52 100644 --- a/tests/Generator/DefaultTimeGeneratorTest.php +++ b/tests/Generator/DefaultTimeGeneratorTest.php @@ -16,6 +16,7 @@ use Ramsey\Uuid\Generator\DefaultTimeGenerator; use Ramsey\Uuid\Provider\NodeProviderInterface; use Ramsey\Uuid\Provider\TimeProviderInterface; use Ramsey\Uuid\Test\TestCase; +use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Time; class DefaultTimeGeneratorTest extends TestCase @@ -46,7 +47,7 @@ class DefaultTimeGeneratorTest extends TestCase private $currentTime; /** - * @var string[] + * @var Hexadecimal */ private $calculatedTime; @@ -61,7 +62,7 @@ class DefaultTimeGeneratorTest extends TestCase $this->nodeProvider = $this->getMockBuilder(NodeProviderInterface::class)->getMock(); $this->timeConverter = $this->getMockBuilder(TimeConverterInterface::class)->getMock(); $this->currentTime = ['sec' => 1458733431, 'usec' => 877449]; - $this->calculatedTime = ['low' => '83cb98e0', 'mid' => '98e0', 'hi' => '03cb']; + $this->calculatedTime = new Hexadecimal('03cb98e083cb98e0'); $time = new Time($this->currentTime['sec'], $this->currentTime['usec']); $this->timeProvider = Mockery::mock(TimeProviderInterface::class, [