From bfafb4d28d5962fa58a9d2617c3b0a8acf95b4a8 Mon Sep 17 00:00:00 2001 From: azjezz Date: Sat, 23 Mar 2024 07:02:14 +0000 Subject: [PATCH] - Signed-off-by: azjezz --- Makefile | 4 +- docs/component/file.md | 6 +- docs/component/io.md | 38 ++--- docs/component/shell.md | 2 +- docs/component/tcp.md | 2 +- docs/component/unix.md | 2 +- examples/async/usleep.php | 4 +- examples/channel/main.php | 3 +- examples/io/benchmark.php | 6 +- examples/io/pipe.php | 5 +- examples/io/queued.php | 2 +- examples/run.php | 6 +- examples/shell/timeout.php | 3 +- sample.php | 15 +- src/Psl/Async/OptionalIncrementalTimeout.php | 38 +++-- src/Psl/Async/Scheduler.php | 19 +-- src/Psl/Async/sleep.php | 11 +- src/Psl/DateTime/DatePattern.php | 3 +- src/Psl/DateTime/DateTime.php | 140 ++++++++++++----- .../DateTimeConvenienceMethodsTrait.php | 144 +++--------------- src/Psl/DateTime/DateTimeInterface.php | 11 +- src/Psl/DateTime/Duration.php | 37 +++-- .../DateTime/Internal/default_timezone.php | 25 +++ .../DateTime/Internal/to_intl_timezone.php | 24 ++- src/Psl/DateTime/Month.php | 10 ++ .../TemporalConvenienceMethodsTrait.php | 4 +- src/Psl/DateTime/TemporalInterface.php | 11 +- src/Psl/DateTime/Timestamp.php | 108 +++++++++---- src/Psl/DateTime/Timezone.php | 10 ++ src/Psl/DateTime/is_leap_year.php | 4 +- src/Psl/File/ReadHandle.php | 2 +- src/Psl/File/ReadWriteHandle.php | 4 +- src/Psl/File/WriteHandle.php | 2 +- src/Psl/IO/CloseReadStreamHandle.php | 2 +- src/Psl/IO/CloseReadWriteStreamHandle.php | 4 +- src/Psl/IO/CloseSeekReadStreamHandle.php | 2 +- src/Psl/IO/CloseSeekReadWriteStreamHandle.php | 4 +- src/Psl/IO/CloseSeekWriteStreamHandle.php | 2 +- src/Psl/IO/CloseWriteStreamHandle.php | 2 +- src/Psl/IO/Internal/ResourceHandle.php | 24 +-- src/Psl/IO/MemoryHandle.php | 6 +- .../IO/ReadHandleConvenienceMethodsTrait.php | 4 +- src/Psl/IO/ReadHandleInterface.php | 6 +- src/Psl/IO/ReadStreamHandle.php | 2 +- src/Psl/IO/ReadWriteStreamHandle.php | 4 +- src/Psl/IO/Reader.php | 12 +- src/Psl/IO/SeekReadStreamHandle.php | 2 +- src/Psl/IO/SeekReadWriteStreamHandle.php | 4 +- src/Psl/IO/SeekWriteStreamHandle.php | 2 +- .../IO/WriteHandleConvenienceMethodsTrait.php | 2 +- src/Psl/IO/WriteHandleInterface.php | 4 +- src/Psl/IO/WriteStreamHandle.php | 2 +- src/Psl/IO/streaming.php | 8 +- src/Psl/Internal/Loader.php | 1 + src/Psl/Network/Internal/Socket.php | 4 +- src/Psl/Network/Internal/socket_connect.php | 8 +- src/Psl/Shell/execute.php | 2 +- src/Psl/TCP/connect.php | 2 +- src/Psl/Unix/connect.php | 2 +- tests/unit/Async/AllTest.php | 15 +- tests/unit/Async/AnyTest.php | 11 +- tests/unit/Async/AwaitableTest.php | 11 +- tests/unit/Async/DeferredTest.php | 5 +- tests/unit/Async/FirstTest.php | 11 +- tests/unit/Async/KeyedSemaphoreTest.php | 37 ++--- tests/unit/Async/KeyedSequenceTest.php | 33 ++-- tests/unit/Async/ParallelTest.php | 11 +- tests/unit/Async/ReflectTest.php | 3 +- tests/unit/Async/RunTest.php | 7 +- tests/unit/Async/SemaphoreTest.php | 33 ++-- tests/unit/Async/SequenceTest.php | 33 ++-- tests/unit/Async/SeriesTest.php | 9 +- tests/unit/Channel/BoundedChannelTest.php | 20 +-- tests/unit/DateTime/TimeIntervalTest.php | 55 +++---- .../Filesystem/AbstractFilesystemTest.php | 4 +- tests/unit/IO/PipeTest.php | 7 +- tests/unit/TCP/ServerTest.php | 3 +- 77 files changed, 624 insertions(+), 506 deletions(-) create mode 100644 src/Psl/DateTime/Internal/default_timezone.php diff --git a/Makefile b/Makefile index ba7b8ffe..7cad2c6f 100644 --- a/Makefile +++ b/Makefile @@ -22,8 +22,8 @@ compare-benchmark-to-reference: ./vendor/bin/phpbench run --config config/phpbench.json --ref=benchmark_reference static-analysis: ## run static analysis checks - ./vendor/bin/psalm -c config/psalm.xml --show-info=true --no-cache --threads=1 - ./vendor/bin/psalm -c config/psalm.xml tests/static-analysis --no-cache --threads=1 + ./vendor/bin/psalm -c config/psalm.xml --show-info=true --no-cache --threads=2 + ./vendor/bin/psalm -c config/psalm.xml tests/static-analysis --no-cache --threads=2 type-coverage: ## send static analysis type coverage metrics to https://shepherd.dev/ ./vendor/bin/psalm -c config/psalm.xml --shepherd --stats --threads=1 diff --git a/docs/component/file.md b/docs/component/file.md index d280981d..28374bb6 100644 --- a/docs/component/file.md +++ b/docs/component/file.md @@ -28,9 +28,9 @@ #### `Classes` - [Lock](./../../src/Psl/File/Lock.php#L9) -- [ReadHandle](./../../src/Psl/File/ReadHandle.php#L10) -- [ReadWriteHandle](./../../src/Psl/File/ReadWriteHandle.php#L11) -- [WriteHandle](./../../src/Psl/File/WriteHandle.php#L11) +- [ReadHandle](./../../src/Psl/File/ReadHandle.php#L11) +- [ReadWriteHandle](./../../src/Psl/File/ReadWriteHandle.php#L12) +- [WriteHandle](./../../src/Psl/File/WriteHandle.php#L12) #### `Enums` diff --git a/docs/component/io.md b/docs/component/io.md index 66083467..b5f18496 100644 --- a/docs/component/io.md +++ b/docs/component/io.md @@ -16,7 +16,7 @@ - [input_handle](./../../src/Psl/IO/input_handle.php#L20) - [output_handle](./../../src/Psl/IO/output_handle.php#L20) - [pipe](./../../src/Psl/IO/pipe.php#L24) -- [streaming](./../../src/Psl/IO/streaming.php#L38) +- [streaming](./../../src/Psl/IO/streaming.php#L41) - [write](./../../src/Psl/IO/write.php#L21) - [write_error](./../../src/Psl/IO/write_error.php#L23) - [write_error_line](./../../src/Psl/IO/write_error_line.php#L23) @@ -41,7 +41,7 @@ - [CloseWriteHandleInterface](./../../src/Psl/IO/CloseWriteHandleInterface.php#L7) - [CloseWriteStreamHandleInterface](./../../src/Psl/IO/CloseWriteStreamHandleInterface.php#L9) - [HandleInterface](./../../src/Psl/IO/HandleInterface.php#L21) -- [ReadHandleInterface](./../../src/Psl/IO/ReadHandleInterface.php#L10) +- [ReadHandleInterface](./../../src/Psl/IO/ReadHandleInterface.php#L12) - [ReadStreamHandleInterface](./../../src/Psl/IO/ReadStreamHandleInterface.php#L9) - [ReadWriteHandleInterface](./../../src/Psl/IO/ReadWriteHandleInterface.php#L7) - [ReadWriteStreamHandleInterface](./../../src/Psl/IO/ReadWriteStreamHandleInterface.php#L9) @@ -54,32 +54,32 @@ - [SeekWriteHandleInterface](./../../src/Psl/IO/SeekWriteHandleInterface.php#L7) - [SeekWriteStreamHandleInterface](./../../src/Psl/IO/SeekWriteStreamHandleInterface.php#L9) - [StreamHandleInterface](./../../src/Psl/IO/StreamHandleInterface.php#L9) -- [WriteHandleInterface](./../../src/Psl/IO/WriteHandleInterface.php#L10) +- [WriteHandleInterface](./../../src/Psl/IO/WriteHandleInterface.php#L12) - [WriteStreamHandleInterface](./../../src/Psl/IO/WriteStreamHandleInterface.php#L9) #### `Classes` -- [CloseReadStreamHandle](./../../src/Psl/IO/CloseReadStreamHandle.php#L12) -- [CloseReadWriteStreamHandle](./../../src/Psl/IO/CloseReadWriteStreamHandle.php#L12) -- [CloseSeekReadStreamHandle](./../../src/Psl/IO/CloseSeekReadStreamHandle.php#L12) -- [CloseSeekReadWriteStreamHandle](./../../src/Psl/IO/CloseSeekReadWriteStreamHandle.php#L12) +- [CloseReadStreamHandle](./../../src/Psl/IO/CloseReadStreamHandle.php#L13) +- [CloseReadWriteStreamHandle](./../../src/Psl/IO/CloseReadWriteStreamHandle.php#L13) +- [CloseSeekReadStreamHandle](./../../src/Psl/IO/CloseSeekReadStreamHandle.php#L13) +- [CloseSeekReadWriteStreamHandle](./../../src/Psl/IO/CloseSeekReadWriteStreamHandle.php#L13) - [CloseSeekStreamHandle](./../../src/Psl/IO/CloseSeekStreamHandle.php#L10) -- [CloseSeekWriteStreamHandle](./../../src/Psl/IO/CloseSeekWriteStreamHandle.php#L12) +- [CloseSeekWriteStreamHandle](./../../src/Psl/IO/CloseSeekWriteStreamHandle.php#L13) - [CloseStreamHandle](./../../src/Psl/IO/CloseStreamHandle.php#L10) -- [CloseWriteStreamHandle](./../../src/Psl/IO/CloseWriteStreamHandle.php#L12) -- [MemoryHandle](./../../src/Psl/IO/MemoryHandle.php#L13) -- [ReadStreamHandle](./../../src/Psl/IO/ReadStreamHandle.php#L12) -- [ReadWriteStreamHandle](./../../src/Psl/IO/ReadWriteStreamHandle.php#L12) -- [Reader](./../../src/Psl/IO/Reader.php#L16) -- [SeekReadStreamHandle](./../../src/Psl/IO/SeekReadStreamHandle.php#L12) -- [SeekReadWriteStreamHandle](./../../src/Psl/IO/SeekReadWriteStreamHandle.php#L12) +- [CloseWriteStreamHandle](./../../src/Psl/IO/CloseWriteStreamHandle.php#L13) +- [MemoryHandle](./../../src/Psl/IO/MemoryHandle.php#L14) +- [ReadStreamHandle](./../../src/Psl/IO/ReadStreamHandle.php#L13) +- [ReadWriteStreamHandle](./../../src/Psl/IO/ReadWriteStreamHandle.php#L13) +- [Reader](./../../src/Psl/IO/Reader.php#L17) +- [SeekReadStreamHandle](./../../src/Psl/IO/SeekReadStreamHandle.php#L13) +- [SeekReadWriteStreamHandle](./../../src/Psl/IO/SeekReadWriteStreamHandle.php#L13) - [SeekStreamHandle](./../../src/Psl/IO/SeekStreamHandle.php#L10) -- [SeekWriteStreamHandle](./../../src/Psl/IO/SeekWriteStreamHandle.php#L12) -- [WriteStreamHandle](./../../src/Psl/IO/WriteStreamHandle.php#L12) +- [SeekWriteStreamHandle](./../../src/Psl/IO/SeekWriteStreamHandle.php#L13) +- [WriteStreamHandle](./../../src/Psl/IO/WriteStreamHandle.php#L13) #### `Traits` -- [ReadHandleConvenienceMethodsTrait](./../../src/Psl/IO/ReadHandleConvenienceMethodsTrait.php#L15) -- [WriteHandleConvenienceMethodsTrait](./../../src/Psl/IO/WriteHandleConvenienceMethodsTrait.php#L16) +- [ReadHandleConvenienceMethodsTrait](./../../src/Psl/IO/ReadHandleConvenienceMethodsTrait.php#L16) +- [WriteHandleConvenienceMethodsTrait](./../../src/Psl/IO/WriteHandleConvenienceMethodsTrait.php#L17) diff --git a/docs/component/shell.md b/docs/component/shell.md index 364fdd49..90a27c32 100644 --- a/docs/component/shell.md +++ b/docs/component/shell.md @@ -12,7 +12,7 @@ #### `Functions` -- [execute](./../../src/Psl/Shell/execute.php#L41) +- [execute](./../../src/Psl/Shell/execute.php#L42) - [stream_unpack](./../../src/Psl/Shell/stream_unpack.php#L30) - [unpack](./../../src/Psl/Shell/unpack.php#L16) diff --git a/docs/component/tcp.md b/docs/component/tcp.md index 8c208bab..11694b10 100644 --- a/docs/component/tcp.md +++ b/docs/component/tcp.md @@ -12,7 +12,7 @@ #### `Functions` -- [connect](./../../src/Psl/TCP/connect.php#L18) +- [connect](./../../src/Psl/TCP/connect.php#L19) #### `Classes` diff --git a/docs/component/unix.md b/docs/component/unix.md index cec4b206..0b22b2bb 100644 --- a/docs/component/unix.md +++ b/docs/component/unix.md @@ -12,7 +12,7 @@ #### `Functions` -- [connect](./../../src/Psl/Unix/connect.php#L18) +- [connect](./../../src/Psl/Unix/connect.php#L19) #### `Classes` diff --git a/examples/async/usleep.php b/examples/async/usleep.php index 622317dc..dafcbebb 100644 --- a/examples/async/usleep.php +++ b/examples/async/usleep.php @@ -11,7 +11,7 @@ require __DIR__ . '/../../vendor/autoload.php'; Async\main(static function (): int { - $start = DateTime\Timestamp::now(); + $start = DateTime\Timestamp::monotonic(); Async\concurrently([ static fn() => Async\sleep(DateTime\Duration::hours(0)), @@ -23,7 +23,7 @@ static fn() => Async\sleep(DateTime\Duration::milliseconds(2000)), ]); - $duration = DateTime\Timestamp::now()->since($start); + $duration = DateTime\Timestamp::monotonic()->since($start); IO\write_error_line("duration : %s.", $duration->toString(max_decimals: 5)); diff --git a/examples/channel/main.php b/examples/channel/main.php index 1d899555..2f5faa5c 100644 --- a/examples/channel/main.php +++ b/examples/channel/main.php @@ -6,6 +6,7 @@ use Psl\Async; use Psl\Channel; +use Psl\DateTime\Duration; use Psl\IO; require __DIR__ . '/../../vendor/autoload.php'; @@ -16,7 +17,7 @@ */ [$receiver, $sender] = Channel\unbounded(); -Async\Scheduler::delay(1, static function () use ($sender) { +Async\Scheduler::delay(Duration::seconds(1), static function () use ($sender) { $sender->send('Hello, World!'); }); diff --git a/examples/io/benchmark.php b/examples/io/benchmark.php index cfe33604..219b3828 100644 --- a/examples/io/benchmark.php +++ b/examples/io/benchmark.php @@ -28,7 +28,7 @@ $args = getopt('i:o:t:'); $input_file = $args['i'] ?? '/dev/zero'; $output_file = $args['o'] ?? '/dev/null'; - $seconds = (int)($args['t'] ?? 5); + $seconds = DateTime\Duration::seconds((int)($args['t'] ?? 5)); // passing file descriptors requires mapping paths (https://bugs.php.net/bug.php?id=53465) $input_file = Regex\replace($input_file, '(^/dev/fd/)', 'php://fd/'); @@ -41,7 +41,7 @@ Async\Scheduler::delay($seconds, static fn() => $input->close()); - $start = DateTime\Timestamp::now(); + $start = DateTime\Timestamp::monotonic(); $i = 0; try { while ($chunk = $input->read(65536)) { @@ -53,7 +53,7 @@ } catch (IO\Exception\AlreadyClosedException) { } - $duration = DateTime\Timestamp::now()->since($start); + $duration = DateTime\Timestamp::monotonic()->since($start); $bytes = $i * 65536; $bytes_formatted = Math\round($bytes / 1024 / 1024 / $duration->getTotalSeconds(), 1); diff --git a/examples/io/pipe.php b/examples/io/pipe.php index 69f6d2bc..55e9b2c0 100644 --- a/examples/io/pipe.php +++ b/examples/io/pipe.php @@ -5,6 +5,7 @@ namespace Psl\Example\IO; use Psl\Async; +use Psl\DateTime\Duration; use Psl\IO; require __DIR__ . '/../../vendor/autoload.php'; @@ -16,7 +17,7 @@ static function() use($read): void { IO\write_error_line("< sleeping."); - Async\sleep(0.01); + Async\sleep(Duration::milliseconds(10)); IO\write_error_line("< waiting for content."); @@ -30,7 +31,7 @@ static function() use($read): void { static function() use($write): void { IO\write_error_line('> sleeping.'); - Async\sleep(0.1); + Async\sleep(Duration::milliseconds(100)); IO\write_error_line('> writing.'); diff --git a/examples/io/queued.php b/examples/io/queued.php index dc02ae57..ae344e77 100644 --- a/examples/io/queued.php +++ b/examples/io/queued.php @@ -15,7 +15,7 @@ $he = Async\run(static fn(): string => $read->readFixedSize(2)); - Async\sleep(0.001); + Async\sleep(Psl\DateTime\Duration::milliseconds(200)); $write->write("hello"); diff --git a/examples/run.php b/examples/run.php index 1613c400..a97aad79 100644 --- a/examples/run.php +++ b/examples/run.php @@ -35,9 +35,9 @@ IO\write_error_line('- %s/%s -> started', $component, $script); $awaitables[] = Async\run(static function() use($component, $script, $file): array { - $start = DateTime\Timestamp::now(); + $start = DateTime\Timestamp::monotonic(); Shell\execute(PHP_BINARY, [$file]); - $duration = DateTime\Timestamp::now()->since($start); + $duration = DateTime\Timestamp::monotonic()->since($start); return [$component, $script, $duration]; }); @@ -47,7 +47,7 @@ foreach (Async\Awaitable::iterate($awaitables) as $awaitable) { [$component, $script, $duration] = $awaitable->await(); - IO\write_error_line('+ %s/%s -> finished in %s', $component, $script, $duration); + IO\write_error_line('+ %s/%s -> finished in %s', $component, $script, $duration->toString(10)); } return 0; diff --git a/examples/shell/timeout.php b/examples/shell/timeout.php index 824068cc..8a9ebe68 100644 --- a/examples/shell/timeout.php +++ b/examples/shell/timeout.php @@ -5,6 +5,7 @@ namespace Psl\Example\Shell; use Psl\Async; +use Psl\DateTime; use Psl\IO; use Psl\Shell; @@ -12,7 +13,7 @@ Async\main(static function (): void { try { - Shell\execute('sleep', ['1'], timeout: 0.5); + Shell\execute('sleep', ['1'], timeout: DateTime\Duration::milliseconds(500)); } catch (Shell\Exception\TimeoutException $exception) { IO\write_error_line($exception->getMessage()); } diff --git a/sample.php b/sample.php index b42b084b..005e497c 100644 --- a/sample.php +++ b/sample.php @@ -11,11 +11,24 @@ require 'vendor/autoload.php'; Async\main(static function(): void { + $a = DateTime\Timestamp::now(); + $b = DateTime\Timestamp::now(); + + var_dump($b->since($a)->toString(20)); + + $a = DateTime\Timestamp::monotonic(); + $b = DateTime\Timestamp::monotonic(); + + var_dump($b->since($a)->toString(20)); + $someday = DateTime\DateTime::fromTimestamp( - DateTime\Timestamp::fromRaw(1711846900), DateTime\Timezone::EuropeLondon, + DateTime\Timestamp::fromRaw(1711846900), ); + var_dump($someday->format(DateTime\DatePattern::Http->value)); + die(); + IO\write_line('The offset of the timezone: %s', $someday->getTimezone()->getOffset($someday)->getTotalMinutes()); IO\write_line('The raw offset of the timezone: %s', $someday->getTimezone()->getRawOffset()->getTotalMinutes()); IO\write_line('The dst savings of the timezone: %s', $someday->getTimezone()->getDaylightSavingTimeSavings()->getTotalMinutes()); diff --git a/src/Psl/Async/OptionalIncrementalTimeout.php b/src/Psl/Async/OptionalIncrementalTimeout.php index 99a7349d..7bd1e5ea 100644 --- a/src/Psl/Async/OptionalIncrementalTimeout.php +++ b/src/Psl/Async/OptionalIncrementalTimeout.php @@ -5,8 +5,8 @@ namespace Psl\Async; use Closure; - -use function microtime; +use Psl\DateTime\Duration; +use Psl\DateTime\Timestamp; /** * Manages optional incremental timeouts for asynchronous operations. @@ -16,28 +16,40 @@ * particularly useful in asynchronous programming where operations * might need to be interrupted or handled differently if they take * too long to complete. + * + * @psalm-suppress MissingThrowsDocblock */ final class OptionalIncrementalTimeout { /** - * @var ?float The end time in microseconds. + * @var ?Timestamp The end time. */ - private ?float $end; + private ?Timestamp $end; /** - * @var (Closure(): ?float) The handler to be called upon timeout. + * @var (Closure(): ?Duration) The handler to be called upon timeout. */ private Closure $handler; /** - * @param float|null $timeout The timeout duration in seconds. Null to disable timeout. - * @param (Closure(): ?float) $handler The handler to be executed if the timeout is reached. + * @param null|Duration $timeout The timeout duration. Null to disable timeout. + * @param (Closure(): ?Duration) $handler The handler to be executed if the timeout is reached. */ - public function __construct(?float $timeout, Closure $handler) + public function __construct(?Duration $timeout, Closure $handler) { $this->handler = $handler; - $this->end = $timeout !== null ? (microtime(true) + $timeout) : null; + if (null === $timeout) { + $this->end = null; + + return; + } + + if (!$timeout->isPositive()) { + $this->end = Timestamp::monotonic(); + } else { + $this->end = Timestamp::monotonic()->plus($timeout); + } } /** @@ -45,18 +57,18 @@ public function __construct(?float $timeout, Closure $handler) * * If the timeout has already been exceeded, the handler is invoked, and its return value is provided. * - * @return float|null The remaining time in seconds, null if no timeout is set, or the handler's return value if the timeout is exceeded. + * @return Duration|null The remaining time duration, null if no timeout is set, or the handler's return value if the timeout is exceeded. * * @external-mutation-free */ - public function getRemaining(): ?float + public function getRemaining(): ?Duration { if ($this->end === null) { return null; } - $remaining = $this->end - microtime(true); + $remaining = $this->end->since(Timestamp::monotonic()); - return $remaining <= 0 ? ($this->handler)() : $remaining; + return $remaining->isPositive() ? $remaining : ($this->handler)(); } } diff --git a/src/Psl/Async/Scheduler.php b/src/Psl/Async/Scheduler.php index 26831e7e..fbd715ee 100644 --- a/src/Psl/Async/Scheduler.php +++ b/src/Psl/Async/Scheduler.php @@ -115,40 +115,33 @@ public static function defer(Closure $callback): string /** * Delay the execution of a callback. * - * @param DateTime\Duration|float $delay The amount of time, to delay the execution for in seconds. + * @param DateTime\Duration $delay The amount of time, to delay the execution for in seconds. * @param Closure(string): void $callback The callback to delay. * * @return non-empty-string A unique identifier that can be used to cancel, enable or disable the callback. * * @see EventLoop::delay() */ - public static function delay(DateTime\Duration|float $delay, Closure $callback): string + public static function delay(DateTime\Duration $delay, Closure $callback): string { - if ($delay instanceof DateTime\Duration) { - $delay = $delay->getTotalSeconds(); - } - /** @var non-empty-string */ - return EventLoop::delay($delay, $callback); + return EventLoop::delay($delay->getTotalSeconds(), $callback); } /** * Repeatedly execute a callback. * - * @param DateTime\Duration|float $interval The time interval, to wait between executions in seconds. + * @param DateTime\Duration $interval The time interval, to wait between executions in seconds. * @param Closure(string): void $callback The callback to repeat. * * @return non-empty-string A unique identifier that can be used to cancel, enable or disable the callback. * * @see EventLoop::repeat() */ - public static function repeat(DateTime\Duration|float $interval, Closure $callback): string + public static function repeat(DateTime\Duration $interval, Closure $callback): string { - if ($interval instanceof DateTime\Duration) { - $interval = $interval->getTotalSeconds(); - } /** @var non-empty-string */ - return EventLoop::repeat($interval, $callback); + return EventLoop::repeat($interval->getTotalSeconds(), $callback); } diff --git a/src/Psl/Async/sleep.php b/src/Psl/Async/sleep.php index 109452f9..394ab5c7 100644 --- a/src/Psl/Async/sleep.php +++ b/src/Psl/Async/sleep.php @@ -10,14 +10,13 @@ /** * Non-blocking sleep for the specified number of seconds. */ -function sleep(DateTime\Duration|float $seconds): void +function sleep(DateTime\Duration $duration): void { - if ($seconds instanceof DateTime\Duration) { - $seconds = $seconds->getTotalSeconds(); - } - $suspension = EventLoop::getSuspension(); - $watcher = EventLoop::delay($seconds, static fn () => $suspension->resume()); + $watcher = EventLoop::delay( + $duration->getTotalSeconds(), + static fn () => $suspension->resume(), + ); try { $suspension->suspend(); diff --git a/src/Psl/DateTime/DatePattern.php b/src/Psl/DateTime/DatePattern.php index c3ac97ce..e227ed4e 100644 --- a/src/Psl/DateTime/DatePattern.php +++ b/src/Psl/DateTime/DatePattern.php @@ -16,7 +16,8 @@ enum DatePattern: string case Iso8601 = 'yyyy-MM-dd\'T\'HH:mm:ssXXX'; case Http = 'EEE, dd MMM yyyy HH:mm:ss zzz'; case Cookie = 'EEEE, dd-MMM-yyyy HH:mm:ss zzz'; - case Sql = 'yyyy-MM-dd HH:mm:ss'; + case SqlDate = 'yyyy-MM-dd'; + case SqlDateTime = 'yyyy-MM-dd HH:mm:ss'; case XmlRpc = 'yyyyMMdd\'T\'HH:mm:ss'; case IsoWeekDate = 'Y-ww-E'; case IsoOrdinalDate = 'yyyy-DDD'; diff --git a/src/Psl/DateTime/DateTime.php b/src/Psl/DateTime/DateTime.php index d8934786..01971257 100644 --- a/src/Psl/DateTime/DateTime.php +++ b/src/Psl/DateTime/DateTime.php @@ -7,45 +7,50 @@ use IntlCalendar; use Psl\Locale\Locale; +/** + * @immutable + * + * @psalm-suppress ImpureMethodCall + */ final class DateTime implements DateTimeInterface { use DateTimeConvenienceMethodsTrait; - private Timezone $timezone; + private readonly Timezone $timezone; - private Timestamp $timestamp; + private readonly Timestamp $timestamp; - private int $year; + private readonly int $year; /** * @var int<1, 12> */ - private int $month; + private readonly int $month; /** * @var int<1, 31> */ - private int $day; + private readonly int $day; /** * @var int<0, 23> */ - private int $hours; + private readonly int $hours; /** * @var int<0, 59> */ - private int $minutes; + private readonly int $minutes; /** * @var int<0, 59> */ - private int $seconds; + private readonly int $seconds; /** * @var int<0, 999999999> */ - private int $nanoseconds; + private readonly int $nanoseconds; /** * Constructs a new date-time instance with specified components and timezone. @@ -59,6 +64,8 @@ final class DateTime implements DateTimeInterface * * @throws Exception\InvalidArgumentException If any of the date or time components are outside their valid ranges, * indicating an invalid date-time configuration. + * + * @psalm-external-mutation-free */ private function __construct(Timezone $timezone, Timestamp $timestamp, int $year, int $month, int $day, int $hours, int $minutes, int $seconds, int $nanoseconds) { @@ -68,7 +75,7 @@ private function __construct(Timezone $timezone, Timestamp $timestamp, int $year $minutes < 0 || $minutes >= 60 || $hours < 0 || $hours >= 24 || $month < 1 || $month > 12 || - $day < 1 || $day > Month::from($month)->getDaysForYear($year) + $day < 1 || $day > 31 || $day > Month::from($month)->getDaysForYear($year) ) { throw new Exception\InvalidArgumentException('One or more components of the date-time are out of valid ranges.'); } @@ -89,12 +96,10 @@ private function __construct(Timezone $timezone, Timestamp $timestamp, int $year * * This static method returns a {@see DateTime} object set to the current date and time. If a specific timezone is * provided, the returned {@see DateTime} will be adjusted to reflect the date and time in that timezone. - * - * @pure */ - public static function now(Timezone $timezone): DateTime + public static function now(?Timezone $timezone = null): DateTime { - return self::fromTimestamp(Timestamp::now(), $timezone); + return self::fromTimestamp($timezone ?? Timezone::default(), Timestamp::now()); } /** @@ -115,10 +120,8 @@ public static function now(Timezone $timezone): DateTime * * @throws Exception\InvalidArgumentException If any of the time components are outside their valid ranges, * indicating an invalid date-time configuration. - * - * @pure */ - public static function todayAt(Timezone $timezone, int $hours, int $minutes, int $seconds = 0, int $nanoseconds = 0): DateTime + public static function todayAt(int $hours, int $minutes, int $seconds = 0, int $nanoseconds = 0, ?Timezone $timezone = null): DateTime { return self::now($timezone)->withTime($hours, $minutes, $seconds, $nanoseconds); } @@ -140,34 +143,39 @@ public static function todayAt(Timezone $timezone, int $hours, int $minutes, int * * @pure */ - public static function fromParts(int $year, Month|int $month, int $day, int $hours = 0, int $minutes = 0, int $seconds = 0, int $nanoseconds = 0, Timezone $timezone = null): self + public static function fromParts(Timezone $timezone, int $year, Month|int $month, int $day, int $hours = 0, int $minutes = 0, int $seconds = 0, int $nanoseconds = 0): self { if ($month instanceof Month) { $month = $month->value; } - /** @var IntlCalendar $calendar */ + /** + * @var IntlCalendar $calendar + */ $calendar = IntlCalendar::createInstance( - $timezone === null ? null : Internal\to_intl_timezone($timezone), + Internal\to_intl_timezone($timezone), ); $calendar->set($year, $month - 1, $day, $hours, $minutes, $seconds); // Validate the date-time components by comparing them to what was set if ( - !($calendar->get(IntlCalendar::FIELD_YEAR) === $year && - ($calendar->get(IntlCalendar::FIELD_MONTH) + 1) === $month && - $calendar->get(IntlCalendar::FIELD_DAY_OF_MONTH) === $day && - $calendar->get(IntlCalendar::FIELD_HOUR_OF_DAY) === $hours && - $calendar->get(IntlCalendar::FIELD_MINUTE) === $minutes && - $calendar->get(IntlCalendar::FIELD_SECOND) === $seconds) + !( + $calendar->get(IntlCalendar::FIELD_YEAR) === $year && + ($calendar->get(IntlCalendar::FIELD_MONTH) + 1) === $month && + $calendar->get(IntlCalendar::FIELD_DAY_OF_MONTH) === $day && + $calendar->get(IntlCalendar::FIELD_HOUR_OF_DAY) === $hours && + $calendar->get(IntlCalendar::FIELD_MINUTE) === $minutes && + $calendar->get(IntlCalendar::FIELD_SECOND) === $seconds + ) ) { throw new Exception\InvalidArgumentException( 'The given components do not form a valid date-time.', ); } - $timestampInSeconds = (int) ($calendar->getTime() / MILLISECONDS_PER_SECOND); + $timestampInSeconds = (int) ($calendar->getTime() / ((float) MILLISECONDS_PER_SECOND)); + /** @psalm-suppress MissingThrowsDocblock */ $timestamp = Timestamp::fromRaw($timestampInSeconds, $nanoseconds); return new self( @@ -190,7 +198,7 @@ public static function fromParts(int $year, Month|int $month, int $day, int $hou * * @pure */ - public static function fromTimestamp(Timestamp $timestamp, Timezone $timezone): DateTime + public static function fromTimestamp(Timezone $timezone, Timestamp $timestamp): DateTime { /** @var IntlCalendar $calendar */ $calendar = IntlCalendar::createInstance( @@ -209,6 +217,7 @@ public static function fromTimestamp(Timestamp $timestamp, Timezone $timezone): $second = $calendar->get(IntlCalendar::FIELD_SECOND); $nanoseconds = $timestamp->getNanoseconds(); + /** @psalm-suppress MissingThrowsDocblock */ return new static( $timezone, $timestamp, @@ -229,13 +238,12 @@ public static function fromTimestamp(Timestamp $timestamp, Timezone $timezone): * making it versatile for handling various date/time formats. * * @throws Exception\RuntimeException If parsing fails or the date/time string is invalid. + * + * @pure */ public static function fromPattern(string|DatePattern $pattern, string $raw_string, Timezone $timezone, ?Locale $locale = null): self { - return self::fromTimestamp( - Timestamp::fromPattern($pattern, $raw_string, $timezone, $locale), - $timezone, - ); + return self::fromTimestamp($timezone, Timestamp::fromPattern($pattern, $raw_string, $timezone, $locale)); } /** @@ -244,13 +252,12 @@ public static function fromPattern(string|DatePattern $pattern, string $raw_stri * This method is a convenience wrapper for parsing date/time strings without specifying a custom pattern. * * @throws Exception\RuntimeException If parsing fails or the format of the date/time string is invalid. + * + * @pure */ public static function parse(string $raw_string, Timezone $timezone, ?Locale $locale = null): self { - return self::fromTimestamp( - Timestamp::parse($raw_string, $timezone, $locale), - $timezone, - ); + return self::fromTimestamp($timezone, Timestamp::parse($raw_string, $timezone, $locale)); } /** @@ -297,7 +304,7 @@ public function getMonth(): int /** * Returns the day. * - * @return int<0, 31> + * @return int<1, 31> * * @mutation-free */ @@ -364,28 +371,81 @@ public function getTimezone(): Timezone return $this->timezone; } + /** + * Returns a new instance with the specified date. + * + * @param Month|int<1, 12> $month + * @param int<1, 31> $day + * + * @throws Exception\InvalidArgumentException If specifying the date would result in an invalid date/time. + * This can happen if the combination of year, month, and day does not constitute a valid date (e.g., April 31st, February 29th in a non-leap year). + * + * @mutation-free + */ + public function withDate(int $year, Month|int $month, int $day): static + { + return static::fromParts( + $this->getTimezone(), + $year, + $month, + $day, + $this->getHours(), + $this->getMinutes(), + $this->getSeconds(), + $this->getNanoseconds(), + ); + } + + /** + * Returns a new instance with the specified time. + * + * @param int<0, 23> $hours + * @param int<0, 59> $minutes + * @param int<0, 59> $seconds + * @param int<0, 999999999> $nanoseconds + * + * @throws Exception\InvalidArgumentException If specifying the time would result in an invalid time (e.g., hours greater than 23, minutes or seconds greater than 59). + * + * @mutation-free + */ + public function withTime(int $hours, int $minutes, int $seconds = 0, int $nanoseconds = 0): static + { + return static::fromParts( + $this->getTimezone(), + $this->getYear(), + $this->getMonth(), + $this->getDay(), + $hours, + $minutes, + $seconds, + $nanoseconds, + ); + } + /** * Adds the specified duration to this date-time object, returning a new instance with the added duration. * + * @throws Exception\UnderflowException If adding the duration results in an arithmetic underflow. * @throws Exception\OverflowException If adding the duration results in an arithmetic overflow. * * @mutation-free */ public function plus(Duration $duration): static { - return static::fromTimestamp($this->getTimestamp()->plus($duration), $this->timezone); + return static::fromTimestamp($this->timezone, $this->getTimestamp()->plus($duration)); } /** * Subtracts the specified duration from this date-time object, returning a new instance with the subtracted duration. * + * @throws Exception\UnderflowException If subtracting the duration results in an arithmetic underflow. * @throws Exception\OverflowException If subtracting the duration results in an arithmetic overflow. * * @mutation-free */ public function minus(Duration $duration): static { - return static::fromTimestamp($this->getTimestamp()->minus($duration), $this->timezone); + return static::fromTimestamp($this->timezone, $this->getTimestamp()->minus($duration)); } /** @@ -397,7 +457,7 @@ public function minus(Duration $duration): static */ public function convertToTimezone(Timezone $timezone): static { - return static::fromTimestamp($this->getTimestamp(), $timezone); + return static::fromTimestamp($timezone, $this->getTimestamp()); } public function jsonSerialize(): array diff --git a/src/Psl/DateTime/DateTimeConvenienceMethodsTrait.php b/src/Psl/DateTime/DateTimeConvenienceMethodsTrait.php index d63e84be..6ccbeedc 100644 --- a/src/Psl/DateTime/DateTimeConvenienceMethodsTrait.php +++ b/src/Psl/DateTime/DateTimeConvenienceMethodsTrait.php @@ -38,16 +38,7 @@ public function equalsIncludingTimezone(DateTimeInterface $other): bool */ public function withYear(int $year): static { - return static::fromParts( - $year, - $this->getMonth(), - $this->getDay(), - $this->getHours(), - $this->getMinutes(), - $this->getSeconds(), - $this->getNanoseconds(), - $this->getTimezone(), - ); + return $this->withDate($year, $this->getMonth(), $this->getDay()); } /** @@ -61,16 +52,7 @@ public function withYear(int $year): static */ public function withMonth(Month|int $month): static { - return static::fromParts( - $this->getYear(), - $month, - $this->getDay(), - $this->getHours(), - $this->getMinutes(), - $this->getSeconds(), - $this->getNanoseconds(), - $this->getTimezone(), - ); + return $this->withDate($this->getYear(), $month, $this->getDay()); } /** @@ -84,16 +66,7 @@ public function withMonth(Month|int $month): static */ public function withDay(int $day): static { - return static::fromParts( - $this->getYear(), - $this->getMonth(), - $day, - $this->getHours(), - $this->getMinutes(), - $this->getSeconds(), - $this->getNanoseconds(), - $this->getTimezone(), - ); + return $this->withDate($this->getYear(), $this->getMonth(), $day); } /** @@ -107,16 +80,7 @@ public function withDay(int $day): static */ public function withHours(int $hours): static { - return static::fromParts( - $this->getYear(), - $this->getMonth(), - $this->getDay(), - $hours, - $this->getMinutes(), - $this->getSeconds(), - $this->getNanoseconds(), - $this->getTimezone(), - ); + return $this->withTime($hours, $this->getMinutes(), $this->getSeconds(), $this->getNanoseconds()); } /** @@ -130,16 +94,7 @@ public function withHours(int $hours): static */ public function withMinutes(int $minutes): static { - return static::fromParts( - $this->getYear(), - $this->getMonth(), - $this->getDay(), - $this->getHours(), - $minutes, - $this->getSeconds(), - $this->getNanoseconds(), - $this->getTimezone(), - ); + return $this->withTime($this->getHours(), $minutes, $this->getSeconds(), $this->getNanoseconds()); } /** @@ -153,16 +108,7 @@ public function withMinutes(int $minutes): static */ public function withSeconds(int $seconds): static { - return static::fromParts( - $this->getYear(), - $this->getMonth(), - $this->getDay(), - $this->getHours(), - $this->getMinutes(), - $seconds, - $this->getNanoseconds(), - $this->getTimezone(), - ); + return $this->withTime($this->getHours(), $this->getMinutes(), $seconds, $this->getNanoseconds()); } /** @@ -176,67 +122,7 @@ public function withSeconds(int $seconds): static */ public function withNanoseconds(int $nanoseconds): static { - return static::fromParts( - $this->getYear(), - $this->getMonth(), - $this->getDay(), - $this->getHours(), - $this->getMinutes(), - $this->getSeconds(), - $nanoseconds, - $this->getTimezone(), - ); - } - - /** - * Returns a new instance with the specified date. - * - * @param Month|int<1, 12> $month - * @param int<1, 31> $day - * - * @throws Exception\InvalidArgumentException If specifying the date would result in an invalid date/time. - * This can happen if the combination of year, month, and day does not constitute a valid date (e.g., April 31st, February 29th in a non-leap year). - * - * @mutation-free - */ - public function withDate(int $year, Month|int $month, int $day): static - { - return static::fromParts( - $year, - $month, - $day, - $this->getHours(), - $this->getMinutes(), - $this->getSeconds(), - $this->getNanoseconds(), - $this->getTimezone(), - ); - } - - /** - * Returns a new instance with the specified time. - * - * @param int<0, 23> $hours - * @param int<0, 59> $minutes - * @param int<0, 59> $seconds - * @param int<0, 999999999> $nanoseconds - * - * @throws Exception\InvalidArgumentException If specifying the time would result in an invalid time (e.g., hours greater than 23, minutes or seconds greater than 59). - * - * @mutation-free - */ - public function withTime(int $hours, int $minutes, int $seconds = 0, int $nanoseconds = 0): static - { - return static::fromParts( - $this->getYear(), - $this->getMonth(), - $this->getDay(), - $hours, - $minutes, - $seconds, - $nanoseconds, - $this->getTimezone(), - ); + return $this->withTime($this->getHours(), $this->getMinutes(), $this->getSeconds(), $nanoseconds); } /** @@ -329,7 +215,7 @@ public function getCentury(): int /** * Returns the short format of the year (last 2 digits). * - * @return int<0, 99> The short format of the year. + * @return int<-99, 99> The short format of the year. * * @mutation-free */ @@ -382,6 +268,7 @@ public function getTwelveHours(): array public function getISOWeekNumber(): array { $year = $this->getYear(); + /** @var int<1, 53> $week */ $week = (int)$this->format('W'); // Correct format specifier for ISO-8601 week number is 'W', not '%V' // Adjust the year based on ISO week numbering rules @@ -458,6 +345,7 @@ public function plusYears(int $years): static // February 29 adjustment for non-leap target years $target_day = Math\minva($days_in_target_month, $current_day); + /** @psalm-suppress MissingThrowsDocblock */ return $this->withDate($target_year, $current_month, $target_day); } @@ -471,6 +359,7 @@ public function plusYears(int $years): static public function minusYears(int $years): static { if ($years < 0) { + /** @psalm-suppress MissingThrowsDocblock */ return $this->plusYears(-$years); } @@ -489,6 +378,7 @@ public function minusYears(int $years): static $days_in_target_month = Month::from($current_month)->getDaysForYear($target_year); $target_day = Math\minva($current_day, $days_in_target_month); + /** @psalm-suppress MissingThrowsDocblock */ return $this->withDate($target_year, $current_month, $target_day); } @@ -516,6 +406,7 @@ public function plusMonths(int $months): static // Calculate target month and year $total_months = $current_month + $months; + /** @psalm-suppress MissingThrowsDocblock */ $target_year = $current_year + Math\div($total_months - 1, 12); if ($target_year >= Math\INT64_MAX || $target_year <= Math\INT64_MIN) { throw new Exception\OverflowException("Adding months results in a year that exceeds the representable integer range."); @@ -532,7 +423,7 @@ public function plusMonths(int $months): static $target_day = Math\minva($current_day, $days_in_target_month); - // Assuming withDate properly constructs a new instance + /** @psalm-suppress MissingThrowsDocblock */ return $this->withDate($target_year, $target_month, $target_day); } @@ -554,7 +445,11 @@ public function minusMonths(int $months): static $current_month = $this->getMonth(); $current_year = $this->getYear(); - // Calculate how many years to subtract based on the months + /** + * Calculate how many years to subtract based on the months. + * + * @psalm-suppress MissingThrowsDocblock + */ $years_to_subtract = Math\div($months, 12); $months_to_subtract_after_years = $months % 12; @@ -580,6 +475,7 @@ public function minusMonths(int $months): static $days_in_new_month = Month::from($new_month)->getDaysForYear($new_year); $new_day = Math\minva($current_day, $days_in_new_month); + /** @psalm-suppress MissingThrowsDocblock */ return $this->withDate($new_year, $new_month, $new_day); } diff --git a/src/Psl/DateTime/DateTimeInterface.php b/src/Psl/DateTime/DateTimeInterface.php index db8ec4c3..75c591c4 100644 --- a/src/Psl/DateTime/DateTimeInterface.php +++ b/src/Psl/DateTime/DateTimeInterface.php @@ -199,7 +199,7 @@ public function getYear(): int; /** * Returns the short format of the year (last 2 digits). * - * @return int<00, 99> The short format of the year. + * @return int<-99, 99> The short format of the year. * * @mutation-free */ @@ -400,7 +400,12 @@ public function convertToTimezone(Timezone $timezone): static; /** * Formats the date and time of this instance into a string based on the provided pattern, timezone, and locale. * - * If no pattern is specified, a default pattern will be used. + * The pattern determines the format of the output string. + * + * If no pattern is provided, an implementation-specific default pattern will be used. + * + * Implementations may choose any pattern they deem appropriate for the default format, + * and this pattern may vary between implementations or change over time. * * The method uses the associated timezone of the instance by default, but this can be overridden with the * provided timezone parameter. @@ -408,8 +413,6 @@ public function convertToTimezone(Timezone $timezone): static; * The method also accounts for locale-specific formatting rules if a locale is provided. * * @mutation-free - * - * @note The default pattern is subject to change at any time and should not be relied upon for consistent formatting. */ public function format(DatePattern|string|null $pattern = null, ?Timezone $timezone = null, ?Locale\Locale $locale = null): string; } diff --git a/src/Psl/DateTime/Duration.php b/src/Psl/DateTime/Duration.php index c58b997d..9704dff4 100644 --- a/src/Psl/DateTime/Duration.php +++ b/src/Psl/DateTime/Duration.php @@ -5,7 +5,6 @@ namespace Psl\DateTime; use JsonSerializable; -use Psl; use Psl\Comparison; use Psl\Iter; use Psl\Math; @@ -39,6 +38,8 @@ final class Duration implements Comparison\Comparable, Comparison\Equable, JsonS * @param int<-59, 59> $minutes * @param int<-59, 59> $seconds * @param int<-999999999, 999999999> $nanoseconds + * + * @pure */ private function __construct( private readonly int $hours, @@ -251,9 +252,10 @@ public function getNanoseconds(): int */ public function getTotalHours(): float { - return $this->hours + ($this->minutes / MINUTES_PER_HOUR) + + /** @psalm-suppress InvalidOperand */ + return ($this->hours + ($this->minutes / MINUTES_PER_HOUR) + ($this->seconds / SECONDS_PER_HOUR) + - ($this->nanoseconds / (SECONDS_PER_HOUR * NANOSECONDS_PER_SECOND)); + ($this->nanoseconds / (SECONDS_PER_HOUR * NANOSECONDS_PER_SECOND))); } /** @@ -264,9 +266,10 @@ public function getTotalHours(): float */ public function getTotalMinutes(): float { - return ($this->hours * MINUTES_PER_HOUR) + + /** @psalm-suppress InvalidOperand */ + return (($this->hours * MINUTES_PER_HOUR) + $this->minutes + ($this->seconds / SECONDS_PER_MINUTE) + - ($this->nanoseconds / (SECONDS_PER_MINUTE * NANOSECONDS_PER_SECOND)); + ($this->nanoseconds / (SECONDS_PER_MINUTE * NANOSECONDS_PER_SECOND))); } /** @@ -277,10 +280,11 @@ public function getTotalMinutes(): float */ public function getTotalSeconds(): float { - return $this->seconds + + /** @psalm-suppress InvalidOperand */ + return ($this->seconds + ($this->minutes * SECONDS_PER_MINUTE) + ($this->hours * SECONDS_PER_HOUR) + - ($this->nanoseconds / NANOSECONDS_PER_SECOND); + ($this->nanoseconds / NANOSECONDS_PER_SECOND)); } /** @@ -291,10 +295,11 @@ public function getTotalSeconds(): float */ public function getTotalMilliseconds(): float { - return ($this->hours * SECONDS_PER_HOUR * MILLISECONDS_PER_SECOND) + + /** @psalm-suppress InvalidOperand */ + return (($this->hours * SECONDS_PER_HOUR * MILLISECONDS_PER_SECOND) + ($this->minutes * SECONDS_PER_MINUTE * MILLISECONDS_PER_SECOND) + ($this->seconds * MILLISECONDS_PER_SECOND) + - ($this->nanoseconds / NANOSECONDS_PER_MILLISECOND); + ($this->nanoseconds / NANOSECONDS_PER_MILLISECOND)); } /** @@ -305,10 +310,11 @@ public function getTotalMilliseconds(): float */ public function getTotalMicroseconds(): float { - return ($this->hours * SECONDS_PER_HOUR * MICROSECONDS_PER_SECOND) + + /** @psalm-suppress InvalidOperand */ + return (($this->hours * SECONDS_PER_HOUR * MICROSECONDS_PER_SECOND) + ($this->minutes * SECONDS_PER_MINUTE * MICROSECONDS_PER_SECOND) + ($this->seconds * MICROSECONDS_PER_SECOND) + - ($this->nanoseconds / NANOSECONDS_PER_MICROSECOND); + ($this->nanoseconds / NANOSECONDS_PER_MICROSECOND)); } /** @@ -646,14 +652,14 @@ public function minus(self $other): self * meant to be a comprehensive way to format time durations for user-facing * output. * - * @param 0|positive-int $max_decimals + * @param int<0, max> $max_decimals * * @mutation-free + * + * @psalm-suppress MissingThrowsDocblock */ public function toString(int $max_decimals = 3): string { - Psl\invariant($max_decimals >= 0, 'Expected a non-negative number of decimals.'); - $decimal_part = ''; if ($max_decimals > 0) { $decimal_part = (string)Math\abs($this->nanoseconds); @@ -669,10 +675,11 @@ public function toString(int $max_decimals = 3): string $sec_sign = $this->seconds < 0 || $this->nanoseconds < 0 ? '-' : ''; $sec = Math\abs($this->seconds); + /** @var list $values */ $values = [ [((string) $this->hours), 'hour(s)'], [((string) $this->minutes), 'minute(s)'], - [$sec_sign . $sec . $decimal_part, 'second(s)'], + [$sec_sign . ((string) $sec) . $decimal_part, 'second(s)'], ]; $end = Iter\count($values); diff --git a/src/Psl/DateTime/Internal/default_timezone.php b/src/Psl/DateTime/Internal/default_timezone.php new file mode 100644 index 00000000..adf2de26 --- /dev/null +++ b/src/Psl/DateTime/Internal/default_timezone.php @@ -0,0 +1,25 @@ +name, + $timezone->value, + $value, + ); + + Psl\invariant( + $tz->getID() !== 'Etc/Unknown' || $tz->getRawOffset() !== 0, + 'Failed to create a valid intl timezone, unknown timezone "%s" ( "%s" / "%s" ) given.', + $timezone->name, + $timezone->value, + $value, + ); + + return $tz; } diff --git a/src/Psl/DateTime/Month.php b/src/Psl/DateTime/Month.php index 49db2c3c..d593ee76 100644 --- a/src/Psl/DateTime/Month.php +++ b/src/Psl/DateTime/Month.php @@ -35,6 +35,8 @@ enum Month: int * If the current instance is January, it wraps around and returns December. * * @return Month The previous month. + * + * @mutation-free */ public function getPrevious(): Month { @@ -62,6 +64,8 @@ public function getPrevious(): Month * If the current instance is December, it wraps around and returns January. * * @return Month The next month. + * + * @mutation-free */ public function getNext(): Month { @@ -91,6 +95,8 @@ public function getNext(): Month * @param int $year The year for which the day count is needed. * * @return int<28, 31> The number of days in the month for the specified year. + * + * @mutation-free */ public function getDaysForYear(int $year): int { @@ -109,6 +115,8 @@ public function getDaysForYear(int $year): int * February returns 28, while April, June, September, and November return 30, and the rest return 31. * * @return int<28, 31> The number of days in the month for a non-leap year. + * + * @mutation-free */ public function getNonLeapYearDays(): int { @@ -127,6 +135,8 @@ public function getNonLeapYearDays(): int * February returns 29, while April, June, September, and November return 30, and the rest return 31. * * @return int<29, 31> The number of days in the month for a leap year. + * + * @mutation-free */ public function getLeapYearDays(): int { diff --git a/src/Psl/DateTime/TemporalConvenienceMethodsTrait.php b/src/Psl/DateTime/TemporalConvenienceMethodsTrait.php index 74e683ec..d4f34bc9 100644 --- a/src/Psl/DateTime/TemporalConvenienceMethodsTrait.php +++ b/src/Psl/DateTime/TemporalConvenienceMethodsTrait.php @@ -8,6 +8,8 @@ /** * @require-implements TemporalInterface + * + * @psalm-suppress MoreSpecificImplementedParamType */ trait TemporalConvenienceMethodsTrait { @@ -246,6 +248,6 @@ public function since(TemporalInterface $other): Duration */ public function convertToTimezone(Timezone $timezone): DateTimeInterface { - return DateTime::fromTimestamp($this->getTimestamp(), $timezone); + return DateTime::fromTimestamp($timezone, $this->getTimestamp()); } } diff --git a/src/Psl/DateTime/TemporalInterface.php b/src/Psl/DateTime/TemporalInterface.php index 311ec362..4286cb14 100644 --- a/src/Psl/DateTime/TemporalInterface.php +++ b/src/Psl/DateTime/TemporalInterface.php @@ -207,15 +207,18 @@ public function convertToTimezone(Timezone $timezone): DateTimeInterface; /** * Formats the date and time of this instance into a string based on the provided pattern, timezone, and locale. * - * If no pattern is specified, a default pattern will be used. + * The pattern determines the format of the output string. * - * If no timezone is specified, {@see Timezone::UTC} will be used. + * If no pattern is provided, an implementation-specific default pattern will be used. + * + * Implementations may choose any pattern they deem appropriate for the default format, + * and this pattern may vary between implementations or change over time. + * + * The default timezone used is {@see Timezone::default()} if none is specified. * * The method also accounts for locale-specific formatting rules if a locale is provided. * * @mutation-free - * - * @note The default pattern is subject to change at any time and should not be relied upon for consistent formatting. */ public function format(DatePattern|string|null $pattern = null, ?Timezone $timezone = null, ?Locale\Locale $locale = null): string; } diff --git a/src/Psl/DateTime/Timestamp.php b/src/Psl/DateTime/Timestamp.php index be436361..dfff1463 100644 --- a/src/Psl/DateTime/Timestamp.php +++ b/src/Psl/DateTime/Timestamp.php @@ -5,21 +5,26 @@ namespace Psl\DateTime; use IntlDateFormatter; +use Psl; +use Psl\Exception\InvariantViolationException; use Psl\Locale; use Psl\Math; +use Psl\Str; use function hrtime; -use function time; +use function microtime; /** * Represents a precise point in time, with seconds and nanoseconds since the Unix epoch. + * + * @immutable */ final class Timestamp implements TemporalInterface { use TemporalConvenienceMethodsTrait; /** - * @var null|array{seconds: int, nanoseconds: int} + * @var null|array{int, int} * * @internal */ @@ -28,6 +33,8 @@ final class Timestamp implements TemporalInterface /** * @param int $seconds * @param int<0, 999999999> $nanoseconds + * + * @pure */ private function __construct( private readonly int $seconds, @@ -36,37 +43,64 @@ private function __construct( } /** - * Creates a new instance for the current moment. - * - * Returns the current time with precision up to nanoseconds, adjusted to maintain accuracy. - * - * @return self The current timestamp. - * - * @pure + * Create a high-precision instance representing the current time using the system clock. */ public static function now(): self { - $hrTime = hrtime(); + $time = microtime(); + + $parts = Str\split($time, ' '); + $seconds = (int) $parts[1]; + $nanoseconds = (int) (((float)$parts[0]) * ((float)NANOSECONDS_PER_SECOND)); - // Calculate the offset if not already calculated + /** @psalm-suppress MissingThrowsDocblock */ + return self::fromRaw( + seconds: $seconds, + nanoseconds: $nanoseconds, + ); + } + + /** + * Create a current time instance using a monotonic clock with high precision + * to the nanosecond for precise measurements. + * + * This method ensures that the time is always moving forward, unaffected by adjustments in the system clock, + * making it suitable for measuring durations or intervals accurately. + * + * @throws InvariantViolationException If the system does not provide a monotonic timer. + */ + public static function monotonic(): self + { if (self::$offset === null) { - $now = time(); - $nowHrTime = hrtime(); + $offset = hrtime(); + + /** @psalm-suppress RedundantCondition - This is not redundant, hrtime can return false. */ + Psl\invariant(false !== $offset, 'The system does not provide a monotonic timer.'); + + $time = Str\split(microtime(), ' ', 2); self::$offset = [ - 'seconds' => $now - $nowHrTime[0], - 'nanoseconds' => $nowHrTime[1], + ((Str\to_int($time[1]) ?? 0) - $offset[0]), + ((Str\to_int($time[0]) ?? 0) * NANOSECONDS_PER_SECOND) - $offset[1], ]; } - // Add the offset to the current hrtime to get the precise time - $seconds = $hrTime[0] + self::$offset['seconds']; - $nanoseconds = $hrTime[1] + self::$offset['nanoseconds']; + [$seconds_offset, $nanoseconds_offset] = self::$offset; + [$seconds, $nanoseconds] = hrtime(); + + $nanoseconds_adjusted = $nanoseconds + $nanoseconds_offset; + if ($nanoseconds_adjusted >= NANOSECONDS_PER_SECOND) { + ++$seconds; + $nanoseconds_adjusted -= NANOSECONDS_PER_SECOND; + } elseif ($nanoseconds_adjusted < 0) { + --$seconds; + $nanoseconds_adjusted += NANOSECONDS_PER_SECOND; + } - // Normalize nanoseconds - $seconds += (int)($nanoseconds / NANOSECONDS_PER_SECOND); - $nanoseconds %= NANOSECONDS_PER_SECOND; + $seconds += $seconds_offset; + $nanoseconds = $nanoseconds_adjusted; + /** @psalm-suppress MissingThrowsDocblock */ return self::fromRaw($seconds, $nanoseconds); } @@ -92,20 +126,22 @@ public static function fromRaw(int $seconds, int $nanoseconds = 0): Timestamp if ($seconds === Math\INT64_MAX && $nanoseconds > 0) { throw new Exception\OverflowException("Adding nanoseconds would cause an overflow."); } + if ($seconds === Math\INT64_MIN && $nanoseconds < 0) { throw new Exception\UnderflowException("Subtracting nanoseconds would cause an underflow."); } - $secondsAdjustment = Math\div($nanoseconds, NANOSECONDS_PER_SECOND); - $finalSeconds = $seconds + $secondsAdjustment; + /** @psalm-suppress MissingThrowsDocblock */ + $seconds_adjustment = Math\div($nanoseconds, NANOSECONDS_PER_SECOND); + $adjusted_seconds = $seconds + $seconds_adjustment; - $normalizedNanoseconds = $nanoseconds % NANOSECONDS_PER_SECOND; - if ($normalizedNanoseconds < 0) { - --$finalSeconds; - $normalizedNanoseconds += NANOSECONDS_PER_SECOND; + $adjusted_nanoseconds = $nanoseconds % NANOSECONDS_PER_SECOND; + if ($adjusted_nanoseconds < 0) { + --$adjusted_seconds; + $adjusted_nanoseconds += NANOSECONDS_PER_SECOND; } - return new self($finalSeconds, $normalizedNanoseconds); + return new self($adjusted_seconds, $adjusted_nanoseconds); } /** @@ -115,11 +151,14 @@ public static function fromRaw(int $seconds, int $nanoseconds = 0): Timestamp * making it versatile for handling various date/time formats. * * @throws Exception\RuntimeException If parsing fails or the date/time string is invalid. + * + * @pure */ public static function fromPattern(string|DatePattern $pattern, string $raw_string, ?Timezone $timezone = null, ?Locale\Locale $locale = null): self { $pattern = $pattern instanceof DatePattern ? $pattern->value : $pattern; + /** @psalm-suppress ImpureMethodCall */ $formatter = new IntlDateFormatter( $locale?->value, IntlDateFormatter::FULL, @@ -129,14 +168,16 @@ public static function fromPattern(string|DatePattern $pattern, string $raw_stri $pattern, ); - $timestamp = $formatter->parse($raw_string, $offset); + /** @psalm-suppress ImpureMethodCall */ + $timestamp = $formatter->parse($raw_string); if ($timestamp === false) { throw new Exception\RuntimeException( "Parsing error: Unable to interpret '$raw_string' as a valid date/time using pattern '$pattern'.", ); } - return self::fromRaw($timestamp); + /** @psalm-suppress MissingThrowsDocblock */ + return self::fromRaw((int) $timestamp); } /** @@ -145,9 +186,12 @@ public static function fromPattern(string|DatePattern $pattern, string $raw_stri * This method is a convenience wrapper for parsing date/time strings without specifying a custom pattern. * * @throws Exception\RuntimeException If parsing fails or the format of the date/time string is invalid. + * + * @pure */ public static function parse(string $raw_string, ?Timezone $timezone = null, ?Locale\Locale $locale = null): self { + /** @psalm-suppress ImpureMethodCall */ $formatter = new IntlDateFormatter( $locale?->value, IntlDateFormatter::FULL, @@ -156,6 +200,7 @@ public static function parse(string $raw_string, ?Timezone $timezone = null, ?Lo IntlDateFormatter::GREGORIAN, ); + /** @psalm-suppress ImpureMethodCall */ $timestamp = $formatter->parse($raw_string); if ($timestamp === false) { throw new Exception\RuntimeException( @@ -163,7 +208,8 @@ public static function parse(string $raw_string, ?Timezone $timezone = null, ?Lo ); } - return self::fromRaw($timestamp); + /** @psalm-suppress MissingThrowsDocblock */ + return self::fromRaw((int) $timestamp); } /** diff --git a/src/Psl/DateTime/Timezone.php b/src/Psl/DateTime/Timezone.php index 8f21def9..36185325 100644 --- a/src/Psl/DateTime/Timezone.php +++ b/src/Psl/DateTime/Timezone.php @@ -484,6 +484,16 @@ enum Timezone : string case PacificWake = 'Pacific/Wake'; case PacificWallis = 'Pacific/Wallis'; + /** + * Retrieves the default timezone set in the PHP environment. + * + * @return self The default timezone as an enum instance. + */ + public static function default(): self + { + return Internal\default_timezone(); + } + /** * Calculates the total time zone offset for a given {@see TemporalInterface} instance. * diff --git a/src/Psl/DateTime/is_leap_year.php b/src/Psl/DateTime/is_leap_year.php index 8c3f985e..644a9206 100644 --- a/src/Psl/DateTime/is_leap_year.php +++ b/src/Psl/DateTime/is_leap_year.php @@ -10,9 +10,9 @@ * Returns true if the specified year is a leap year according to the Gregorian * calendar; otherwise, returns false. * - * @param 0|positive-int $year The year to check. - * * @return bool True if the year is a leap year, false otherwise. + * + * @pure */ function is_leap_year(int $year): bool { diff --git a/src/Psl/File/ReadHandle.php b/src/Psl/File/ReadHandle.php index ca166a3f..57919f36 100644 --- a/src/Psl/File/ReadHandle.php +++ b/src/Psl/File/ReadHandle.php @@ -59,7 +59,7 @@ public function tryRead(?int $max_bytes = null): string /** * {@inheritDoc} */ - public function read(?int $max_bytes = null, null|Duration|float $timeout = null): string + public function read(?int $max_bytes = null, ?Duration $timeout = null): string { return $this->readHandle->read($max_bytes, $timeout); } diff --git a/src/Psl/File/ReadWriteHandle.php b/src/Psl/File/ReadWriteHandle.php index 69a008d5..1f6cbe1c 100644 --- a/src/Psl/File/ReadWriteHandle.php +++ b/src/Psl/File/ReadWriteHandle.php @@ -87,7 +87,7 @@ public function tryRead(?int $max_bytes = null): string /** * {@inheritDoc} */ - public function read(?int $max_bytes = null, null|Duration|float $timeout = null): string + public function read(?int $max_bytes = null, ?Duration $timeout = null): string { return $this->readWriteHandle->read($max_bytes, $timeout); } @@ -103,7 +103,7 @@ public function tryWrite(string $bytes): int /** * {@inheritDoc} */ - public function write(string $bytes, null|Duration|float $timeout = null): int + public function write(string $bytes, ?Duration $timeout = null): int { return $this->readWriteHandle->write($bytes, $timeout); } diff --git a/src/Psl/File/WriteHandle.php b/src/Psl/File/WriteHandle.php index fd29d1e2..c0651575 100644 --- a/src/Psl/File/WriteHandle.php +++ b/src/Psl/File/WriteHandle.php @@ -67,7 +67,7 @@ public function tryWrite(string $bytes): int /** * {@inheritDoc} */ - public function write(string $bytes, null|Duration|float $timeout = null): int + public function write(string $bytes, ?Duration $timeout = null): int { return $this->writeHandle->write($bytes, $timeout); } diff --git a/src/Psl/IO/CloseReadStreamHandle.php b/src/Psl/IO/CloseReadStreamHandle.php index c095606d..f61dba4c 100644 --- a/src/Psl/IO/CloseReadStreamHandle.php +++ b/src/Psl/IO/CloseReadStreamHandle.php @@ -43,7 +43,7 @@ public function tryRead(?int $max_bytes = null): string /** * {@inheritDoc} */ - public function read(?int $max_bytes = null, null|Duration|float $timeout = null): string + public function read(?int $max_bytes = null, ?Duration $timeout = null): string { return $this->handle->read($max_bytes, $timeout); } diff --git a/src/Psl/IO/CloseReadWriteStreamHandle.php b/src/Psl/IO/CloseReadWriteStreamHandle.php index 62fc9de6..d3a56f14 100644 --- a/src/Psl/IO/CloseReadWriteStreamHandle.php +++ b/src/Psl/IO/CloseReadWriteStreamHandle.php @@ -44,7 +44,7 @@ public function tryRead(?int $max_bytes = null): string /** * {@inheritDoc} */ - public function read(?int $max_bytes = null, null|Duration|float $timeout = null): string + public function read(?int $max_bytes = null, ?Duration $timeout = null): string { return $this->handle->read($max_bytes, $timeout); } @@ -60,7 +60,7 @@ public function tryWrite(string $bytes): int /** * {@inheritDoc} */ - public function write(string $bytes, null|Duration|float $timeout = null): int + public function write(string $bytes, ?Duration $timeout = null): int { return $this->handle->write($bytes, $timeout); } diff --git a/src/Psl/IO/CloseSeekReadStreamHandle.php b/src/Psl/IO/CloseSeekReadStreamHandle.php index 2eeb3f62..b88fbd56 100644 --- a/src/Psl/IO/CloseSeekReadStreamHandle.php +++ b/src/Psl/IO/CloseSeekReadStreamHandle.php @@ -43,7 +43,7 @@ public function tryRead(?int $max_bytes = null): string /** * {@inheritDoc} */ - public function read(?int $max_bytes = null, null|Duration|float $timeout = null): string + public function read(?int $max_bytes = null, ?Duration $timeout = null): string { return $this->handle->read($max_bytes, $timeout); } diff --git a/src/Psl/IO/CloseSeekReadWriteStreamHandle.php b/src/Psl/IO/CloseSeekReadWriteStreamHandle.php index 8ed7aa13..02fc796e 100644 --- a/src/Psl/IO/CloseSeekReadWriteStreamHandle.php +++ b/src/Psl/IO/CloseSeekReadWriteStreamHandle.php @@ -44,7 +44,7 @@ public function tryRead(?int $max_bytes = null): string /** * {@inheritDoc} */ - public function read(?int $max_bytes = null, null|Duration|float $timeout = null): string + public function read(?int $max_bytes = null, ?Duration $timeout = null): string { return $this->handle->read($max_bytes, $timeout); } @@ -60,7 +60,7 @@ public function tryWrite(string $bytes): int /** * {@inheritDoc} */ - public function write(string $bytes, null|Duration|float $timeout = null): int + public function write(string $bytes, ?Duration $timeout = null): int { return $this->handle->write($bytes, $timeout); } diff --git a/src/Psl/IO/CloseSeekWriteStreamHandle.php b/src/Psl/IO/CloseSeekWriteStreamHandle.php index 244dfb94..480ae3e0 100644 --- a/src/Psl/IO/CloseSeekWriteStreamHandle.php +++ b/src/Psl/IO/CloseSeekWriteStreamHandle.php @@ -35,7 +35,7 @@ public function tryWrite(string $bytes): int /** * {@inheritDoc} */ - public function write(string $bytes, null|Duration|float $timeout = null): int + public function write(string $bytes, ?Duration $timeout = null): int { return $this->handle->write($bytes, $timeout); } diff --git a/src/Psl/IO/CloseWriteStreamHandle.php b/src/Psl/IO/CloseWriteStreamHandle.php index 58dea355..76017268 100644 --- a/src/Psl/IO/CloseWriteStreamHandle.php +++ b/src/Psl/IO/CloseWriteStreamHandle.php @@ -35,7 +35,7 @@ public function tryWrite(string $bytes): int /** * {@inheritDoc} */ - public function write(string $bytes, null|Duration|float $timeout = null): int + public function write(string $bytes, ?Duration $timeout = null): int { return $this->handle->write($bytes, $timeout); } diff --git a/src/Psl/IO/Internal/ResourceHandle.php b/src/Psl/IO/Internal/ResourceHandle.php index 3e1f0c80..a42789e3 100644 --- a/src/Psl/IO/Internal/ResourceHandle.php +++ b/src/Psl/IO/Internal/ResourceHandle.php @@ -52,14 +52,14 @@ class ResourceHandle implements IO\CloseSeekReadWriteStreamHandleInterface protected mixed $stream; /** - * @var null|Async\Sequence> + * @var null|Async\Sequence> */ private ?Async\Sequence $writeSequence = null; private ?Suspension $writeSuspension = null; private string $writeWatcher = 'invalid'; /** - * @var null|Async\Sequence, null|float}, string> + * @var null|Async\Sequence, null|Duration}, string> */ private ?Async\Sequence $readSequence = null; private ?Suspension $readSuspension = null; @@ -104,7 +104,7 @@ public function __construct(mixed $stream, bool $read, bool $write, bool $seek, $this->readSequence = new Async\Sequence( /** - * @param array{null|int<1, max>, null|Duration|float} $input + * @param array{null|int<1, max>, null|Duration} $input */ function (array $input) use ($blocks): string { [$max_bytes, $timeout] = $input; @@ -117,11 +117,7 @@ function (array $input) use ($blocks): string { EventLoop::enable($this->readWatcher); $delay_watcher = null; if (null !== $timeout) { - if ($timeout instanceof Duration) { - $timeout = $timeout->getTotalSeconds(); - } - - $timeout = max($timeout, 0.0); + $timeout = max($timeout->getTotalSeconds(), 0.0); $delay_watcher = EventLoop::delay( $timeout, static fn () => $suspension->throw(new Exception\TimeoutException('Reached timeout while the handle is still not readable.')), @@ -164,7 +160,7 @@ function (array $input) use ($blocks): string { $this->writeSequence = new Async\Sequence( /** - * @param array{string, null|float|Duration} $input + * @param array{string, null|Duration} $input * * @return int<0, max> */ @@ -180,11 +176,7 @@ function (array $input) use ($blocks): int { EventLoop::enable($this->writeWatcher); $delay_watcher = null; if (null !== $timeout) { - if ($timeout instanceof Duration) { - $timeout = $timeout->getTotalSeconds(); - } - - $timeout = max($timeout, 0.0); + $timeout = max($timeout->getTotalSeconds(), 0.0); $delay_watcher = EventLoop::delay( $timeout, static fn () => $suspension->throw(new Exception\TimeoutException('Reached timeout while the handle is still not readable.')), @@ -213,7 +205,7 @@ function (array $input) use ($blocks): int { /** * {@inheritDoc} */ - public function write(string $bytes, null|Duration|float $timeout = null): int + public function write(string $bytes, ?Duration $timeout = null): int { Psl\invariant($this->writeSequence !== null, 'The resource handle is not writable.'); @@ -292,7 +284,7 @@ public function reachedEndOfDataSource(): bool /** * {@inheritDoc} */ - public function read(?int $max_bytes = null, null|Duration|float $timeout = null): string + public function read(?int $max_bytes = null, ?Duration $timeout = null): string { Psl\invariant($this->readSequence !== null, 'The resource handle is not readable.'); diff --git a/src/Psl/IO/MemoryHandle.php b/src/Psl/IO/MemoryHandle.php index 29ad77cb..be42535f 100644 --- a/src/Psl/IO/MemoryHandle.php +++ b/src/Psl/IO/MemoryHandle.php @@ -68,7 +68,7 @@ public function tryRead(?int $max_bytes = null): string /** * {@inheritDoc} */ - public function read(?int $max_bytes = null, null|Duration|float $timeout = null): string + public function read(?int $max_bytes = null, ?Duration $timeout = null): string { return $this->tryRead($max_bytes); } @@ -96,7 +96,7 @@ public function tell(): int /** * {@inheritDoc} */ - public function tryWrite(string $bytes, null|Duration|float $timeout = null): int + public function tryWrite(string $bytes, ?Duration $timeout = null): int { $this->assertHandleIsOpen(); $length = strlen($this->buffer); @@ -121,7 +121,7 @@ public function tryWrite(string $bytes, null|Duration|float $timeout = null): in /** * {@inheritDoc} */ - public function write(string $bytes, null|Duration|float $timeout = null): int + public function write(string $bytes, ?Duration $timeout = null): int { return $this->tryWrite($bytes); } diff --git a/src/Psl/IO/ReadHandleConvenienceMethodsTrait.php b/src/Psl/IO/ReadHandleConvenienceMethodsTrait.php index 9e9639cd..8cede651 100644 --- a/src/Psl/IO/ReadHandleConvenienceMethodsTrait.php +++ b/src/Psl/IO/ReadHandleConvenienceMethodsTrait.php @@ -31,7 +31,7 @@ trait ReadHandleConvenienceMethodsTrait * @throws Exception\RuntimeException If an error occurred during the operation. * @throws Exception\TimeoutException If $timeout is reached before being able to read from the handle. */ - public function readAll(?int $max_bytes = null, null|Duration|float $timeout = null): string + public function readAll(?int $max_bytes = null, ?Duration $timeout = null): string { $to_read = $max_bytes; @@ -79,7 +79,7 @@ static function () use ($data): void { * @throws Exception\RuntimeException If an error occurred during the operation. * @throws Exception\TimeoutException If $timeout is reached before being able to read from the handle. */ - public function readFixedSize(int $size, null|Duration|float $timeout = null): string + public function readFixedSize(int $size, ?Duration $timeout = null): string { $data = $this->readAll($size, $timeout); diff --git a/src/Psl/IO/ReadHandleInterface.php b/src/Psl/IO/ReadHandleInterface.php index 76ad455d..9de72326 100644 --- a/src/Psl/IO/ReadHandleInterface.php +++ b/src/Psl/IO/ReadHandleInterface.php @@ -63,7 +63,7 @@ public function tryRead(?int $max_bytes = null): string; * Up to `$max_bytes` may be allocated in a buffer; large values may lead to * unnecessarily hitting the request memory limit. */ - public function read(?int $max_bytes = null, null|Duration|float $timeout = null): string; + public function read(?int $max_bytes = null, ?Duration $timeout = null): string; /** * Read until there is no more data to read. @@ -81,7 +81,7 @@ public function read(?int $max_bytes = null, null|Duration|float $timeout = null * @throws Exception\RuntimeException If an error occurred during the operation. * @throws Exception\TimeoutException If $timeout is reached before being able to read from the handle. */ - public function readAll(?int $max_bytes = null, null|Duration|float $timeout = null): string; + public function readAll(?int $max_bytes = null, ?Duration $timeout = null): string; /** * Read a fixed amount of data. @@ -96,5 +96,5 @@ public function readAll(?int $max_bytes = null, null|Duration|float $timeout = n * @throws Exception\RuntimeException If an error occurred during the operation. * @throws Exception\TimeoutException If $timeout is reached before being able to read from the handle. */ - public function readFixedSize(int $size, null|Duration|float $timeout = null): string; + public function readFixedSize(int $size, ?Duration $timeout = null): string; } diff --git a/src/Psl/IO/ReadStreamHandle.php b/src/Psl/IO/ReadStreamHandle.php index a7c97f5f..9530daba 100644 --- a/src/Psl/IO/ReadStreamHandle.php +++ b/src/Psl/IO/ReadStreamHandle.php @@ -43,7 +43,7 @@ public function tryRead(?int $max_bytes = null): string /** * {@inheritDoc} */ - public function read(?int $max_bytes = null, null|Duration|float $timeout = null): string + public function read(?int $max_bytes = null, ?Duration $timeout = null): string { return $this->handle->read($max_bytes, $timeout); } diff --git a/src/Psl/IO/ReadWriteStreamHandle.php b/src/Psl/IO/ReadWriteStreamHandle.php index 7e36cf5a..9f30d5c4 100644 --- a/src/Psl/IO/ReadWriteStreamHandle.php +++ b/src/Psl/IO/ReadWriteStreamHandle.php @@ -44,7 +44,7 @@ public function tryRead(?int $max_bytes = null): string /** * {@inheritDoc} */ - public function read(?int $max_bytes = null, null|Duration|float $timeout = null): string + public function read(?int $max_bytes = null, ?Duration $timeout = null): string { return $this->handle->read($max_bytes, $timeout); } @@ -60,7 +60,7 @@ public function tryWrite(string $bytes): int /** * {@inheritDoc} */ - public function write(string $bytes, null|Duration|float $timeout = null): int + public function write(string $bytes, ?Duration $timeout = null): int { return $this->handle->write($bytes, $timeout); } diff --git a/src/Psl/IO/Reader.php b/src/Psl/IO/Reader.php index d4e00c2b..ee5b3e0b 100644 --- a/src/Psl/IO/Reader.php +++ b/src/Psl/IO/Reader.php @@ -58,7 +58,7 @@ public function reachedEndOfDataSource(): bool /** * {@inheritDoc} */ - public function readFixedSize(int $size, null|Duration|float $timeout = null): string + public function readFixedSize(int $size, ?Duration $timeout = null): string { $timer = new Async\OptionalIncrementalTimeout( $timeout, @@ -101,7 +101,7 @@ function (): void { * @throws Exception\RuntimeException If an error occurred during the operation, or reached end of file. * @throws Exception\TimeoutException If $timeout is reached before being able to read from the handle. */ - public function readByte(null|Duration|float $timeout = null): string + public function readByte(?Duration $timeout = null): string { if ($this->buffer === '' && !$this->eof) { $this->fillBuffer(null, $timeout); @@ -129,7 +129,7 @@ public function readByte(null|Duration|float $timeout = null): string * @throws Exception\RuntimeException If an error occurred during the operation. * @throws Exception\TimeoutException If $timeout is reached before being able to read from the handle. */ - public function readLine(null|Duration|float $timeout = null): ?string + public function readLine(?Duration $timeout = null): ?string { $timer = new Async\OptionalIncrementalTimeout( $timeout, @@ -165,7 +165,7 @@ static function (): void { * @throws Exception\RuntimeException If an error occurred during the operation. * @throws Exception\TimeoutException If $timeout is reached before being able to read from the handle. */ - public function readUntil(string $suffix, null|Duration|float $timeout = null): ?string + public function readUntil(string $suffix, ?Duration $timeout = null): ?string { $buf = $this->buffer; $idx = strpos($buf, $suffix); @@ -209,7 +209,7 @@ static function () use ($suffix): void { /** * {@inheritDoc} */ - public function read(?int $max_bytes = null, null|Duration|float $timeout = null): string + public function read(?int $max_bytes = null, ?Duration $timeout = null): string { if ($this->eof) { return ''; @@ -263,7 +263,7 @@ public function getHandle(): ReadHandleInterface * @throws Exception\RuntimeException If an error occurred during the operation. * @throws Exception\TimeoutException If $timeout is reached before being able to read from the handle. */ - private function fillBuffer(?int $desired_bytes, null|float|Duration $timeout): void + private function fillBuffer(?int $desired_bytes, ?Duration $timeout): void { $this->buffer .= $chunk = $this->handle->read($desired_bytes, $timeout); if ($chunk === '') { diff --git a/src/Psl/IO/SeekReadStreamHandle.php b/src/Psl/IO/SeekReadStreamHandle.php index eac082d4..bc0a0392 100644 --- a/src/Psl/IO/SeekReadStreamHandle.php +++ b/src/Psl/IO/SeekReadStreamHandle.php @@ -43,7 +43,7 @@ public function tryRead(?int $max_bytes = null): string /** * {@inheritDoc} */ - public function read(?int $max_bytes = null, null|Duration|float $timeout = null): string + public function read(?int $max_bytes = null, ?Duration $timeout = null): string { return $this->handle->read($max_bytes, $timeout); } diff --git a/src/Psl/IO/SeekReadWriteStreamHandle.php b/src/Psl/IO/SeekReadWriteStreamHandle.php index 43d2cf71..80b35fcc 100644 --- a/src/Psl/IO/SeekReadWriteStreamHandle.php +++ b/src/Psl/IO/SeekReadWriteStreamHandle.php @@ -44,7 +44,7 @@ public function tryRead(?int $max_bytes = null): string /** * {@inheritDoc} */ - public function read(?int $max_bytes = null, null|Duration|float $timeout = null): string + public function read(?int $max_bytes = null, ?Duration $timeout = null): string { return $this->handle->read($max_bytes, $timeout); } @@ -60,7 +60,7 @@ public function tryWrite(string $bytes): int /** * {@inheritDoc} */ - public function write(string $bytes, null|Duration|float $timeout = null): int + public function write(string $bytes, ?Duration $timeout = null): int { return $this->handle->write($bytes, $timeout); } diff --git a/src/Psl/IO/SeekWriteStreamHandle.php b/src/Psl/IO/SeekWriteStreamHandle.php index 99492200..aaa6bb7e 100644 --- a/src/Psl/IO/SeekWriteStreamHandle.php +++ b/src/Psl/IO/SeekWriteStreamHandle.php @@ -35,7 +35,7 @@ public function tryWrite(string $bytes): int /** * {@inheritDoc} */ - public function write(string $bytes, null|Duration|float $timeout = null): int + public function write(string $bytes, ?Duration $timeout = null): int { return $this->handle->write($bytes, $timeout); } diff --git a/src/Psl/IO/WriteHandleConvenienceMethodsTrait.php b/src/Psl/IO/WriteHandleConvenienceMethodsTrait.php index 38ec5282..fd851c66 100644 --- a/src/Psl/IO/WriteHandleConvenienceMethodsTrait.php +++ b/src/Psl/IO/WriteHandleConvenienceMethodsTrait.php @@ -31,7 +31,7 @@ trait WriteHandleConvenienceMethodsTrait * @throws Exception\RuntimeException If an error occurred during the operation. * @throws Exception\TimeoutException If reached timeout before completing the operation. */ - public function writeAll(string $bytes, null|Duration|float $timeout = null): void + public function writeAll(string $bytes, ?Duration $timeout = null): void { if ($bytes === '') { return; diff --git a/src/Psl/IO/WriteHandleInterface.php b/src/Psl/IO/WriteHandleInterface.php index 17038bde..406c1f92 100644 --- a/src/Psl/IO/WriteHandleInterface.php +++ b/src/Psl/IO/WriteHandleInterface.php @@ -36,7 +36,7 @@ public function tryWrite(string $bytes): int; * * @return int<0, max> the number of bytes written, which may be less than the length of input string. */ - public function write(string $bytes, null|Duration|float $timeout = null): int; + public function write(string $bytes, ?Duration $timeout = null): int; /** * Write all of the requested data. @@ -53,5 +53,5 @@ public function write(string $bytes, null|Duration|float $timeout = null): int; * @throws Exception\RuntimeException If an error occurred during the operation. * @throws Exception\TimeoutException If reached timeout before completing the operation. */ - public function writeAll(string $bytes, null|Duration|float $timeout = null): void; + public function writeAll(string $bytes, ?Duration $timeout = null): void; } diff --git a/src/Psl/IO/WriteStreamHandle.php b/src/Psl/IO/WriteStreamHandle.php index bec4738d..bf746997 100644 --- a/src/Psl/IO/WriteStreamHandle.php +++ b/src/Psl/IO/WriteStreamHandle.php @@ -35,7 +35,7 @@ public function tryWrite(string $bytes): int /** * {@inheritDoc} */ - public function write(string $bytes, null|Duration|float $timeout = null): int + public function write(string $bytes, ?Duration $timeout = null): int { return $this->handle->write($bytes, $timeout); } diff --git a/src/Psl/IO/streaming.php b/src/Psl/IO/streaming.php index beceac3e..463d3c2d 100644 --- a/src/Psl/IO/streaming.php +++ b/src/Psl/IO/streaming.php @@ -12,6 +12,8 @@ use Psl\Str; use Revolt\EventLoop; +use function max; + /** * Streaming the output of the given read stream handles using a generator. * @@ -36,7 +38,7 @@ * * @return Generator */ -function streaming(iterable $handles, null|Duration|float $timeout = null): Generator +function streaming(iterable $handles, ?Duration $timeout = null): Generator { /** * @psalm-suppress UnnecessaryVarAnnotation @@ -73,9 +75,7 @@ function streaming(iterable $handles, null|Duration|float $timeout = null): Gene $timeout_watcher = null; if ($timeout !== null) { - if ($timeout instanceof Duration) { - $timeout = $timeout->getTotalSeconds(); - } + $timeout = max($timeout->getTotalSeconds(), 0.0); $timeout_watcher = EventLoop::delay($timeout, static function () use ($sender): void { /** @var Result\ResultInterface $failure */ diff --git a/src/Psl/Internal/Loader.php b/src/Psl/Internal/Loader.php index 51d26c9f..b5f1f304 100644 --- a/src/Psl/Internal/Loader.php +++ b/src/Psl/Internal/Loader.php @@ -538,6 +538,7 @@ final class Loader 'Psl\\Range\\full' => 'Psl/Range/full.php', 'Psl\\DateTime\\is_leap_year' => 'Psl/DateTime/is_leap_year.php', 'Psl\\DateTime\\Internal\\to_intl_timezone' => 'Psl/DateTime/Internal/to_intl_timezone.php', + 'Psl\\DateTime\\Internal\\default_timezone' => 'Psl/DateTime/Internal/default_timezone.php', ]; public const INTERFACES = [ diff --git a/src/Psl/Network/Internal/Socket.php b/src/Psl/Network/Internal/Socket.php index 2549c43c..43f5327f 100644 --- a/src/Psl/Network/Internal/Socket.php +++ b/src/Psl/Network/Internal/Socket.php @@ -52,7 +52,7 @@ public function tryRead(?int $max_bytes = null): string /** * {@inheritDoc} */ - public function read(?int $max_bytes = null, null|Duration|float $timeout = null): string + public function read(?int $max_bytes = null, ?Duration $timeout = null): string { return $this->handle->read($max_bytes, $timeout); } @@ -68,7 +68,7 @@ public function tryWrite(string $bytes): int /** * {@inheritDoc} */ - public function write(string $bytes, null|Duration|float $timeout = null): int + public function write(string $bytes, ?Duration $timeout = null): int { return $this->handle->write($bytes, $timeout); } diff --git a/src/Psl/Network/Internal/socket_connect.php b/src/Psl/Network/Internal/socket_connect.php index c981aa6c..088e92bd 100644 --- a/src/Psl/Network/Internal/socket_connect.php +++ b/src/Psl/Network/Internal/socket_connect.php @@ -11,6 +11,7 @@ use function fclose; use function is_resource; +use function max; use function stream_context_create; use function stream_socket_client; @@ -29,7 +30,7 @@ * * @codeCoverageIgnore */ -function socket_connect(string $uri, array $context = [], null|Duration|float $timeout = null): mixed +function socket_connect(string $uri, array $context = [], ?Duration $timeout = null): mixed { return Internal\suppress(static function () use ($uri, $context, $timeout): mixed { $context = stream_context_create($context); @@ -43,10 +44,7 @@ function socket_connect(string $uri, array $context = [], null|Duration|float $t $write_watcher = ''; $timeout_watcher = ''; if (null !== $timeout) { - if ($timeout instanceof Duration) { - $timeout = $timeout->getTotalSeconds(); - } - + $timeout = max($timeout->getTotalSeconds(), 0.0); $timeout_watcher = EventLoop::delay($timeout, static function () use ($suspension, &$write_watcher, $socket) { EventLoop::cancel($write_watcher); diff --git a/src/Psl/Shell/execute.php b/src/Psl/Shell/execute.php index 47895414..090651c3 100644 --- a/src/Psl/Shell/execute.php +++ b/src/Psl/Shell/execute.php @@ -45,7 +45,7 @@ function execute( ?string $working_directory = null, array $environment = [], ErrorOutputBehavior $error_output_behavior = ErrorOutputBehavior::Discard, - null|Duration|float $timeout = null + ?Duration $timeout = null ): string { $arguments = Vec\map($arguments, Internal\escape_argument(...)); $commandline = Str\join([$command, ...$arguments], ' '); diff --git a/src/Psl/TCP/connect.php b/src/Psl/TCP/connect.php index b737fd31..ed26a37d 100644 --- a/src/Psl/TCP/connect.php +++ b/src/Psl/TCP/connect.php @@ -16,7 +16,7 @@ * @throws Network\Exception\RuntimeException If failed to connect to client on the given address. * @throws Network\Exception\TimeoutException If $timeout is non-null, and the operation timed-out. */ -function connect(string $host, int $port = 0, ?ConnectOptions $options = null, null|Duration|float $timeout = null): Network\StreamSocketInterface +function connect(string $host, int $port = 0, ?ConnectOptions $options = null, ?Duration $timeout = null): Network\StreamSocketInterface { $options ??= ConnectOptions::create(); diff --git a/src/Psl/Unix/connect.php b/src/Psl/Unix/connect.php index a3894a46..9ceb71ed 100644 --- a/src/Psl/Unix/connect.php +++ b/src/Psl/Unix/connect.php @@ -16,7 +16,7 @@ * @throws Network\Exception\RuntimeException If failed to connect to client on the given address. * @throws Network\Exception\TimeoutException If $timeout is non-null, and the operation timed-out. */ -function connect(string $path, null|Duration|float $timeout = null): Network\StreamSocketInterface +function connect(string $path, ?Duration $timeout = null): Network\StreamSocketInterface { // @codeCoverageIgnoreStart if (OS\is_windows()) { diff --git a/tests/unit/Async/AllTest.php b/tests/unit/Async/AllTest.php index e06a486c..26957cd3 100644 --- a/tests/unit/Async/AllTest.php +++ b/tests/unit/Async/AllTest.php @@ -7,6 +7,7 @@ use PHPUnit\Framework\TestCase; use Psl; use Psl\Async; +use Psl\DateTime; use Psl\Exception\InvariantViolationException; final class AllTest extends TestCase @@ -15,17 +16,17 @@ public function testAll(): void { $awaitables = [ 'a' => Async\run(static function (): string { - Async\sleep(0.003); + Async\sleep(DateTime\Duration::milliseconds(3)); return 'a'; }), 'b' => Async\run(static function (): string { - Async\sleep(0.001); + Async\sleep(DateTime\Duration::milliseconds(1)); return 'b'; }), 'c' => Async\run(static function (): string { - Async\sleep(0.01); + Async\sleep(DateTime\Duration::milliseconds(10)); return 'c'; }), @@ -81,23 +82,23 @@ public function testAllAwaitablesAreCompletedAtALaterTime(): void throw new InvariantViolationException('a'); }), Async\run(static function () use ($ref): void { - Async\sleep(0.02); + Async\sleep(DateTime\Duration::milliseconds(20)); $ref->value .= 'b'; throw new InvariantViolationException('b'); }), Async\run(static function () use ($ref): void { - Async\sleep(0.05); + Async\sleep(DateTime\Duration::milliseconds(50)); $ref->value .= 'c'; }), Async\run(static function () use ($ref): void { - Async\sleep(0.00005); + Async\sleep(DateTime\Duration::microseconds(5)); Async\later(); - Async\sleep(0.00005); + Async\sleep(DateTime\Duration::microseconds(5)); $ref->value .= 'd'; }), diff --git a/tests/unit/Async/AnyTest.php b/tests/unit/Async/AnyTest.php index 1359d137..a054a6d3 100644 --- a/tests/unit/Async/AnyTest.php +++ b/tests/unit/Async/AnyTest.php @@ -6,6 +6,7 @@ use PHPUnit\Framework\TestCase; use Psl\Async; +use Psl\DateTime; use Psl\Exception\InvariantViolationException; final class AnyTest extends TestCase @@ -14,26 +15,26 @@ public function testAny(): void { $result = Async\any([ Async\run(static function (): string { - Async\sleep(0.0001); + Async\sleep(DateTime\Duration::milliseconds(1)); throw new InvariantViolationException('a'); }), Async\run(static function (): string { - Async\sleep(0.0002); + Async\sleep(DateTime\Duration::milliseconds(2)); throw new InvariantViolationException('b'); }), Async\run(static function (): string { - Async\sleep(0.0003); + Async\sleep(DateTime\Duration::milliseconds(3)); return 'c'; }), Async\run(static function (): string { - Async\sleep(0.00005); + Async\sleep(DateTime\Duration::microseconds(500)); Async\later(); - Async\sleep(0.00005); + Async\sleep(DateTime\Duration::microseconds(500)); return 'c'; }), diff --git a/tests/unit/Async/AwaitableTest.php b/tests/unit/Async/AwaitableTest.php index 1784c735..b694e75a 100644 --- a/tests/unit/Async/AwaitableTest.php +++ b/tests/unit/Async/AwaitableTest.php @@ -10,6 +10,7 @@ use Psl\Async\Awaitable; use Psl\Async\Exception\UnhandledAwaitableException; use Psl\Async\Internal\State; +use Psl\DateTime; use Psl\Dict; use Psl\Exception\InvariantViolationException; use Psl\Str; @@ -94,12 +95,12 @@ public function testIterate(): void 'foo' => Awaitable::complete('foo'), 'bar' => Awaitable::error(new InvariantViolationException('bar')), 'baz' => Async\run(static function () { - Async\sleep(0.0001); + Async\sleep(DateTime\Duration::milliseconds(1)); throw new InvariantViolationException('baz'); }), 'qux' => Async\run(static function () { - Async\sleep(0.003); + Async\sleep(DateTime\Duration::milliseconds(30)); return 'qux'; }), @@ -141,7 +142,7 @@ public function testIterateGenerator(): void $generator1 = Async\run(static function (): iterable { yield 'foo' => 'foo'; - Async\sleep(0.0003); + Async\sleep(DateTime\Duration::milliseconds(3)); yield 'bar' => 'bar'; }); @@ -149,7 +150,7 @@ public function testIterateGenerator(): void $generator2 = Async\run(static function (): iterable { yield 'baz' => 'baz'; - Async\sleep(0.0001); + Async\sleep(DateTime\Duration::milliseconds(1)); yield 'qux' => 'qux'; }); @@ -157,7 +158,7 @@ public function testIterateGenerator(): void $generator3 = Async\run(static function () use ($generator1, $generator2): iterable { yield 'gen1' => $generator1; - Async\sleep(0.0002); + Async\sleep(DateTime\Duration::milliseconds(2)); yield 'gen2' => $generator2; })->await(); diff --git a/tests/unit/Async/DeferredTest.php b/tests/unit/Async/DeferredTest.php index e1995e51..f9a80b5d 100644 --- a/tests/unit/Async/DeferredTest.php +++ b/tests/unit/Async/DeferredTest.php @@ -6,6 +6,7 @@ use PHPUnit\Framework\TestCase; use Psl\Async; +use Psl\DateTime; use Psl\Exception\InvariantViolationException; final class DeferredTest extends TestCase @@ -15,7 +16,7 @@ public function testComplete(): void $deferred = new Async\Deferred(); $placeholder = Async\run(static function () use ($deferred) { - Async\sleep(0.0001); + Async\sleep(DateTime\Duration::milliseconds(1)); $deferred->complete('hello'); }); @@ -34,7 +35,7 @@ public function testError(): void $deferred = new Async\Deferred(); $placeholder = Async\run(static function () use ($deferred) { - Async\sleep(0.0001); + Async\sleep(DateTime\Duration::milliseconds(1)); $deferred->error(new InvariantViolationException('hello')); }); diff --git a/tests/unit/Async/FirstTest.php b/tests/unit/Async/FirstTest.php index 4702b5c2..48f6be5f 100644 --- a/tests/unit/Async/FirstTest.php +++ b/tests/unit/Async/FirstTest.php @@ -6,6 +6,7 @@ use PHPUnit\Framework\TestCase; use Psl\Async; +use Psl\DateTime; final class FirstTest extends TestCase { @@ -13,26 +14,26 @@ public function testFirst(): void { $result = Async\first([ Async\run(static function (): string { - Async\sleep(0.001); + Async\sleep(DateTime\Duration::milliseconds(1)); return 'a'; }), Async\run(static function (): string { - Async\sleep(0.002); + Async\sleep(DateTime\Duration::milliseconds(2)); return 'b'; }), Async\run(static function (): string { - Async\sleep(0.003); + Async\sleep(DateTime\Duration::milliseconds(3)); return 'c'; }), Async\run(static function (): string { - Async\sleep(0.0005); + Async\sleep(DateTime\Duration::milliseconds(5)); Async\later(); - Async\sleep(0.0005); + Async\sleep(DateTime\Duration::milliseconds(5)); return 'c'; }), diff --git a/tests/unit/Async/KeyedSemaphoreTest.php b/tests/unit/Async/KeyedSemaphoreTest.php index b731c419..cc9821c5 100644 --- a/tests/unit/Async/KeyedSemaphoreTest.php +++ b/tests/unit/Async/KeyedSemaphoreTest.php @@ -7,6 +7,7 @@ use PHPUnit\Framework\TestCase; use Psl; use Psl\Async; +use Psl\DateTime; final class KeyedSemaphoreTest extends TestCase { @@ -26,7 +27,7 @@ public function testSequenceOperationWaitsForPendingOperationsWhenLimitIsNotReac $spy = new Psl\Ref([]); /** - * @var Async\KeyedSemaphore + * @var Async\KeyedSemaphore */ $ks = new Async\KeyedSemaphore(1, static function (string $key, array $data) use ($spy): void { static::assertSame('operation', $key); @@ -38,9 +39,9 @@ public function testSequenceOperationWaitsForPendingOperationsWhenLimitIsNotReac $spy->value[] = $data['value']; }); - Async\run(static fn() => $ks->waitFor('operation', ['time' => 0.003, 'value' => 'a'])); - Async\run(static fn() => $ks->waitFor('operation', ['time' => 0.004, 'value' => 'b'])); - Async\run(static fn() => $ks->waitFor('operation', ['time' => 0.005, 'value' => 'c'])); + Async\run(static fn() => $ks->waitFor('operation', ['time' => DateTime\Duration::milliseconds(3), 'value' => 'a'])); + Async\run(static fn() => $ks->waitFor('operation', ['time' => DateTime\Duration::milliseconds(4), 'value' => 'b'])); + Async\run(static fn() => $ks->waitFor('operation', ['time' => DateTime\Duration::milliseconds(5), 'value' => 'c'])); $last = Async\run(static fn() => $ks->waitFor('operation', ['time' => null, 'value' => 'd'])); $last->await(); @@ -52,7 +53,7 @@ public function testOperationWaitsForPendingOperationsWhenLimitIsNotReached(): v $spy = new Psl\Ref([]); /** - * @var Async\KeyedSemaphore + * @var Async\KeyedSemaphore */ $ks = new Async\KeyedSemaphore(2, static function (string $_, array $data) use ($spy): void { if ($data['time'] !== null) { @@ -62,9 +63,9 @@ public function testOperationWaitsForPendingOperationsWhenLimitIsNotReached(): v $spy->value[] = $data['value']; }); - Async\run(static fn() => $ks->waitFor('key', ['time' => 0.003, 'value' => 'a'])); - Async\run(static fn() => $ks->waitFor('key', ['time' => 0.004, 'value' => 'b'])); - $beforeLast = Async\run(static fn() => $ks->waitFor('key', ['time' => 0.005, 'value' => 'c'])); + Async\run(static fn() => $ks->waitFor('key', ['time' => DateTime\Duration::milliseconds(3), 'value' => 'a'])); + Async\run(static fn() => $ks->waitFor('key', ['time' => DateTime\Duration::milliseconds(4), 'value' => 'b'])); + $beforeLast = Async\run(static fn() => $ks->waitFor('key', ['time' => DateTime\Duration::milliseconds(5), 'value' => 'c'])); Async\run(static fn() => $ks->waitFor('key', ['time' => null, 'value' => 'd'])); $beforeLast->await(); @@ -82,7 +83,7 @@ public function testOperationIsStartedIfLimitIsNotReached(): void $ks = new Async\KeyedSemaphore(1, static function (string $_, string $input) use ($spy): void { $spy->value[] = $input; - Async\sleep(0.002); + Async\sleep(DateTime\Duration::milliseconds(2)); }); $awaitable = Async\run(static fn() => $ks->waitFor('x', 'hello')); @@ -104,13 +105,13 @@ public function testOperationIsNotStartedIfLimitIsReached(): void $semaphore = new Async\KeyedSemaphore(1, static function (string $_, string $input) use ($spy): void { $spy->value[] = $input; - Async\sleep(0.002); + Async\sleep(DateTime\Duration::milliseconds(2)); }); Async\run(static fn() => $semaphore->waitFor('x', 'hello')); $awaitable = Async\run(static fn() => $semaphore->waitFor('x', 'world')); - Async\sleep(0.001); + Async\sleep(DateTime\Duration::milliseconds(1)); static::assertNotContains('world', $spy->value); @@ -137,7 +138,7 @@ public function testCancelPendingOperationsButNotTheOngoingOne(): void * @var Async\KeyedSemaphore */ $ks = new Async\KeyedSemaphore(1, static function (string $_, string $input): string { - Async\sleep(0.04); + Async\sleep(DateTime\Duration::milliseconds(40)); return $input; }); @@ -145,7 +146,7 @@ public function testCancelPendingOperationsButNotTheOngoingOne(): void $one = Async\run(static fn() => $ks->waitFor('foo', 'one')); $two = Async\run(static fn() => $ks->waitFor('foo', 'two')); - Async\sleep(0.01); + Async\sleep(DateTime\Duration::milliseconds(10)); $ks->cancel('foo', new Async\Exception\TimeoutException('The semaphore is destroyed.')); @@ -163,7 +164,7 @@ public function testCancelAllPendingOperations(): void * @var Async\KeyedSemaphore */ $ks = new Async\KeyedSemaphore(1, static function (string $_, string $input): string { - Async\sleep(0.04); + Async\sleep(DateTime\Duration::milliseconds(40)); return $input; }); @@ -180,7 +181,7 @@ public function testCancelAllPendingOperations(): void Async\run(static fn() => $ks->waitFor('baz', 'pending')) ]; - Async\sleep(0.01); + Async\sleep(DateTime\Duration::milliseconds(10)); $ks->cancelAll(new Async\Exception\TimeoutException('The semaphore is destroyed.')); @@ -203,7 +204,7 @@ public function testSemaphoreStatus(): void * @var Async\KeyedSemaphore */ $ks = new Async\KeyedSemaphore(1, static function (string $_, string $input): string { - Async\sleep(0.04); + Async\sleep(DateTime\Duration::milliseconds(40)); return $input; }); @@ -255,7 +256,7 @@ public function testWaitForRoom(): void * @var Async\KeyedSemaphore */ $ks = new Async\KeyedSemaphore(1, static function (string $_, string $input): string { - Async\sleep(0.04); + Async\sleep(DateTime\Duration::milliseconds(40)); return $input; }); @@ -273,7 +274,7 @@ public function testConcurrencyLimitOnDifferentKeys(): void * @var Async\KeyedSemaphore */ $ks = new Async\KeyedSemaphore(1, static function (string $_, string $input): string { - Async\sleep(0.04); + Async\sleep(DateTime\Duration::milliseconds(40)); return $input; }); static::assertSame(1, $ks->getConcurrencyLimit()); diff --git a/tests/unit/Async/KeyedSequenceTest.php b/tests/unit/Async/KeyedSequenceTest.php index 0be9737e..5f1074d1 100644 --- a/tests/unit/Async/KeyedSequenceTest.php +++ b/tests/unit/Async/KeyedSequenceTest.php @@ -7,6 +7,7 @@ use PHPUnit\Framework\TestCase; use Psl; use Psl\Async; +use Psl\DateTime; final class KeyedSequenceTest extends TestCase { @@ -38,9 +39,9 @@ public function testSequenceOperationWaitsForPendingOperationsWhenLimitIsNotReac $spy->value[] = $data['value']; }); - Async\run(static fn() => $ks->waitFor('operation', ['time' => 0.003, 'value' => 'a'])); - Async\run(static fn() => $ks->waitFor('operation', ['time' => 0.004, 'value' => 'b'])); - Async\run(static fn() => $ks->waitFor('operation', ['time' => 0.005, 'value' => 'c'])); + Async\run(static fn() => $ks->waitFor('operation', ['time' => DateTime\Duration::milliseconds(3), 'value' => 'a'])); + Async\run(static fn() => $ks->waitFor('operation', ['time' => DateTime\Duration::milliseconds(4), 'value' => 'b'])); + Async\run(static fn() => $ks->waitFor('operation', ['time' => DateTime\Duration::milliseconds(5), 'value' => 'c'])); $last = Async\run(static fn() => $ks->waitFor('operation', ['time' => null, 'value' => 'd'])); $last->await(); @@ -54,10 +55,12 @@ public function testOperationIsStartedIfLimitIsNotReached(): void /** * @var Async\KeyedSequence */ - $ks = new Async\KeyedSequence(static function (string $_, string $input) use ($spy): void { + $ks = new Async\KeyedSequence(static function (string $key, string $input) use ($spy): void { + static::assertSame('x', $key); + $spy->value[] = $input; - Async\sleep(0.002); + Async\sleep(DateTime\Duration::milliseconds(2)); }); $awaitable = Async\run(static fn() => $ks->waitFor('x', 'hello')); @@ -79,13 +82,13 @@ public function testOperationIsNotStartedIfLimitIsReached(): void $ks = new Async\KeyedSequence(static function (string $_, string $input) use ($spy): void { $spy->value[] = $input; - Async\sleep(0.002); + Async\sleep(DateTime\Duration::milliseconds(2)); }); Async\run(static fn() => $ks->waitFor('x', 'hello')); $awaitable = Async\run(static fn() => $ks->waitFor('x', 'world')); - Async\sleep(0.001); + Async\sleep(DateTime\Duration::milliseconds(1)); static::assertNotContains('world', $spy->value); @@ -112,7 +115,7 @@ public function testCancelPendingOperationsButNotTheOngoingOne(): void * @var Async\KeyedSequence */ $ks = new Async\KeyedSequence(static function (string $_, string $input): string { - Async\sleep(0.04); + Async\sleep(DateTime\Duration::milliseconds(40)); return $input; }); @@ -120,7 +123,7 @@ public function testCancelPendingOperationsButNotTheOngoingOne(): void $one = Async\run(static fn() => $ks->waitFor('foo', 'one')); $two = Async\run(static fn() => $ks->waitFor('foo', 'two')); - Async\sleep(0.01); + Async\sleep(DateTime\Duration::milliseconds(10)); $ks->cancel('foo', new Async\Exception\TimeoutException('The semaphore is destroyed.')); @@ -138,7 +141,7 @@ public function testCancelAllPendingOperations(): void * @var Async\KeyedSequence */ $ks = new Async\KeyedSequence(static function (string $_, string $input): string { - Async\sleep(0.04); + Async\sleep(DateTime\Duration::milliseconds(40)); return $input; }); @@ -155,7 +158,7 @@ public function testCancelAllPendingOperations(): void Async\run(static fn() => $ks->waitFor('baz', 'pending')) ]; - Async\sleep(0.01); + Async\sleep(DateTime\Duration::milliseconds(10)); $ks->cancelAll(new Async\Exception\TimeoutException('The semaphore is destroyed.')); @@ -178,7 +181,7 @@ public function testSemaphoreStatus(): void * @var Async\KeyedSequence */ $ks = new Async\KeyedSequence(static function (string $_, string $input): string { - Async\sleep(0.04); + Async\sleep(DateTime\Duration::milliseconds(40)); return $input; }); @@ -226,7 +229,8 @@ public function testWaitForRoom(): void * @var Async\KeyedSequence */ $ks = new Async\KeyedSequence(static function (string $_, string $input): string { - Async\sleep(0.04); + Async\sleep(DateTime\Duration::milliseconds(40)); + return $input; }); @@ -244,7 +248,8 @@ public function testConcurrencyLimitOnDifferentKeys(): void * @var Async\KeyedSequence */ $ks = new Async\KeyedSequence(static function (string $_, string $input): string { - Async\sleep(0.04); + Async\sleep(DateTime\Duration::milliseconds(40)); + return $input; }); diff --git a/tests/unit/Async/ParallelTest.php b/tests/unit/Async/ParallelTest.php index faac7241..b90fe5fc 100644 --- a/tests/unit/Async/ParallelTest.php +++ b/tests/unit/Async/ParallelTest.php @@ -8,6 +8,7 @@ use PHPUnit\Util\Exception; use Psl; use Psl\Async; +use Psl\DateTime; final class ParallelTest extends TestCase { @@ -17,17 +18,17 @@ public function testParallel(): void Async\concurrently([ static function () use ($spy): void { - Async\sleep(0.03); + Async\sleep(DateTime\Duration::milliseconds(30)); $spy->value .= '1'; }, static function () use ($spy): void { - Async\sleep(0.01); + Async\sleep(DateTime\Duration::milliseconds(10)); $spy->value .= '2'; }, static function () use ($spy): void { - Async\sleep(0.01); + Async\sleep(DateTime\Duration::milliseconds(10)); $spy->value .= '3'; }, @@ -45,12 +46,12 @@ public function testParallelThrowsForTheFirstAndDoesNotCallTheRest(): void try { Async\concurrently([ static function (): void { - Async\sleep(0.003); + Async\sleep(DateTime\Duration::milliseconds(3)); throw new Exception('foo'); }, static function () use ($spy): void { - Async\sleep(0.004); + Async\sleep(DateTime\Duration::milliseconds(4)); $spy->value = 'thrown'; diff --git a/tests/unit/Async/ReflectTest.php b/tests/unit/Async/ReflectTest.php index 94e9f979..37609c84 100644 --- a/tests/unit/Async/ReflectTest.php +++ b/tests/unit/Async/ReflectTest.php @@ -7,6 +7,7 @@ use Exception; use PHPUnit\Framework\TestCase; use Psl\Async; +use Psl\DateTime; use Psl\Result; final class ReflectTest extends TestCase @@ -15,7 +16,7 @@ public function testReflectParallel(): void { [$one, $two] = Async\concurrently([ Result\reflect(static function (): void { - Async\sleep(0.0001); + Async\sleep(DateTime\Duration::milliseconds(1)); throw new Exception('failure'); }), diff --git a/tests/unit/Async/RunTest.php b/tests/unit/Async/RunTest.php index 671c3d3d..8fdba0da 100644 --- a/tests/unit/Async/RunTest.php +++ b/tests/unit/Async/RunTest.php @@ -6,6 +6,7 @@ use PHPUnit\Framework\TestCase; use Psl\Async; +use Psl\DateTime; final class RunTest extends TestCase { @@ -13,9 +14,9 @@ public function testRun(): void { $awaitable = Async\run(static function (): string { Async\concurrently([ - static fn() => Async\sleep(0.001), - static fn() => Async\sleep(0.001), - static fn() => Async\sleep(0.001), + static fn() => Async\sleep(DateTime\Duration::milliseconds(1)), + static fn() => Async\sleep(DateTime\Duration::milliseconds(1)), + static fn() => Async\sleep(DateTime\Duration::milliseconds(1)), ]); return 'hello'; diff --git a/tests/unit/Async/SemaphoreTest.php b/tests/unit/Async/SemaphoreTest.php index c8168196..5b7443d8 100644 --- a/tests/unit/Async/SemaphoreTest.php +++ b/tests/unit/Async/SemaphoreTest.php @@ -7,6 +7,7 @@ use PHPUnit\Framework\TestCase; use Psl; use Psl\Async; +use Psl\DateTime; final class SemaphoreTest extends TestCase { @@ -24,7 +25,7 @@ public function testSequenceOperationWaitsForPendingOperationsWhenLimitIsNotReac $spy = new Psl\Ref([]); /** - * @var Async\Semaphore + * @var Async\Semaphore */ $semaphore = new Async\Semaphore(1, static function (array $data) use ($spy): void { if ($data['time'] !== null) { @@ -34,9 +35,9 @@ public function testSequenceOperationWaitsForPendingOperationsWhenLimitIsNotReac $spy->value[] = $data['value']; }); - Async\run(static fn() => $semaphore->waitFor(['time' => 0.003, 'value' => 'a'])); - Async\run(static fn() => $semaphore->waitFor(['time' => 0.004, 'value' => 'b'])); - Async\run(static fn() => $semaphore->waitFor(['time' => 0.005, 'value' => 'c'])); + Async\run(static fn() => $semaphore->waitFor(['time' => DateTime\Duration::milliseconds(3), 'value' => 'a'])); + Async\run(static fn() => $semaphore->waitFor(['time' => DateTime\Duration::milliseconds(4), 'value' => 'b'])); + Async\run(static fn() => $semaphore->waitFor(['time' => DateTime\Duration::milliseconds(5), 'value' => 'c'])); $last = Async\run(static fn() => $semaphore->waitFor(['time' => null, 'value' => 'd'])); $last->await(); @@ -48,7 +49,7 @@ public function testOperationWaitsForPendingOperationsWhenLimitIsNotReached(): v $spy = new Psl\Ref([]); /** - * @var Async\Semaphore + * @var Async\Semaphore */ $semaphore = new Async\Semaphore(2, static function (array $data) use ($spy): void { if ($data['time'] !== null) { @@ -58,9 +59,9 @@ public function testOperationWaitsForPendingOperationsWhenLimitIsNotReached(): v $spy->value[] = $data['value']; }); - Async\run(static fn() => $semaphore->waitFor(['time' => 0.003, 'value' => 'a'])); - Async\run(static fn() => $semaphore->waitFor(['time' => 0.004, 'value' => 'b'])); - $beforeLast = Async\run(static fn() => $semaphore->waitFor(['time' => 0.005, 'value' => 'c'])); + Async\run(static fn() => $semaphore->waitFor(['time' => Datetime\Duration::milliseconds(3), 'value' => 'a'])); + Async\run(static fn() => $semaphore->waitFor(['time' => Datetime\Duration::milliseconds(4), 'value' => 'b'])); + $beforeLast = Async\run(static fn() => $semaphore->waitFor(['time' => Datetime\Duration::milliseconds(5), 'value' => 'c'])); Async\run(static fn() => $semaphore->waitFor(['time' => null, 'value' => 'd'])); $beforeLast->await(); @@ -78,12 +79,12 @@ public function testOperationIsStartedIfLimitIsNotReached(): void $semaphore = new Async\Semaphore(1, static function (string $input) use ($spy): void { $spy->value[] = $input; - Async\sleep(0.002); + Async\sleep(Datetime\Duration::milliseconds(2)); }); $awaitable = Async\run(static fn() => $semaphore->waitFor('hello')); - Async\sleep(0.001); + Async\sleep(Datetime\Duration::milliseconds(1)); static::assertSame(['hello'], $spy->value); @@ -100,13 +101,13 @@ public function testOperationIsNotStartedIfLimitIsReached(): void $semaphore = new Async\Semaphore(1, static function (string $input) use ($spy): void { $spy->value[] = $input; - Async\sleep(0.002); + Async\sleep(Datetime\Duration::milliseconds(2)); }); Async\run(static fn() => $semaphore->waitFor('hello')); $awaitable = Async\run(static fn() => $semaphore->waitFor('world')); - Async\sleep(0.001); + Async\sleep(Datetime\Duration::milliseconds(1)); static::assertNotContains('world', $spy->value); @@ -133,7 +134,7 @@ public function testCancelPendingOperationsButNotTheOngoingOne(): void * @var Async\Semaphore */ $semaphore = new Async\Semaphore(1, static function (string $input): string { - Async\sleep(0.04); + Async\sleep(Datetime\Duration::milliseconds(40)); return $input; }); @@ -143,7 +144,7 @@ public function testCancelPendingOperationsButNotTheOngoingOne(): void $one = Async\run(static fn() => $semaphore->waitFor('one')); $two = Async\run(static fn() => $semaphore->waitFor('two')); - Async\sleep(0.01); + Async\sleep(Datetime\Duration::milliseconds(10)); $semaphore->cancel(new Async\Exception\TimeoutException('The semaphore is destroyed.')); @@ -161,7 +162,7 @@ public function testSemaphoreStatus(): void * @var Async\Semaphore */ $semaphore = new Async\Semaphore(1, static function (string $input): string { - Async\sleep(0.04); + Async\sleep(Datetime\Duration::milliseconds(40)); return $input; }); @@ -195,7 +196,7 @@ public function testWaitForPending(): void * @var Async\Semaphore */ $semaphore = new Async\Semaphore(1, static function (string $input): string { - Async\sleep(0.04); + Async\sleep(Datetime\Duration::milliseconds(40)); return $input; }); diff --git a/tests/unit/Async/SequenceTest.php b/tests/unit/Async/SequenceTest.php index c10c6803..a3fb9dc5 100644 --- a/tests/unit/Async/SequenceTest.php +++ b/tests/unit/Async/SequenceTest.php @@ -7,6 +7,7 @@ use PHPUnit\Framework\TestCase; use Psl; use Psl\Async; +use Psl\DateTime; use Psl\Str; use function microtime; @@ -37,9 +38,9 @@ public function testSequenceOperationWaitsForPendingOperationsWhenLimitIsNotReac $spy->value[] = $data['value']; }); - Async\run(static fn() => $sequence->waitFor(['time' => 0.003, 'value' => 'a'])); - Async\run(static fn() => $sequence->waitFor(['time' => 0.004, 'value' => 'b'])); - Async\run(static fn() => $sequence->waitFor(['time' => 0.005, 'value' => 'c'])); + Async\run(static fn() => $sequence->waitFor(['time' => DateTime\Duration::milliseconds(3), 'value' => 'a'])); + Async\run(static fn() => $sequence->waitFor(['time' => DateTime\Duration::milliseconds(4), 'value' => 'b'])); + Async\run(static fn() => $sequence->waitFor(['time' => DateTime\Duration::milliseconds(5), 'value' => 'c'])); $last = Async\run(static fn() => $sequence->waitFor(['time' => null, 'value' => 'd'])); $last->await(); @@ -56,12 +57,12 @@ public function testOperationIsStartedIfLimitIsNotReached(): void $sequence = new Async\Sequence(static function (string $input) use ($spy): void { $spy->value[] = $input; - Async\sleep(0.002); + Async\sleep(DateTime\Duration::milliseconds(2)); }); $awaitable = Async\run(static fn() => $sequence->waitFor('hello')); - Async\sleep(0.001); + Async\sleep(DateTime\Duration::milliseconds(1)); static::assertSame(['hello'], $spy->value); @@ -78,13 +79,13 @@ public function testOperationIsNotStartedIfLimitIsReached(): void $sequence = new Async\Sequence(static function (string $input) use ($spy): void { $spy->value[] = $input; - Async\sleep(0.002); + Async\sleep(DateTime\Duration::milliseconds(2)); }); Async\run(static fn() => $sequence->waitFor('hello')); $awaitable = Async\run(static fn() => $sequence->waitFor('world')); - Async\sleep(0.001); + Async\sleep(DateTime\Duration::milliseconds(1)); static::assertNotContains('world', $spy->value); @@ -111,7 +112,7 @@ public function testCancelPendingOperationsButNotTheOngoingOne(): void * @var Async\Sequence */ $sequence = new Async\Sequence(static function (string $input): string { - Async\sleep(0.04); + Async\sleep(DateTime\Duration::milliseconds(40)); return $input; }); @@ -119,7 +120,7 @@ public function testCancelPendingOperationsButNotTheOngoingOne(): void $one = Async\run(static fn() => $sequence->waitFor('one')); $two = Async\run(static fn() => $sequence->waitFor('two')); - Async\sleep(0.01); + Async\sleep(DateTime\Duration::milliseconds(10)); $sequence->cancel(new Async\Exception\TimeoutException('The semaphore is destroyed.')); @@ -138,8 +139,8 @@ public function testBug327(): void { $ref = new Psl\Ref(''); - $sequence = new Async\Sequence(static function (float $value) use ($ref): void { - $ref->value .= Str\format('%f', $value); + $sequence = new Async\Sequence(static function (DateTime\Duration $value) use ($ref): void { + $ref->value .= Str\format('%f', $value->getTotalSeconds()); Async\sleep($value); }); @@ -148,10 +149,10 @@ public function testBug327(): void Async\concurrently([ static function () use ($sequence): void { - $sequence->waitFor(0.02); - $sequence->waitFor(0.02); + $sequence->waitFor(DateTime\Duration::milliseconds(20)); + $sequence->waitFor(DateTime\Duration::milliseconds(20)); }, - static fn() => $sequence->waitFor(0.02), + static fn() => $sequence->waitFor(DateTime\Duration::milliseconds(20)), ]); $duration = microtime(true) - $time; @@ -166,7 +167,7 @@ public function testStatus(): void * @var Async\Sequence */ $s = new Async\Sequence(static function (string $input): string { - Async\sleep(0.04); + Async\sleep(DateTime\Duration::milliseconds(40)); return $input; }); @@ -196,7 +197,7 @@ public function testWaitForPending(): void * @var Async\Sequence */ $s = new Async\Sequence(static function (string $input): string { - Async\sleep(0.04); + Async\sleep(DateTime\Duration::milliseconds(40)); return $input; }); diff --git a/tests/unit/Async/SeriesTest.php b/tests/unit/Async/SeriesTest.php index 27049e27..30325893 100644 --- a/tests/unit/Async/SeriesTest.php +++ b/tests/unit/Async/SeriesTest.php @@ -8,6 +8,7 @@ use PHPUnit\Util\Exception; use Psl; use Psl\Async; +use Psl\DateTime; final class SeriesTest extends TestCase { @@ -17,17 +18,17 @@ public function testSeries(): void Async\series([ static function () use ($spy): void { - Async\sleep(0.003); + Async\sleep(DateTime\Duration::milliseconds(3)); $spy->value .= '1'; }, static function () use ($spy): void { - Async\sleep(0.001); + Async\sleep(DateTime\Duration::milliseconds(1)); $spy->value .= '2'; }, static function () use ($spy): void { - Async\sleep(0.001); + Async\sleep(DateTime\Duration::milliseconds(1)); $spy->value .= '3'; }, @@ -45,7 +46,7 @@ public function testSeriesThrowsForTheFirstAndDoesNotCallTheRest(): void try { Async\series([ static function (): void { - Async\sleep(0.003); + Async\sleep(DateTime\Duration::milliseconds(3)); throw new Exception('foo'); }, diff --git a/tests/unit/Channel/BoundedChannelTest.php b/tests/unit/Channel/BoundedChannelTest.php index 20dab41e..5242e9f5 100644 --- a/tests/unit/Channel/BoundedChannelTest.php +++ b/tests/unit/Channel/BoundedChannelTest.php @@ -7,6 +7,7 @@ use PHPUnit\Framework\TestCase; use Psl\Async; use Psl\Channel; +use Psl\DateTime; final class BoundedChannelTest extends TestCase { @@ -109,10 +110,9 @@ public function testIsFull(): void public function testTrySendThrowsOnFullChannel(): void { /** - * @var Channel\ReceiverInterface $receiver * @var Channel\SenderInterface $sender */ - [$receiver, $sender] = Channel\bounded(1); + [$_, $sender] = Channel\bounded(1); $sender->send('hello'); @@ -131,7 +131,7 @@ public function testSendWaitsForFullChannel(): void $sender->send('hello'); - Async\Scheduler::delay(0.001, static function () use ($receiver) { + Async\Scheduler::delay(DateTime\Duration::milliseconds(1), static function () use ($receiver) { $receiver->receive(); }); @@ -166,7 +166,7 @@ public function testSendThrowsForLateClosedChannel(): void $sender->send('hello'); - Async\Scheduler::delay(0.001, static function () use ($receiver): void { + Async\Scheduler::delay(DateTime\Duration::milliseconds(1), static function () use ($receiver): void { $receiver->close(); }); @@ -233,7 +233,7 @@ public function testReceiveThrowsForLateClosedChannel(): void */ [$receiver, $sender] = Channel\bounded(1); - Async\Scheduler::delay(0.0001, static function () use ($sender): void { + Async\Scheduler::delay(DateTime\Duration::milliseconds(1), static function () use ($sender): void { $sender->close(); }); @@ -267,9 +267,10 @@ public function testReceiveWaitsWhenChannelIsEmpty(): void */ [$receiver, $sender] = Channel\bounded(1); - Async\Scheduler::delay(0.001, static function () use ($sender) { - $sender->send('hello'); - }); + Async\Scheduler::delay( + DateTime\Duration::milliseconds(1), + static fn() => $sender->send('hello'), + ); static::assertTrue($receiver->isEmpty()); @@ -280,9 +281,8 @@ public function testTryReceiveThrowsForEmptyChannel(): void { /** * @var Channel\ReceiverInterface $receiver - * @var Channel\SenderInterface $sender */ - [$receiver, $sender] = Channel\bounded(1); + [$receiver, $_] = Channel\bounded(1); $this->expectException(Channel\Exception\EmptyChannelException::class); $this->expectExceptionMessage('Attempted to receiver from an empty channel.'); diff --git a/tests/unit/DateTime/TimeIntervalTest.php b/tests/unit/DateTime/TimeIntervalTest.php index 959f9cd9..e5e4f083 100644 --- a/tests/unit/DateTime/TimeIntervalTest.php +++ b/tests/unit/DateTime/TimeIntervalTest.php @@ -5,6 +5,7 @@ namespace Psl\Tests\Unit\DateTime; use PHPUnit\Framework\TestCase; +use Psl\Comparison\Order; use Psl\DateTime; use Psl\Json; @@ -234,31 +235,33 @@ public function testPositiveNegative(int $h, int $m, int $s, int $ns, int $expec public static function provideCompare(): array { return [ - array(DateTime\Duration::hours(1), DateTime\Duration::minutes(42), 1), - array(DateTime\Duration::minutes(2), DateTime\Duration::seconds(120), 0), - array(DateTime\Duration::zero(), DateTime\Duration::nanoseconds(1), -1), + array(DateTime\Duration::hours(1), DateTime\Duration::minutes(42), Order::Greater), + array(DateTime\Duration::minutes(2), DateTime\Duration::seconds(120), Order::Equal), + array(DateTime\Duration::zero(), DateTime\Duration::nanoseconds(1), Order::Less), ]; } /** * @dataProvider provideCompare */ - public function testCompare(DateTime\Duration $a, DateTime\Duration $b, int $expected): void + public function testCompare(DateTime\Duration $a, DateTime\Duration $b, Order $expected): void { + $opposite = Order::from(-$expected->value); + static::assertEquals($expected, $a->compare($b)); - static::assertEquals(-$expected, $b->compare($a)); - static::assertEquals($expected === 0, $a->isEqual($b)); - static::assertEquals($expected === -1, $a->isShorter($b)); - static::assertEquals($expected !== 1, $a->isShorterOrEqual($b)); - static::assertEquals($expected === 1, $a->isLonger($b)); - static::assertEquals($expected !== -1, $a->isLongerOrEqual($b)); - static::assertFalse($a->isBetweenExclusive($a, $a)); - static::assertFalse($a->isBetweenExclusive($a, $b)); - static::assertFalse($a->isBetweenExclusive($b, $a)); - static::assertFalse($a->isBetweenExclusive($b, $b)); - static::assertTrue($a->isBetweenInclusive($a, $a)); - static::assertTrue($a->isBetweenInclusive($a, $b)); - static::assertTrue($a->isBetweenInclusive($b, $a)); - static::assertEquals($expected === 0, $a->isBetweenInclusive($b, $b)); + static::assertEquals($opposite, $b->compare($a)); + static::assertEquals($expected === Order::Equal, $a->equals($b)); + static::assertEquals($expected === Order::Less, $a->shorter($b)); + static::assertEquals($expected !== Order::Greater, $a->shorterOrEqual($b)); + static::assertEquals($expected === Order::Greater, $a->longer($b)); + static::assertEquals($expected !== Order::Less, $a->longerOrEqual($b)); + static::assertFalse($a->betweenExclusive($a, $a)); + static::assertFalse($a->betweenExclusive($a, $b)); + static::assertFalse($a->betweenExclusive($b, $a)); + static::assertFalse($a->betweenExclusive($b, $b)); + static::assertTrue($a->betweenInclusive($a, $a)); + static::assertTrue($a->betweenInclusive($a, $b)); + static::assertTrue($a->betweenInclusive($b, $a)); + static::assertEquals($expected === Order::Equal, $a->betweenInclusive($b, $b)); } public function testIsBetween(): void @@ -266,14 +269,14 @@ public function testIsBetween(): void $a = DateTime\Duration::hours(1); $b = DateTime\Duration::minutes(64); $c = DateTime\Duration::fromParts(1, 30); - static::assertTrue($b->isBetweenExclusive($a, $c)); - static::assertTrue($b->isBetweenExclusive($c, $a)); - static::assertTrue($b->isBetweenInclusive($a, $c)); - static::assertTrue($b->isBetweenInclusive($c, $a)); - static::assertFalse($a->isBetweenExclusive($b, $c)); - static::assertFalse($a->isBetweenInclusive($c, $b)); - static::assertFalse($c->isBetweenInclusive($a, $b)); - static::assertFalse($c->isBetweenExclusive($b, $a)); + static::assertTrue($b->betweenExclusive($a, $c)); + static::assertTrue($b->betweenExclusive($c, $a)); + static::assertTrue($b->betweenInclusive($a, $c)); + static::assertTrue($b->betweenInclusive($c, $a)); + static::assertFalse($a->betweenExclusive($b, $c)); + static::assertFalse($a->betweenInclusive($c, $b)); + static::assertFalse($c->betweenInclusive($a, $b)); + static::assertFalse($c->betweenExclusive($b, $a)); } public function testOperations(): void diff --git a/tests/unit/Filesystem/AbstractFilesystemTest.php b/tests/unit/Filesystem/AbstractFilesystemTest.php index 1feabac4..7436026d 100644 --- a/tests/unit/Filesystem/AbstractFilesystemTest.php +++ b/tests/unit/Filesystem/AbstractFilesystemTest.php @@ -20,8 +20,8 @@ abstract class AbstractFilesystemTest extends TestCase protected function setUp(): void { - if (OS\is_windows() || OS\is_darwin()) { - static::markTestSkipped('Filesystem tests are only executed on linux.'); + if (OS\is_windows()) { + static::markTestSkipped('Test can only be executed under *nix OS.'); } $this->cacheDirectory = Type\string()->assert(Filesystem\canonicalize(Str\join([ diff --git a/tests/unit/IO/PipeTest.php b/tests/unit/IO/PipeTest.php index 54b12984..a0c06ab8 100644 --- a/tests/unit/IO/PipeTest.php +++ b/tests/unit/IO/PipeTest.php @@ -7,6 +7,7 @@ use PHPUnit\Framework\TestCase; use Psl; use Psl\Async; +use Psl\DateTime; use Psl\IO; final class PipeTest extends TestCase @@ -39,7 +40,7 @@ public function testReadWriteInParallel(): void $read_awaitable = Async\run(static function () use ($read, $spy): string { $spy->value .= '[read:sleep]'; - Async\sleep(0.003); + Async\sleep(DateTime\Duration::milliseconds(3)); $spy->value .= '[read:start]'; $content = $read->readAll(1000); $spy->value .= '[read:complete]'; @@ -50,7 +51,7 @@ public function testReadWriteInParallel(): void Async\run(static function () use ($write, $spy): void { $spy->value .= '[write:sleep]'; - Async\sleep(0.0035); + Async\sleep(DateTime\Duration::milliseconds(5)); $spy->value .= '[write:start]'; $write->writeAll('hello'); $spy->value .= '[write:complete]'; @@ -96,7 +97,7 @@ public function testReadAllTimedOut(): void $this->expectException(IO\Exception\TimeoutException::class); $this->expectExceptionMessage('Reached timeout while the handle is still not readable.'); - $read->readAll(timeout: 0.001); + $read->readAll(timeout: DateTime\Duration::milliseconds(1)); } public function testReadOnAlreadyClosedPipe(): void diff --git a/tests/unit/TCP/ServerTest.php b/tests/unit/TCP/ServerTest.php index 8d73774d..b62084bf 100644 --- a/tests/unit/TCP/ServerTest.php +++ b/tests/unit/TCP/ServerTest.php @@ -6,6 +6,7 @@ use PHPUnit\Framework\TestCase; use Psl\Async; +use Psl\DateTime; use Psl\Network; use Psl\Network\Exception\AlreadyStoppedException; use Psl\TCP; @@ -78,7 +79,7 @@ public function testIncoming(): void { $server = TCP\Server::create('127.0.0.1'); $incoming = $server->incoming(); - Async\Scheduler::delay(0.01, static fn() => $server->close()); + Async\Scheduler::delay(DateTime\Duration::milliseconds(1), static fn() => $server->close()); Async\Scheduler::defer(static function () use ($server) { TCP\connect('127.0.0.1', $server->getLocalAddress()->port); });