Skip to content

Commit 48779cc

Browse files
feat: implemented version 7 UUID (#6)
Two new interfaces have been added: - `Castor\Uuid\TimeBased` which is implemented by Version 1, 6 and 7 UUIDs - `Castor\Uuid\System\Time` which has two implementations: Gregorian (v1 and v6) and Unix (v7) BREAKING CHANGE: `Castor\Uuid\System\Time` has been renamed to `Castor\Uuid\System\Time\Gregorian` BREAKING CHANGE: `Castor\Uuid\Version1::getTime` now returns `Castor\Uuid\System\Gregorian` BREAKING CHANGE: `Castor\Uuid\Version6::getTime` now returns `Castor\Uuid\System\Gregorian`
1 parent 6064863 commit 48779cc

File tree

19 files changed

+506
-120
lines changed

19 files changed

+506
-120
lines changed

src/Uuid/Any.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ public static function fromBytes(Bytes|string $bytes): Uuid
141141
0x40 => new Version4($bytes), // 0100 0000
142142
0x50 => new Version5($bytes), // 0101 0000
143143
0x60 => new Version6($bytes), // 0110 0000
144+
0x70 => new Version7($bytes), // 0111 0000
144145
default => new Any($bytes)
145146
};
146147
}
@@ -190,6 +191,7 @@ protected static function lazy(string $uuid): Uuid
190191
'4' => new Version4(new Bytes(''), $uuid),
191192
'5' => new Version5(new Bytes(''), $uuid),
192193
'6' => new Version6(new Bytes(''), $uuid),
194+
'7' => new Version7(new Bytes(''), $uuid),
193195
default => new Any(new Bytes(''), $uuid)
194196
};
195197
}

src/Uuid/System/Random.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* @project Castor UUID
7+
* @link https://github.com/castor-labs/php-lib-uuid
8+
* @package castor/uuid
9+
* @author Matias Navarro-Carter [email protected]
10+
* @license MIT
11+
* @copyright 2024 CastorLabs Ltd
12+
*
13+
* For the full copyright and license information, please view the LICENSE
14+
* file that was distributed with this source code.
15+
*/
16+
17+
namespace Castor\Uuid\System;
18+
19+
use Random\Engine\Secure;
20+
use Random\Randomizer;
21+
22+
final class Random
23+
{
24+
private static ?Randomizer $global = null;
25+
26+
public static function global(): Randomizer
27+
{
28+
if (self::$global === null) {
29+
self::$global = new Randomizer(new Secure());
30+
}
31+
32+
return self::$global;
33+
}
34+
}

src/Uuid/System/State.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
namespace Castor\Uuid\System;
1818

1919
use Castor\Bytes;
20+
use Castor\Uuid\System\Time\Gregorian;
2021

2122
/**
2223
* State represents the system state needed to construct some UUIDs.
@@ -31,7 +32,7 @@ public function getClockSequence(): Bytes;
3132
/**
3233
* Returns the system time.
3334
*/
34-
public function getTime(): Time;
35+
public function getTime(): Gregorian;
3536

