stubbles / date
Simplified handling of dates and datespans.
Installs: 2 938
Dependents: 2
Suggesters: 1
Security: 0
Stars: 2
Watchers: 3
Forks: 0
Open Issues: 2
Requires
- php: ^8.2
- ext-ctype: *
Requires (Dev)
- bovigo/assert: ^8.0
- phpunit/phpunit: ^10.5
- stubbles/reflect: ^10.0
Suggests
- bovigo/assert: To use predicates when asserting dates with stubbles\date\assert\equalsDate()
README
Handling dates and date spans in a immutable way, using a beautiful API.
Build status
Installation
stubbles/date is distributed as Composer package. To install it as a dependency of your package use the following command:
composer require "stubbles/date": "^9.0"
Requirements
stubbles/date requires at least PHP 8.2.
Introduction
The origin of the classes in this library are from the days when PHP's object- oriented date/time handling classes were not immutable. In the meantime immutable versions of them have been added to PHP, but we like the API of the classes here more, as they lead to a better grammar in the code in which they are used.
The underlaying implementation makes use of PHP's object-oriented date/time handling classes, but abstracts them such that the stubbles/date instances are immutable. Every change to a date will result in a new instance. Although the date class exposes the underlaying handle, the user will only receive a clone of that, so modifying such a handle will not mutate the date instance from which the handle was obtained.
stubbles\date\Date
Please note that this class not only covers a date, but an exact point in time.
Each instance also has a time part. If you are interested in a concrete day only
please use stubbles\date\span\Day
(see below).
Create a new instance
When creating a new date instance there are different ways to set the actual date:
- integer, interpreted as timestamp:
new Date(1187872547)
- string, parsed into a date:
new Date('2007-01-01 01:00:00 Europe/Berlin')
\DateTime
object, will be used as is:new Date(new \DateTime())
- no argument,creates a date representing the current time:
new Date()
, equivalent toDate::now()
The second argument to the constructor is an optional timezone. Timezone assignment works through these rules:
- If the time is given as string and contains a parseable timezone identifier that one is used.
- If no timezone could be determined, the timezone given by the second constructor parameter is used.
- If no timezone has been given as second parameter, the default timezone of the system is used.
Liberal type hinting
If you have a method which accepts an instance of stubbles\date\Date
you can
choose to be liberal in what you accept. Instead of type hinting against the
concrete type your method can cast the provided value into an instance:
/** * does something cool * * @param int|string|stubbles\date\Date $date */ function doSomething($date) { $date = Date::castFrom($date); // now do something with $date, which is an instance of stubbles\date\Date }
The Date::castFrom();
accepts four different value types:
- integer, interpreted as unix timestamp:
Date:castFrom(1187872547)
- string, parsed into a date:
Date:castFrom('2007-01-01 01:00:00 Europe/Berlin')
\DateTime
object:Date:castFrom(new \DateTime())
- instances of
stubbles\date\Date
itself
An instance of stubbles\date\Date
will always be returned as is, the other
allowed values will result in the creation of a stubbles\date\Date
instance.
Passing any other value will result in a \InvalidArgumentException
.
Change a date
To change a date the Date::change()
method can be called. This will return an
instance of stubbles\date\DateModifier
which provides several different methods
to change the date and/or time. All changes will result in a new stubbles\date\Date
instance, the instance on which Date::change()
is originally called remains
unchanged:
$currentDate = Date::now(); // create new date with current time but 48 hours ago, this will not change $currentDate $newDate = $currentDate->change()->byHours(-48);
Here's a list of methods offered by stubbles\date\DateModifier
:
to($target)
: change date by relative format accepted by strtotime()timeTo($time)
: keep date, i.e. day, month and year, but change time to given value, must be in format HH:mm:sscreateDateWithNewTime($hour, $minute, $second)
: same as above, but with separate parameters for all valuestimeToStartOfDay()
: alias fortimeTo('00:00:00')
timeToEndOfDay()
: alias fortimeTo('23:59:59')
hourTo($hour)
; keep day, month, year, minutes and seconds, but change hour to given valuebyHours($hours)
: add given amount of hours to current date and time. A negative value will subtract the hoursminuteTo($minute)
: keep day, month, year, hours and seconds, but change minutes to given valuebyMinutes($minutes)
: add given amount of minutes to current date and time. A negative value will subtract the minutes.secondTo($second)
: keep day, month, year, hours and minutes, but change seconds to given valuebySeconds($seconds)
: add given amount of seconds to current date and time. A negative value will subtract the seconds.dateTo($date)
: change date but keep time, given date must be in format YYYY-MM-DDyearTo($year)
: keep day, month, hours, minutes and seconds, but change year to given valuebyYears($years)
: add given amount of years to current date and time. A negative value will subtract the years.monthTo($month)
: keep day, year, hours, minutes and seconds, but change month to given valuebyMonths($months)
: add given amount of months to current date and time. A negative value will subtract the months.dayTo($day)
: keep month, year, hours, minutes and seconds, but change day to given valuebyDays($days)
: add given amount of days to current date and time. A negative value will subtract the days.
Comparing dates
Instances of stubbles\date\Date
can be compared with each other:
$date = Date::now(); if ($date->isBefore('2017-01-01 00:00:00')) { // execute when current date is before 2017 }
$date = Date::now(); if ($date->isAfter('2017-01-01 00:00:00')) { // execute when current date is after beginning of 2017 }
Comparison is done based on the unix timestamp.
Both isBefore()
and isAfter()
accept all values that are accepted by
Date::castFrom()
, see above.
Date formatting
The date can be displayed as a string by formatting:
echo 'Current date and time in system timezone: ' . Date::now()->format('Y-m-d H:i:s') . PHP_EOL; echo 'Current date and time in timezone Europe/Berlin: ' . Date::now()->format('Y-m-d H:i:s', new TimeZone('Europe/Berlin')) . PHP_EOL;
When an instances is casted to a string, the output format will be Y-m-d H:i:sO.
Date spans
Sometimes it is necessary to not cover a specific date only, but a span between
two points in time. Most notably these are things like a single day, months,
weeks or even a year. As it is impractical to always carry the starting and
ending point of such a span, stubbles/date provides the stubbles\date\span\Datespan
interface and various implementations.
Default methods of each date span
start()
: returns the exact starting point of the spanstartsBefore($date)
: checks if the date span starts before the given pointstartsAfter($date)
: checks if the date span starts after the given pointend()
: returns exact ending point of the spanendsBefore($date)
: checks if date span ends before the given pointendsAfter($date)
: checks if date span ends after the given pointformatStart($format, TimeZone $timeZone = null)
: format start point in given formatformatEnd($format, TimeZone $timeZone = null)
: format end point in given formatamountOfDays()
: returns amount of days that are covered by the date spandays()
: returns an iterator which allows to iterate over each single day within this date spanisInFuture()
: checks whether date span is completely in the future based on current date and timecontainsDate($date)
: checks if given date and time are contained in the date span
All methods which have a $date
parameter accept all values that are accepted
by Date::castFrom()
, see above.
List of provided date span implementations
stubbles\dates\span\Day
Covers a whole day, starting at 00:00:00 and ending at 23:59:59.
// create without argument always points to current day $today = new Day(); // create with given date $another = new Day('2016-06-27'); // create day from given stubbles\date\Date instance $oneMore = new Day(new Date('2013-05-28')); // creates a new instance representing tomorrow $tomorrow = Day::tomorrow(); // creates a new instance representing yesterday $yesterday = Day::yesterday();
Additional methods:
next()
: creates a new instance with the day after the represented daybefore()
: create a new instance with the day before the represented dayisToday()
: checks if the day represents the current day
stubbles\dates\span\Week
Covers a whole week, starting on the given date at 00:00:00 and ending at seven days later at 23:59:59.
// create a week starting today $week1 = new Week(Date::now()); // create a week which starts tomorrow $week2 = new Week('tomorrow'); // create a week which represents the 5th calender week of 2016 $week3 = Week::fromString('2016-W05')
Additional methods:
number()
: returns the week number
stubbles\dates\span\Month
Covers a month, starting at the first of the month at 00:00:00 and ending at the last day of the month at 23:59:59.
// creates instance representing the current month $currentMonth = new Month(); // creates instance with current month but in the year 2014-05 $currentMonthIn2015 = new Month(2015); // create instance representing June 2016 $exactMonth = new Month(2016, 6); // create instance representing month given as string, format must be YYYY-MM $otherMonth = Month::fromString('2016-07'); // creates instance representing the month before current month $lastMonth = Month::last(); // creates instance for current month execpt when today is the first day of a // month, the the instance represents the month before // ideally suited when creating reports, as most often the report created on the // first month of a day should be for the last month instead of for the current // month $reportingMonth = Month::currentOrLastWhenFirstDay()
Additional methods:
next()
: creates instance with month after the currently represented monthbefore()
: creates instance with month before the currently represented monthyear()
: returns the year in which the month isisCurrentMonth()
: checks whether month instance represents the current month
stubbles\dates\span\Year
Covers a year, starting at January 01 00:00:00 and ending at December 31 23:59:59.
// create instance representing the current year $currentYear = new Year(); // creates instance representing the year 2015 $year2015 = new Year(2015);
Additional methods:
months()
: returns an iterator which contains all instances ofstubbles\dates\span\Month
for the yearisLeapYear()
: checks whether year is a leap yearisCurrentYear()
: checks whether year represents the current year
stubbles\dates\span\CustomDatespan
Covers a custom date span starting at the given date at 00:00:00 and ending at the given date at 23:59:59.
// create a span from 2006-04-04 00:00:00 to 2006-04-20 23:59:59 $custom = new CustomDatespan('2006-04-04', '2006-04-20');
Constructor parameters accept all values that are accepted by Date::castFrom()
,
see above. Please note that the time for the start is always set to 00:00:00
and for the end is always set to 23:59:59. It is not possible to change this
to another time.
Integration with bovigo/assert
In case you want to unit test your code and need to test for date equality you
can use bovigo/assert
for the assertions. stubbles/date provides the predicate stubbles\date\assert\equalsDate()
which can be used to check for equality of dates. It can take any argument that
stubbles\date\Date
accepts, and compares the unix timestamp with the actual
value. In case they don't refer to the same point in time the error message will
contain a diff with both dates in human readable form.