Skip to content

Commit fb99895

Browse files
[Clock] A new component to decouple applications from the system clock
1 parent 2894680 commit fb99895

15 files changed

+606
-0
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"symfony/asset": "self.version",
5858
"symfony/browser-kit": "self.version",
5959
"symfony/cache": "self.version",
60+
"symfony/clock": "self.version",
6061
"symfony/config": "self.version",
6162
"symfony/console": "self.version",
6263
"symfony/css-selector": "self.version",
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/Tests export-ignore
2+
/phpunit.xml.dist export-ignore
3+
/.gitattributes export-ignore
4+
/.gitignore export-ignore
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
vendor/
2+
composer.lock
3+
phpunit.xml
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
CHANGELOG
2+
=========
3+
4+
6.2
5+
---
6+
7+
* Add the component
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Clock;
13+
14+
/**
15+
* @author Nicolas Grekas <[email protected]>
16+
*/
17+
class HrClock implements TimeInterface
18+
{
19+
private \DateTimeZone $timezone;
20+
21+
public function __construct(\DateTimeZone|string $timezone = null)
22+
{
23+
if (\is_string($timezone ??= date_default_timezone_get())) {
24+
$this->timezone = new \DateTimeZone($timezone);
25+
} else {
26+
$this->timezone = $timezone;
27+
}
28+
}
29+
30+
/**
31+
* {@inheritdoc}
32+
*/
33+
public function now(): \DateTimeImmutable
34+
{
35+
$hrtime = hrtime();
36+
$now = '@'.$hrtime[0].'.'.substr(str_pad($hrtime[1], 9, 0, \STR_PAD_LEFT), 0, -3);
37+
38+
return (new \DateTimeImmutable($now, $this->timezone))->setTimezone($this->timezone);
39+
}
40+
41+
/**
42+
* {@inheritdoc}
43+
*/
44+
public function timeArray(): array
45+
{
46+
$now = hrtime();
47+
48+
return [$now[0], (float) ($now[1] / 1E3)];
49+
}
50+
51+
/**
52+
* {@inheritdoc}
53+
*/
54+
public function timeFloat(): float
55+
{
56+
return hrtime(true) / 1E9;
57+
}
58+
59+
public function sleep(float $seconds): void
60+
{
61+
if (0 < $s = (int) $seconds) {
62+
sleep($s);
63+
}
64+
65+
if (0 < $us = $seconds - $s) {
66+
usleep($us * 1E6);
67+
}
68+
}
69+
}

