frostealth/kronika

DateTime value objects such as Date, Time, LocalDateTime, etc.

Installs: 232

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/frostealth/kronika

0.2.6 2025-11-19 19:50 UTC

README

The library provides date-time value objects such as "Date", "Time", "LocalDateTime", etc.

Installation

The recommended way to install Kronika is through Composer.

composer require frostealth/kronika

Version Guidance

Version Status Branch PHP Version
0.2 latest 0.x ^8.4
0.1 support 0.1 >=8.3,<=8.5

Usage

Date

Kronika\Date represents a date without specifying a time of day.

The following units of date are available:

  • Year
  • Month
  • DayOfMonth
  • DayOfWeek
  • DayOfYear
// creating the "Date" instance
$date = Date::of(year: 2025, month: 12, day:31);
// or from "\DateTimeInterface"
$date = Date::ofDateTime(new \DateTimeImmutable('2025-12-31'));
// or from a date string
$date = Date::parse('2025-12-31');

// formatting the "Date"
echo $date->format('Y-m-d');        // '2025-12-31'
echo $date->format('l, F jS, Y.');  // 'Wednesday, December 31st, 2025.'

$year      = $date->year();       // Year::of(2025)
$month     = $date->month();      // Month::of(12)
$day       = $date->day();        // DayOfMonth::of(31)
$dayOfWeek = $date->dayOfWeek();  // DayOfWeek::of(3)

// changing the year, month, day, weekday
$date = $date->with(Year::of(2026));
echo $date->format('l, F jS, Y.');  // 'Thursday, December 31st, 2026.'
echo $date->is(Year::of(2026));     // true

$date = $date->with(Month::January)->with(DayOfMonth::of(12));
echo $date->format('l, F jS, Y.');  // 'Monday, January 12th, 2026.'
echo $date->is(DayOfMonth::of(12)); // true

$date = $date->startOfMonth();
echo $date->format('l, F jS, Y.');  // 'Thursday, January 1st, 2026.'

// changing the day of week
$date = $date->with(DayOfWeek::Friday);
echo $date->format('l, F jS, Y.');  // 'Friday, January 2nd, 2026.'
echo $date->is(DayOfWeek::Friday);  // true

// adding an amount of days
$date = $date->add(Duration::of(days: 3));
echo $date->format('l, F jS, Y.');  // 'Monday, January 5th, 2026.'

// subtracting an amount of days
$date = $date->sub(Duration::of(hours: 48));
echo $date->format('l, F jS, Y.');  // 'Saturday, January 3rd, 2026.'

// getting the duration from one date to another one
$duration = $date->until(Date::of(year: 2026, month: 1, day: 5));
echo $duration->days();     // 2
echo $duration->hours();    // 0
echo $duration->minutes();  // 0

Time

Kronika\Time represents a time of day without specifying a date.

The following units of time are available:

  • Hour
  • Minute
  • Second
// creating the "Time" instance
$time = Time::of(hour: 9, minutes: 10, seconds:30);
// or from "\DateTimeInterface"
$time = Time::ofDateTime(new \DateTimeImmutable('09:10:30'));
// or from time string
$time = Time::parse('09:10:30.000000');

// formatting the "Time"
echo $time->format('H:i:s');    // '09:10:30'
echo $time->format('H:i:s.u');  // '09:10:30.000000'

$hour   = $time->hour();    // Hour::of(9)
$minute = $time->minute();  // Minute::of(10)
$second = $time->second();  // Second::of(30)

// changing the hour, minute and second
$time = $time->with(Hour::of(12));
echo $time->format('H:i:s');  // '12:10:30'
echo $time->is(Hour::of(12)); // true

$time = $time->with(Minute::of(30))->with(Second::zero());
echo $time->format('H:i:s');  // '12:30:00'

// comparing time or its unit to another one
$other = $time->with(Second::of(0, micro: 999999));
echo $time->is($other);                     // false
echo $time->is($other, Precision::Second);  // true
echo $time->is(                             // true
    $other->with(Second::of(59)),
    Precision::Minute,
);

// adding an amount of hours, minutes, seconds
$time = $time->add(Duration::of(hours: 3, minutes: 30, seconds: 30));
echo $time->format('H:i:s');  // '16:00:30'

// subtracting an amount of hours, minutes, seconds
$time = $time->sub(Duration::of(hours: 2, minutes: 120, seconds: 30));
echo $time->format('H:i:s');  // '12:00:00'

// getting the duration from one time to another one
$duration = $time->until(Time::of(hour: 18, minute: 30, second: 30));
echo $duration->hours();      // 6
echo $duration->minutes();    // 30
echo $duration->second();     // 30
echo $duration->inMinutes();  // 390

LocalDateTime

Kronika\LocalDateTime represents a local date-time without time-zone.

