55use Illuminate \Container \Container ;
66use InvalidArgumentException ;
77use NAL \TimeTracker \Exception \InvalidUnitName ;
8+ use NAL \TimeTracker \Exception \NoActivePausedTimerToResume ;
89use NAL \TimeTracker \Exception \NoActiveTimerToStopException ;
10+ use NAL \TimeTracker \Exception \TimerAlreadyPaused ;
911use NAL \TimeTracker \Exception \TimerNotStarted ;
12+ use NAL \TimeTracker \Exception \UnmatchedPauseWithoutResume ;
1013use NAL \TimeTracker \Exception \UnsupportedLogic ;
1114
1215class TimeTracker
@@ -25,6 +28,27 @@ class TimeTracker
2528 */
2629 private array $ end = [];
2730
31+ /**
32+ * Stores pause times of tracked operations by ID.
33+ *
34+ * @var array
35+ */
36+ private array $ pause = [];
37+
38+ /**
39+ * Stores resume times of tracked operations by ID.
40+ *
41+ * @var array
42+ */
43+ private array $ resume = [];
44+
45+ /**
46+ * Stores laps of tracked operations by ID.
47+ *
48+ * @var array
49+ */
50+ private array $ laps = [];
51+
2852 private Unit $ unit ;
2953
3054 /**
@@ -96,6 +120,106 @@ public function stop(?string $id = null): void
96120 $ this ->end [$ id ] = microtime (true );
97121 }
98122
123+ /**
124+ * Records a lap time for the specified timer.
125+ *
126+ * A lap represents a checkpoint within an ongoing timer session. Each lap is recorded
127+ * with its timestamp and an optional description for context.
128+ *
129+ * @param string $id The identifier of the timer.
130+ * @param string $description An optional label or note for this lap (default: empty string).
131+ *
132+ * @throws TimerNotStarted If the timer with the given ID has not been started.
133+ *
134+ * @return void
135+ */
136+ public function lap (string $ id , string $ description = '' ): void
137+ {
138+ if (!isset ($ this ->start [$ id ])) {
139+ throw new TimerNotStarted ($ id );
140+ }
141+
142+ $ this ->laps [$ id ][] = [
143+ 'description ' => $ description ,
144+ 'time ' => microtime (true )
145+ ];
146+ }
147+
148+ /**
149+ * Retrieves all recorded laps for a given timer.
150+ *
151+ * Each lap contains its timestamp and optional description.
152+ * If no laps have been recorded, an empty array is returned.
153+ *
154+ * @param string $id The identifier of the timer.
155+ *
156+ * @return array<int, array{description: string, time: float}> A list of laps with their details.
157+ */
158+ public function getLaps (string $ id ): array
159+ {
160+ return $ this ->laps [$ id ] ?? [];
161+ }
162+
163+ /**
164+ * Pauses the specified timer.
165+ *
166+ * If the timer is already paused and not yet resumed, this method throws an exception.
167+ * Each pause event is recorded with a timestamp and an optional description.
168+ *
169+ * @param string $id The identifier of the timer.
170+ * @param string $description An optional label or note for this pause (default: empty string).
171+ *
172+ * @throws TimerNotStarted If the timer with the given ID has not been started.
173+ * @throws TimerAlreadyPaused If the timer is already paused.
174+ *
175+ * @return void
176+ */
177+ public function pause (string $ id , string $ description = '' ): void
178+ {
179+ if (!isset ($ this ->start [$ id ])) {
180+ throw new TimerNotStarted ($ id );
181+ }
182+
183+ if (isset ($ this ->pause [$ id ]) && count ($ this ->pause [$ id ]) > count ($ this ->resume [$ id ] ?? [])) {
184+ throw new TimerAlreadyPaused ($ id );
185+ }
186+
187+ $ this ->pause [$ id ][] = [
188+ 'description ' => $ description ,
189+ 'time ' => microtime (true )
190+ ];
191+ }
192+
193+ /**
194+ * Resumes a previously paused timer.
195+ *
196+ * A timer can only be resumed if it has an active pause entry that has not yet been resumed.
197+ * Each resume event is recorded with a timestamp and an optional description.
198+ *
199+ * @param string $id The identifier of the timer.
200+ * @param string $description An optional label or note for this resume (default: empty string).
201+ *
202+ * @throws TimerNotStarted If the timer with the given ID has not been started.
203+ * @throws NoActivePausedTimerToResume If there is no active pause to resume.
204+ *
205+ * @return void
206+ */
207+ public function resume (string $ id , string $ description = '' ): void
208+ {
209+ if (!isset ($ this ->start [$ id ])) {
210+ throw new TimerNotStarted ($ id );
211+ }
212+
213+ if (!isset ($ this ->pause [$ id ]) || count ($ this ->pause [$ id ]) === count ($ this ->resume [$ id ] ?? [])) {
214+ throw new NoActivePausedTimerToResume ($ id );
215+ }
216+
217+ $ this ->resume [$ id ][] = [
218+ 'description ' => $ description ,
219+ 'time ' => microtime (true )
220+ ];
221+ }
222+
99223 /**
100224 * Check whether timer with given id is started or not
101225 *
@@ -195,16 +319,35 @@ public static function watch(callable $callback, array $params = [], string $uni
195319 *
196320 * @param string $id The identifier for the timer.
197321 * @return Result|null The Result instance or null if the timer does not exist.
322+ *
323+ * @throws UnmatchedPauseWithoutResume
198324 */
199325 public function calculate (string $ id ): ?Result
200326 {
201327 if (!$ this ->exists ($ id )) {
202328 return null ;
203329 }
204330
205- return new Result ($ this ->unit , $ this ->end [$ id ] - $ this ->start [$ id ], 's ' );
331+ $ calculate = $ this ->end [$ id ] - $ this ->start [$ id ];
332+
333+ $ pausedTime = 0 ;
334+
335+ if (!empty ($ this ->pause [$ id ]) && !empty ($ this ->resume [$ id ])) {
336+ foreach ($ this ->pause [$ id ] as $ index => $ pauseTime ) {
337+ if (isset ($ this ->resume [$ id ][$ index ])) {
338+ $ pausedTime += $ this ->resume [$ id ][$ index ]['time ' ] - $ pauseTime ['time ' ];
339+ } else {
340+ throw new UnmatchedPauseWithoutResume ($ id );
341+ }
342+ }
343+ }
344+
345+ $ calculate -= $ pausedTime ;
346+
347+ return new Result ($ this ->unit , $ calculate , 's ' );
206348 }
207349
350+
208351 /**
209352 * Adds a custom unit definition based on seconds.
210353 *
@@ -297,4 +440,30 @@ public function getUnit(): Unit
297440 {
298441 return $ this ->unit ;
299442 }
443+
444+ /**
445+ * Inspects and retrieves detailed timing data for a specific timer.
446+ *
447+ * @param string $id The identifier of the timer to inspect.
448+ *
449+ * @return array{
450+ * start: float|null,
451+ * end: float|null,
452+ * paused: array<int, array{description: string, time: float}>,
453+ * resumed: array<int, array{description: string, time: float}>,
454+ * status: string,
455+ * laps: array<int, array{description: string, time: float}>
456+ * } A structured array containing all tracked data for the specified timer.
457+ */
458+ public function inspect (string $ id ): array
459+ {
460+ return [
461+ 'start ' => $ this ->start [$ id ] ?? null ,
462+ 'end ' => $ this ->end [$ id ] ?? null ,
463+ 'paused ' => $ this ->pause [$ id ] ?? [],
464+ 'resumed ' => $ this ->resume [$ id ] ?? [],
465+ 'status ' => $ this ->status ($ id ),
466+ 'laps ' => $ this ->laps [$ id ] ?? []
467+ ];
468+ }
300469}
0 commit comments