bluedigit / php-date-component
Library of date components.
Requires
- php: ^8.3
Requires (Dev)
- phpstan/phpstan: ^2.2
- slevomat/coding-standard: ^8.31
- squizlabs/php_codesniffer: ^4.0
README
A PHP 8.3+ library for working with date and time components such as years, months, weeks, days, hours, minutes, and seconds.
The library represents individual date/time components as immutable value objects with well-defined start and end boundaries. Components can be navigated, compared, related to other components, and used to create lazy sequences.
Requirements
- PHP 8.3 or higher
Installation
Install the package with Composer:
composer require bluedigit/php-date-component
Concepts
The library provides seven date/time components:
YearMonthWeekDayHourMinuteSecond
Each component represents a complete, normalized time period.
For example, a Month created from 2026-08-15 14:30:00 represents the entire month of August 2026:
use BlueDigit\DateComponent\Month; $month = Month::make('2026-08-15 14:30:00'); $month->getStart(); // 2026-08-01 00:00:00 $month->getEnd(); // 2026-08-31 23:59:59
Components are immutable and can therefore safely be passed around and reused.
Creating Components
Components can be created using make():
use BlueDigit\DateComponent\Day; $day = Day::make('2026-08-22');
make() accepts:
- a component of the same type
- another
DateComponent - a
DateTimeImmutable - a
DateTime - a scalar value accepted by
DateTimeImmutable
For example:
$day = Day::make('2026-08-22'); $day = Day::make(new DateTimeImmutable('2026-08-22')); $day = Day::make($anotherComponent);
Invalid date strings result in an InvalidArgumentException. Unsupported types result in a TypeError.
Component Boundaries
Every component has a start and end:
$day = Day::make('2026-08-22 15:42:17'); $start = $day->getStart(); $end = $day->getEnd();
For a day, the boundaries are:
2026-08-22 00:00:00
2026-08-22 23:59:59
The same principle applies to all components.
| Component | Start | End |
|---|---|---|
Year |
January 1, 00:00:00 | December 31, 23:59:59 |
Month |
First day, 00:00:00 | Last day, 23:59:59 |
Week |
Monday, 00:00:00 | Sunday, 23:59:59 |
Day |
00:00:00 | 23:59:59 |
Hour |
HH:00:00 |
HH:59:59 |
Minute |
HH:MM:00 |
HH:MM:59 |
Second |
Exact second | Exact second |
Weeks use ISO week numbering and therefore start on Monday.
Navigation
Every component can be moved relative to itself:
$month = Month::make('2026-08-15'); $previous = $month->getPrevious(); $next = $month->getNext();
Equivalent offset-based navigation is available through getByOffset():
$month->getByOffset(-2); // two months before $month->getByOffset(3); // three months after
An offset of 0 returns the current instance:
$month->getByOffset(0) === $month; // true
Relationships Between Components
Components expose their natural relationships through dedicated interfaces.
For example, a Day has a year, month, and week:
$day = Day::make('2026-08-22'); $year = $day->getYear(); $month = $day->getMonth(); $week = $day->getWeek();
Likewise, an Hour belongs to a day:
$hour = Hour::make('2026-08-22 15:00'); $day = $hour->getDay();
The relationship model deliberately follows the actual calendar hierarchy rather than forcing every component into every possible relationship.
For example, a month provides its days:
$month = Month::make('2026-08'); foreach ($month->getDays() as $day) { // ... }
A month does not provide a sequence of weeks because weeks and months do not have a strict hierarchical relationship: an ISO week can span two different months.
A year, on the other hand, can provide both its months and its ISO weeks:
$year = Year::make('2026'); $months = $year->getMonths(); $weeks = $year->getWeeks();
Sequences
Components can create lazy sequences of adjacent components.
$month = Month::make('2026-08'); $months = $month->getSequence(12);
Sequences implement:
IteratorAggregateCountableArrayAccess
This allows them to be used with foreach, count(), and array-style access:
foreach ($months as $month) { // ... } $count = count($months); $first = $months[0]; $third = $months[2];
Sequences are lazy. Components are generated from the original component and an offset when they are accessed or iterated.
First and Last Component
Sequences provide convenient access to their boundaries:
$months->getFirst(); $months->getLast();
For an empty sequence, both methods return null:
$sequence = $month->getSequence(0); $sequence->getFirst(); // null $sequence->getLast(); // null
The sequence itself can also be checked:
$sequence->isEmpty();
Read-Only Array Access
Sequences support reading through array access:
$month = $months[3];
They are intentionally read-only. Attempting to assign or unset an element throws a LogicException:
$months[0] = $month; // LogicException unset($months[0]); // LogicException
Calendar Hierarchy
The components form a hierarchy based on their natural containment relationships:
Year
├── Month
│ └── Day
│ ├── Hour
│ │ └── Minute
│ │ └── Second
│ └── Week
└── Week
└── Day
This is not intended to define an artificial parent-child hierarchy for every component.
In particular:
- A year contains 12 months.
- A year contains all ISO weeks belonging to that ISO year.
- A year contains all days of the year.
- A month contains its days.
- A week contains seven days.
- A day contains 24 hours.
- An hour contains 60 minutes.
- A minute contains 60 seconds.
The relationship between months and weeks is intentionally not represented as containment.
Component Information
Components expose useful calendar information.
Year
$year = Year::make('2026'); $year->getNumber(); // 2026 $year->getNumberOfWeeks(); $year->getNumberOfDays(); // 365 $year->isLeapYear(); // false
A leap year contains 366 days.
Month
$month = Month::make('2026-08'); $month->getNumber(); // 8 $month->getNumberOfDays(); // 31 $month->getYear();
Week
Weeks use ISO week numbering:
$week = Week::make('2026-08-22'); $week->getNumber(); $week->getYear();
A week always contains seven days:
foreach ($week->getDays() as $day) { // Monday through Sunday }
Day
$day = Day::make('2026-08-22'); $day->getDayOfWeek(); // ISO weekday: 1–7 $day->getDayOfMonth(); $day->getDayOfYear(); $day->getYear(); $day->getMonth(); $day->getWeek();
Hour, Minute and Second
The smaller components expose their numeric value:
$hour = Hour::make('2026-08-22 15:42:17'); $hour->getNumber(); // 15
$minute = Minute::make('2026-08-22 15:42:17'); $minute->getNumber(); // 42
$second = Second::make('2026-08-22 15:42:17'); $second->getNumber(); // 17
They can also navigate to their containing components:
$minute->getDay(); $minute->getHour(); $second->getDay(); $second->getHour(); $second->getMinute();
Comparing Components
Components can be compared with arbitrary supported date values:
$month = Month::make('2026-08'); $month->compareTo('2026-07'); // > 0 $month->compareTo('2026-08'); // 0 $month->compareTo('2026-09'); // < 0
The comparison is based on the component's start date.
Components can also be checked for exact identity of type and start date:
$month = Month::make('2026-08'); $month->isSameAs(Month::make('2026-08')); // true $month->isSameAs(Month::make('2026-09')); // false $month->isSameAs(Day::make('2026-08-01')); // false
Interfaces
The library provides small interfaces for component relationships:
HasYearHasMonthHasWeekHasDayHasHourHasMinuteHasSecond
For example, code that only needs access to a year does not need to depend on a concrete component implementation:
function getYearNumber(HasYear $component): int { return $component->getYear()->getNumber(); }
This allows relationships to be expressed through capabilities rather than concrete classes.
Date Component Interface
All components implement DateComponent:
interface DateComponent { public static function make(mixed $date): static; public function getStart(): DateTimeImmutable; public function getEnd(): DateTimeImmutable; public function getByOffset(int $offset): static; public function getNext(): static; public function getPrevious(): static; public function getSequence(int $count): DateComponentSequence; public function compareTo(mixed $date): int; public function isSameAs(DateComponent $component): bool; }
This makes it possible to write generic code that works with any component type.
Example: Building a Calendar
The components are particularly useful for calendar-related applications.
For example, a monthly calendar can be built from a Month:
$month = Month::make('2026-08'); foreach ($month->getDays() as $day) { echo $day->getDayOfMonth(); }
A year view can be built from its months:
$year = Year::make('2026'); foreach ($year->getMonths() as $month) { echo $month->getNumber(); }
A weekly view can use an ISO week directly:
$week = Week::make('2026-08-22'); foreach ($week->getDays() as $day) { // Render the day }
Because components are immutable and navigation is offset-based, generating adjacent calendar views is straightforward:
$current = Month::make('2026-08'); $previous = $current->getPrevious(); $next = $current->getNext();
Design Goals
The library is intentionally small and focused.
Its goals are:
- provide strongly typed date/time components
- normalize arbitrary dates to meaningful calendar boundaries
- make navigation between adjacent components simple
- model natural calendar relationships
- provide lazy, read-only sequences
- build on PHP's native
DateTimeImmutable - avoid introducing its own date/time implementation
The library does not attempt to replace DateTimeImmutable, localization, formatting, or calendar rendering. It provides a domain-oriented layer on top of PHP's existing date/time functionality.
Development
Install the development dependencies:
composer install
Run static analysis:
composer analyse
Run coding-standard checks:
composer lint
License
This library is released under the MIT License.
See LICENSE for the full license text.
Author
blueDigit LP