Skip to content

Commit d5fb930

Browse files
authored
Release/1.3.0 (#4)
1 parent 745c3d7 commit d5fb930

9 files changed

Lines changed: 1690 additions & 21 deletions

File tree

README.md

Lines changed: 224 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
* [Installation](#installation)
77
* [How to use](#how-to-use)
88
* [Instant](#instant)
9+
* [Duration](#duration)
10+
* [Period](#period)
911
* [Timezone](#timezone)
1012
* [Timezones](#timezones)
1113
* [License](#license)
@@ -15,7 +17,8 @@
1517

1618
## Overview
1719

18-
Value Object representing time in an immutable and strict way, focused on safe parsing, formatting and normalization.
20+
Value Objects representing time in an immutable and strict way, focused on safe parsing, formatting, normalization and
21+
temporal arithmetic.
1922

2023
<div id='installation'></div>
2124

@@ -29,8 +32,8 @@ composer require tiny-blocks/time
2932

3033
## How to use
3134

32-
The library provides immutable Value Objects for representing points in time and IANA timezones. All instants are
33-
normalized to UTC internally.
35+
The library provides immutable Value Objects for representing points in time, quantities of time and time intervals.
36+
All instants are normalized to UTC internally.
3437

3538
### Instant
3639

@@ -45,9 +48,9 @@ use TinyBlocks\Time\Instant;
4548

4649
$instant = Instant::now();
4750

48-
$instant->toIso8601(); # 2026-02-17T10:30:00+00:00 (current UTC time)
49-
$instant->toUnixSeconds(); # 1771324200 (current Unix timestamp)
50-
$instant->toDateTimeImmutable(); # DateTimeImmutable (UTC, with microseconds)
51+
$instant->toIso8601(); # 2026-02-17T10:30:00+00:00
52+
$instant->toUnixSeconds(); # 1771324200
53+
$instant->toDateTimeImmutable(); # DateTimeImmutable (UTC, with microseconds)
5154
```
5255

5356
#### Creating from a string
@@ -59,9 +62,8 @@ use TinyBlocks\Time\Instant;
5962

6063
$instant = Instant::fromString(value: '2026-02-17T13:30:00-03:00');
6164

62-
$instant->toIso8601(); # 2026-02-17T16:30:00+00:00
63-
$instant->toUnixSeconds(); # 1771345800
64-
$instant->toDateTimeImmutable(); # DateTimeImmutable (UTC)
65+
$instant->toIso8601(); # 2026-02-17T16:30:00+00:00
66+
$instant->toUnixSeconds(); # 1771345800
6567
```
6668

6769
#### Creating from a database timestamp
@@ -74,8 +76,8 @@ use TinyBlocks\Time\Instant;
7476

7577
$instant = Instant::fromString(value: '2026-02-17 08:27:21.106011');
7678

77-
$instant->toIso8601(); # 2026-02-17T08:27:21+00:00
78-
$instant->toDateTimeImmutable()->format('Y-m-d H:i:s.u'); # 2026-02-17 08:27:21.106011
79+
$instant->toIso8601(); # 2026-02-17T08:27:21+00:00
80+
$instant->toDateTimeImmutable()->format('Y-m-d H:i:s.u'); # 2026-02-17 08:27:21.106011
7981
```
8082

8183
Also supports timestamps without fractional seconds:
@@ -101,30 +103,231 @@ $instant->toIso8601(); # 1970-01-01T00:00:00+00:00
101103
$instant->toUnixSeconds(); # 0
102104
```
103105

104-
#### Formatting as ISO 8601
106+
#### Adding and subtracting time
105107

106-
The `toIso8601` method always returns the format `YYYY-MM-DDTHH:MM:SS+00:00`, without fractional seconds.
108+
Returns a new `Instant` shifted forward or backward by a `Duration`.
107109

108110
```php
109111
use TinyBlocks\Time\Instant;
112+
use TinyBlocks\Time\Duration;
110113

111-
$instant = Instant::fromString(value: '2026-02-17T19:30:00+09:00');
114+
$instant = Instant::fromString(value: '2026-02-17T10:00:00+00:00');
112115

113-
$instant->toIso8601(); # 2026-02-17T10:30:00+00:00
116+
$instant->plus(duration: Duration::ofMinutes(minutes: 30))->toIso8601(); # 2026-02-17T10:30:00+00:00
117+
$instant->plus(duration: Duration::ofHours(hours: 2))->toIso8601(); # 2026-02-17T12:00:00+00:00
118+
$instant->minus(duration: Duration::ofSeconds(seconds: 60))->toIso8601(); # 2026-02-17T09:59:00+00:00
114119
```
115120

116-
#### Accessing the underlying DateTimeImmutable
121+
#### Measuring distance between instants
117122

118-
Returns a `DateTimeImmutable` in UTC with full microsecond precision.
123+
Returns the absolute `Duration` between two `Instant` objects.
119124

120125
```php
121126
use TinyBlocks\Time\Instant;
122127

123-
$instant = Instant::fromString(value: '2026-02-17T10:30:00+00:00');
124-
$dateTime = $instant->toDateTimeImmutable();
128+
$start = Instant::fromString(value: '2026-02-17T10:00:00+00:00');
129+
$end = Instant::fromString(value: '2026-02-17T11:30:00+00:00');
125130

126-
$dateTime->getTimezone()->getName(); # UTC
127-
$dateTime->format('Y-m-d\TH:i:s.u'); # 2026-02-17T10:30:00.000000
131+
$duration = $start->durationUntil(other: $end);
132+
133+
$duration->seconds; # 5400
134+
$duration->toMinutes(); # 90
135+
$duration->toHours(); # 1
136+
```
137+
138+
The result is always non-negative regardless of direction:
139+
140+
```php
141+
$end->durationUntil(other: $start)->seconds; # 5400
142+
```
143+
144+
#### Comparing instants
145+
146+
Provides strict temporal ordering between two `Instant` instances.
147+
148+
```php
149+
use TinyBlocks\Time\Instant;
150+
151+
$earlier = Instant::fromString(value: '2026-02-17T10:00:00+00:00');
152+
$later = Instant::fromString(value: '2026-02-17T10:30:00+00:00');
153+
154+
$earlier->isBefore(other: $later); # true
155+
$earlier->isAfter(other: $later); # false
156+
$earlier->isBeforeOrEqual(other: $later); # true
157+
$earlier->isAfterOrEqual(other: $later); # false
158+
$later->isAfter(other: $earlier); # true
159+
$later->isAfterOrEqual(other: $earlier); # true
160+
```
161+
162+
### Duration
163+
164+
A `Duration` represents an immutable, unsigned quantity of time measured in seconds. It has no reference point on the
165+
timeline — it expresses only "how much" time.
166+
167+
#### Creating durations
168+
169+
```php
170+
use TinyBlocks\Time\Duration;
171+
172+
$zero = Duration::zero();
173+
$seconds = Duration::ofSeconds(seconds: 90);
174+
$minutes = Duration::ofMinutes(minutes: 30);
175+
$hours = Duration::ofHours(hours: 2);
176+
$days = Duration::ofDays(days: 7);
177+
```
178+
179+
All factories reject negative values:
180+
181+
```php
182+
Duration::ofMinutes(minutes: -5); # throws InvalidDuration
183+
```
184+
185+
#### Arithmetic
186+
187+
```php
188+
use TinyBlocks\Time\Duration;
189+
190+
$a = Duration::ofMinutes(minutes: 30);
191+
$b = Duration::ofMinutes(minutes: 15);
192+
193+
$a->plus(other: $b)->seconds; # 2700 (45 minutes)
194+
$a->minus(other: $b)->seconds; # 900 (15 minutes)
195+
```
196+
197+
Subtraction that would produce a negative result throws an exception:
198+
199+
```php
200+
$b->minus(other: $a); # throws InvalidDuration
201+
```
202+
203+
#### Comparing durations
204+
205+
```php
206+
use TinyBlocks\Time\Duration;
207+
208+
$short = Duration::ofMinutes(minutes: 15);
209+
$long = Duration::ofHours(hours: 2);
210+
211+
$short->isLessThan(other: $long); # true
212+
$long->isGreaterThan(other: $short); # true
213+
$short->isZero(); # false
214+
Duration::zero()->isZero(); # true
215+
```
216+
217+
#### Converting to other units
218+
219+
Conversions truncate toward zero when the duration is not an exact multiple:
220+
221+
```php
222+
use TinyBlocks\Time\Duration;
223+
224+
$duration = Duration::ofSeconds(seconds: 5400);
225+
226+
$duration->toMinutes(); # 90
227+
$duration->toHours(); # 1
228+
$duration->toDays(); # 0
229+
```
230+
231+
### Period
232+
233+
A `Period` represents a half-open time interval `[from, to)` between two UTC instants. The start is inclusive and the
234+
end is exclusive.
235+
236+
#### Creating from two instants
237+
238+
```php
239+
use TinyBlocks\Time\Instant;
240+
use TinyBlocks\Time\Period;
241+
242+
$period = Period::of(
243+
from: Instant::fromString(value: '2026-02-17T10:00:00+00:00'),
244+
to: Instant::fromString(value: '2026-02-17T11:00:00+00:00')
245+
);
246+
247+
$period->from->toIso8601(); # 2026-02-17T10:00:00+00:00
248+
$period->to->toIso8601(); # 2026-02-17T11:00:00+00:00
249+
```
250+
251+
The start must be strictly before the end:
252+
253+
```php
254+
Period::of(from: $later, to: $earlier); # throws InvalidPeriod
255+
```
256+
257+
#### Creating from a start and duration
258+
259+
```php
260+
use TinyBlocks\Time\Duration;
261+
use TinyBlocks\Time\Instant;
262+
use TinyBlocks\Time\Period;
263+
264+
$period = Period::startingAt(
265+
from: Instant::fromString(value: '2026-02-17T10:00:00+00:00'),
266+
duration: Duration::ofMinutes(minutes: 90)
267+
);
268+
269+
$period->from->toIso8601(); # 2026-02-17T10:00:00+00:00
270+
$period->to->toIso8601(); # 2026-02-17T11:30:00+00:00
271+
```
272+
273+
#### Getting the duration
274+
275+
```php
276+
$period->duration()->seconds; # 5400
277+
$period->duration()->toMinutes(); # 90
278+
```
279+
280+
#### Checking if an instant is contained
281+
282+
The check is inclusive at the start and exclusive at the end:
283+
284+
```php
285+
use TinyBlocks\Time\Instant;
286+
287+
$period->contains(instant: Instant::fromString(value: '2026-02-17T10:00:00+00:00')); # true (start, inclusive)
288+
$period->contains(instant: Instant::fromString(value: '2026-02-17T10:30:00+00:00')); # true (middle)
289+
$period->contains(instant: Instant::fromString(value: '2026-02-17T11:30:00+00:00')); # false (end, exclusive)
290+
```
291+
292+
#### Detecting overlap
293+
294+
Two half-open intervals `[A, B)` and `[C, D)` overlap when `A < D` and `C < B`:
295+
296+
```php
297+
use TinyBlocks\Time\Duration;
298+
use TinyBlocks\Time\Instant;
299+
use TinyBlocks\Time\Period;
300+
301+
$periodA = Period::startingAt(
302+
from: Instant::fromString(value: '2026-02-17T10:00:00+00:00'),
303+
duration: Duration::ofHours(hours: 1)
304+
);
305+
$periodB = Period::startingAt(
306+
from: Instant::fromString(value: '2026-02-17T10:30:00+00:00'),
307+
duration: Duration::ofHours(hours: 1)
308+
);
309+
310+
$periodA->overlapsWith(other: $periodB); # true
311+
$periodB->overlapsWith(other: $periodA); # true
312+
```
313+
314+
Adjacent periods do not overlap:
315+
316+
```php
317+
use TinyBlocks\Time\Duration;
318+
use TinyBlocks\Time\Instant;
319+
use TinyBlocks\Time\Period;
320+
321+
$first = Period::startingAt(
322+
from: Instant::fromString(value: '2026-02-17T10:00:00+00:00'),
323+
duration: Duration::ofHours(hours: 1)
324+
);
325+
$second = Period::startingAt(
326+
from: Instant::fromString(value: '2026-02-17T11:00:00+00:00'),
327+
duration: Duration::ofHours(hours: 1)
328+
);
329+
330+
$first->overlapsWith(other: $second); # false
128331
```
129332

130333
### Timezone

0 commit comments

Comments
 (0)