src/Symfony/Component/Clock/LICENSE

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2022 Fabien Potencier
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is furnished
8+
to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all
11+
copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
THE SOFTWARE.
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Clock;
13+
14+
/**
15+
* @author Nicolas Grekas <[email protected]>
16+
*/
17+
class MockClock implements TimeInterface
18+
{
19+
private \DateTimeImmutable $now;
20+
21+
public function __construct(\DateTimeImmutable|\DateTimeZone|string $now = null)
22+
{
23+
if (\is_string($now ??= 'UTC')) {
24+
try {
25+
$now = new \DateTimeZone($now);
26+
} catch (\Exception) {
27+
$now = new \DateTimeImmutable($now);
28+
}
29+
}
30+
31+
$this->now = $now instanceof \DateTimeImmutable ? $now : new \DateTimeImmutable('now', $now);
32+
}
33+
34+
/**
35+
* {@inheritdoc}
36+
*/
37+
public function now(): \DateTimeImmutable
38+
{
39+
return clone $this->now;
40+
}
41+
42+
/**
43+
* {@inheritdoc}
44+
*/
45+
public function timeArray(): array
46+
{
47+
$now = explode('.', $this->now->format('U.u'));
48+
49+
return [(int) $now[0], (float) $now[1]];
50+
}
51+
52+
/**
53+
* {@inheritdoc}
54+
*/
55+
public function timeFloat(): float
56+
{
57+
return (float) $this->now->format('U.u');
58+
}
59+
60+
/**
61+
* {@inheritdoc}
62+
*/
63+
public function sleep(float $seconds): void
64+
{
65+
$now = explode('.', $this->now->format('U.u'));
66+
67+
if (0 < $s = (int) $seconds) {
68+
$now[0] += $s;
69+
}
70+
71+
if (0 < ($us = $seconds - $s) && 1E6 <= $now[1] += $us * 1E6) {
72+
++$now[0];
73+
$now[1] -= 1E6;
74+
}
75+
76+
$datetime = '@'.$now[0].'.'.str_pad($now[1], 6, 0, \STR_PAD_LEFT);
77+
$timezone = $this->now->getTimezone();
78+
79+
$this->now = (new \DateTimeImmutable($datetime, $timezone))->setTimezone($timezone);
80+
}
81+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Clock;
13+
14+
/**
15+
* @author Nicolas Grekas <[email protected]>
16+
*/
17+
class NativeClock implements TimeInterface
18+
{
19+
private \DateTimeZone $timezone;
20+
21+
public function __construct(\DateTimeZone|string $timezone = null)
22+
{
23+
if (\is_string($timezone ??= date_default_timezone_get())) {
24+
$this->timezone = new \DateTimeZone($timezone);
25+
} else {
26+
$this->timezone = $timezone;
27+
}
28+
}
29+
30+
/**
31+
* {@inheritdoc}
32+
*/
33+
public function now(): \DateTimeImmutable
34+
{
35+
return new \DateTimeImmutable('now', $this->timezone);
36+
}
37+
38+
/**
39+
* {@inheritdoc}
40+
*/
41+
public function timeArray(): array
42+
{
43+
$now = gettimeofday();
44+
45+
return [$now['sec'], (float) $now['usec']];
46+
}
47+
48+
/**
49+
* {@inheritdoc}
50+
*/
51+
public function timeFloat(): float
52+
{
53+
return microtime(true);
54+
}
55+
56+
public function sleep(float $seconds): void
57+
{
58+
if (0 < $s = (int) $seconds) {
59+
sleep($s);
60+
}
61+
62+
if (0 < $us = $seconds - $s) {
63+
usleep($us * 1E6);
64+
}
65+
}
66+
}

src/Symfony/Component/Clock/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Clock Component
2+
===============
3+
4+
Symfony Clock enables decoupling applications from the system clock.
5+
6+
Resources
7+
---------
8+
9+
* [Documentation](https://symfony.com/doc/current/clock.html)
10+
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
11+
* [Report issues](https://github.com/symfony/symfony/issues) and
12+
[send Pull Requests](https://github.com/symfony/symfony/pulls)
13+
in the [main Symfony repository](https://github.com/symfony/symfony)
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Clock\Tests;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Clock\HrClock;
16+
17+
class HrClockTest extends TestCase
18+
{
19+
public function testNow()
20+
{
21+
$clock = new HrClock();
22+
$before = new \DateTimeImmutable(sprintf('@%.6f', hrtime(true) / 1E9));
23+
usleep(10);
24+
$now = $clock->now();
25+
usleep(10);
26+
$after = new \DateTimeImmutable(sprintf('@%.6f', hrtime(true) / 1E9));
27+
28+
$this->assertGreaterThan($before, $now);
29+
$this->assertLessThan($after, $now);
30+
}
31+
32+
public function testConstruct()
33+
{
34+
$clock = new HrClock('UTC');
35+
$this->assertSame('UTC', $clock->now()->getTimezone()->getName());
36+
37+
$tz = date_default_timezone_get();
38+
$clock = new HrClock();
39+
$this->assertSame($tz, $clock->now()->getTimezone()->getName());
40+
41+
$clock = new HrClock(new \DateTimeZone($tz));
42+
$this->assertSame($tz, $clock->now()->getTimezone()->getName());
43+
}
44+
45+
public function testTimeArray()
46+
{
47+
$clock = new HrClock();
48+
$before = hrtime(true) / 1E9;
49+
usleep(10);
50+
$now = $clock->timeArray();
51+
usleep(10);
52+
$after = hrtime(true) / 1E9;
53+
54+
$this->assertSame([0, 1], array_keys($now));
55+
$this->assertGreaterThan($before, $now[0] + $now[1] / 1E6);
56+
$this->assertLessThan($after, $now[0] + $now[1] / 1E6);
57+
}
58+
59+
public function testTimeFloat()
60+
{
61+
$clock = new HrClock();
62+
$before = hrtime(true) / 1E9;
63+
usleep(10);
64+
$now = $clock->timeFloat();
65+
usleep(10);
66+
$after = hrtime(true) / 1E9;
67+
68+
$this->assertGreaterThan($before, $now);
69+
$this->assertLessThan($after, $now);
70+
}
71+
72+
public function testSleep()
73+
{
74+
$clock = new HrClock();
75+
$tz = $clock->now()->getTimezone()->getName();
76+
77+
$before = hrtime(true) / 1E9;
78+
$clock->sleep(1.5);
79+
$now = $clock->timeFloat();
80+
usleep(10);
81+
$after = hrtime(true) / 1E9;
82+
83+
$this->assertGreaterThan($before + 1.5, $now);
84+
$this->assertLessThan($after, $now);
85+
$this->assertLessThan(1.9, $now - $before);
86+
$this->assertSame($tz, $clock->now()->getTimezone()->getName());
87+
}
88+
}

0 commit comments

Comments
 (0)