// creating the "LocalDateTime" instance
$date     = Date::of(year: 2025, month: 12, day: 31);
$time     = Time::midday();
$datetime = LocalDateTime::of($date, $time);
// or
$datetime = $date->at($time);
// or from "\DateTimeInterface"
$datetime = LocalDateTime::ofDateTime(new \DateTimeImmutable('2025-12-31 12:00:00'));
// or from a date-time string
$datetime = LocalDateTime::parse('2025-12-31 12:00:00');

// formatting the "LocalDateTime"
echo $datetime->format('Y-m-d H:i:s');  // '2025-12-31 12:00:00'

$year   = $datetime->year();    // Year::of(2025)
$month  = $datetime->month();   // Month::of(12)
$day    = $datetime->day();     // DayOfMonth::of(31)
$hour   = $datetime->hour();    // Hour::of(12)
$minute = $datetime->minute();  // Minute::zero()
$second = $datetime->second();  // Second::zero()
$date   = $datetime->date();    // Date::of(2025, 12, 31)
$time   = $datetime->time();    // Time::of(12, 0, 0)

// changing the year, month, day, hour, minute and second is similar to "Date" and "Time"
$datetime = $datetime->with(Hour::of(18))->with(Minute::of(30));
echo $datetime->format('Y-m-d H:i:s');  // '2025-12-31 18:30:00'
echo $datetime->is(Hour::of(18));       // true

// adding and subtracting an amount of days, hours,
// minutes and seconds are similar to "Date" and "Time"
$datetime = $datetime->add(Duration::of(hours: 6, minutes: 30, seconds: 30));
echo $datetime->format('Y-m-d H:i:s');  // '2026-01-01 01:00:30'

$datetime = $datetime->sub(Duration::of(hours: 12, minutes: 60, seconds: 30));
echo $datetime->format('Y-m-d H:i:s');  // '2025-12-31 12:00:00'

// getting the duration from one "LocalDateTime" to another one
$duration = $datetime->until(
    LocalDateTime::of(Date::of(year: 2026, month: 1, day: 14), Time::midnight()),
);
echo $duration->days();     // 13
echo $duration->hours();    // 12
echo $duration->minutes();  // 0
echo $duration->second();   // 0
echo $duration->inHours();  // 324

// getting "\DateTimeImmutable" and "\DateTime"
$immutable = $datetime->toNative(new \DateTimeZone('UTC'));         // "\DateTimeImmutable"
$mutable   = $datetime->toNativeMutable(new \DateTimeZone('UTC'));  // "\DateTime"

ZonedDateTime

Kronika\ZonedDateTime represents a date-time with time-zone.

The API of Kronika\ZonedDateTime is similar to Kronika\LocalDateTime.

Kronika\ZonedDateTime class extends \DateTimeImmutable.

// creating the "ZonedDateTime" instance
$date     = Date::of(year: 2025, month: 12, day: 31);
$time     = Time::midday();
$timezone = new \DateTimeZone('UTC')
$datetime = ZonedDateTime::of($date, $time, $timezone);
// or
$datetime = $date->at($time)->at($timezone);
// or
$datetime = ZonedDateTime::ofLocal(LocalDateTime::of($date, $time), $timezone);
// or
$datetime = LocalDateTime::of($date, $time)->at($timezone);
// or
$datetime = ZonedDateTime::utcOf($date, $time);
// or with current time and specified time-zone
$datetime = now($timezone);
// or from "\DateTimeInterface"
$datetime = ZonedDateTime::ofDateTime(new \DateTimeImmutable('2025-12-31 12:00:00 UTC'));
// or from a date-time string with time-zone
$datetime = ZonedDateTime::parse('2025-12-31 12:00:00 UTC');
// or from a date-time string without time-zone
$datetime = ZonedDateTime::parse('2025-12-31 12:00:00', new \DateTimeZone('UTC'));

// formatting the "ZonedDateTime"
echo $datetime->format(\DateTimeInterface::ATOM);  // '2025-12-31T12:00:00+00:00'

$year      = $datetime->year();       // Year::of(2025)
$month     = $datetime->month();      // Month::of(12)
$day       = $datetime->day();        // DayOfMonth::of(31)
$hour      = $datetime->hour();       // Hour::of(12)
$minute    = $datetime->minute();     // Minute::zero()
$second    = $datetime->second();     // Second::zero()
$date      = $datetime->date();       // Date::of(2025, 12, 31)
$time      = $datetime->time();       // Time::of(12, 0, 0)
$timezone  = $datetime->timezone();   // \DateTimeZone('UTC')
$timestamp = $datetime->timestamp();  // float(1767182400.001234)

