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