mirror of
https://github.com/beste/clock.git
synced 2026-08-17 17:47:13 +00:00
Initial Release
This commit is contained in:
@@ -0,0 +1,21 @@
|
|||||||
|
# EditorConfig helps developers define and maintain consistent
|
||||||
|
# coding styles between different editors and IDEs
|
||||||
|
# editorconfig.org
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
[*.{neon,neon.dist}]
|
||||||
|
indent_style = tab
|
||||||
|
|
||||||
|
[*.{yml,yaml}]
|
||||||
|
indent_size = 2
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/examples/ export-ignore
|
||||||
|
/tests/ export-ignore
|
||||||
|
|
||||||
|
/.editorconfig export-ignore
|
||||||
|
/.gitattributes export-ignore
|
||||||
|
/.gitignore export-ignore
|
||||||
|
/phpstan.neon.dist export-ignore
|
||||||
|
/phpunit.xml.dist export-ignore
|
||||||
|
/psalm.xml.dist export-ignore
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
name: Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'main'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tests:
|
||||||
|
name: PHP ${{ matrix.php }}
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php:
|
||||||
|
- "7.4"
|
||||||
|
- "8.0"
|
||||||
|
- "8.1"
|
||||||
|
dependencies:
|
||||||
|
- "lowest"
|
||||||
|
- "highest"
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php }}
|
||||||
|
tools: composer:v2
|
||||||
|
coverage: xdebug
|
||||||
|
env:
|
||||||
|
COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- uses: "ramsey/composer-install@v1"
|
||||||
|
with:
|
||||||
|
dependency-versions: "${{ matrix.dependencies }}"
|
||||||
|
composer-options: "${{ matrix.composer-options }}"
|
||||||
|
|
||||||
|
- name: Setup problem matchers for PHP
|
||||||
|
run: echo "::add-matcher::${{ runner.tool_cache }}/php.json"
|
||||||
|
|
||||||
|
- name: Run PHPStan
|
||||||
|
run: vendor/bin/phpstan analyse --error-format=github
|
||||||
|
|
||||||
|
- name: Run Psalm
|
||||||
|
run: vendor/bin/psalm --output-format=github
|
||||||
|
|
||||||
|
- name: Setup Problem Matchers for PHPUnit
|
||||||
|
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
|
||||||
|
|
||||||
|
- name: Run PHPUnit
|
||||||
|
run: vendor/bin/phpunit --testdox --coverage-text
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
/.build/
|
||||||
|
/vendor/
|
||||||
|
|
||||||
|
/composer.lock
|
||||||
|
/phpstan.neon
|
||||||
|
/phpunit.xml
|
||||||
|
/psalm.xml
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# CHANGELOG
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
## 1.0.0 - 2021-12-10
|
||||||
|
Initial Release
|
||||||
|
|
||||||
|
[Unreleased]: https://github.com/beste/clock/compare/1.0.0...main
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) Jérôme Gamez, https://github.com/beste <[email protected]>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
# Clock
|
||||||
|
|
||||||
|
[](https://packagist.org/packages/beste/clock)
|
||||||
|
[](https://packagist.org/packages/beste/clock)
|
||||||
|
[](https://github.com/beste/clock/actions/workflows/tests.yml)
|
||||||
|
|
||||||
|
A collection of Clock implementations.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Installation](#installation)
|
||||||
|
- [Clocks](#clocks)
|
||||||
|
- [`SystemClock`](#systemclock) - Time, as your computer (k)nows it
|
||||||
|
- [`LocalizedClock`](#localizedclock) - A clock in a(nother) time zone
|
||||||
|
- [`UTCClock`](#utcclock) - The clock that you should™ use
|
||||||
|
- [`FrozenClock`](#frozenclock) - A clock that stopped moving (perfect for tests)
|
||||||
|
- [`MinuteClock`](#minuteclock) - Who cares about seconds or even less?
|
||||||
|
- [Running Tests](#running-tests)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```shell
|
||||||
|
composer require beste/clock
|
||||||
|
```
|
||||||
|
|
||||||
|
## Clocks
|
||||||
|
|
||||||
|
### `SystemClock`
|
||||||
|
|
||||||
|
A System Clock will return a time just as if you would use `new DateTimeImmutable()`. The time zone of the returned
|
||||||
|
value is determined by the clock's environment, for example by the time zone that has been configured in your
|
||||||
|
application, by a previously used `date_default_timezone_set()` or by the value of `date.timezone` in the
|
||||||
|
`php.ini`. If none of these are explicitly set, it uses the `UTC` timezone.
|
||||||
|
|
||||||
|
```php
|
||||||
|
# examples/system_clock.php
|
||||||
|
|
||||||
|
use Beste\Clock\SystemClock;
|
||||||
|
|
||||||
|
$clock = SystemClock::create();
|
||||||
|
|
||||||
|
printf("On your system, the current date and time is %s\n", $clock->now()->format('Y-m-d H:i:s T (P)'));
|
||||||
|
|
||||||
|
date_default_timezone_set('America/Los_Angeles');
|
||||||
|
|
||||||
|
printf("Now it's %s\n", $clock->now()->format('Y-m-d H:i:s T (P)'));
|
||||||
|
|
||||||
|
date_default_timezone_set('Europe/Berlin');
|
||||||
|
|
||||||
|
printf("Now it's %s\n", $clock->now()->format('Y-m-d H:i:s T (P)'));
|
||||||
|
```
|
||||||
|
|
||||||
|
### `LocalizedClock`
|
||||||
|
|
||||||
|
A localized clock is aware of the time zone in which it is located. While the time zone of the `SystemClock` is
|
||||||
|
determined from the environment (your PHP configuration), this clock uses the time zone that you initialize it with.
|
||||||
|
|
||||||
|
```php
|
||||||
|
# examples/localized_clock.php
|
||||||
|
|
||||||
|
use Beste\Clock\LocalizedClock;
|
||||||
|
|
||||||
|
$berlin = LocalizedClock::in('Europe/Berlin');
|
||||||
|
$denver = LocalizedClock::in(new DateTimeZone('America/Denver'));
|
||||||
|
|
||||||
|
printf("Berlin: %s\n", $berlin->now()->format('Y-m-d H:i:s T (P)'));
|
||||||
|
printf("Denver: %s\n", $denver->now()->format('Y-m-d H:i:s T (P)'));
|
||||||
|
```
|
||||||
|
|
||||||
|
### `UTCClock`
|
||||||
|
|
||||||
|
`UTC` is the abbreviation for [Coordinated Universal Time](https://en.wikipedia.org/wiki/Coordinated_Universal_Time)
|
||||||
|
and a special kind of time zone that is not affected by daylight saving time. It is commonly used for the communication
|
||||||
|
of time across different systems (e.g. between your PHP application and a database, or between a backend
|
||||||
|
and a frontend). An `UTCClock` instance behaves exactly the same as an instance of `LocalizedClock::in('UTC')`.
|
||||||
|
|
||||||
|
```php
|
||||||
|
# examples/utc_clock.php
|
||||||
|
|
||||||
|
use Beste\Clock\UTCClock;
|
||||||
|
|
||||||
|
$clock = UTCClock::create();
|
||||||
|
|
||||||
|
$anotherTimeZone = 'Africa/Casablanca';
|
||||||
|
|
||||||
|
date_default_timezone_set($anotherTimeZone);
|
||||||
|
|
||||||
|
printf("The system time zone is %s.\n", $anotherTimeZone);
|
||||||
|
printf("The clock's time zone is %s.\n", $clock->now()->getTimezone()->getName());
|
||||||
|
```
|
||||||
|
|
||||||
|
### `FrozenClock`
|
||||||
|
|
||||||
|
A frozen clock doesn't move - the time we set it with will stay the same... unless we change it. That makes the
|
||||||
|
frozen clock perfect for testing the behaviour of your time-based use cases, for example in Unit Tests.
|
||||||
|
|
||||||
|
```php
|
||||||
|
# examples/frozen_clock.php
|
||||||
|
|
||||||
|
use Beste\Clock\FrozenClock;
|
||||||
|
use Beste\Clock\SystemClock;
|
||||||
|
|
||||||
|
$frozenClock = FrozenClock::withNowFrom(SystemClock::create());
|
||||||
|
|
||||||
|
printf("\nThe clock is frozen at %s", $frozenClock->now()->format('Y-m-d H:i:s T (P)'));
|
||||||
|
printf("\nLet's wait a second…");
|
||||||
|
sleep(1);
|
||||||
|
printf("\nIt's one second later, but the clock is still frozen at %s", $frozenClock->now()->format('Y-m-d H:i:s T (P)'));
|
||||||
|
|
||||||
|
$frozenClock->setTo($frozenClock->now()->modify('-5 minutes'));
|
||||||
|
printf("\nAfter turning back the clock 5 minutes, it's %s", $frozenClock->now()->format('Y-m-d H:i:s T (P)'));
|
||||||
|
```
|
||||||
|
|
||||||
|
### `MinuteClock`
|
||||||
|
|
||||||
|
In some cases, microseconds, milliseconds, or even seconds are too precise for some use cases - sometimes it's just
|
||||||
|
enough if something happened in the same minute. Using the minute
|
||||||
|
|
||||||
|
```php
|
||||||
|
# examples/minute_clock.php
|
||||||
|
|
||||||
|
use Beste\Clock\FrozenClock;
|
||||||
|
use Beste\Clock\MinuteClock;
|
||||||
|
|
||||||
|
$frozenClock = FrozenClock::at(new DateTimeImmutable('01:23:45'));
|
||||||
|
$clock = MinuteClock::wrapping($frozenClock);
|
||||||
|
|
||||||
|
printf("For %s, the minute clock returns %s\n",
|
||||||
|
$frozenClock->now()->format('H:i:s'),
|
||||||
|
$clock->now()->format('H:i:s')
|
||||||
|
);
|
||||||
|
|
||||||
|
$frozenClock->setTo($frozenClock->now()->modify('+10 seconds')); // 01:23:55
|
||||||
|
|
||||||
|
printf("For %s, the minute clock still returns %s\n",
|
||||||
|
$frozenClock->now()->format('H:i:s'),
|
||||||
|
$clock->now()->format('H:i:s')
|
||||||
|
);
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running tests
|
||||||
|
|
||||||
|
```shell
|
||||||
|
composer test
|
||||||
|
```
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"name": "beste/clock",
|
||||||
|
"type": "library",
|
||||||
|
"description": "A collection of Clock implementations",
|
||||||
|
"keywords": ["clock", "clock-interface", "psr20", "psr-20"],
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Jérôme Gamez",
|
||||||
|
"email": "[email protected]"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"php": "~7.4.0 || ~8.0.0 || ~8.1.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/extension-installer": "^1.1",
|
||||||
|
"phpstan/phpstan": "^1.0",
|
||||||
|
"phpstan/phpstan-phpunit": "^1.0",
|
||||||
|
"phpstan/phpstan-strict-rules": "^1.0",
|
||||||
|
"phpunit/phpunit": "^9.5.10",
|
||||||
|
"psalm/plugin-phpunit": "^0.16.0",
|
||||||
|
"vimeo/psalm": "^4.15"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Beste\\Clock\\": "src/Clock"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"src/psr-polyfill.php",
|
||||||
|
"src/Clock.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"Beste\\Clock\\Tests\\": "tests/Clock"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"provide": {
|
||||||
|
"psr/clock-implementation": "1.0"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"sort-packages": true
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": [
|
||||||
|
"@phpstan",
|
||||||
|
"@psalm",
|
||||||
|
"@phpunit"
|
||||||
|
],
|
||||||
|
"phpunit": "vendor/bin/phpunit",
|
||||||
|
"phpstan": "vendor/bin/phpstan analyse",
|
||||||
|
"psalm": "vendor/bin/psalm"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require __DIR__.'/../vendor/autoload.php';
|
||||||
|
|
||||||
|
use Beste\Clock\FrozenClock;
|
||||||
|
use Beste\Clock\SystemClock;
|
||||||
|
|
||||||
|
$frozenClock = FrozenClock::withNowFrom(SystemClock::create());
|
||||||
|
|
||||||
|
printf("\nThe clock is frozen at %s", $frozenClock->now()->format('Y-m-d H:i:s T (P)'));
|
||||||
|
printf("\nLet's wait a second…");
|
||||||
|
sleep(1);
|
||||||
|
printf("\nIt's one second later, but the clock is still frozen at %s", $frozenClock->now()->format('Y-m-d H:i:s T (P)'));
|
||||||
|
|
||||||
|
$frozenClock->setTo($frozenClock->now()->modify('-5 minutes'));
|
||||||
|
printf("\nAfter turning back the clock 5 minutes, it's %s", $frozenClock->now()->format('Y-m-d H:i:s T (P)'));
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require __DIR__.'/../vendor/autoload.php';
|
||||||
|
|
||||||
|
use Beste\Clock\LocalizedClock;
|
||||||
|
|
||||||
|
$berlin = LocalizedClock::in('Europe/Berlin');
|
||||||
|
$denver = LocalizedClock::in(new DateTimeZone('America/Denver'));
|
||||||
|
|
||||||
|
printf("Berlin: %s\n", $berlin->now()->format('Y-m-d H:i:s T (P)'));
|
||||||
|
printf("Denver: %s\n", $denver->now()->format('Y-m-d H:i:s T (P)'));
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require __DIR__.'/../vendor/autoload.php';
|
||||||
|
|
||||||
|
use Beste\Clock\FrozenClock;
|
||||||
|
use Beste\Clock\MinuteClock;
|
||||||
|
|
||||||
|
$frozenClock = FrozenClock::at(new DateTimeImmutable('01:23:45'));
|
||||||
|
$clock = MinuteClock::wrapping($frozenClock);
|
||||||
|
|
||||||
|
printf("For %s, the minute clock returns %s\n",
|
||||||
|
$frozenClock->now()->format('H:i:s'),
|
||||||
|
$clock->now()->format('H:i:s')
|
||||||
|
);
|
||||||
|
|
||||||
|
$frozenClock->setTo($frozenClock->now()->modify('+10 seconds')); // 01:23:55
|
||||||
|
|
||||||
|
printf("For %s, the minute clock still returns %s\n",
|
||||||
|
$frozenClock->now()->format('H:i:s'),
|
||||||
|
$clock->now()->format('H:i:s')
|
||||||
|
);
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require __DIR__.'/../vendor/autoload.php';
|
||||||
|
|
||||||
|
use Beste\Clock\SystemClock;
|
||||||
|
|
||||||
|
$clock = SystemClock::create();
|
||||||
|
|
||||||
|
printf("On your system, the current date and time is %s\n", $clock->now()->format('Y-m-d H:i:s T (P)'));
|
||||||
|
|
||||||
|
date_default_timezone_set('America/Los_Angeles');
|
||||||
|
|
||||||
|
printf("Now it's %s\n", $clock->now()->format('Y-m-d H:i:s T (P)'));
|
||||||
|
|
||||||
|
date_default_timezone_set('Europe/Berlin');
|
||||||
|
|
||||||
|
printf("Now it's %s\n", $clock->now()->format('Y-m-d H:i:s T (P)'));
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require __DIR__.'/../vendor/autoload.php';
|
||||||
|
|
||||||
|
use Beste\Clock\UTCClock;
|
||||||
|
|
||||||
|
$clock = UTCClock::create();
|
||||||
|
|
||||||
|
$anotherTimeZone = 'Africa/Casablanca';
|
||||||
|
|
||||||
|
date_default_timezone_set($anotherTimeZone);
|
||||||
|
|
||||||
|
printf("The system time zone is %s.\n", $anotherTimeZone);
|
||||||
|
printf("The clock's time zone is %s.\n", $clock->now()->getTimezone()->getName());
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
parameters:
|
||||||
|
level: max
|
||||||
|
|
||||||
|
paths:
|
||||||
|
- src/
|
||||||
|
- tests/
|
||||||
|
|
||||||
|
tmpDir: .build/phpstan/
|
||||||
|
|
||||||
|
bootstrapFiles:
|
||||||
|
- ./src/psr-polyfill.php
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
||||||
|
bootstrap="vendor/autoload.php"
|
||||||
|
cacheResultFile=".build/phpunit/phpunit.cache"
|
||||||
|
beStrictAboutCoversAnnotation="true"
|
||||||
|
colors="true"
|
||||||
|
>
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="Tests">
|
||||||
|
<directory>tests</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
|
||||||
|
<coverage>
|
||||||
|
<include>
|
||||||
|
<directory>src/Clock</directory>
|
||||||
|
</include>
|
||||||
|
</coverage>
|
||||||
|
|
||||||
|
</phpunit>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<psalm
|
||||||
|
xmlns="https://getpsalm.org/schema/config"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
|
||||||
|
cacheDirectory=".build/psalm"
|
||||||
|
errorLevel="1"
|
||||||
|
resolveFromConfigFile="true"
|
||||||
|
>
|
||||||
|
<projectFiles>
|
||||||
|
<directory name="src" />
|
||||||
|
<directory name="tests" />
|
||||||
|
<ignoreFiles>
|
||||||
|
<directory name="vendor" />
|
||||||
|
</ignoreFiles>
|
||||||
|
</projectFiles>
|
||||||
|
|
||||||
|
<plugins>
|
||||||
|
<pluginClass class="Psalm\PhpUnitPlugin\Plugin"/>
|
||||||
|
</plugins>
|
||||||
|
</psalm>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Beste;
|
||||||
|
|
||||||
|
use Psr\Clock\ClockInterface;
|
||||||
|
|
||||||
|
interface Clock extends ClockInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Beste\Clock;
|
||||||
|
|
||||||
|
use Beste\Clock;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use DateTimeZone;
|
||||||
|
use Psr\Clock\ClockInterface;
|
||||||
|
|
||||||
|
final class FrozenClock implements Clock
|
||||||
|
{
|
||||||
|
private DateTimeImmutable $frozenAt;
|
||||||
|
|
||||||
|
private function __construct(DateTimeImmutable $frozenAt)
|
||||||
|
{
|
||||||
|
$this->frozenAt = $frozenAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function at(DateTimeImmutable $time): self
|
||||||
|
{
|
||||||
|
return new self($time);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function withNowFrom(ClockInterface $clock): self
|
||||||
|
{
|
||||||
|
return new self($clock->now());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function fromUTC(): self
|
||||||
|
{
|
||||||
|
return new self(new DateTimeImmutable('now', new DateTimeZone('UTC')));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTo(DateTimeImmutable $time): self
|
||||||
|
{
|
||||||
|
$this->frozenAt = $time;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function now(): DateTimeImmutable
|
||||||
|
{
|
||||||
|
return clone $this->frozenAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Beste\Clock;
|
||||||
|
|
||||||
|
use Beste\Clock;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use DateTimeZone;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
final class LocalizedClock implements Clock
|
||||||
|
{
|
||||||
|
private DateTimeZone $timeZone;
|
||||||
|
|
||||||
|
private function __construct(DateTimeZone $timeZone)
|
||||||
|
{
|
||||||
|
$this->timeZone = $timeZone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param DateTimeZone|string $timeZone
|
||||||
|
*/
|
||||||
|
public static function in($timeZone): self
|
||||||
|
{
|
||||||
|
if (is_string($timeZone)) {
|
||||||
|
try {
|
||||||
|
$timeZone = new DateTimeZone($timeZone);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
throw new InvalidArgumentException($e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new self($timeZone);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function now(): DateTimeImmutable
|
||||||
|
{
|
||||||
|
return new DateTimeImmutable('now', $this->timeZone);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Beste\Clock;
|
||||||
|
|
||||||
|
use Beste\Clock;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use Psr\Clock\ClockInterface;
|
||||||
|
|
||||||
|
final class MinuteClock implements Clock
|
||||||
|
{
|
||||||
|
private ClockInterface $clock;
|
||||||
|
|
||||||
|
private function __construct(ClockInterface $clock)
|
||||||
|
{
|
||||||
|
$this->clock = $clock;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function wrapping(ClockInterface $clock): self
|
||||||
|
{
|
||||||
|
return new self($clock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function now(): DateTimeImmutable
|
||||||
|
{
|
||||||
|
return $this->floor($this->clock->now());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function floor(DateTimeImmutable $now): DateTimeImmutable
|
||||||
|
{
|
||||||
|
return $now->setTime(
|
||||||
|
(int) $now->format('H'),
|
||||||
|
(int) $now->format('i')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Beste\Clock;
|
||||||
|
|
||||||
|
use Beste\Clock;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
|
||||||
|
final class SystemClock implements Clock
|
||||||
|
{
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function create(): self
|
||||||
|
{
|
||||||
|
return new self();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function now(): DateTimeImmutable
|
||||||
|
{
|
||||||
|
return new DateTimeImmutable();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Beste\Clock;
|
||||||
|
|
||||||
|
use Beste\Clock;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use DateTimeZone;
|
||||||
|
|
||||||
|
final class UTCClock implements Clock
|
||||||
|
{
|
||||||
|
private DateTimeZone $timeZone;
|
||||||
|
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
$this->timeZone = new DateTimeZone('UTC');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function create(): self
|
||||||
|
{
|
||||||
|
return new self();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function now(): DateTimeImmutable
|
||||||
|
{
|
||||||
|
return new DateTimeImmutable('now', $this->timeZone);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Psr\Clock;
|
||||||
|
|
||||||
|
if (class_exists('\Psr\Clock\ClockInterface')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
use DateTimeImmutable;
|
||||||
|
|
||||||
|
interface ClockInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns the current time as a DateTimeImmutable Object
|
||||||
|
*/
|
||||||
|
public function now(): DateTimeImmutable;
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Beste\Clock\Tests;
|
||||||
|
|
||||||
|
use Beste\Clock\FrozenClock;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Psr\Clock\ClockInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @coversDefaultClass \Beste\Clock\FrozenClock
|
||||||
|
*/
|
||||||
|
final class FrozenClockTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
* @covers ::__construct
|
||||||
|
* @covers ::at
|
||||||
|
* @covers ::now
|
||||||
|
*/
|
||||||
|
public function itFreezesTime(): void
|
||||||
|
{
|
||||||
|
$now = new DateTimeImmutable('2021-03-21 22:16:00');
|
||||||
|
$clock = FrozenClock::at($now);
|
||||||
|
|
||||||
|
self::assertEquals($now, $clock->now());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*
|
||||||
|
* @uses \Beste\Clock\FrozenClock::__construct
|
||||||
|
* @uses \Beste\Clock\FrozenClock::fromUTC()
|
||||||
|
*
|
||||||
|
* @covers ::now()
|
||||||
|
*/
|
||||||
|
public function itReturnsAnEqualNowButNotTheSame(): void
|
||||||
|
{
|
||||||
|
$clock = FrozenClock::fromUTC();
|
||||||
|
|
||||||
|
$first = $clock->now();
|
||||||
|
$second = $clock->now();
|
||||||
|
|
||||||
|
self::assertEquals($first, $second);
|
||||||
|
self::assertNotSame($first, $second);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*
|
||||||
|
* @uses \Beste\Clock\FrozenClock::__construct
|
||||||
|
* @uses \Beste\Clock\FrozenClock::now
|
||||||
|
*
|
||||||
|
* @covers ::withNowFrom
|
||||||
|
*/
|
||||||
|
public function itFreezesTimeFromAnotherClock(): void
|
||||||
|
{
|
||||||
|
$now = new DateTimeImmutable('now');
|
||||||
|
|
||||||
|
$clock = $this->createMock(ClockInterface::class);
|
||||||
|
$clock
|
||||||
|
->expects(self::once())
|
||||||
|
->method('now')
|
||||||
|
->willReturn($now);
|
||||||
|
|
||||||
|
$frozenClock = FrozenClock::withNowFrom($clock);
|
||||||
|
|
||||||
|
self::assertEquals($now, $frozenClock->now());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*
|
||||||
|
* @uses \Beste\Clock\FrozenClock::__construct
|
||||||
|
* @uses \Beste\Clock\FrozenClock::now
|
||||||
|
*
|
||||||
|
* @covers ::fromUTC
|
||||||
|
*/
|
||||||
|
public function itFreezesTheCurrentUTCTime(): void
|
||||||
|
{
|
||||||
|
$clock = FrozenClock::fromUTC();
|
||||||
|
|
||||||
|
self::assertSame('UTC', $clock->now()->getTimezone()->getName());
|
||||||
|
self::assertEquals($clock->now(), $clock->now());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*
|
||||||
|
* @uses \Beste\Clock\FrozenClock::__construct
|
||||||
|
* @uses \Beste\Clock\FrozenClock::at
|
||||||
|
* @uses \Beste\Clock\FrozenClock::now
|
||||||
|
*
|
||||||
|
* @covers ::setTo
|
||||||
|
*/
|
||||||
|
public function itCanBeSet(): void
|
||||||
|
{
|
||||||
|
$now = new DateTimeImmutable('2021-03-21 18:18:18');
|
||||||
|
$then = new DateTimeImmutable('2021-03-21 19:19:19');
|
||||||
|
|
||||||
|
$clock = FrozenClock::at($now);
|
||||||
|
$clock->setTo($then);
|
||||||
|
|
||||||
|
self::assertEquals($then, $clock->now());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Beste\Clock\Tests;
|
||||||
|
|
||||||
|
use Beste\Clock\LocalizedClock;
|
||||||
|
use DateTimeZone;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @coversDefaultClass \Beste\Clock\LocalizedClock
|
||||||
|
*/
|
||||||
|
final class LocalizedClockTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
* @covers ::in
|
||||||
|
*/
|
||||||
|
public function itRejectsAnInvalidTimeZone(): void
|
||||||
|
{
|
||||||
|
$this->expectException(InvalidArgumentException::class);
|
||||||
|
|
||||||
|
LocalizedClock::in('invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
* @covers ::__construct
|
||||||
|
* @covers ::in
|
||||||
|
* @covers ::now
|
||||||
|
*/
|
||||||
|
public function itUsesTheGivenTimeZone(): void
|
||||||
|
{
|
||||||
|
$timeZone = new DateTimeZone('Asia/Bangkok');
|
||||||
|
$clock = LocalizedClock::in($timeZone);
|
||||||
|
$now = $clock->now();
|
||||||
|
|
||||||
|
self::assertSame($timeZone->getName(), $now->getTimezone()->getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
* @covers ::__construct
|
||||||
|
* @covers ::in
|
||||||
|
* @covers ::now
|
||||||
|
*/
|
||||||
|
public function itAcceptsTheTimeZoneAsAString(): void
|
||||||
|
{
|
||||||
|
$timeZone = 'Pacific/Guam';
|
||||||
|
$clock = LocalizedClock::in($timeZone);
|
||||||
|
$now = $clock->now();
|
||||||
|
|
||||||
|
self::assertSame($timeZone, $now->getTimezone()->getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Beste\Clock\Tests;
|
||||||
|
|
||||||
|
use Beste\Clock\MinuteClock;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Psr\Clock\ClockInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @coversDefaultClass \Beste\Clock\MinuteClock
|
||||||
|
*/
|
||||||
|
final class MinuteClockTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*
|
||||||
|
* @covers ::__construct
|
||||||
|
* @covers ::wrapping
|
||||||
|
* @covers ::now
|
||||||
|
* @covers ::floor
|
||||||
|
*
|
||||||
|
* @uses \Beste\Clock\FrozenClock
|
||||||
|
*
|
||||||
|
* @dataProvider dateTimeStringProvider
|
||||||
|
*/
|
||||||
|
public function itDiscardsEverythingSmallerThanMinutes(string $format, string $input, string $expected): void
|
||||||
|
{
|
||||||
|
$mockClock = $this->createMock(ClockInterface::class);
|
||||||
|
$mockClock->method('now')->willReturn(new DateTimeImmutable($input));
|
||||||
|
|
||||||
|
// Just to be sure that the test input is valid ^^
|
||||||
|
self::assertSame($input, $mockClock->now()->format($format));
|
||||||
|
|
||||||
|
$clock = MinuteClock::wrapping($mockClock);
|
||||||
|
|
||||||
|
self::assertSame($expected, $clock->now()->format($format));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string[]>
|
||||||
|
*/
|
||||||
|
public function dateTimeStringProvider(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'microseconds' => [
|
||||||
|
'Y-m-d H:i:s.u',
|
||||||
|
'2021-03-24 01:23:45.123456',
|
||||||
|
'2021-03-24 01:23:00.000000',
|
||||||
|
],
|
||||||
|
'milliseconds' => [
|
||||||
|
'Y-m-d H:i:s.v',
|
||||||
|
'2021-03-24 01:23:45.123',
|
||||||
|
'2021-03-24 01:23:00.000',
|
||||||
|
],
|
||||||
|
'seconds' => [
|
||||||
|
'Y-m-d H:i:s',
|
||||||
|
'2021-03-24 01:23:45',
|
||||||
|
'2021-03-24 01:23:00',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Beste\Clock\Tests;
|
||||||
|
|
||||||
|
use Beste\Clock\SystemClock;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @coversDefaultClass \Beste\Clock\SystemClock
|
||||||
|
*/
|
||||||
|
final class SystemClockTest extends TestCase
|
||||||
|
{
|
||||||
|
private string $defaultTimeZone;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
$this->defaultTimeZone = date_default_timezone_get();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function tearDown(): void
|
||||||
|
{
|
||||||
|
date_default_timezone_set($this->defaultTimeZone);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*
|
||||||
|
* @covers ::__construct
|
||||||
|
* @covers ::create
|
||||||
|
* @covers ::now
|
||||||
|
*/
|
||||||
|
public function itUsesTheSystemTimeZone(): void
|
||||||
|
{
|
||||||
|
$timeZone = 'Europe/Berlin';
|
||||||
|
self::assertNotSame($timeZone, $this->defaultTimeZone);
|
||||||
|
|
||||||
|
date_default_timezone_set($timeZone);
|
||||||
|
|
||||||
|
$clock = SystemClock::create();
|
||||||
|
$now = $clock->now();
|
||||||
|
|
||||||
|
self::assertSame($timeZone, $now->getTimezone()->getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Beste\Clock\Tests;
|
||||||
|
|
||||||
|
use Beste\Clock\UTCClock;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @coversDefaultClass \Beste\Clock\UTCClock
|
||||||
|
*/
|
||||||
|
final class UTCClockTest extends TestCase
|
||||||
|
{
|
||||||
|
private string $defaultTimeZone;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
$this->defaultTimeZone = date_default_timezone_get();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function tearDown(): void
|
||||||
|
{
|
||||||
|
date_default_timezone_set($this->defaultTimeZone);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*
|
||||||
|
* @covers ::__construct
|
||||||
|
* @covers ::create
|
||||||
|
* @covers ::now
|
||||||
|
*/
|
||||||
|
public function itUsesUTCRegardlessOfTheSystemTimeZone(): void
|
||||||
|
{
|
||||||
|
date_default_timezone_set('Europe/Berlin');
|
||||||
|
|
||||||
|
$clock = UTCClock::create();
|
||||||
|
$now = $clock->now();
|
||||||
|
|
||||||
|
self::assertSame('UTC', $now->getTimezone()->getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user