// changing the year, month, day, hour, minute and second is similar to "LocalDateTime"
$datetime = $datetime->with(Hour::of(18))->with(Minute::of(30));
echo $datetime->format(\DateTimeInterface::ATOM);  // '2025-12-31T18:30:00+00:00'
echo $datetime->is(Hour::of(18));                  // true

// changing the time-zone doesn't shift the time,
// to shift the time use "shift()" method
echo $datetime->with(new \DateTimeZone('+01:00'))
              ->format(\DateTimeInterface::ATOM);  // '2025-12-31T18:30:00+01:00

// adding and subtracting an amount of days, hours, minutes
// and seconds are similar to "LocalDateTime"
$datetime = $datetime->add(Duration::of(hours: 6, minutes: 30, seconds: 30));
echo $datetime->format(\DateTimeInterface::ATOM);  // '2026-01-01T01:00:30+00:00'

$datetime = $datetime->sub(Duration::of(hours: 12, minutes: 60, seconds: 30));
echo $datetime->format(\DateTimeInterface::ATOM);  // '2025-12-31T12:00:00+00:00'

// shifting the timezone
$datetime = $datetime->shift(new \DateTimeZone('+01:00'));
echo $datetime->format(\DateTimeInterface::ATOM);  // '2025-12-31T13:00:00+01:00'

// getting the duration from one "ZonedDateTime" to another one
$duration = $datetime->until(new \DateTime('2026-01-14T12:30:15+00:00'));
$days     = $duration->days();     // 14
$hours    = $duration->hours();    // 0
$minutes  = $duration->minutes();  // 30
$seconds  = $duration->second();   // 15
$inHours  = $duration->inHours();  // 336

// getting "\DateTimeImmutable" and "\DateTime"
$immutable = $datetime->toNative();         // "\DateTimeImmutable"
$mutable   = $datetime->toNativeMutable();  // "\DateTime"

Clock

Kronika\Clock decouples your code from the system clock and has the following implementations:

  • SystemClock returns the current time, this is the same as doing new \DateTime().
  • InaccurateClock ignores a second or microsecond of the current time.
  • PsrClock implements PSR-20: Clock.
  • FrozenClock doesn't move forward on its own, useful in tests.
  • MutableClock allows to manipulate with clock, useful in tests.

TimeRange

Kronika\Range\TimeRange represents a range between two moments of day.

// creating "TimeRange"
$range = TimeRange::of(
    since: Time::midday(),   // inclusive
    till: Time::of(13, 30),  // exclusive
);
echo $range->since()->format('H:i:s');  // 12:00:00
echo $range->till()->format('H:i:s');   // 13:30:00

echo $range->contains(Time::midday());    // true
echo $range->contains(Time::of(13, 30));  // false

// getting each item of this range with the specified step
// minimal step is 1 second
foreach ($range->each(Duration::of(minutes: 30)) as $item) {
    echo $item->format('H:i:s');
}
// 12:00:00
// 12:30:00
// 13:00:00

DateRange

Kronika\Range\DateRange represents a range between two dates.

// creating "DateRange"
$range = DateRange::of(
    since: Date::of(2025, 12, 15),  // inclusive
    till: Date::of(2025, 12, 18),   // exclusive
);
echo $range->since()->format('Y-m-d');  // 2025-12-15
echo $range->till()->format('Y-m-d');   // 2025-12-18

echo $range->contains(Date::of(2025, 12, 15));  // true
echo $range->contains(Date::of(2025, 12, 18));  // false

// getting each item of this range with the specified step
// minimal step is 1 day
foreach ($range->each(Duration::of(days: 2)) as $item) {
    echo $item->format('Y-m-d');
}
// 2025-12-15
// 2025-12-17

DateTimeRange

Kronika\Range\DateTimeRange represents a range between two moments of time.

Both Kronika\ZonedDateTime and Kronika\LocalDateTime are supported.

// creating "DateTimeRange"
$range = DateTimeRange::of(
    since: ZonedDateTime::parse('2025-12-15 12:30:45 +01:00'),  // inclusive
    till: LocalDateTime::parse('2025-12-18 10:00:30'),          // exclusive
);
echo $range->since()->format('Y-m-d H:i:s P');  // 2025-12-15 12:30:45 +01:00
echo $range->till()->format('Y-m-d H:i:s');     // 2025-12-18 10:00:30

echo $range->contains(LocalDateTime::parse('2025-12-15 12:30:45'));  // true
echo $range->contains(LocalDateTime::parse('2025-12-18 10:00:30'));  // false

// getting each item of this range with the specified step
// the type of each item will be the same as "since"
// minimal step is 1 second
foreach ($range->each(Duration::ofDay()) as $item) {
    echo $item->format('Y-m-d H:i:s P');
}
// 2025-12-15 12:30:45 +01:00
// 2025-12-16 12:30:45 +01:00
// 2025-12-17 12:30:45 +01:00