/
TestClock.php
270 lines (237 loc) · 6.85 KB
/
TestClock.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
<?php
namespace Icecave\Chrono\Clock;
use Icecave\Chrono\Date;
use Icecave\Chrono\DateTime;
use Icecave\Chrono\Interval\Month;
use Icecave\Chrono\Interval\Year;
use Icecave\Chrono\TimeOfDay;
use Icecave\Chrono\TimePointInterface;
use Icecave\Chrono\Timer\Timer;
use Icecave\Chrono\Timer\TimerInterface;
use Icecave\Chrono\TimeSpan\TimeSpanInterface;
use Icecave\Chrono\TimeZone;
use Icecave\Isolator\Isolator;
/**
* A manually controlled clock designed for time-sensitive unit/functional
* testing.
*/
class TestClock implements TestClockInterface
{
/**
* Construct a new test clock.
*
* @param TimePointInterface|null $localDateTime The current local date/time, or null to use the Unix epoch.
* @param Isolator|null $isolator The isolator to use.
*/
public function __construct(
TimePointInterface $localDateTime = null,
Isolator $isolator = null
) {
if (null === $localDateTime) {
$localDateTime = DateTime::fromUnixTime(0);
}
$this->isolator = Isolator::get($isolator);
$this->setLocalDateTime($localDateTime);
$this->resume();
}
/**
* Add a time span to the current local date/time.
*
* @param TimeSpanInterface|int $timeSpan A time span instance, or an integer representing seconds.
*/
public function addTime($timeSpan)
{
$this->setLocalDateTime($this->localDateTime()->add($timeSpan));
}
/**
* Subtract a time span from the current local date/time.
*
* @param TimeSpanInterface|int $timeSpan A time span instance, or an integer representing seconds.
*/
public function subtractTime($timeSpan)
{
$this->setLocalDateTime($this->localDateTime()->subtract($timeSpan));
}
/**
* Set the current local date/time.
*
* @param TimePointInterface $localDateTime The current local date/time.
*/
public function setLocalDateTime(TimePointInterface $localDateTime)
{
$this->localDateTime = $localDateTime;
}
/**
* @return TimeOfDay The current local time.
*/
public function localTime()
{
return $this->localDateTime()->time();
}
/**
* @return DateTime The current local date/time.
*/
public function localDateTime()
{
return $this->localDateTime;
}
/**
* @return Date The current local date.
*/
public function localDate()
{
return $this->localDateTime()->date();
}
/**
* @return Month The current local month.
*/
public function localMonth()
{
return new Month($this->localYear(), $this->localDateTime()->month());
}
/**
* @return Year The current local year.
*/
public function localYear()
{
return new Year($this->localDate()->year());
}
/**
* @return TimeOfDay The current UTC time.
*/
public function utcTime()
{
return $this->utcDateTime()->time();
}
/**
* @return DateTime The current UTC date/time.
*/
public function utcDateTime()
{
return $this->localDateTime()->toTimeZone(new TimeZone());
}
/**
* @return Date The current UTC date.
*/
public function utcDate()
{
return $this->utcDateTime()->date();
}
/**
* @return Month The current UTC month.
*/
public function utcMonth()
{
return new Month($this->utcYear(), $this->utcDateTime()->month());
}
/**
* @return Year The current UTC year.
*/
public function utcYear()
{
return new Year($this->utcDate()->year());
}
/**
* @return TimeZone The local timezone.
*/
public function timeZone()
{
return $this->localDateTime()->timeZone();
}
/**
* @return int The current time as a unix timestamp.
*/
public function unixTime()
{
return $this->localDateTime()->unixTime();
}
/**
* @return float The current time as a unix timestamp, including partial seconds.
*/
public function unixTimeAsFloat()
{
return floatval($this->unixTime());
}
/**
* Sleep for the given time span.
*
* @param TimeSpanInterface|int $timeSpan A time span instance, or an integer representing seconds.
* @param bool $dispatchSignals True to dispatch to signal handlers when sleep is interrupted.
* @param bool $restart True to continue sleeping after interrupt.
*
* @return bool True if the sleep completed, false if the sleep was interrupted.
*/
public function sleep($timeSpan, $dispatchSignals = true, $restart = false)
{
$timePoint = $this->localDateTime()->add($timeSpan);
return $this->sleepUntil($timePoint, $dispatchSignals, $restart);
}
/**
* Sleep until the given time point.
*
* @param TimePointInterface $timePoint The the point to sleep until.
* @param bool $dispatchSignals True to dispatch to signal handlers when sleep is interrupted.
* @param bool $restart True to continue sleeping after interrupt.
*
* @return bool True if the sleep completed, false if the sleep was interrupted.
*/
public function sleepUntil(TimePointInterface $timePoint, $dispatchSignals = true, $restart = false)
{
if (!$this->isolator()->function_exists('pcntl_signal_dispatch')) {
$dispatchSignals = false;
}
$remaining = $timePoint->differenceAsSeconds($this->localDateTime());
$this->addTime($remaining);
if ($dispatchSignals) {
$this->isolator()->pcntl_signal_dispatch();
}
return true;
}
/**
* Create a new timer.
*
* @return TimerInterface
*/
public function createTimer()
{
return new Timer($this);
}
/**
* Suspend the clock at the current time.
*/
public function suspend()
{
$this->isSuspended = true;
}
/**
* Resume the clock from the current real time.
*
* {@see SuspendableClockInterface::resume()} must be called an equal number of times as {@see SuspendableClockInterface::suspend()}
* at which time the clock resumes from the current time (not the time at which it was suspended).
*
* It is not an error to resume an un-suspended clock.
*/
public function resume()
{
$this->isSuspended = false;
}
/**
* @return bool True if the clock is currently suspended; otherwise, false.
*/
public function isSuspended()
{
return $this->isSuspended;
}
/**
* Get the isolator.
*
* @return Isolator The isolator.
*/
public function isolator()
{
return $this->isolator;
}
private $localDateTime;
private $isSuspended;
private $isolator;
}