-
-
Notifications
You must be signed in to change notification settings - Fork 73
/
Copy pathTimestamp.php
292 lines (267 loc) · 10.3 KB
/
Timestamp.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
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
<?php
declare(strict_types=1);
namespace Psl\DateTime;
use Psl\Exception\InvariantViolationException;
use Psl\Locale\Locale;
use Psl\Math;
/**
* Represents a precise point in time, with seconds and nanoseconds since the Unix epoch.
*
* @immutable
*/
final readonly class Timestamp implements TemporalInterface
{
use TemporalConvenienceMethodsTrait;
/**
* @param int $seconds
* @param int<0, 999999999> $nanoseconds
*
* @pure
*/
private function __construct(
private int $seconds,
private int $nanoseconds,
) {}
/**
* Creates a timestamp from seconds and nanoseconds since the epoch.
*
* Normalizes so nanoseconds are within 0-999999999. For instance:
* - `fromRaw(42, -100)` becomes (41, 999999900).
* - `fromRaw(-42, -100)` becomes (-43, 999999900).
* - `fromRaw(42, 1000000100)` becomes (43, 100).
*
* @param int $seconds Seconds since the epoch.
* @param int $nanoseconds Additional nanoseconds to adjust by.
*
* @throws Exception\OverflowException
* @throws Exception\UnderflowException
*
* @pure
*/
public static function fromParts(int $seconds, int $nanoseconds = 0): Timestamp
{
// Check for potential overflow or underflow before doing any operation
if ($seconds === Math\INT64_MAX && $nanoseconds >= NANOSECONDS_PER_SECOND) {
throw new Exception\OverflowException('Adding nanoseconds would cause an overflow.');
}
if ($seconds === Math\INT64_MIN && $nanoseconds <= -NANOSECONDS_PER_SECOND) {
throw new Exception\UnderflowException('Subtracting nanoseconds would cause an underflow.');
}
/** @psalm-suppress MissingThrowsDocblock */
$seconds_adjustment = Math\div($nanoseconds, NANOSECONDS_PER_SECOND);
$adjusted_seconds = $seconds + $seconds_adjustment;
$adjusted_nanoseconds = $nanoseconds % NANOSECONDS_PER_SECOND;
if ($adjusted_nanoseconds < 0) {
--$adjusted_seconds;
$adjusted_nanoseconds += NANOSECONDS_PER_SECOND;
}
return new self($adjusted_seconds, $adjusted_nanoseconds);
}
/**
* Create a high-precision instance representing the current time using the system clock.
*
* @psalm-mutation-free
*/
public static function now(): self
{
[$seconds, $nanoseconds] = Internal\system_time();
/** @psalm-suppress MissingThrowsDocblock */
return self::fromParts($seconds, $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.
*
* @psalm-mutation-free
*/
public static function monotonic(): self
{
[$seconds, $nanoseconds] = Internal\high_resolution_time();
/** @psalm-suppress MissingThrowsDocblock */
return self::fromParts($seconds, $nanoseconds);
}
/**
* Parses a date and time string into an instance of {@see Timestamp} using a specific format pattern, with optional customization for timezone and locale.
*
* This method is specifically designed for cases where a custom format pattern is used to parse the input string.
*
* It allows for precise control over the parsing process by specifying the exact format pattern that matches the input string.
*
* Additionally, the method supports specifying a timezone and locale for parsing, enabling accurate interpretation of locale-specific formats.
*
* Example usage:
*
* ```php
* $raw_string = '2023-03-15 12:00:00';
* $parsed_timestamp = DateTime\Timestamp::parse($raw_string, 'yyyy-MM-dd HH:mm:ss', DateTime\Timezone::Utc, Locale\Locale::English);
* ```
*
* @param string $raw_string The date and time string to parse.
* @param null|FormatPattern|string $pattern The custom format pattern for parsing the date and time. If null, uses a default pattern.
* @param null|Timezone $timezone Optional timezone for parsing. If null, uses the system's default timezone.
* @param null|Locale $locale Optional locale for parsing. If null, uses the system's default locale.
*
* @throws Exception\RuntimeException If the parsing process fails.
*
* @return static Returns an instance of {@see Timestamp} representing the parsed date and time.
*
* @see https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax
* @see TemporalInterface::format()
*
* @psalm-mutation-free
*/
public static function parse(
string $raw_string,
null|FormatPattern|string $pattern = null,
null|Timezone $timezone = null,
null|Locale $locale = null,
): static {
/** @psalm-suppress MissingThrowsDocblock */
return self::fromParts(Internal\parse(
raw_string: $raw_string,
pattern: $pattern,
timezone: $timezone,
locale: $locale,
));
}
/**
* Creates an instance of {@see Timestamp} from a date and time string, formatted according to specified styles for date and time,
* with optional customization for timezone and locale.
*
* This method provides a more abstracted approach to parsing, allowing users to specify styles rather than a custom pattern.
*
* This is particularly useful for parsing strings that follow common date and time formats.
*
* Additionally, the timezone and locale parameters enable accurate parsing of strings in locale-specific formats.
*
* Example usage:
*
* ```php
* $raw_string = "March 15, 2023, 12:00 PM";
*
* $timestamp = DateTime\Timestamp::fromString($raw_string, FormatDateStyle::Long, FormatTimeStyle::Short, DateTime\Timezone::Utc, Locale\Locale::English);
* ```
*
* @param string $raw_string The date and time string to parse.
* @param null|DateStyle $date_style The style for the date portion of the string. If null, a default style is used.
* @param null|TimeStyle $time_style The style for the time portion of the string. If null, a default style is used.
* @param null|Timezone $timezone Optional timezone for parsing. If null, uses the system's default timezone.
* @param null|Locale $locale Optional locale for parsing. If null, uses the system's default locale.
*
* @throws Exception\RuntimeException If the parsing process fails.
*
* @return static Returns an instance of {@see Timestamp} representing the parsed date and time.
*
* @see TemporalInterface::toString()
*
* @psalm-mutation-free
*/
public static function fromString(
string $raw_string,
null|DateStyle $date_style = null,
null|TimeStyle $time_style = null,
null|Timezone $timezone = null,
null|Locale $locale = null,
): static {
/** @psalm-suppress MissingThrowsDocblock */
return self::fromParts(Internal\parse(
raw_string: $raw_string,
date_style: $date_style,
time_style: $time_style,
timezone: $timezone,
locale: $locale,
));
}
/**
* Returns this Timestamp instance itself, as it already represents a timestamp.
*
* @psalm-mutation-free
*/
#[\Override]
public function getTimestamp(): self
{
return $this;
}
/**
* Returns the {@see Timestamp} parts (seconds, nanoseconds).
*
* @return array{int, int<0, 999999999>}
*
* @psalm-mutation-free
*/
public function toParts(): array
{
return [$this->seconds, $this->nanoseconds];
}
/**
* Returns the number of seconds since the Unix epoch represented by this timestamp.
*
* @return int Seconds since the epoch. Can be negative for times before the epoch.
*
* @psalm-mutation-free
*/
public function getSeconds(): int
{
return $this->seconds;
}
/**
* Returns the nanoseconds part of this timestamp.
*
* @return int<0, 999999999> The nanoseconds part, ranging from 0 to 999999999.
*
* @psalm-mutation-free
*/
public function getNanoseconds(): int
{
return $this->nanoseconds;
}
/**
* Adds the specified duration to this timestamp 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.
*
* @psalm-mutation-free
*/
#[\Override]
public function plus(Duration $duration): static
{
[$h, $m, $s, $ns] = $duration->getParts();
$totalSeconds = (SECONDS_PER_MINUTE * $m) + (SECONDS_PER_HOUR * $h) + $s;
$newSeconds = $this->seconds + $totalSeconds;
$newNanoseconds = $this->nanoseconds + $ns;
// No manual normalization required here due to fromRaw handling it
return self::fromParts($newSeconds, $newNanoseconds);
}
/**
* Subtracts the specified duration from this timestamp 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.
*
* @psalm-mutation-free
*/
#[\Override]
public function minus(Duration $duration): static
{
[$h, $m, $s, $ns] = $duration->getParts();
$totalSeconds = (SECONDS_PER_MINUTE * $m) + (SECONDS_PER_HOUR * $h) + $s;
$newSeconds = $this->seconds - $totalSeconds;
$newNanoseconds = $this->nanoseconds - $ns;
// No manual normalization required here due to fromRaw handling it
return self::fromParts($newSeconds, $newNanoseconds);
}
#[\Override]
public function jsonSerialize(): array
{
return [
'seconds' => $this->seconds,
'nanoseconds' => $this->nanoseconds,
];
}
}