3637
/**
3738
* Returns the system's node.

src/Uuid/System/State/Fixed.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@
1818

1919
use Castor\Bytes;
2020
use Castor\Uuid\System\State;
21-
use Castor\Uuid\System\Time;
21+
use Castor\Uuid\System\Time\Gregorian;
2222

2323
final readonly class Fixed implements State
2424
{
2525
public function __construct(
26-
private Time $time,
26+
private Gregorian $time,
2727
private Bytes $clockSeq,
2828
private Bytes $node,
2929
) {}
@@ -33,7 +33,7 @@ public function getClockSequence(): Bytes
3333
return $this->clockSeq;
3434
}
3535

36-
public function getTime(): Time
36+
public function getTime(): Gregorian
3737
{
3838
return $this->time;
3939
}

src/Uuid/System/State/Standard.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
use Castor\Uuid\System\MacProvider\Fallback;
2323
use Castor\Uuid\System\MacProvider\FromOs;
2424
use Castor\Uuid\System\State;
25-
use Castor\Uuid\System\Time;
25+
use Castor\Uuid\System\Time\Gregorian;
2626
use Random\Engine\Secure;
2727
use Random\Randomizer;
2828

@@ -67,9 +67,9 @@ public function getClockSequence(): Bytes
6767
return $this->clockSequence;
6868
}
6969

70-
public function getTime(): Time
70+
public function getTime(): Gregorian
7171
{
72-
$gregorianTime = Time::now($this->clock);
72+
$gregorianTime = Gregorian::now($this->clock);
7373

7474
if ($gregorianTime->bytes->equals($this->lastTimestamp)) {
7575
$this->clockSequence = $this->generateClockSequence();

src/Uuid/System/Time.php

Lines changed: 5 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -16,79 +16,18 @@
1616

1717
namespace Castor\Uuid\System;
1818

19-
use Brick\DateTime\Clock;
2019
use Brick\DateTime\Instant;
2120
use Brick\Math\BigInteger;
22-
use Brick\Math\RoundingMode;
23-
use Castor\Bytes;
2421

25-
use function Castor\Err\must;
26-
27-
/**
28-
* Represents the time as the number of 100-nanosecond intervals since the gregorian epoch.
29-
*/
30-
class Time
22+
interface Time
3123
{
3224
/**
33-
* The number of 100-nanosecond intervals from the Gregorian calendar epoch
34-
* to the Unix epoch.
35-
*/
36-
private const string GREGORIAN_TO_UNIX_OFFSET = '122192928000000000';
37-
38-
/**
39-
* The number of 100-nanosecond intervals in one second.
25+
* Returns the instant from this time.
4026
*/
41-
private const string SECOND_INTERVALS = '10000000';
42-
43-
public function __construct(
44-
public readonly Bytes $bytes,
45-
) {}
46-
47-
public static function fromTimestamp(BigInteger $timestamp): Time
48-
{
49-
return must(static function () use ($timestamp) {
50-
$hex = \str_pad($timestamp->toBase(16), 16, '0', STR_PAD_LEFT);
51-
52-
return new self(Bytes::fromHex($hex));
53-
});
54-
}
55-
56-
public static function fromInstant(Instant $instant): Time
57-
{
58-
return must(function () use ($instant) {
59-
$epochSeconds = BigInteger::of($instant->getEpochSecond());
60-
$nanoSeconds = BigInteger::of($instant->getNano());
61-
62-
$secondsTicks = $epochSeconds->multipliedBy(self::SECOND_INTERVALS);
63-
$nanoTicks = $nanoSeconds->dividedBy(100, RoundingMode::DOWN);
64-
$ticksSinceEpoch = $secondsTicks->plus($nanoTicks);
65-
66-
return self::fromTimestamp($ticksSinceEpoch->plus(self::GREGORIAN_TO_UNIX_OFFSET));
67-
});
68-
}
69-
70-
public static function now(Clock $clock): Time
71-
{
72-
return self::fromInstant($clock->getTime());
73-
}
74-
75-
public function getInstant(): Instant
76-
{
77-
return must(function () {
78-
$ticksSinceEpoch = $this->getTimestamp()->minus(self::GREGORIAN_TO_UNIX_OFFSET); // Subtract gregorian offset
79-
80-
$epochSeconds = $ticksSinceEpoch->dividedBy(self::SECOND_INTERVALS, RoundingMode::DOWN);
81-
$nanoSeconds = $ticksSinceEpoch->remainder(self::SECOND_INTERVALS)->multipliedBy(100);
82-
83-
return Instant::of($epochSeconds->toInt(), $nanoSeconds->toInt());
84-
});
85-
}
27+
public function getInstant(): Instant;
8628

8729
/**
88-
* Returns the number of 100 nanosecond intervals since 1582-10-15 00:00:00 UTC as a numeric string.
30+
* Returns the timestamp as an integer from this time.
8931
*/
90-
public function getTimestamp(): BigInteger
91-
{
92-
return must(fn () => BigInteger::fromBase($this->bytes->toHex(), 16));
93-
}
32+
public function getTimestamp(): BigInteger;
9433
}

