From a03f8550a505cab799ef219aad617fb9f293a0a6 Mon Sep 17 00:00:00 2001 From: Andrew Voloshyn Date: Sun, 18 May 2025 21:27:20 +0300 Subject: [PATCH 1/3] README.md --- README.md | 127 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 125 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9b65c75..6674879 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,28 @@ [![Code Coverage](https://codecov.io/gh/thesis-php/time-span/branch/0.2.x/graph/badge.svg)](https://codecov.io/gh/thesis-php/time-span/tree/0.2.x) [![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fthesis-php%2Ftime-span%2F0.2.x)](https://dashboard.stryker-mutator.io/reports/github.com/thesis-php/time-span/0.2.x) +# TimeSpan - PHP Time Duration Library + +A PHP library for representing and manipulating time durations with nanosecond precision. It provides an immutable `TimeSpan` object, making it safe and predictable to work with time differences. + +This library is particularly useful when you need to: +* Calculate precise differences between two `DateTimeImmutable` objects. +* Represent fixed durations (e.g., timeouts, intervals) with high accuracy. +* Convert durations between various units (days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds). + +## Features + +* **Nanosecond Precision:** Internally stores all durations in nanoseconds. +* **Immutable:** `TimeSpan` objects are immutable, ensuring that once a duration is created, it cannot be accidentally changed. +* **Flexible Creation:** + * From individual time components (days, hours, minutes, etc.). + * From a total number of nanoseconds, microseconds, milliseconds, seconds, minutes, hours, or days. + * By calculating the difference between two `DateTimeImmutable` objects. + * From a `DateInterval` object (with some limitations). +* **Easy Conversion:** Convert `TimeSpan` objects to various time units, with options for precision and rounding. +* **Handles `DateInterval` Caveats:** Provides specific warnings and behavior when converting from `DateInterval` to avoid inaccuracies related to months, years, and DST transitions. + + ## Installation ```shell @@ -13,10 +35,111 @@ composer require thesis/time-span ## Usage +### Creating TimeSpans + ```php use Thesis\Time\TimeSpan; -$delay = TimeSpan::fromSeconds(25.123); +// From specific units +$span1 = TimeSpan::fromNanoseconds(1_000_000); +$span2 = TimeSpan::fromMicroseconds(1000); +$span3 = TimeSpan::fromMilliseconds(1); +$span4 = TimeSpan::fromSeconds(60); +$span5 = TimeSpan::fromMinutes(1); +$span6 = TimeSpan::fromHours(1); +$span7 = TimeSpan::fromDays(1); + +// From multiple units +$span8 = TimeSpan::from( + days: 1, + hours: 2, + minutes: 30, + seconds: 15, + milliseconds: 500, + microseconds: 250, + nanoseconds: 100 +); + +// From DateTime difference +$start = new \DateTimeImmutable('2023-01-01 00:00:00'); +$end = new \DateTimeImmutable('2023-01-02 01:30:15.500250100'); +$span9 = TimeSpan::diff($end, $start); + +// From DateInterval (with limitations) +$interval = new \DateInterval('P1DT2H30M15S'); +$span10 = TimeSpan::fromInterval($interval); +``` + +### Converting TimeSpans + +```php +$span = TimeSpan::fromHours(1.5); + +// Exact conversions +$nanoseconds = $span->toNanoseconds(); // int(5400000000000) -echo $delay->toMilliseconds(); // 25123 +// Convert with precision control +$seconds1 = $span->toSeconds(); // int(5400) +$seconds2 = $span->toSeconds(2); // float(5400.00) +$millis1 = $span->toMilliseconds(); // int(5400000) +$millis2 = $span->toMilliseconds(1); // float(5400000.0) + +// With different rounding modes +$minutes = $span->toMinutes(1, PHP_ROUND_HALF_UP); // float(90.0) +$hours = $span->toHours(3, PHP_ROUND_HALF_DOWN); // float(1.500) ``` + +## Limitations + +### DateInterval Conversion + +* ❌ **Does not support months or years** + Throws `InvalidArgumentException` if the interval contains month/year values +* ❌ **Cannot handle intervals from `DateTime::diff()`** + Due to DST changeovers, these intervals can't be interpreted correctly +* ✅ **Recommended alternative**: + ```php + // Instead of: + $interval = $date1->diff($date2); + TimeSpan::fromInterval($interval); + + // Use: + TimeSpan::diff($date1, $date2); + ``` + +## Best Practices + +### When Creating TimeSpans + +1. For date/time differences: + ```php + // 👍 Preferred + TimeSpan::diff($end, $start); + + // 👎 Avoid (unless you handle limitations) + TimeSpan::fromInterval($start->diff($end)); + ``` +2. For high precision: + ```php + // Work with smaller units when precision matters + $span = TimeSpan::fromMicroseconds(1500.75); + ``` + +### When Converting Units + +1. Be explicit about rounding: + ```php + // 👍 Good - explicit about precision and rounding + $hours = $span->toHours(2, PHP_ROUND_HALF_UP); + + // 👎 Avoid - implicit integer conversion + $hours = $span->toHours(); // Loses fractional part + ``` +2. Choose appropriate units: + ```php + // For precise calculations: + $nanos = $span->toNanoseconds(); + + // For human-readable output: + $readable = $span->toHours(2); + ``` \ No newline at end of file From b57e7a29c99a0880479e6a1ad42e2ac1fd0829cc Mon Sep 17 00:00:00 2001 From: Andrew Voloshyn Date: Fri, 28 Nov 2025 17:26:28 +0300 Subject: [PATCH 2/3] Update README.md --- README.md | 224 +++++++++++++++++++++++++----------------------------- 1 file changed, 105 insertions(+), 119 deletions(-) diff --git a/README.md b/README.md index 6674879..f093f00 100644 --- a/README.md +++ b/README.md @@ -1,145 +1,131 @@ -# Thesis TimeSpan +# TimeSpan -[![PHP Version Requirement](https://img.shields.io/packagist/dependency-v/thesis/time-span/php)](https://packagist.org/packages/thesis/time-span) -[![GitHub Release](https://img.shields.io/github/v/release/thesis-php/time-span)](https://github.com/thesis-php/time-span/releases) -[![Code Coverage](https://codecov.io/gh/thesis-php/time-span/branch/0.2.x/graph/badge.svg)](https://codecov.io/gh/thesis-php/time-span/tree/0.2.x) -[![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fthesis-php%2Ftime-span%2F0.2.x)](https://dashboard.stryker-mutator.io/reports/github.com/thesis-php/time-span/0.2.x) +`TimeSpan` is an immutable value object representing a time interval with **nanosecond precision**. It provides a robust way to handle time durations, perform arithmetic operations, and format time strings without the common pitfalls of floating-point math or mutable state. -# TimeSpan - PHP Time Duration Library +## Installation -A PHP library for representing and manipulating time durations with nanosecond precision. It provides an immutable `TimeSpan` object, making it safe and predictable to work with time differences. +```bash +composer require your-vendor/time-span +``` -This library is particularly useful when you need to: -* Calculate precise differences between two `DateTimeImmutable` objects. -* Represent fixed durations (e.g., timeouts, intervals) with high accuracy. -* Convert durations between various units (days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds). +## Usage -## Features +You can create a `TimeSpan` instance using various static factory methods depending on your source unit. -* **Nanosecond Precision:** Internally stores all durations in nanoseconds. -* **Immutable:** `TimeSpan` objects are immutable, ensuring that once a duration is created, it cannot be accidentally changed. -* **Flexible Creation:** - * From individual time components (days, hours, minutes, etc.). - * From a total number of nanoseconds, microseconds, milliseconds, seconds, minutes, hours, or days. - * By calculating the difference between two `DateTimeImmutable` objects. - * From a `DateInterval` object (with some limitations). -* **Easy Conversion:** Convert `TimeSpan` objects to various time units, with options for precision and rounding. -* **Handles `DateInterval` Caveats:** Provides specific warnings and behavior when converting from `DateInterval` to avoid inaccuracies related to months, years, and DST transitions. +```php +use YourVendor\TimeSpan; +// From specific units +$span = TimeSpan::fromSeconds(90); +$span = TimeSpan::fromMinutes(5.5); +$span = TimeSpan::fromHours(1); +$span = TimeSpan::fromDays(2); -## Installation +// From a combination of units +$span = TimeSpan::from( + days: 1, + hours: 12, + minutes: 30 +); -```shell -composer require thesis/time-span +// From a DateInterval +$span = TimeSpan::fromInterval(new \DateInterval('P1DT12H')); ``` -## Usage +### ⚠️ Important: Nanoseconds and IEEE 754 + +The core method `TimeSpan::fromNanoseconds(int|float $nanoseconds)` accepts both integers and floating-point numbers. However, users must be aware of **IEEE 754 floating-point limitations**. + +When passing large nanosecond values as `float`, you may encounter precision loss. This is not a bug in the library but a fundamental property of how computers handle floating-point numbers. + +* **Safe:** Integers (up to `PHP_INT_MAX`) are always precise. +* **Risky:** Large floats (e.g., converting huge values from other units) may be rounded to the nearest representable number. -### Creating TimeSpans +If the provided float value exceeds the safe integer range for nanoseconds, the library will throw an `OutOfBoundsException` to prevent silent overflow errors. ```php -use Thesis\Time\TimeSpan; +// Safe (Integer) +$span = TimeSpan::fromNanoseconds(1000); -// From specific units -$span1 = TimeSpan::fromNanoseconds(1_000_000); -$span2 = TimeSpan::fromMicroseconds(1000); -$span3 = TimeSpan::fromMilliseconds(1); -$span4 = TimeSpan::fromSeconds(60); -$span5 = TimeSpan::fromMinutes(1); -$span6 = TimeSpan::fromHours(1); -$span7 = TimeSpan::fromDays(1); - -// From multiple units -$span8 = TimeSpan::from( - days: 1, - hours: 2, - minutes: 30, - seconds: 15, - milliseconds: 500, - microseconds: 250, - nanoseconds: 100 -); +// Caution (Float): Very large floats may lose 1-2 nanoseconds of precision +$span = TimeSpan::fromNanoseconds(1000.5); // Rounded to nearest integer +``` -// From DateTime difference -$start = new \DateTimeImmutable('2023-01-01 00:00:00'); -$end = new \DateTimeImmutable('2023-01-02 01:30:15.500250100'); -$span9 = TimeSpan::diff($end, $start); +## Formatting -// From DateInterval (with limitations) -$interval = new \DateInterval('P1DT2H30M15S'); -$span10 = TimeSpan::fromInterval($interval); +The `format()` method allows you to generate human-readable strings from the time interval. + +This method is **context-aware**: it automatically calculates values based on the largest unit you request. For example, if you only ask for minutes (`%i`), it will show the *total* minutes. If you ask for hours and minutes (`%h:%i`), it will show total hours and the remaining minutes. + +```php +public function format(string $format = '%h:%i:%s'): string ``` -### Converting TimeSpans +### Available Placeholders + +| Placeholder | Description | Example Output | +| :--- | :--- | :--- | +| `%d` | Days | `1`, `106751` | +| `%h` | Hours (00-23 usually, or total if largest) | `05`, `150` | +| `%i` | Minutes (00-59 usually, or total if largest) | `09`, `90` | +| `%s` | Seconds (00-59 usually, or total if largest) | `30`, `120` | +| `%ms` | Milliseconds (000-999) | `050` | +| `%us` | Microseconds (000-999) | `001` | +| `%ns` | Nanoseconds (000-999) | `999` | + +### Formatting Examples + +Assuming a time span of **1 day, 7 hours, 7 minutes, 3 seconds, 56ms, 89us, 23ns**: ```php -$span = TimeSpan::fromHours(1.5); +// Full detail +echo $span->format('%d %h:%i:%s.%ms_%us_%ns'); +// Output: "1 07:07:03.056_089_023" +// or +echo $span->format('%d %h:%i:%s.%ns'); +// Output: "1 07:07:03.056089023" + +// Total hours (Days are converted to hours) +echo $span->format('%h:%i:%s'); +// Output: "31:07:03" (1 day + 7 hours = 31 hours) + +// Total seconds +echo $span->format('%s'); +// Output: "112023" (Total seconds in the span) + +// Just minutes and seconds +echo $span->format('%i:%s'); +// Output: "1867:03" +``` + +## Math Operations -// Exact conversions -$nanoseconds = $span->toNanoseconds(); // int(5400000000000) +The library includes a comprehensive set of immutable mathematical operations. Since `TimeSpan` is a value object, these methods always return a **new instance**. -// Convert with precision control -$seconds1 = $span->toSeconds(); // int(5400) -$seconds2 = $span->toSeconds(2); // float(5400.00) -$millis1 = $span->toMilliseconds(); // int(5400000) -$millis2 = $span->toMilliseconds(1); // float(5400000.0) +* **Arithmetic:** `add()`, `sub()`, `mul()`, `div()` +* **Helpers:** `abs()`, `negated()` -// With different rounding modes -$minutes = $span->toMinutes(1, PHP_ROUND_HALF_UP); // float(90.0) -$hours = $span->toHours(3, PHP_ROUND_HALF_DOWN); // float(1.500) +```php +$span1 = TimeSpan::fromMinutes(10); +$span2 = TimeSpan::fromMinutes(5); + +$total = $span1->add($span2); // 15 minutes +$diff = $span1->sub($span2); // 5 minutes +$double = $span1->mul(2); // 20 minutes ``` -## Limitations - -### DateInterval Conversion - -* ❌ **Does not support months or years** - Throws `InvalidArgumentException` if the interval contains month/year values -* ❌ **Cannot handle intervals from `DateTime::diff()`** - Due to DST changeovers, these intervals can't be interpreted correctly -* ✅ **Recommended alternative**: - ```php - // Instead of: - $interval = $date1->diff($date2); - TimeSpan::fromInterval($interval); - - // Use: - TimeSpan::diff($date1, $date2); - ``` - -## Best Practices - -### When Creating TimeSpans - -1. For date/time differences: - ```php - // 👍 Preferred - TimeSpan::diff($end, $start); - - // 👎 Avoid (unless you handle limitations) - TimeSpan::fromInterval($start->diff($end)); - ``` -2. For high precision: - ```php - // Work with smaller units when precision matters - $span = TimeSpan::fromMicroseconds(1500.75); - ``` - -### When Converting Units - -1. Be explicit about rounding: - ```php - // 👍 Good - explicit about precision and rounding - $hours = $span->toHours(2, PHP_ROUND_HALF_UP); - - // 👎 Avoid - implicit integer conversion - $hours = $span->toHours(); // Loses fractional part - ``` -2. Choose appropriate units: - ```php - // For precise calculations: - $nanos = $span->toNanoseconds(); - - // For human-readable output: - $readable = $span->toHours(2); - ``` \ No newline at end of file +## Comparison + +You can compare `TimeSpan` objects directly without converting them manually: + +* `compareTo()` +* `isEqualTo()` +* `isGreaterThan()` / `isGreaterThanOrEqualTo()` +* `isLessThan()` / `isLessThanOrEqualTo()` +* `isZero()`, `isPositive()`, `isNegative()` + +```php +if ($span1->isGreaterThan($span2)) { + // ... +} +``` From a2f3d75e1004042de811a667aeea19b2f370c6de Mon Sep 17 00:00:00 2001 From: Andrew Voloshyn Date: Fri, 28 Nov 2025 17:34:32 +0300 Subject: [PATCH 3/3] Fix README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f093f00..d2972b1 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ## Installation ```bash -composer require your-vendor/time-span +composer require thesis/time-span ``` ## Usage