Chronos
#######

Chronos provides a zero-dependency collection of extensions to the ``DateTime``
object. In addition to convenience methods, Chronos provides:

* ``Date`` objects for representing calendar dates.
* Immutable date and datetime objects.
* A pluggable translation system. Only English translations are included in the
  library. However, ``cakephp/i18n`` can be used for full language support.

Installation
------------

To install Chronos, you should use ``composer``. From your
application's ROOT directory (where composer.json file is located) run the
following::

    php composer.phar require "cakephp/chronos:^2.0"

Overview
--------

Chronos provides a number of extensions to the DateTime objects provided by PHP.
Chronos provides 5 classes that cover mutable and immutable date/time variants
and extensions to ``DateInterval``.

* ``Cake\Chronos\Chronos`` is an immutable *date and time* object.
* ``Cake\Chronos\Date`` is a immutable *date* object.
* ``Cake\Chronos\MutableDateTime`` is a mutable *date and time* object.
* ``Cake\Chronos\MutableDate`` is a mutable *date* object.
* ``Cake\Chronos\ChronosInterval`` is an extension to the ``DateInterval``
  object.

Lastly, if you want to typehint against Chronos-provided date/time objects you
should use ``Cake\Chronos\ChronosInterface``. All of the date and time objects
implement this interface.

Creating Instances
------------------

There are many ways to get an instance of Chronos or Date. There are a number of
factory methods that work with different argument sets::

    use Cake\Chronos\Chronos;

    $now = Chronos::now();
    $today = Chronos::today();
    $yesterday = Chronos::yesterday();
    $tomorrow = Chronos::tomorrow();

    // Parse relative expressions
    $date = Chronos::parse('+2 days, +3 hours');

    // Date and time integer values.
    $date = Chronos::create(2015, 12, 25, 4, 32, 58);

    // Date or time integer values.
    $date = Chronos::createFromDate(2015, 12, 25);
    $date = Chronos::createFromTime(11, 45, 10);

    // Parse formatted values.
    $date = Chronos::createFromFormat('m/d/Y', '06/15/2015');

Working with Immutable Objects
------------------------------

If you've used PHP's ``DateTime`` objects, you're comfortable with *mutable*
objects. Chronos offers mutable objects, but it also provides *immutable*
objects. Immutable objects create copies of objects each time an object is
modified. Because modifier methods around datetimes are not always transparent,
data can be modified accidentally or without the developer knowing.
Immutable objects prevent accidental changes to
data, and make code free of order-based dependency issues. Immutability
does mean that you will need to remember to replace variables when using
modifiers::

    // This code doesn't work with immutable objects
    $time->addDay(1);
    doSomething($time);
    return $time;

    // This works like you'd expect
    $time = $time->addDay(1);
    $time = doSomething($time);
    return $time;

By capturing the return value of each modification your code will work as
expected. If you ever have an immutable object, and want to create a mutable
one, you can use ``toMutable()``::

    $inplace = $time->toMutable();

Date Objects
------------

PHP only provides a single DateTime object. Representing calendar dates can be
a bit awkward with this class as it includes timezones, and time components that
don't really belong in the concept of a 'day'. Chronos provides a ``Date``
object that allows you to represent dates. The time for date objects is always
fixed to ``00:00:00``, and the timezone is set to the server local timezone. All
formatting/difference methods operate at the day resolution::

    use Cake\Chronos\Date;

    $today = Date::today();

    // Changes to the time/timezone are ignored.
    $today->modify('+1 hours');

    // Outputs '2015-12-20'
    echo $today;

Although ``Date`` uses a fixed time zone internally, you can specify which
time zone to use for current time such as ``now()`` or ``today()``::

    use Cake\Chronos\Date:

    // Takes the current date from Asia/Tokyo time zone
    $today = Date::today('Asia/Tokyo');

Modifier Methods
----------------

Chronos objects provide modifier methods that let you modify the value in
a granular way::

    // Set components of the datetime value.
    $halloween = Chronos::create()
        ->year(2015)
        ->month(10)
        ->day(31)
        ->hour(20)
        ->minute(30);

You can also modify parts of the datetime relatively::

    $future = Chronos::create()
        ->addYear(1)
        ->subMonth(2)
        ->addDays(15)
        ->addHours(20)
        ->subMinutes(2);

It is also possible to make big jumps to defined points in time::

    $time = Chronos::create();
    $time->startOfDay();
    $time->endOfDay();
    $time->startOfMonth();
    $time->endOfMonth();
    $time->startOfYear();
    $time->endOfYear();
    $time->startOfWeek();
    $time->endOfWeek();

Or jump to specific days of the week::

    $time->next(ChronosInterface::TUESDAY);
    $time->previous(ChronosInterface::MONDAY);