src/Uuid/System/Time/Gregorian.php

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* @project Castor UUID
7+
* @link https://github.com/castor-labs/php-lib-uuid
8+
* @package castor/uuid
9+
* @author Matias Navarro-Carter [email protected]
10+
* @license MIT
11+
* @copyright 2024 CastorLabs Ltd
12+
*
13+
* For the full copyright and license information, please view the LICENSE
14+
* file that was distributed with this source code.
15+
*/
16+
17+
namespace Castor\Uuid\System\Time;
18+
19+
use Brick\DateTime\Clock;
20+
use Brick\DateTime\Instant;
21+
use Brick\Math\BigInteger;
22+
use Brick\Math\RoundingMode;
23+
use Castor\Bytes;
24+
use Castor\Uuid\System\Time;
25+
26+
use function Castor\Err\must;
27+
28+
/**
29+
* Represents the time as the number of 100-nanosecond intervals since the gregorian epoch.
30+
*/
31+
readonly class Gregorian implements Time
32+
{
33+
/**
34+
* The number of 100-nanosecond intervals from the Gregorian calendar epoch
35+
* to the Unix epoch.
36+
*/
37+
private const string GREGORIAN_TO_UNIX_OFFSET = '122192928000000000';
38+
39+
/**
40+
* The number of 100-nanosecond intervals in one second.
41+
*/
42+
private const string SECOND_INTERVALS = '10000000';
43+
44+
public function __construct(
45+
public Bytes $bytes,
46+
) {
47+
if ($this->bytes->len() !== 8) {
48+
throw new \InvalidArgumentException('Gregorian time must be 64 bits long');
49+
}
50+
}
51+
52+
public static function fromTimestamp(BigInteger $timestamp): self
53+
{
54+
return must(static function () use ($timestamp) {
55+
$hex = \str_pad($timestamp->toBase(16), 16, '0', STR_PAD_LEFT);
56+
57+
return new self(Bytes::fromHex($hex));
58+
});
59+
}
60+
61+
public static function fromInstant(Instant $instant): self
62+
{
63+
return must(function () use ($instant) {
64+
$epochSeconds = BigInteger::of($instant->getEpochSecond());
65+
$nanoSeconds = BigInteger::of($instant->getNano());
66+
67+
$secondsTicks = $epochSeconds->multipliedBy(self::SECOND_INTERVALS);
68+
$nanoTicks = $nanoSeconds->dividedBy(100, RoundingMode::DOWN);
69+
$ticksSinceEpoch = $secondsTicks->plus($nanoTicks);
70+
71+
return self::fromTimestamp($ticksSinceEpoch->plus(self::GREGORIAN_TO_UNIX_OFFSET));
72+
});
73+
}
74+
75+
public static function now(Clock $clock): self
76+
{
77+
return self::fromInstant($clock->getTime());
78+
}
79+
80+
public function getInstant(): Instant
81+
{
82+
return must(function () {
83+
$ticksSinceEpoch = $this->getTimestamp()->minus(self::GREGORIAN_TO_UNIX_OFFSET); // Subtract gregorian offset
84+
85+
$epochSeconds = $ticksSinceEpoch->dividedBy(self::SECOND_INTERVALS, RoundingMode::DOWN);
86+
$nanoSeconds = $ticksSinceEpoch->remainder(self::SECOND_INTERVALS)->multipliedBy(100);
87+
88+
return Instant::of($epochSeconds->toInt(), $nanoSeconds->toInt());
89+
});
90+
}
91+
92+
/**
93+
* Returns the number of 100 nanosecond intervals since 1582-10-15 00:00:00 UTC as a numeric string.
94+
*/
95+
public function getTimestamp(): BigInteger
96+
{
97+
return must(fn () => BigInteger::fromBase($this->bytes->toHex(), 16));
98+
}
99+
}

src/Uuid/System/Time/Unix.php

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* @project Castor UUID
7+
* @link https://github.com/castor-labs/php-lib-uuid
8+
* @package castor/uuid
9+
* @author Matias Navarro-Carter [email protected]
10+
* @license MIT
11+
* @copyright 2024 CastorLabs Ltd
12+
*
13+
* For the full copyright and license information, please view the LICENSE
14+
* file that was distributed with this source code.
15+
*/
16+
17+
namespace Castor\Uuid\System\Time;
18+
19+
use Brick\DateTime\Clock;
20+
use Brick\DateTime\Instant;
21+
use Brick\Math\BigInteger;
22+
use Brick\Math\RoundingMode;
23+
use Castor\Bytes;
24+
use Castor\Uuid\System\Time;
25+
26+
use function Castor\Err\must;
27+
28+
readonly class Unix implements Time
29+
{
30+
public function __construct(
31+
public Bytes $bytes
32+
) {
33+
if ($this->bytes->len() !== 6) {
34+
throw new \InvalidArgumentException('Unix time must be 48 bits long');
35+
}
36+
}
37+
38+
public static function fromInstant(Instant $instant): self
39+
{
40+
return must(function () use ($instant) {
41+
$secondsInMilliseconds = BigInteger::of($instant->getEpochSecond())->multipliedBy(1000);
42+
$nanosInMilliseconds = BigInteger::of($instant->getNano())->dividedBy(1e+6, RoundingMode::DOWN);
43+
44+
return self::fromTimestamp($secondsInMilliseconds->plus($nanosInMilliseconds));
45+
});
46+
}
47+
48+
public static function fromTimestamp(BigInteger $timestamp): self
49+
{
50+
return must(static function () use ($timestamp) {
51+
$hex = \str_pad($timestamp->toBase(16), 12, '0', STR_PAD_LEFT);
52+
53+
return new self(Bytes::fromHex($hex));
54+
});
55+
}
56+
57+
public static function now(Clock $clock): self
58+
{
59+
return self::fromInstant($clock->getTime());
60+
}
61+
62+
public function getInstant(): Instant
63+
{
64+
return must(function () {
65+
$timestamp = $this->getTimestamp();
66+
$nanoSeconds = $timestamp->multipliedBy(1e+6);
67+
68+
return Instant::of(0, $nanoSeconds->toInt());
69+
});
70+
}
71+
72+
/**
73+
* Returns the number of millisecond elapsed since 1970-01-01 00:00:00 UTC.
74+
*/
75+
public function getTimestamp(): BigInteger
76+
{
77+
return must(fn () => BigInteger::fromBase($this->bytes->toHex(), 16));
78+
}
79+
}

0 commit comments

Comments
 (0)