2020 */
2121class TimeInterval extends \DateInterval implements \Stringable
2222{
23+ /*******************
24+ * Public properties
25+ *******************/
26+
27+ /**
28+ * @var int
29+ *
30+ * Number of years.
31+ */
32+ public int $ y ;
33+
34+ /**
35+ * @var int
36+ *
37+ * Number of months.
38+ */
39+ public int $ m ;
40+
41+ /**
42+ * @var int
43+ *
44+ * Number of days.
45+ */
46+ public int $ d ;
47+
48+ /**
49+ * @var int
50+ *
51+ * Number of hours.
52+ */
53+ public int $ h ;
54+
55+ /**
56+ * @var int
57+ *
58+ * Number of minutes.
59+ */
60+ public int $ i ;
61+
62+ /**
63+ * @var int
64+ *
65+ * Number of seconds.
66+ */
67+ public int $ s ;
68+
69+ /**
70+ * @var float
71+ *
72+ * Number of microseconds, as a fraction of a second.
73+ */
74+ public float $ f ;
75+
76+ /**
77+ * @var int
78+ *
79+ * 1 if the interval represents a negative time period and 0 otherwise.
80+ */
81+ public int $ invert ;
82+
83+ /**
84+ * @var mixed
85+ *
86+ * If the object was created by Time::diff(), then this is the total number
87+ * of full days between the start and end dates. Otherwise, false.
88+ */
89+ public mixed $ days ;
90+
91+ /**
92+ * @var bool
93+ *
94+ * Whether the object was created by TimeInterval::createFromDateString().
95+ */
96+ public bool $ from_string ;
97+
98+ /**
99+ * @var string
100+ *
101+ * The string used as argument to TimeInterval::createFromDateString().
102+ */
103+ public string $ date_string ;
104+
23105 /****************
24106 * Public methods
25107 ****************/
@@ -128,36 +210,28 @@ public function __construct(string $duration)
128210 $ can_be_fractional = false ;
129211 }
130212
131- if (!isset ($ frac ['prop ' ])) {
132- // If we have no fractional values, construction is easy.
133- parent ::__construct ($ duration );
134- } else {
135- // Rebuild $duration without the fractional value.
136- $ duration = 'P ' ;
137-
138- foreach (array_reverse ($ props ) as $ prop => $ info ) {
139- if ($ prop === 'h ' ) {
140- $ duration .= 'T ' ;
141- }
142-
143- if (!empty ($ matches [$ prop ])) {
144- $ duration .= $ matches [$ prop ] . $ info ['unit ' ];
145- }
146- }
147-
148- // Construct.
149- parent ::__construct (rtrim ($ duration , 'PT ' ));
150-
151- // Finally, set the fractional value.
213+ // Add the fractional value where appropriate.
214+ if (isset ($ frac ['prop ' ])) {
152215 $ this ->{$ frac ['prop ' ]} += $ frac ['value ' ];
153216 }
217+
218+ // Set our properties.
219+ $ this ->y = $ matches ['y ' ] ?? 0 ;
220+ $ this ->m = $ matches ['m ' ] ?? 0 ;
221+ $ this ->d = $ matches ['d ' ] ?? 0 ;
222+ $ this ->h = $ matches ['h ' ] ?? 0 ;
223+ $ this ->i = $ matches ['i ' ] ?? 0 ;
224+ $ this ->s = $ matches ['s ' ] ?? 0 ;
225+ $ this ->f = $ matches ['f ' ] ?? 0.0 ;
226+ $ this ->days = false ;
227+ $ this ->from_string = false ;
154228 }
155229
156230 /**
157- * Formats the object as a string so it can be reconstructed later.
231+ * Formats the interval as a string so it can be reconstructed later.
158232 *
159233 * @return string A ISO 8601 duration string suitable for reconstructing
160- * this object .
234+ * this interval .
161235 */
162236 public function __toString (): string
163237 {
@@ -187,10 +261,11 @@ public function __toString(): string
187261 }
188262
189263 /**
190- * Formats the object as a string that can be parsed by strtotime().
264+ * Formats this interval as a string that can be parsed by
265+ * TimeInterval::createFromDateString().
191266 *
192- * @return string A strtotime parsable string suitable for reconstructing
193- * this object .
267+ * @return string A parsable string suitable for reconstructing
268+ * this interval .
194269 */
195270 public function toParsable (): string
196271 {
@@ -252,6 +327,37 @@ public function toSeconds(\DateTimeInterface $when): int|float
252327 return ($ later ->format ($ fmt ) - $ when ->format ($ fmt ));
253328 }
254329
330+ /**
331+ * Formats the interval using an arbitrary format string.
332+ *
333+ * @param string $format The format string. Accepts all the same format
334+ * specifiers that \DateInterval::format() accepts.
335+ * @return string The formatted interval.
336+ */
337+ public function format (string $ format ): string
338+ {
339+ return strtr ($ format , [
340+ '%Y ' => sprintf ('%02d ' , $ this ->y ),
341+ '%y ' => sprintf ('%01d ' , $ this ->y ),
342+ '%M ' => sprintf ('%02d ' , $ this ->m ),
343+ '%m ' => sprintf ('%01d ' , $ this ->m ),
344+ '%D ' => sprintf ('%02d ' , $ this ->d ),
345+ '%d ' => sprintf ('%01d ' , $ this ->d ),
346+ '%a ' => is_int ($ this ->days ) ? sprintf ('%01d ' , $ this ->days ) : '(unknown) ' ,
347+ '%H ' => sprintf ('%02d ' , $ this ->h ),
348+ '%h ' => sprintf ('%01d ' , $ this ->h ),
349+ '%I ' => sprintf ('%02d ' , $ this ->i ),
350+ '%i ' => sprintf ('%01d ' , $ this ->i ),
351+ '%S ' => sprintf ('%02d ' , $ this ->s ),
352+ '%s ' => sprintf ('%01d ' , $ this ->s ),
353+ '%F ' => substr (sprintf ('%06f ' , $ this ->f ), 2 ),
354+ '%f ' => ltrim (sprintf ('%06f ' , $ this ->f ), '0. ' ),
355+ '%R ' => $ this ->invert ? '- ' : '+ ' ,
356+ '%r ' => $ this ->invert ? '- ' : '' ,
357+ '%% ' => '% ' ,
358+ ]);
359+ }
360+
255361 /***********************
256362 * Public static methods
257363 ***********************/
@@ -266,12 +372,45 @@ public static function createFromDateInterval(\DateInterval $object): static
266372 {
267373 $ new = new TimeInterval ('P0D ' );
268374
269- foreach ([ ' y ' , ' m ' , ' d ' , ' h ' , ' i ' , ' s ' , ' f ' , ' invert ' ] as $ prop ) {
270- $ new ->{$ prop } = $ object ->{ $ prop } ;
375+ foreach (get_object_vars ( $ object ) as $ prop => $ value ) {
376+ $ new ->{$ prop } = $ value ;
271377 }
272378
273379 return $ new ;
274380 }
381+
382+ /**
383+ * Creates a TimeInterval from the relative parts of a date string.
384+ *
385+ * Because of the very quirky way that \DateInterval handles its properties,
386+ * it is not possible for SMF\TimeInterval to implement the $from_string and
387+ * $date_string properties the way its parent class does. As a result, the
388+ * inherited $from_string property will always be false and the inherited
389+ * $date_string property will never be set. Instead, TimeInterval instances
390+ * created by this method will have the y, m, d, h, i, s, f, and invert
391+ * properties, just like instances created by the constructor. One could
392+ * argue that this is an improvement.
393+ *
394+ * @param string $datetime A date with relative parts.
395+ * @return TimeInterval A TimeInterval object.
396+ */
397+ public static function createFromDateString (string $ datetime ): static
398+ {
399+ $ object = parent ::createFromDateString ($ datetime );
400+
401+ $ new = self ::createFromDateInterval ($ object );
402+
403+ $ new ->y = (int ) $ object ->format ('%y ' );
404+ $ new ->m = (int ) $ object ->format ('%m ' );
405+ $ new ->d = abs ((int ) $ object ->format ('%d ' ));
406+ $ new ->h = (int ) $ object ->format ('%h ' );
407+ $ new ->i = (int ) $ object ->format ('%i ' );
408+ $ new ->s = (int ) $ object ->format ('%s ' );
409+ $ new ->f = (float ) ('0. ' . $ object ->format ('%F ' ));
410+ $ new ->invert = (int ) ($ object ->format ('%d ' ) < 0 );
411+
412+ return $ new ;
413+ }
275414}
276415
277416?>
0 commit comments