-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTimeOfDay.php
More file actions
195 lines (169 loc) · 5.73 KB
/
TimeOfDay.php
File metadata and controls
195 lines (169 loc) · 5.73 KB
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
<?php
declare(strict_types=1);
namespace TinyBlocks\Time;
use TinyBlocks\Time\Internal\Exceptions\InvalidTimeOfDay;
use TinyBlocks\Vo\ValueObject;
use TinyBlocks\Vo\ValueObjectBehavior;
/**
* Represents a time of day (hour and minute) without date or timezone context.
* Values range from 00:00 to 23:59.
*/
final readonly class TimeOfDay implements ValueObject
{
use ValueObjectBehavior;
private const int MAX_HOUR = 23;
private const int MAX_MINUTE = 59;
private const int MINUTES_PER_HOUR = 60;
private const string PATTERN = '/^(?P<hour>\d{2}):(?P<minute>\d{2})(?::(?:[0-5]\d))?$/';
private function __construct(public int $hour, public int $minute)
{
}
/**
* Creates a TimeOfDay from hour and minute components.
*
* @param int $hour The hour (0-23).
* @param int $minute The minute (0-59).
* @return TimeOfDay The created time of day.
* @throws InvalidTimeOfDay If hour or minute is out of range.
*/
public static function from(int $hour, int $minute): TimeOfDay
{
if ($hour < 0 || $hour > self::MAX_HOUR) {
throw InvalidTimeOfDay::becauseHourIsOutOfRange(hour: $hour);
}
if ($minute < 0 || $minute > self::MAX_MINUTE) {
throw InvalidTimeOfDay::becauseMinuteIsOutOfRange(minute: $minute);
}
return new TimeOfDay(hour: $hour, minute: $minute);
}
/**
* Creates a TimeOfDay from a string in "HH:MM" or "HH:MM:SS" format.
* When seconds are present, they are discarded.
*
* @param string $value The time string (e.g. "08:30", "14:00", "08:30:00").
* @return TimeOfDay The created time of day.
* @throws InvalidTimeOfDay If the format is invalid or values are out of range.
*/
public static function fromString(string $value): TimeOfDay
{
if (preg_match(self::PATTERN, $value, $matches) !== 1) {
throw InvalidTimeOfDay::becauseFormatIsInvalid(value: $value);
}
return self::from(hour: (int)$matches['hour'], minute: (int)$matches['minute']);
}
/**
* Derives the time of day from an Instant (in UTC).
*
* @param Instant $instant The point in time to extract the time from.
* @return TimeOfDay The corresponding time of day.
*/
public static function fromInstant(Instant $instant): TimeOfDay
{
$dateTime = $instant->toDateTimeImmutable();
return new TimeOfDay(
hour: (int)$dateTime->format('G'),
minute: (int)$dateTime->format('i')
);
}
/**
* Creates a TimeOfDay representing noon (12:00).
*
* @return TimeOfDay Noon.
*/
public static function noon(): TimeOfDay
{
return new TimeOfDay(hour: 12, minute: 0);
}
/**
* Creates a TimeOfDay representing midnight (00:00).
*
* @return TimeOfDay Midnight.
*/
public static function midnight(): TimeOfDay
{
return new TimeOfDay(hour: 0, minute: 0);
}
/**
* Returns the total number of minutes since midnight.
*
* @return int Minutes since 00:00.
*/
public function toMinutesSinceMidnight(): int
{
return ($this->hour * self::MINUTES_PER_HOUR) + $this->minute;
}
/**
* Returns the Duration from midnight to this time of day.
*
* @return Duration The duration since midnight.
*/
public function toDuration(): Duration
{
return Duration::fromMinutes(minutes: $this->toMinutesSinceMidnight());
}
/**
* Returns true if this time is strictly before another.
*
* @param TimeOfDay $other The time to compare against.
* @return bool True if this time precedes the other.
*/
public function isBefore(TimeOfDay $other): bool
{
return $this->toMinutesSinceMidnight() < $other->toMinutesSinceMidnight();
}
/**
* Returns true if this time is strictly after another.
*
* @param TimeOfDay $other The time to compare against.
* @return bool True if this time follows the other.
*/
public function isAfter(TimeOfDay $other): bool
{
return $this->toMinutesSinceMidnight() > $other->toMinutesSinceMidnight();
}
/**
* Returns true if this time is before or equal to another.
*
* @param TimeOfDay $other The time to compare against.
* @return bool True if this time is at or before the other.
*/
public function isBeforeOrEqual(TimeOfDay $other): bool
{
return $this->toMinutesSinceMidnight() <= $other->toMinutesSinceMidnight();
}
/**
* Returns true if this time is after or equal to another.
*
* @param TimeOfDay $other The time to compare against.
* @return bool True if this time is at or after the other.
*/
public function isAfterOrEqual(TimeOfDay $other): bool
{
return $this->toMinutesSinceMidnight() >= $other->toMinutesSinceMidnight();
}
/**
* Returns the Duration between this time and another.
* The other time must be after this time.
*
* @param TimeOfDay $other The later time of day.
* @return Duration The duration between the two times.
* @throws InvalidTimeOfDay If the other time is not after this time.
*/
public function durationUntil(TimeOfDay $other): Duration
{
$diff = $other->toMinutesSinceMidnight() - $this->toMinutesSinceMidnight();
if ($diff <= 0) {
throw InvalidTimeOfDay::becauseEndIsNotAfterStart(from: $this, to: $other);
}
return Duration::fromMinutes(minutes: $diff);
}
/**
* Formats this time as "HH:MM".
*
* @return string The formatted time string.
*/
public function toString(): string
{
return sprintf('%02d:%02d', $this->hour, $this->minute);
}
}