bluedigit/php-date-component

Library of date components.

Maintainers

Package info

github.com/blueDigit/php-date-component

pkg:composer/bluedigit/php-date-component

Transparency log

Statistics

Installs: 65

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

0.1.0 2026-08-23 03:16 UTC

This package is auto-updated.

Last update: 2026-08-23 04:14:51 UTC


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:

  • Year
  • Month
  • Week
  • Day
  • Hour
  • Minute
  • Second

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:

  • IteratorAggregate
  • Countable
  • ArrayAccess

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:

  • HasYear
  • HasMonth
  • HasWeek
  • HasDay
  • HasHour
  • HasMinute
  • HasSecond

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