diff --git a/src/Contracts/MakesBits.php b/src/Contracts/MakesBits.php index 891970a..470c5e1 100644 --- a/src/Contracts/MakesBits.php +++ b/src/Contracts/MakesBits.php @@ -11,6 +11,8 @@ public function make(): Bits; public function makeFromTimestamp(CarbonInterface $timestamp): Bits; + public function firstForTimestamp(CarbonInterface $timestamp): Bits; + public function fromId(int|string $id): Bits; public function coerce(int|string|Bits $value): Bits; diff --git a/src/Factories/GenericFactory.php b/src/Factories/GenericFactory.php index 59adfed..a9a4c3f 100644 --- a/src/Factories/GenericFactory.php +++ b/src/Factories/GenericFactory.php @@ -47,6 +47,19 @@ public function makeFromTimestamp(CarbonInterface $timestamp): Bits return new Bits($values, $this->config, $this->epoch); } + public function firstForTimestamp(CarbonInterface $timestamp): Bits + { + $timestamp = $this->diffFromEpoch($timestamp); + + $values = $this->config->organize( + ids: new WorkerIds(...array_map(fn() => 0, $this->ids->ids)), + timestamp: $timestamp, + sequence: 0 + ); + + return new Bits($values, $this->config, $this->epoch); + } + public function fromId(int|string $id): Bits { $values = $this->config->parse((int) $id); diff --git a/src/Factories/SnowflakeFactory.php b/src/Factories/SnowflakeFactory.php index d1166e5..d82a5c9 100644 --- a/src/Factories/SnowflakeFactory.php +++ b/src/Factories/SnowflakeFactory.php @@ -46,6 +46,13 @@ public function makeFromTimestamp(CarbonInterface $timestamp): Snowflake return new Snowflake($timestamp, $this->datacenter_id, $this->worker_id, $sequence, $this->epoch, $this->config); } + public function firstForTimestamp(CarbonInterface $timestamp): Snowflake + { + $timestamp = $this->diffFromEpoch($timestamp); + + return new Snowflake($timestamp, 0, 0, 0, $this->epoch, $this->config); + } + public function fromId(int|string $id): Snowflake { [$_, $timestamp, $datacenter_id, $worker_id, $sequence] = $this->config->parse((int) $id); diff --git a/src/Factories/SonyflakeFactory.php b/src/Factories/SonyflakeFactory.php index 2c4702b..12c7136 100644 --- a/src/Factories/SonyflakeFactory.php +++ b/src/Factories/SonyflakeFactory.php @@ -45,6 +45,13 @@ public function makeFromTimestamp(CarbonInterface $timestamp): Sonyflake return new Sonyflake($timestamp, $sequence, $this->machine_id, $this->epoch, $this->config); } + public function firstForTimestamp(CarbonInterface $timestamp): Sonyflake + { + $timestamp = $this->diffFromEpoch($timestamp); + + return new Sonyflake($timestamp, 0, 0, $this->epoch, $this->config); + } + public function fromId(int|string $id): Sonyflake { [$timestamp, $sequence, $machine_id] = $this->config->parse((int) $id); diff --git a/tests/Unit/CustomTest.php b/tests/Unit/CustomTest.php index 1b11a3f..823f917 100644 --- a/tests/Unit/CustomTest.php +++ b/tests/Unit/CustomTest.php @@ -195,12 +195,28 @@ public function test_a_bit_can_be_created_for_a_specific_timestamp(): void $factory = $this->getBitsFactory(14); $bits = $factory->makeFromTimestamp(now()->addMicrosecond()); + $this->assertEquals([1, 14, 0], $bits->values->all()); - $this->assertEquals(1, $bits->values[0]); + $bits = $factory->makeFromTimestamp(now()->addMicrosecond()); + $this->assertEquals([1, 14, 1], $bits->values->all()); $bits = $factory->makeFromTimestamp(now()->addMicroseconds(42)); + $this->assertEquals([42, 14, 0], $bits->values->all()); + } + + public function test_a_bit_can_be_created_for_querying_by_a_specific_timestamp(): void + { + Date::setTestNow(now()); + + $factory = $this->getBitsFactory(14); + + $bits = $factory->firstForTimestamp(now()->addMicrosecond()); + + $this->assertEquals([1, 0, 0], $bits->values->all()); + + $bits = $factory->firstForTimestamp(now()->addMicroseconds(42)); - $this->assertEquals(42, $bits->values[0]); + $this->assertEquals([42, 0, 0], $bits->values->all()); } protected function getBitsFactory(int $id, ResolvesSequences $sequence = null): GenericFactory diff --git a/tests/Unit/SnowflakeTest.php b/tests/Unit/SnowflakeTest.php index 133d550..ac3bdbf 100644 --- a/tests/Unit/SnowflakeTest.php +++ b/tests/Unit/SnowflakeTest.php @@ -203,15 +203,53 @@ public function test_it_sleeps_1ms_when_sequence_limit_is_reached(): void public function test_a_snowflake_can_be_created_for_a_specific_timestamp(): void { - $factory = $this->app->make(MakesSnowflakes::class); + $factory = new SnowflakeFactory( + epoch: now(), + datacenter_id: 1, + worker_id: 15, + config: app(SnowflakesConfig::class), + sequence: new InMemorySequenceResolver(), + ); $snowflake = $factory->makeFromTimestamp($factory->epoch->toImmutable()->addMillisecond()); $this->assertEquals(1, $snowflake->timestamp); + $this->assertEquals(1, $snowflake->datacenter_id); + $this->assertEquals(15, $snowflake->worker_id); + $this->assertEquals(0, $snowflake->sequence); + + $snowflake = $factory->makeFromTimestamp($factory->epoch->toImmutable()->addMillisecond()); + + $this->assertEquals(1, $snowflake->timestamp); + $this->assertEquals(1, $snowflake->datacenter_id); + $this->assertEquals(15, $snowflake->worker_id); + $this->assertEquals(1, $snowflake->sequence); $snowflake = $factory->makeFromTimestamp($factory->epoch->toImmutable()->addMilliseconds(42)); $this->assertEquals(42, $snowflake->timestamp); + $this->assertEquals(1, $snowflake->datacenter_id); + $this->assertEquals(15, $snowflake->worker_id); + $this->assertEquals(0, $snowflake->sequence); + } + + public function test_a_snowflake_can_be_created_for_querying_by_a_specific_timestamp(): void + { + $factory = $this->app->make(MakesSnowflakes::class); + + $snowflake = $factory->firstForTimestamp($factory->epoch->toImmutable()->addMillisecond()); + + $this->assertEquals(1, $snowflake->timestamp); + $this->assertEquals(0, $snowflake->datacenter_id); + $this->assertEquals(0, $snowflake->worker_id); + $this->assertEquals(0, $snowflake->sequence); + + $snowflake = $factory->firstForTimestamp($factory->epoch->toImmutable()->addMilliseconds(42)); + + $this->assertEquals(42, $snowflake->timestamp); + $this->assertEquals(0, $snowflake->datacenter_id); + $this->assertEquals(0, $snowflake->worker_id); + $this->assertEquals(0, $snowflake->sequence); } public function test_a_snowflake_can_be_serialized_to_json(): void diff --git a/tests/Unit/SonyflakeTest.php b/tests/Unit/SonyflakeTest.php index 1bd60fb..1762629 100644 --- a/tests/Unit/SonyflakeTest.php +++ b/tests/Unit/SonyflakeTest.php @@ -152,7 +152,7 @@ public function test_it_sleeps_10ms_when_sequence_limit_is_reached(): void Sleep::assertSlept(fn(CarbonInterval $interval) => (int) $interval->totalMicroseconds === 10000); } - public function test_a_snowflake_can_be_created_for_a_specific_timestamp(): void + public function test_a_sonyflake_can_be_created_for_a_specific_timestamp(): void { $factory = $this->app->make(MakesSonyflakes::class); @@ -164,4 +164,21 @@ public function test_a_snowflake_can_be_created_for_a_specific_timestamp(): void $this->assertEquals(42, $sonyflake->timestamp); } + + public function test_a_sonyflake_can_be_created_for_querying_by_a_specific_timestamp(): void + { + $factory = $this->app->make(MakesSonyflakes::class); + + $sonyflake = $factory->firstForTimestamp($factory->epoch->toImmutable()->addMilliseconds(10)); + + $this->assertEquals(1, $sonyflake->timestamp); + $this->assertEquals(0, $sonyflake->sequence); + $this->assertEquals(0, $sonyflake->machine_id); + + $sonyflake = $factory->firstForTimestamp($factory->epoch->toImmutable()->addMilliseconds(420)); + + $this->assertEquals(42, $sonyflake->timestamp); + $this->assertEquals(0, $sonyflake->sequence); + $this->assertEquals(0, $sonyflake->machine_id); + } }