1
|
|
|
<?php |
|
|
|
|
2
|
|
|
|
3
|
|
|
|
4
|
|
|
namespace Litipk\Jiffy; |
5
|
|
|
|
6
|
|
|
|
7
|
1 |
|
if (!extension_loaded('mongo')) { |
8
|
|
|
trait TsExtension {}; |
9
|
|
|
} else { |
10
|
|
|
trait TsExtension { use MongoAdapter; }; |
|
|
|
|
11
|
|
|
} |
12
|
|
|
|
13
|
|
|
|
14
|
|
|
/** |
15
|
|
|
* Class UniversalTimestamp |
16
|
|
|
* @package Litipk\Jiffy |
17
|
|
|
*/ |
18
|
|
|
class UniversalTimestamp |
|
|
|
|
19
|
|
|
{ |
20
|
|
|
const ISO8601_WITH_MILLISECONDS = '_ISO8601_WITH_MILLIS_'; |
21
|
|
|
const ISO8601_WITH_MILLISECONDS_WITHOUT_TZ = '_ISO8601_WITH_MILLIS_WITHOUT_TZ'; |
22
|
|
|
const ISO8601_WITH_MICROSECONDS = 'Y-m-d\TH:i:s.uO'; |
23
|
|
|
const ISO8601_WITH_MICROSECONDS_WITHOUT_TZ = 'Y-m-d\TH:i:s.u'; |
24
|
|
|
|
25
|
|
|
use TsExtension; |
26
|
|
|
|
27
|
|
|
/** @var int */ |
28
|
|
|
private $millis; |
29
|
|
|
|
30
|
|
|
/** @var int */ |
31
|
|
|
private $micros; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* Constructor. |
35
|
|
|
* |
36
|
|
|
* @param integer $millisSinceEpoch |
37
|
|
|
* @param integer $micros |
38
|
|
|
*/ |
39
|
13 |
|
private function __construct($millisSinceEpoch, $micros = 0) |
40
|
|
|
{ |
41
|
13 |
|
if ($millisSinceEpoch < 0 || $micros < 0) { |
42
|
1 |
|
throw new JiffyException('The number of milliseconds and microseconds must be positive'); |
43
|
|
|
} |
44
|
|
|
|
45
|
12 |
|
$this->millis = $millisSinceEpoch + (int)($micros/1000); |
46
|
12 |
|
$this->micros = $micros % 1000; |
47
|
12 |
|
} |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* @return UniversalTimestamp |
51
|
|
|
*/ |
52
|
7 |
|
public static function now() |
53
|
|
|
{ |
54
|
7 |
|
$ts_parts = explode(' ', microtime()); |
55
|
|
|
|
56
|
7 |
|
return new UniversalTimestamp( |
57
|
7 |
|
(int)floor($ts_parts[0]*1000) + (int)$ts_parts[1]*1000, // Millis |
58
|
7 |
|
((int)round($ts_parts[0]*1000000))%1000 // Micros |
59
|
|
|
); |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* @param \DateTimeInterface $dateTime |
64
|
|
|
* @return UniversalTimestamp |
65
|
|
|
*/ |
66
|
3 |
|
public static function fromDateTimeInterface(\DateTimeInterface $dateTime) |
67
|
|
|
{ |
68
|
3 |
|
$dtU = (int)$dateTime->format('u'); |
69
|
|
|
|
70
|
3 |
|
return new UniversalTimestamp( |
71
|
3 |
|
$dateTime->getTimestamp()*1000 + (int)floor($dtU/1000), |
72
|
3 |
|
$dtU % 1000 |
73
|
|
|
); |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* @param int $secondsSinceEpoch |
78
|
|
|
* @return UniversalTimestamp |
79
|
|
|
*/ |
80
|
1 |
|
public static function fromSecondsTimestamp($secondsSinceEpoch) |
81
|
|
|
{ |
82
|
1 |
|
return new UniversalTimestamp($secondsSinceEpoch*1000); |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* @param int $millisSinceEpoch |
87
|
|
|
* @param int $micros |
88
|
|
|
* @return UniversalTimestamp |
89
|
|
|
*/ |
90
|
5 |
|
public static function fromMillisecondsTimestamp($millisSinceEpoch, $micros = 0) |
91
|
|
|
{ |
92
|
5 |
|
return new UniversalTimestamp($millisSinceEpoch, $micros); |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
/** |
96
|
|
|
* @param mixed $dateObject If it's an integer, then it's understood as milliseconds since epoch |
97
|
|
|
* @return UniversalTimestamp |
98
|
|
|
*/ |
99
|
2 |
|
public static function fromWhatever($dateObject) { |
100
|
2 |
|
if (null === $dateObject) { |
101
|
1 |
|
return static::now(); |
102
|
2 |
|
} elseif (is_int($dateObject)) { |
103
|
1 |
|
return static::fromMillisecondsTimestamp($dateObject); |
104
|
|
|
} elseif ($dateObject instanceof UniversalTimestamp) { |
105
|
|
|
return $dateObject; |
106
|
|
|
} elseif ($dateObject instanceof \DateTimeInterface) { |
107
|
1 |
|
return static::fromDateTimeInterface($dateObject); |
108
|
|
|
} elseif ($dateObject instanceof \MongoDate) { |
109
|
|
|
return static::fromMongoDate($dateObject); |
|
|
|
|
110
|
|
|
} else { |
111
|
1 |
|
throw new JiffyException('The provided value cannot be interpreted as a timestamp'); |
112
|
|
|
} |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* @param UniversalTimestamp $otherTimestamp |
117
|
|
|
* @return boolean |
118
|
|
|
*/ |
119
|
3 |
|
public function isGreaterThan(UniversalTimestamp $otherTimestamp) |
120
|
|
|
{ |
121
|
|
|
return ( |
122
|
3 |
|
$this->millis > $otherTimestamp->millis || |
123
|
3 |
|
$this->millis === $otherTimestamp->millis && $this->micros > $otherTimestamp->micros |
124
|
|
|
); |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
/** |
128
|
|
|
* @param int $seconds |
129
|
|
|
* @return UniversalTimestamp |
130
|
|
|
*/ |
131
|
1 |
|
public function addSeconds($seconds) |
132
|
|
|
{ |
133
|
1 |
|
return new UniversalTimestamp($this->millis + 1000*$seconds, $this->micros); |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
/** |
137
|
|
|
* @param int $millis |
138
|
|
|
* @return UniversalTimestamp |
139
|
|
|
*/ |
140
|
1 |
|
public function addMilliseconds($millis) |
141
|
|
|
{ |
142
|
1 |
|
return new UniversalTimestamp($this->millis + $millis, $this->micros); |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* @return int |
147
|
|
|
*/ |
148
|
8 |
|
public function asSeconds() |
149
|
|
|
{ |
150
|
8 |
|
return (int)floor($this->millis/1000); |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
/** |
154
|
|
|
* @return int |
155
|
|
|
*/ |
156
|
4 |
|
public function asMilliseconds() |
157
|
|
|
{ |
158
|
4 |
|
return $this->millis; |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
/** |
162
|
|
|
* @return int |
163
|
|
|
*/ |
164
|
1 |
|
public function getRemainingMicroseconds() |
165
|
|
|
{ |
166
|
1 |
|
return $this->micros; |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
/** |
170
|
|
|
* @param string|\DateTimeZone $tz |
171
|
|
|
* @return \DateTimeImmutable |
172
|
|
|
*/ |
173
|
2 |
|
public function asDateTimeInterface($tz = 'UTC') |
174
|
|
|
{ |
175
|
2 |
|
$dateTime = new \DateTimeImmutable('@'.((string)$this->asSeconds())); |
176
|
2 |
|
$dateTime = $dateTime->setTimezone(is_string($tz) ? new \DateTimeZone($tz) : $tz); |
177
|
|
|
|
178
|
2 |
|
return new \DateTimeImmutable( |
179
|
2 |
|
$dateTime->format('Y-m-d\TH:i:s').'.'. |
180
|
2 |
|
sprintf("%03d", $this->millis%1000).sprintf("%03d", $this->micros). |
181
|
2 |
|
$dateTime->format('O') |
182
|
|
|
); |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
/** |
186
|
|
|
* @param string $format |
187
|
|
|
* @param string|\DateTimeZone $tz |
188
|
|
|
* @return string |
189
|
|
|
*/ |
190
|
2 |
|
public function asFormattedString($format = self::ISO8601_WITH_MICROSECONDS, $tz = 'UTC') |
191
|
|
|
{ |
192
|
2 |
|
if (self::ISO8601_WITH_MILLISECONDS === $format) { |
193
|
1 |
|
$rParts = preg_split('/\+/', $this->asDateTimeInterface($tz)->format(\DateTime::ISO8601)); |
194
|
1 |
|
return $rParts[0].'.'.((string)$this->millis%1000).'+'.$rParts[1]; |
195
|
2 |
|
} elseif (self::ISO8601_WITH_MILLISECONDS_WITHOUT_TZ === $format) { |
196
|
1 |
|
$rParts = preg_split('/\+/', $this->asDateTimeInterface($tz)->format(\DateTime::ISO8601)); |
197
|
1 |
|
return $rParts[0].'.'.((string)$this->millis%1000); |
198
|
|
|
} else { |
199
|
2 |
|
return $this->asDateTimeInterface($tz)->format($format); |
200
|
|
|
} |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
/** |
204
|
|
|
* @return string |
205
|
|
|
*/ |
206
|
1 |
|
public function __toString() |
207
|
|
|
{ |
208
|
1 |
|
return $this->asFormattedString(); |
209
|
|
|
} |
210
|
|
|
} |
211
|
|
|
|
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.