Framework
Utilities

Date & Time

An immutable Date value object plus helpers for periods, time tables, zones and timing.

Date

Date is an immutable value object — every transform returns a new instance, so a date can be passed around without anyone changing it underneath you.

Creating a date

create() is the general entry point and takes whatever you have: a timestamp, a date string, an optional separate hour, another Date, or nothing for an empty one. The rest are shortcuts for the common cases:

use Framework\Date\Date;

Date::now();                            // right now
Date::create("2026-02-03");             // from a date string
Date::create("2026-02-03", "12:34");    // date plus a separate hour
Date::create($timestamp);               // from a unix timestamp
Date::createTime(3, 2, 2026, 12, 34);   // from day, month, year, hour, minute

Date::createOrNow($maybeDate);          // that value, or now if it is empty
Date::parse($userInput, $language);     // a date written in the user's format
Date::empty();                          // an empty date — isEmpty() is true
Date::max($a, $b, $c);                  // the latest of several dates
createOrNow() is the one to reach for with optional input — it falls back to the current moment instead of leaving you with an empty date, and parse() handles what a user typed, respecting their language's date order.

Reading & transforming

Transforms return a new date, so they chain. The to…Start / to…End family is what you want when building a range:

$date->toDayStart();     // same day at 00:00:00
$date->toDayEnd();       // same day at 23:59:59
$date->toWeekStart();    // and toWeekEnd()
$date->toMonthStart();   // and toMonthEnd()
$date->toYearStart();    // and toYearEnd()

$date->add(days: 7);        // a week later — also hours, months, years…
$date->subtract(months: 1); // a month earlier
$date->setHourMinute(9, 30);
GroupMethods
OutputtoTime (timestamp), toString, format, toISOString, toUTCString, toMinutes.
PartsgetDay, getMonth, getYear, getHour, getMinute, getMonthName, getDayName, getWeekOfYear.
Moveadd, subtract, set, setHourMinute.
CompareisEqual, isBefore, isAfter (with OrEqual variants), isBetween, isEqualDay.
AskisToday, isPast, isFuture, isCurrentMonth, isEmpty, isValid.
DifferencesgetDaysDiff, getHoursDiff, getMinutesDiff, getWeeksDiff, getAge.

Period

A Period is a from/to pair of dates — the date-range filter behind a report or a listing. It exposes fromTime and toTime as Date objects, plus the PeriodType it resolved from:

use Framework\Date\Period;
use Framework\Date\Type\PeriodType;

$period = Period::fromPeriod(PeriodType::ThisMonth);

$period->fromTime;         // a Date — the start
$period->toTime;           // a Date — the end
$period->period;           // the PeriodType it came from
$period->getDaysAmount();  // how many days it spans

Creating a period

There are three ways in, depending on where the range comes from:

WayUse when
Period::fromPeriod(PeriodType::X)You know the named range in code.
new Period($request)The range comes from a request — the usual case.
Period::fromDictionary($data)It comes from stored or decoded data.

Passing a request is the common path: the constructor reads a period field for a named range, or fromDate / toDate (optionally with fromHour / toHour, or raw fromTime / toTime timestamps) for a custom one — so whichever the front end sends, you get the same object:

// "period": "thisMonth"  →  a named range
// "fromDate": "2026-02-01", "toDate": "2026-02-28"  →  Custom
$period = new Period($request);

When a screen carries more than one range, give each a prefix and the constructor reads that set of fields (createdFromDate, createdToDate, …):

$created = new Period($request, prefix: "created");
$updated = new Period($request, prefix: "updated");

The period types

PeriodType covers the ranges an app usually offers, each resolving to its own from/to pair:

GroupTypes
DaysToday, Yesterday, PrevYesterday, Tomorrow, NextTomorrow.
CurrentThisWeek, ThisMonth, ThisYear.
PastPastWeek, PastMonth, PastYear, LastYear.
NextNextWeek, NextMonth, NextYear.
OtherAllPeriod (no bounds), Custom (explicit dates), None.

Use isEmpty() / isNotEmpty() to tell whether a range was actually given before filtering a query by it.

Iterating the days

A period is iterable, yielding one Date per day from start to end — handy for filling a chart or a calendar with every day in the range, including the ones with no data:

foreach ($period as $date) {
    $totals[$date->toString()] = 0;
}

TimeTable

A TimeTable models a weekly schedule — opening hours, availability, working days — as a set of day/time ranges. Create one from stored data (an array, a Dictionary or another table) and ask it questions:

use Framework\Date\TimeTable;

$schedule = TimeTable::create($data, startMonday: true);

$schedule->isCurrent();          // are we inside a slot right now?
$schedule->containsDate($date);  // does a given moment fall inside one?
$schedule->getNextStartTime();   // when the next slot opens
$schedule->getCurrentEndTime();  // when the current one closes
$schedule->hasHoliday();         // does it define holidays?
$schedule->getText();            // a readable, translated summary

encode() turns it back into storable data, and isValid() checks input before you save it.

TimeZone

Timestamps are stored in server time and shown in the user's. TimeZone holds the current offset — set from the signed-in credential on each request — and converts between the two:

use Framework\Date\TimeZone;

TimeZone::setTimeZone(-3);          // the current user offset, in hours
TimeZone::toUserTime($timestamp);   // server → user
TimeZone::toServerTime($timestamp); // user → server
TimeZone::toString(-3);             // "-03:00"

To run a block in another zone — rendering a report for a different account, say — pushTimeZone() swaps the offset and popTimeZone() restores the previous one, so nothing leaks into the rest of the request.

Timer

A stopwatch for measuring how long something took — the build and migrations use it for their timings:

use Framework\Date\Timer;

$timer = new Timer();
// …do the work
$timer->getElapsedText();      // "1 minute 3 seconds"
$timer->getElapsedSeconds();   // 63.4
$timer->getElapsedMinutes();

DateUtils

Static helpers for the pieces around a date — names, minute conversions and validation of raw input before it becomes a Date:

use Framework\Date\DateUtils;

DateUtils::getMonthName(3);         // "March", translated
DateUtils::getDayName(1);           // the week-day name
DateUtils::timeToMinutes("08:30");  // 510
DateUtils::minutesToTime(510);      // "08:30"

DateUtils::isValidDate("2026-02-31");  // false
DateUtils::isValidHour("25:00");       // false
DateUtils::isValidPeriod($from, $to);  // is the range coherent?

parseDate() reads a date written in the user's format, and getDayString() / getMinString() / getSecString() render durations as readable text.