When modifying dates/times across :abbr:`DST (Daylight Savings Time)` transitions
your operations may gain/lose an additional hours resulting in hour values that
don't add up. You can avoid these issues by first changing your timezone to
``UTC``, modifying the time::

    // Additional hour gained.
    $time = new Chronos('2014-03-30 00:00:00', 'Europe/London');
    debug($time->modify('+24 hours')); // 2014-03-31 01:00:00

    // First switch to UTC, and modify
    $time = $time->setTimezone('UTC')
        ->modify('+24 hours');

Once you are done modifying the time you can add the original timezone to get
the localized time.

Comparison Methods
------------------

Once you have 2 instances of Chronos date/time objects you can compare them in
a variety of ways::

    // Full suite of comparators exist
    // ne, gt, lt, lte.
    $first->eq($second);
    $first->gte($second);

    // See if the current object is between two others.
    $now->between($start, $end);

    // Find which argument is closest or farthest.
    $now->closest($june, $november);
    $now->farthest($june, $november);

You can also inquire about where a given value falls on the calendar::

    $now->isToday();
    $now->isYesterday();
    $now->isFuture();
    $now->isPast();

    // Check the day of the week
    $now->isWeekend();

    // All other weekday methods exist too.
    $now->isMonday();

You can also find out if a value was within a relative time period::

    $time->wasWithinLast('3 days');
    $time->isWithinNext('3 hours');

Generating Differences
----------------------

In addition to comparing datetimes, calculating differences or deltas between
two values is a common task::

    // Get a DateInterval representing the difference
    $first->diff($second);

    // Get difference as a count of specific units.
    $first->diffInHours($second);
    $first->diffInDays($second);
    $first->diffInWeeks($second);
    $first->diffInYears($second);

You can generate human readable differences suitable for use in a feed or
timeline::

    // Difference from now.
    echo $date->diffForHumans();

    // Difference from another point in time.
    echo $date->diffForHumans($other); // 1 hour ago;

Formatting Strings
------------------

Chronos provides a number of methods for displaying our outputting datetime
objects::

    // Uses the format controlled by setToStringFormat()
    echo $date;

    // Different standard formats
    echo $time->toAtomString();      // 1975-12-25T14:15:16-05:00
    echo $time->toCookieString();    // Thursday, 25-Dec-1975 14:15:16 EST
    echo $time->toIso8601String();   // 1975-12-25T14:15:16-05:00
    echo $time->toRfc822String();    // Thu, 25 Dec 75 14:15:16 -0500
    echo $time->toRfc850String();    // Thursday, 25-Dec-75 14:15:16 EST
    echo $time->toRfc1036String();   // Thu, 25 Dec 75 14:15:16 -0500
    echo $time->toRfc1123String();   // Thu, 25 Dec 1975 14:15:16 -0500
    echo $time->toRfc2822String();   // Thu, 25 Dec 1975 14:15:16 -0500
    echo $time->toRfc3339String();   // 1975-12-25T14:15:16-05:00
    echo $time->toRssString();       // Thu, 25 Dec 1975 14:15:16 -0500
    echo $time->toW3cString();       // 1975-12-25T14:15:16-05:00

    // Get the quarter/week
    echo $time->toQuarter();         // 4
    echo $time->toWeek();            // 52

    // Generic formatting
    echo $time->toTimeString();           // 14:15:16
    echo $time->toDateString();           // 1975-12-25
    echo $time->toDateTimeString();       // 1975-12-25 14:15:16
    echo $time->toFormattedDateString();  // Dec 25, 1975
    echo $time->toDayDateTimeString();    // Thu, Dec 25, 1975 2:15 PM

Extracting Date Components
--------------------------

Getting parts of a date object can be done by directly accessing properties::

    $time = new Chronos('2015-12-31 23:59:58.123');
    $time->year;    // 2015
    $time->month;   // 12
    $time->day;     // 31
    $time->hour     // 23
    $time->minute   // 59
    $time->second   // 58
    $time->micro    // 123

Other properties that can be accessed are:

- timezone
- timezoneName
- dayOfWeek
- dayOfMonth
- dayOfYear
- daysInMonth
- timestamp
- quarter

Testing Aids
------------

When writing unit tests, it is helpful to fixate the current time. Chronos lets
you fix the current time for each class. As part of your test suite's bootstrap
process you can include the following::

    Chronos::setTestNow(Chronos::now());
    MutableDateTime::setTestNow(MutableDateTime::now());
    Date::setTestNow(Date::now());
    MutableDate::setTestNow(MutableDate::now());

This will fix the current time of all objects to be the point at which the test
suite started.

For example, if you fixate the ``Chronos`` to some moment in the past, any new
instance of ``Chronos`` created with ``now`` or a relative time string, will be
returned relative to the fixated time::

    Chronos::setTestNow(new Chronos('1975-12-25 00:00:00'));

    $time = new Chronos(); // 1975-12-25 00:00:00
    $time = new Chronos('1 hour ago'); // 1975-12-24 23:00:00

To reset the fixation, simply call ``setTestNow()`` again with no parameter or
with ``null`` as a parameter.
