├── .phive └── phars.xml ├── Dockerfile ├── LICENSE ├── README.md ├── composer.json ├── docs.Dockerfile ├── docs ├── config │ ├── __init__.py │ └── all.py ├── en │ ├── 3-x-migration-guide.rst │ ├── conf.py │ ├── contents.rst │ └── index.rst ├── fr │ ├── conf.py │ ├── contents.rst │ └── index.rst ├── ja │ ├── conf.py │ ├── contents.rst │ └── index.rst └── pt │ ├── conf.py │ ├── contents.rst │ └── index.rst ├── psalm-baseline.xml ├── psalm.xml └── src ├── Chronos.php ├── ChronosDate.php ├── ChronosTime.php ├── ClockFactory.php ├── DifferenceFormatter.php ├── DifferenceFormatterInterface.php ├── FormattingTrait.php └── Translator.php /.phive/phars.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Basic docker based environment 2 | # Necessary to trick dokku into building the documentation 3 | # using dockerfile instead of herokuish 4 | FROM php:8.1 5 | 6 | WORKDIR /code 7 | 8 | VOLUME ["/code"] 9 | 10 | CMD [ '/bin/bash' ] 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) Brian Nesbitt 2 | Copyright (C) Cake Software Foundation, Inc. (https://cakefoundation.org) 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is furnished 9 | to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CakePHP Chronos 2 | 3 | ![Build Status](https://github.com/cakephp/chronos/actions/workflows/ci.yml/badge.svg?branch=master) 4 | [![Latest Stable Version](https://img.shields.io/github/v/release/cakephp/chronos?sort=semver&style=flat-square)](https://packagist.org/packages/cakephp/chronos) 5 | [![Total Downloads](https://img.shields.io/packagist/dt/cakephp/chronos?style=flat-square)](https://packagist.org/packages/cakephp/chronos/stats) 6 | [![Code Coverage](https://img.shields.io/coveralls/cakephp/chronos/master.svg?style=flat-square)](https://coveralls.io/r/cakephp/chronos?branch=master) 7 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) 8 | 9 | Chronos focuses on providing immutable date/datetime objects. 10 | Immutable objects help ensure that datetime objects aren't accidentally 11 | modified, keeping data more predictable. 12 | 13 | # Installation 14 | 15 | Installing with composer: 16 | 17 | ``` 18 | $ composer require cakephp/chronos 19 | ``` 20 | 21 | For details on the (minimum/maximum) PHP version see [version map](https://github.com/cakephp/chronos/wiki#version-map). 22 | 23 | # Usage 24 | 25 | ```php 26 | modify('+2 hours'); 53 | 54 | // This will keep modifications 55 | $date = new Chronos('2015-10-21 16:29:00'); 56 | $date = $date->modify('+2 hours'); 57 | ``` 58 | 59 | # Calendar Dates 60 | 61 | PHP only offers datetime objects as part of the native extensions. Chronos adds 62 | a number of conveniences to the traditional DateTime object and introduces 63 | a `ChronosDate` object. `ChronosDate` instances their time frozen to `00:00:00` and the timezone 64 | set to the server default timezone. This makes them ideal when working with 65 | calendar dates as the time components will always match. 66 | 67 | ```php 68 | use Cake\Chronos\ChronosDate; 69 | 70 | $today = new ChronosDate(); 71 | echo $today; 72 | // Outputs '2015-10-21' 73 | 74 | echo $today->modify('+3 hours'); 75 | // Outputs '2015-10-21' 76 | ``` 77 | 78 | Like instances of `Chronos`, `ChronosDate` objects are also *immutable*. 79 | 80 | # Documentation 81 | 82 | A more descriptive documentation can be found at [book.cakephp.org/chronos/3/en/](https://book.cakephp.org/chronos/3/en/). 83 | 84 | # API Documentation 85 | 86 | API documentation can be found on [api.cakephp.org/chronos](https://api.cakephp.org/chronos). 87 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cakephp/chronos", 3 | "description": "A simple API extension for DateTime.", 4 | "license": "MIT", 5 | "type": "library", 6 | "keywords": [ 7 | "date", 8 | "time", 9 | "DateTime" 10 | ], 11 | "authors": [ 12 | { 13 | "name": "Brian Nesbitt", 14 | "email": "brian@nesbot.com", 15 | "homepage": "http://nesbot.com" 16 | }, 17 | { 18 | "name": "The CakePHP Team", 19 | "homepage": "https://cakephp.org" 20 | } 21 | ], 22 | "homepage": "https://cakephp.org", 23 | "support": { 24 | "issues": "https://github.com/cakephp/chronos/issues", 25 | "source": "https://github.com/cakephp/chronos" 26 | }, 27 | "require": { 28 | "php": ">=8.1", 29 | "psr/clock": "^1.0" 30 | }, 31 | "require-dev": { 32 | "cakephp/cakephp-codesniffer": "^5.0", 33 | "phpunit/phpunit": "^10.1.0 || ^11.1.3" 34 | }, 35 | "provide": { 36 | "psr/clock-implementation": "1.0" 37 | }, 38 | "autoload": { 39 | "psr-4": { 40 | "Cake\\Chronos\\": "src/" 41 | } 42 | }, 43 | "autoload-dev": { 44 | "psr-4": { 45 | "Cake\\Chronos\\Test\\": "tests/" 46 | } 47 | }, 48 | "config": { 49 | "allow-plugins": { 50 | "dealerdirect/phpcodesniffer-composer-installer": true 51 | } 52 | }, 53 | "scripts": { 54 | "check": [ 55 | "@test", 56 | "@cs-check", 57 | "@stan" 58 | ], 59 | "cs-check": "phpcs --colors --parallel=16 -p", 60 | "cs-fix": "phpcbf --colors --parallel=16 -p", 61 | "phpstan": "tools/phpstan analyse", 62 | "psalm": "tools/psalm --show-info=false", 63 | "psalm-baseline": "tools/psalm --set-baseline=psalm-baseline.xml", 64 | "stan": [ 65 | "@phpstan", 66 | "@psalm" 67 | ], 68 | "stan-baseline": "tools/phpstan --generate-baseline", 69 | "stan-setup": "phive install", 70 | "test": "phpunit" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /docs.Dockerfile: -------------------------------------------------------------------------------- 1 | # Generate the HTML output. 2 | FROM ghcr.io/cakephp/docs-builder as builder 3 | 4 | RUN pip install git+https://github.com/sphinx-contrib/video.git@master 5 | 6 | COPY docs /data/docs 7 | ENV LANGS="en fr ja pt" 8 | 9 | # build docs with sphinx 10 | RUN cd /data/docs-builder && \ 11 | make website LANGS="$LANGS" SOURCE=/data/docs DEST=/data/website 12 | 13 | # Build a small nginx container with just the static site in it. 14 | FROM ghcr.io/cakephp/docs-builder:runtime as runtime 15 | 16 | ENV LANGS="en fr ja pt" 17 | ENV SEARCH_SOURCE="/usr/share/nginx/html" 18 | ENV SEARCH_URL_PREFIX="/chronos/3" 19 | 20 | COPY --from=builder /data/docs /data/docs 21 | COPY --from=builder /data/website /data/website 22 | COPY --from=builder /data/docs-builder/nginx.conf /etc/nginx/conf.d/default.conf 23 | 24 | # Move docs into place. 25 | RUN cp -R /data/website/html/* /usr/share/nginx/html \ 26 | && rm -rf /data/website 27 | -------------------------------------------------------------------------------- /docs/config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/chronos/8187733ac77bb0a2e20e1fb5ca57a8696d013fdd/docs/config/__init__.py -------------------------------------------------------------------------------- /docs/config/all.py: -------------------------------------------------------------------------------- 1 | # Global configuration information used across all the 2 | # translations of documentation. 3 | # 4 | # Import the base theme configuration 5 | from cakephpsphinx.config.all import * 6 | 7 | # The version info for the project you're documenting, acts as replacement for 8 | # |version| and |release|, also used in various other places throughout the 9 | # built documents. 10 | # 11 | 12 | # The full version, including alpha/beta/rc tags. 13 | release = '3.x' 14 | 15 | # The search index version. 16 | search_version = 'chronos-3' 17 | 18 | # The marketing display name for the book. 19 | version_name = '' 20 | 21 | # Project name shown in the black header bar 22 | project = 'Chronos' 23 | 24 | # Other versions that display in the version picker menu. 25 | version_list = [ 26 | {'name': '1.x', 'number': '/chronos/1', 'title': '1.x'}, 27 | {'name': '2.x', 'number': '/chronos/2', 'title': '2.x'}, 28 | {'name': '3.x', 'number': '/chronos/3', 'title': '3.x', 'current': True}, 29 | ] 30 | 31 | # Languages available. 32 | languages = ['en', 'fr', 'ja', 'pt'] 33 | 34 | # The GitHub branch name for this version of the docs 35 | # for edit links to point at. 36 | branch = '3.x' 37 | 38 | # Current version being built 39 | version = '3.x' 40 | 41 | # Language in use for this directory. 42 | language = 'en' 43 | 44 | show_root_link = True 45 | 46 | repository = 'cakephp/chronos' 47 | 48 | source_path = 'docs/' 49 | 50 | hide_page_contents = ('search', '404', 'contents') 51 | -------------------------------------------------------------------------------- /docs/en/3-x-migration-guide.rst: -------------------------------------------------------------------------------- 1 | 3.x Migration Guide 2 | ################### 3 | 4 | Chronos 3.x contains breaking changes that could impact your application. This 5 | guide provides an overview of the breaking changes made in 3.x 6 | 7 | Minimum of PHP 8.1 8 | ================== 9 | 10 | Chronos 3.x requires at least PHP 8.1. This allows chronos to provide more 11 | comprehensive typehinting and better performance by leveraging features found in 12 | newer PHP versions. 13 | 14 | MutableDateTime and MutableDate removed 15 | ======================================= 16 | 17 | The ``MutableDateTime`` and ``MutableDate`` classes have been removed. Long term 18 | PHP will be deprecating and removing mutable datetime classes in favour of 19 | immutable ones. Chronos has long favoured immutable objects and removing the 20 | mutable variants helps simplify the internals of Chronos and encourages safer 21 | development practices. 22 | -------------------------------------------------------------------------------- /docs/en/conf.py: -------------------------------------------------------------------------------- 1 | import sys, os 2 | 3 | # Append the top level directory of the docs, so we can import from the config dir. 4 | sys.path.insert(0, os.path.abspath('..')) 5 | 6 | # Pull in all the configuration options defined in the global config file.. 7 | from config.all import * 8 | 9 | language = 'en' 10 | -------------------------------------------------------------------------------- /docs/en/contents.rst: -------------------------------------------------------------------------------- 1 | .. toctree:: 2 | :maxdepth: 2 3 | :caption: CakePHP Chronos 4 | 5 | /index 6 | 7 | API -------------------------------------------------------------------------------- /docs/en/index.rst: -------------------------------------------------------------------------------- 1 | Chronos 2 | ####### 3 | 4 | Chronos provides a zero-dependency ``DateTimeImmutable`` extension, Date-only and Time-only classes: 5 | 6 | * ``Cake\Chronos\Chronos`` extends ``DateTimeImmutable`` and provides many helpers. 7 | * ``Cake\Chronos\ChronosDate`` represents calendar dates unaffected by time or time zones. 8 | * ``Cake\Chronos\ChronosTime`` represents clock times independent of date or time zones. 9 | * Only safe, immutable objects. 10 | * A pluggable translation system. Only English translations are included in the 11 | library. However, ``cakephp/i18n`` can be used for full language support. 12 | 13 | The ``Chronos`` class extends ``DateTimeImmutable`` and implements ``DateTimeInterface`` 14 | which allows users to use type declarations that support either. 15 | 16 | ``ChronosDate`` and ``ChronosTime`` do not extend ``DateTimeImmutable`` and do not 17 | share an interface. However, they can be converted to a ``DateTimeImmutable`` instance 18 | using ``toDateTimeImmutable()``. 19 | 20 | Installation 21 | ------------ 22 | 23 | To install Chronos, you should use ``composer``. From your 24 | application's ROOT directory (where composer.json file is located) run the 25 | following:: 26 | 27 | php composer.phar require "cakephp/chronos:^3.0" 28 | 29 | Creating Instances 30 | ------------------ 31 | 32 | There are many ways to get an instance of Chronos or Date. There are a number of 33 | factory methods that work with different argument sets:: 34 | 35 | use Cake\Chronos\Chronos; 36 | 37 | $now = Chronos::now(); 38 | $today = Chronos::today(); 39 | $yesterday = Chronos::yesterday(); 40 | $tomorrow = Chronos::tomorrow(); 41 | 42 | // Parse relative expressions 43 | $date = Chronos::parse('+2 days, +3 hours'); 44 | 45 | // Date and time integer values. 46 | $date = Chronos::create(2015, 12, 25, 4, 32, 58); 47 | 48 | // Date or time integer values. 49 | $date = Chronos::createFromDate(2015, 12, 25); 50 | $date = Chronos::createFromTime(11, 45, 10); 51 | 52 | // Parse formatted values. 53 | $date = Chronos::createFromFormat('m/d/Y', '06/15/2015'); 54 | 55 | Working with Immutable Objects 56 | ------------------------------ 57 | 58 | Chronos provides only *immutable* objects. 59 | 60 | If you've used PHP ``DateTimeImmutable`` and ``DateTime`` classes, then you understand 61 | the difference between *mutable* and *immutable* objects. 62 | 63 | Immutable objects create copies of an object each time a change is made. Because modifier methods 64 | around datetimes are not always easy to identify, data can be modified accidentally 65 | or without the developer knowing. Immutable objects prevent accidental changes 66 | to data, and make code free of order-based dependency issues. Immutability does 67 | mean that you will need to remember to replace variables when using modifiers:: 68 | 69 | // This code doesn't work with immutable objects 70 | $chronos->addDay(1); 71 | doSomething($chronos); 72 | return $chronos; 73 | 74 | // This works like you'd expect 75 | $chronos = $chronos->addDay(1); 76 | $chronos = doSomething($chronos); 77 | return $chronos; 78 | 79 | By capturing the return value of each modification your code will work as 80 | expected. 81 | 82 | Date Objects 83 | ------------ 84 | 85 | PHP provides only date-time classes that combines both dates and time parts. 86 | Representing calendar dates can be a bit awkward with ``DateTimeImmutable`` as it includes 87 | time and timezones, which aren't part of a 'date'. Chronos provides 88 | ``ChronosDate`` that allows you to represent dates. The time these objects 89 | these objects is always fixed to ``00:00:00`` and not affeced by the server time zone 90 | or modify helpers:: 91 | 92 | use Cake\Chronos\ChronosDate; 93 | 94 | $today = ChronosDate::today(); 95 | 96 | // Changes to the time/timezone are ignored. 97 | $today->modify('+1 hours'); 98 | 99 | // Outputs '2015-12-20' 100 | echo $today; 101 | 102 | Although ``ChronosDate`` uses a fixed time zone internally, you can specify which 103 | time zone to use for current time such as ``now()`` or ``today()``:: 104 | 105 | use Cake\Chronos\ChronosDate: 106 | 107 | // Takes the current date from Asia/Tokyo time zone 108 | $today = ChronosDate::today('Asia/Tokyo'); 109 | 110 | Modifier Methods 111 | ---------------- 112 | 113 | Chronos objects provide modifier methods that let you modify the value in 114 | a granular way:: 115 | 116 | // Set components of the datetime value. 117 | $halloween = Chronos::create() 118 | ->year(2015) 119 | ->month(10) 120 | ->day(31) 121 | ->hour(20) 122 | ->minute(30); 123 | 124 | You can also modify parts of the datetime relatively:: 125 | 126 | $future = Chronos::create() 127 | ->addYears(1) 128 | ->subMonths(2) 129 | ->addDays(15) 130 | ->addHours(20) 131 | ->subMinutes(2); 132 | 133 | It is also possible to make big jumps to defined points in time:: 134 | 135 | $time = Chronos::create(); 136 | $time->startOfDay(); 137 | $time->endOfDay(); 138 | $time->startOfMonth(); 139 | $time->endOfMonth(); 140 | $time->startOfYear(); 141 | $time->endOfYear(); 142 | $time->startOfWeek(); 143 | $time->endOfWeek(); 144 | 145 | Or jump to specific days of the week:: 146 | 147 | $time->next(Chronos::TUESDAY); 148 | $time->previous(Chronos::MONDAY); 149 | 150 | When modifying dates/times across :abbr:`DST (Daylight Savings Time)` transitions 151 | your operations may gain/lose an additional hours resulting in hour values that 152 | don't add up. You can avoid these issues by first changing your timezone to 153 | ``UTC``, modifying the time:: 154 | 155 | // Additional hour gained. 156 | $time = new Chronos('2014-03-30 00:00:00', 'Europe/London'); 157 | debug($time->modify('+24 hours')); // 2014-03-31 01:00:00 158 | 159 | // First switch to UTC, and modify 160 | $time = $time->setTimezone('UTC') 161 | ->modify('+24 hours'); 162 | 163 | Once you are done modifying the time you can add the original timezone to get 164 | the localized time. 165 | 166 | Comparison Methods 167 | ------------------ 168 | 169 | Once you have 2 instances of Chronos date/time objects you can compare them in 170 | a variety of ways:: 171 | 172 | // Full suite of comparators exist 173 | // equals, notEquals, greaterThan, greaterThanOrEquals, lessThan, lessThanOrEquals 174 | $first->equals($second); 175 | $first->greaterThanOrEquals($second); 176 | 177 | // See if the current object is between two others. 178 | $now->between($start, $end); 179 | 180 | // Find which argument is closest or farthest. 181 | $now->closest($june, $november); 182 | $now->farthest($june, $november); 183 | 184 | You can also inquire about where a given value falls on the calendar:: 185 | 186 | $now->isToday(); 187 | $now->isYesterday(); 188 | $now->isFuture(); 189 | $now->isPast(); 190 | 191 | // Check the day of the week 192 | $now->isWeekend(); 193 | 194 | // All other weekday methods exist too. 195 | $now->isMonday(); 196 | 197 | You can also find out if a value was within a relative time period:: 198 | 199 | $time->wasWithinLast('3 days'); 200 | $time->isWithinNext('3 hours'); 201 | 202 | Generating Differences 203 | ---------------------- 204 | 205 | In addition to comparing datetimes, calculating differences or deltas between 206 | two values is a common task:: 207 | 208 | // Get a DateInterval representing the difference 209 | $first->diff($second); 210 | 211 | // Get difference as a count of specific units. 212 | $first->diffInHours($second); 213 | $first->diffInDays($second); 214 | $first->diffInWeeks($second); 215 | $first->diffInYears($second); 216 | 217 | You can generate human readable differences suitable for use in a feed or 218 | timeline:: 219 | 220 | // Difference from now. 221 | echo $date->diffForHumans(); 222 | 223 | // Difference from another point in time. 224 | echo $date->diffForHumans($other); // 1 hour ago; 225 | 226 | Formatting Strings 227 | ------------------ 228 | 229 | Chronos provides a number of methods for displaying our outputting datetime 230 | objects:: 231 | 232 | // Uses the format controlled by setToStringFormat() 233 | echo $date; 234 | 235 | // Different standard formats 236 | echo $time->toAtomString(); // 1975-12-25T14:15:16-05:00 237 | echo $time->toCookieString(); // Thursday, 25-Dec-1975 14:15:16 EST 238 | echo $time->toIso8601String(); // 1975-12-25T14:15:16-05:00 239 | echo $time->toRfc822String(); // Thu, 25 Dec 75 14:15:16 -0500 240 | echo $time->toRfc850String(); // Thursday, 25-Dec-75 14:15:16 EST 241 | echo $time->toRfc1036String(); // Thu, 25 Dec 75 14:15:16 -0500 242 | echo $time->toRfc1123String(); // Thu, 25 Dec 1975 14:15:16 -0500 243 | echo $time->toRfc2822String(); // Thu, 25 Dec 1975 14:15:16 -0500 244 | echo $time->toRfc3339String(); // 1975-12-25T14:15:16-05:00 245 | echo $time->toRssString(); // Thu, 25 Dec 1975 14:15:16 -0500 246 | echo $time->toW3cString(); // 1975-12-25T14:15:16-05:00 247 | 248 | // Get the quarter/week 249 | echo $time->toQuarter(); // 4 250 | echo $time->toWeek(); // 52 251 | 252 | // Generic formatting 253 | echo $time->toTimeString(); // 14:15:16 254 | echo $time->toDateString(); // 1975-12-25 255 | echo $time->toDateTimeString(); // 1975-12-25 14:15:16 256 | echo $time->toFormattedDateString(); // Dec 25, 1975 257 | echo $time->toDayDateTimeString(); // Thu, Dec 25, 1975 2:15 PM 258 | 259 | Extracting Date Components 260 | -------------------------- 261 | 262 | Getting parts of a date object can be done by directly accessing properties:: 263 | 264 | $time = new Chronos('2015-12-31 23:59:58.123'); 265 | $time->year; // 2015 266 | $time->month; // 12 267 | $time->day; // 31 268 | $time->hour // 23 269 | $time->minute // 59 270 | $time->second // 58 271 | $time->micro // 123 272 | 273 | Other properties that can be accessed are: 274 | 275 | - timezone 276 | - timezoneName 277 | - dayOfWeek 278 | - dayOfMonth 279 | - dayOfYear 280 | - daysInMonth 281 | - timestamp 282 | - quarter 283 | - half 284 | 285 | Testing Aids 286 | ------------ 287 | 288 | When writing unit tests, it is helpful to fixate the current time. Chronos lets 289 | you fix the current time for each class. As part of your test suite's bootstrap 290 | process you can include the following:: 291 | 292 | Chronos::setTestNow(Chronos::now()); 293 | ChronosDate::setTestNow(ChronosDate::parse(Chronos::now())); 294 | 295 | This will fix the current time of all objects to be the point at which the test 296 | suite started. 297 | 298 | For example, if you fixate the ``Chronos`` to some moment in the past, any new 299 | instance of ``Chronos`` created with ``now`` or a relative time string, will be 300 | returned relative to the fixated time:: 301 | 302 | Chronos::setTestNow(new Chronos('1975-12-25 00:00:00')); 303 | 304 | $time = new Chronos(); // 1975-12-25 00:00:00 305 | $time = new Chronos('1 hour ago'); // 1975-12-24 23:00:00 306 | 307 | To reset the fixation, simply call ``setTestNow()`` again with no parameter or 308 | with ``null`` as a parameter. 309 | -------------------------------------------------------------------------------- /docs/fr/conf.py: -------------------------------------------------------------------------------- 1 | import sys, os 2 | 3 | # Append the top level directory of the docs, so we can import from the config dir. 4 | sys.path.insert(0, os.path.abspath('..')) 5 | 6 | # Pull in all the configuration options defined in the global config file.. 7 | from config.all import * 8 | 9 | language = 'fr' 10 | -------------------------------------------------------------------------------- /docs/fr/contents.rst: -------------------------------------------------------------------------------- 1 | .. toctree:: 2 | :maxdepth: 2 3 | :caption: CakePHP Chronos 4 | 5 | /index 6 | 7 | API -------------------------------------------------------------------------------- /docs/fr/index.rst: -------------------------------------------------------------------------------- 1 | Chronos 2 | ####### 3 | 4 | Chronos fournit une collection d'extensions sans aucune dépendance pour l'objet 5 | ``DateTime``. En plus de méthodes pratiques, Chronos fournit: 6 | 7 | * Des objets ``Date`` pour représenter les dates du calendrier. 8 | * Des objets immutables pour les dates et les datetimes. 9 | * Un système de traduction intégrable. Seules les traductions anglaises sont 10 | incluses dans la librairie. Cependant, ``cakephp/i18n`` peut être utilisé 11 | pour un support complet d'autres langues. 12 | 13 | Installation 14 | ------------ 15 | 16 | Pour installer Chronos, vous devez utiliser ``composer``. À partir du répertoire 17 | ROOT de votre application (celui où se trouve le fichier composer.json), 18 | exécutez ce qui suit:: 19 | 20 | php composer.phar require "cakephp/chronos:^2.0" 21 | 22 | Vue d'Ensemble 23 | -------------- 24 | 25 | Chronos fournit un certain nombre d'extensions pour les objets DateTime fournis 26 | par PHP. Chronos fournit 5 classes qui gèrent les variantes mutables et 27 | immutables de date/time et les extensions de ``DateInterval``. 28 | 29 | * ``Cake\Chronos\Chronos`` est un objet de *date et heure* immutable. 30 | * ``Cake\Chronos\ChronosDate`` est un objet de *date* immutable. 31 | * ``Cake\Chronos\MutableDateTime`` est un objet de *date et heure* mutable. 32 | * ``Cake\Chronos\MutableDate`` est un objet de *date* mutable. 33 | * ``Cake\Chronos\ChronosInterval`` est une extension pour l'objet 34 | ``DateInterval``. 35 | 36 | Créer des Instances 37 | ------------------- 38 | 39 | Il y a plusieurs façons d'obtenir une instance de Chronos ou de Date. Il y a 40 | un certain nombre de méthodes factory qui fonctionnent avec différents ensembles 41 | d'arguments:: 42 | 43 | use Cake\Chronos\Chronos; 44 | 45 | $now = Chronos::now(); 46 | $today = Chronos::today(); 47 | $yesterday = Chronos::yesterday(); 48 | $tomorrow = Chronos::tomorrow(); 49 | 50 | // Parse les expressions relatives 51 | $date = Chronos::parse('+2 days, +3 hours'); 52 | 53 | // Des entiers indiquant la date et l'heure. 54 | $date = Chronos::create(2015, 12, 25, 4, 32, 58); 55 | 56 | // Des entiers indiquant la date ou l'heure. 57 | $date = Chronos::createFromDate(2015, 12, 25); 58 | $date = Chronos::createFromTime(11, 45, 10); 59 | 60 | // Parse les valeurs formatées. 61 | $date = Chronos::createFromFormat('m/d/Y', '06/15/2015'); 62 | 63 | Travailler avec les Objets Immutables 64 | ------------------------------------- 65 | 66 | Si vous avez utilisé les objets ``DateTime`` de PHP, vous êtes à l'aise avec 67 | les objets *mutable*. Chronos offre des objets mutables, mais elle fournit 68 | également des objets *immutables*. Les objets Immutables créent des copies des 69 | objets à chaque fois qu'un objet est modifié. Puisque les méthodes de 70 | modification autour des datetimes ne sont pas toujours transparentes, les 71 | données peuvent être modifiées accidentellement ou sans que le développeur ne 72 | le sache. Les objets immutables évitent les changements accidentels des 73 | données et permettent de s'affranchir de tout problème lié à l'ordre d'appel 74 | des fonctions ou des dépendances. L'immutabilité signifie que vous devez vous 75 | souvenir de remplacer les variables quand vous utilisez les modificateurs:: 76 | 77 | // Ce code ne fonctionne pas avec les objets immutables 78 | $time->addDay(1); 79 | doSomething($time); 80 | return $time; 81 | 82 | // Ceci fonctionne comme vous le souhaitez 83 | $time = $time->addDay(1); 84 | $time = doSomething($time); 85 | return $time; 86 | 87 | En capturant la valeur de retour pour chaque modification, votre code 88 | fonctionnera comme souhaité. Si vous avez déjà créé un objet immutable, et que 89 | vous souhaitez un objet mutable, vous pouvez utiliser ``toMutable()``:: 90 | 91 | $inplace = $time->toMutable(); 92 | 93 | Objets Date 94 | ----------- 95 | 96 | PHP fournit seulement un unique objet DateTime. Représenter les dates de 97 | calendrier peut être un peu gênant avec cette classe puisqu'elle inclut les 98 | timezones, et les composants de time qui n'appartiennent pas vraiment 99 | au concept d'un 'jour'. Chronos fournit un objet ``Date`` qui vous permet 100 | de représenter les dates. Les time et timezone pour ces objets sont toujours 101 | fixés à ``00:00:00 UTC`` et toutes les méthodes de formatage/différence 102 | fonctionnent au niveau du jour:: 103 | 104 | use Cake\Chronos\ChronosDate; 105 | 106 | $today = ChronosDate::today(); 107 | 108 | // Les changements selon le time/timezone sont ignorés. 109 | $today->modify('+1 hours'); 110 | 111 | // Affiche '2015-12-20' 112 | echo $today; 113 | 114 | Bien que ``Date`` utilise en interne un fuseau horaire fixe, vous pouvez 115 | spécifier le fuseau à utiliser pour l'heure courante telle que ``now()`` ou 116 | ``today()``:: 117 | 118 | use Cake\Chronos\ChronosDate: 119 | 120 | // Prend l'heure courante pour le fuseau horaire de Tokyo 121 | $today = ChronosDate::today('Asia/Tokyo'); 122 | 123 | 124 | Méthodes de Modification 125 | ------------------------ 126 | 127 | Les objets Chronos fournissent des méthodes de modification qui vous laissent 128 | modifier la valeur d'une façon assez précise:: 129 | 130 | // Définit les composants de la valeur du datetime. 131 | $halloween = Chronos::create() 132 | ->year(2015) 133 | ->month(10) 134 | ->day(31) 135 | ->hour(20) 136 | ->minute(30); 137 | 138 | Vous pouvez aussi modifier les parties de la date de façon relative:: 139 | 140 | $future = Chronos::create() 141 | ->addYear(1) 142 | ->subMonth(2) 143 | ->addDays(15) 144 | ->addHours(20) 145 | ->subMinutes(2); 146 | 147 | Il est également possible de faire des sauts vers des points définis dans le 148 | temps:: 149 | 150 | $time = Chronos::create(); 151 | $time->startOfDay(); 152 | $time->endOfDay(); 153 | $time->startOfMonth(); 154 | $time->endOfMonth(); 155 | $time->startOfYear(); 156 | $time->endOfYear(); 157 | $time->startOfWeek(); 158 | $time->endOfWeek(); 159 | 160 | Ou de sauter à un jour spécifique de la semaine:: 161 | 162 | $time->next(Chronos::TUESDAY); 163 | $time->previous(Chronos::MONDAY); 164 | 165 | Quand vous modifiez des dates/heures au-delà d'un passage à l'heure d'été ou à 166 | l'heure d'hiver, vous opérations peuvent gagner/perdre une heure de plus, de 167 | sorte que les heures seront incorrectes. Vous pouvez éviter ce problème en 168 | définissant d'abord le timezone à ``UTC``, ce qui change l'heure:: 169 | 170 | // Une heure de plus de gagnée. 171 | $time = new Chronos('2014-03-30 00:00:00', 'Europe/London'); 172 | debug($time->modify('+24 hours')); // 2014-03-31 01:00:00 173 | 174 | // Passez d'abord à UTC, et modifiez ensuite 175 | $time = $time->setTimezone('UTC') 176 | ->modify('+24 hours'); 177 | 178 | Une fois que vous avez modifié l'heure, vous pouvez repasser au timezone 179 | d'origine pour obtenir l'heure locale. 180 | 181 | Méthodes de Comparaison 182 | ----------------------- 183 | 184 | Une fois que vous avez 2 instances d'objets date/time de Chronos, vous pouvez 185 | les comparer de plusieurs façons:: 186 | 187 | // Il existe une suite complète de comparateurs 188 | // equals, notEquals, greaterThan, greaterThanOrEquals, lessThan, lessThanOrEquals 189 | $first->equals($second); 190 | $first->greaterThanOrEquals($second); 191 | 192 | // Regarder si l'objet courant est entre deux autres. 193 | $now->between($start, $end); 194 | 195 | // Trouver l'argument le plus proche ou le plus éloigné. 196 | $now->closest($june, $november); 197 | $now->farthest($june, $november); 198 | 199 | Vous pouvez aussi vous renseigner sur le moment où une valeur donnée tombe dans 200 | le calendrier:: 201 | 202 | $now->isToday(); 203 | $now->isYesterday(); 204 | $now->isFuture(); 205 | $now->isPast(); 206 | 207 | // Vérifie le jour de la semaine 208 | $now->isWeekend(); 209 | 210 | // Toutes les autres méthodes des jours de la semaine existent aussi. 211 | $now->isMonday(); 212 | 213 | Vous pouvez aussi trouver si une valeur était dans une période de temps relative:: 214 | 215 | $time->wasWithinLast('3 days'); 216 | $time->isWithinNext('3 hours'); 217 | 218 | Générer des Différences 219 | ----------------------- 220 | 221 | En plus de comparer les datetimes, calculer les différences ou les deltas entre 222 | des valeurs est une tâche courante:: 223 | 224 | // Récupère un DateInterval représentant la différence 225 | $first->diff($second); 226 | 227 | // Récupère la différence en tant que nombre d'unités spécifiques. 228 | $first->diffInHours($second); 229 | $first->diffInDays($second); 230 | $first->diffInWeeks($second); 231 | $first->diffInYears($second); 232 | 233 | Vous pouvez générer des différences lisibles qui peuvent vous servir pour 234 | l'utilisation d'un feed ou d'une timeline:: 235 | 236 | // Différence à partir de maintenant. 237 | echo $date->diffForHumans(); 238 | 239 | // Différence à partir d'un autre point du temps. 240 | echo $date->diffForHumans($other); // 1 hour ago; 241 | 242 | Formater les Chaînes 243 | -------------------- 244 | 245 | Chronos fournit un certain nombre de méthodes pour afficher nos sorties d'objets 246 | datetime:: 247 | 248 | // Utilise le format contrôlé par setToStringFormat() 249 | echo $date; 250 | 251 | // Différents formats standards 252 | echo $time->toAtomString(); // 1975-12-25T14:15:16-05:00 253 | echo $time->toCookieString(); // Thursday, 25-Dec-1975 14:15:16 EST 254 | echo $time->toIso8601String(); // 1975-12-25T14:15:16-05:00 255 | echo $time->toRfc822String(); // Thu, 25 Dec 75 14:15:16 -0500 256 | echo $time->toRfc850String(); // Thursday, 25-Dec-75 14:15:16 EST 257 | echo $time->toRfc1036String(); // Thu, 25 Dec 75 14:15:16 -0500 258 | echo $time->toRfc1123String(); // Thu, 25 Dec 1975 14:15:16 -0500 259 | echo $time->toRfc2822String(); // Thu, 25 Dec 1975 14:15:16 -0500 260 | echo $time->toRfc3339String(); // 1975-12-25T14:15:16-05:00 261 | echo $time->toRssString(); // Thu, 25 Dec 1975 14:15:16 -0500 262 | echo $time->toW3cString(); // 1975-12-25T14:15:16-05:00 263 | 264 | // Récupère le trimestre 265 | echo $time->toQuarter(); // 4; 266 | // Récupère la semaine 267 | echo $time->toWeek(); // 52; 268 | 269 | // Formatage générique 270 | echo $time->toTimeString(); // 14:15:16 271 | echo $time->toDateString(); // 1975-12-25 272 | echo $time->toDateTimeString(); // 1975-12-25 14:15:16 273 | echo $time->toFormattedDateString(); // Dec 25, 1975 274 | echo $time->toDayDateTimeString(); // Thu, Dec 25, 1975 2:15 PM 275 | 276 | Extraire des Fragments de Date 277 | ------------------------------ 278 | 279 | Il est possible de récupérer des parties d'un objet date en accédant directement 280 | à ses propriétés:: 281 | 282 | $time = new Chronos('2015-12-31 23:59:58.123'); 283 | $time->year; // 2015 284 | $time->month; // 12 285 | $time->day; // 31 286 | $time->hour // 23 287 | $time->minute // 59 288 | $time->second // 58 289 | $time->micro // 123 290 | 291 | Les autres propriétés accessibles sont: 292 | 293 | - timezone 294 | - timezoneName 295 | - dayOfWeek 296 | - dayOfMonth 297 | - dayOfYear 298 | - daysInMonth 299 | - timestamp 300 | - quarter 301 | - half 302 | 303 | Aides aux Tests 304 | --------------- 305 | 306 | Quand vous écrivez des tests unitaires, il peut être utile de fixer le *time* 307 | courant. Chronos vous permet de fixer le time courant pour chaque classe. 308 | Pour l'intégrer dans votre processus de démarrage (bootstrap) de suite de tests, 309 | vous pouvez inclure ce qui suit:: 310 | 311 | Chronos::setTestNow(Chronos::now()); 312 | MutableDateTime::setTestNow(MutableDateTime::now()); 313 | ChronosDate::setTestNow(ChronosDate::parse(Chronos::now())); 314 | MutableDate::setTestNow(MutableDate::now()); 315 | 316 | Ceci va fixer le time courant de tous les objets selon le moment où la suite de 317 | tests a démarré. 318 | 319 | Par exemple, si vous fixez le ``Chronos`` à un moment du passé, chaque nouvelle 320 | instance de ``Chronos`` créée avec ``now`` ou une chaine de temps relative, sera 321 | retournée relativement à la date fixée:: 322 | 323 | Chronos::setTestNow(new Chronos('1975-12-25 00:00:00')); 324 | 325 | $time = new Chronos(); // 1975-12-25 00:00:00 326 | $time = new Chronos('1 hour ago'); // 1975-12-24 23:00:00 327 | 328 | Pour réinitialiser la "fixation" du temps, appelez simplement ``setTestNow()`` 329 | sans paramètre ou avec ``null`` comme paramètre. 330 | -------------------------------------------------------------------------------- /docs/ja/conf.py: -------------------------------------------------------------------------------- 1 | import sys, os 2 | 3 | # Append the top level directory of the docs, so we can import from the config dir. 4 | sys.path.insert(0, os.path.abspath('..')) 5 | 6 | # Pull in all the configuration options defined in the global config file.. 7 | from config.all import * 8 | 9 | language = 'ja' 10 | -------------------------------------------------------------------------------- /docs/ja/contents.rst: -------------------------------------------------------------------------------- 1 | .. toctree:: 2 | :maxdepth: 2 3 | :caption: CakePHP Chronos 4 | 5 | /index 6 | 7 | API -------------------------------------------------------------------------------- /docs/ja/index.rst: -------------------------------------------------------------------------------- 1 | Chronos 2 | ####### 3 | 4 | Chronos (クロノス) は、 ``DateTime`` オブジェクトへの拡張の依存関係の無いコレクションを提供します。 5 | 便利なメソッドに加えて、Chronos は以下を提供します。 6 | 7 | * カレンダー日付のための ``Date`` オブジェクト 8 | * イミュータブルな日付と日時オブジェクト 9 | * プラグインのような翻訳システム。ライブラリーは英語のみの翻訳を含んでいます。 10 | しかし、全ての言語サポートのために、 ``cakephp/i18n`` を使うことができます。 11 | 12 | インストール 13 | ------------ 14 | 15 | Chronos をインストールするためには、 ``composer`` を利用することができます。 16 | アプリケーションの ROOT ディレクトリー(composer.json ファイルのある場所) 17 | で以下のように実行します。 :: 18 | 19 | php composer.phar require cakephp/chronos "@stable" 20 | 21 | 概要 22 | ---- 23 | 24 | Chronos は PHP が提供する DateTime オブジェクトのいくつかの拡張を提供します。 25 | Chronos は ``DateInterval`` の拡張機能および、ミュータブル(変更可能)と 26 | イミュータブル(変更不可)な 日付/時刻 の派生系をカバーする5つのクラスを提供します。 27 | 28 | * ``Cake\Chronos\Chronos`` はイミュータブルな *日付と時刻* オブジェクト。 29 | * ``Cake\Chronos\ChronosDate`` はイミュータブルな *日付* オブジェクト。 30 | * ``Cake\Chronos\MutableDateTime`` はミュータブルな *日付と時刻* オブジェクト。 31 | * ``Cake\Chronos\MutableDate`` はミュータブルな *日付* オブジェクト。 32 | * ``Cake\Chronos\ChronosInterval`` は ``DateInterval`` の拡張機能。 33 | 34 | インスタンスの作成 35 | ------------------ 36 | 37 | Chronos または Date のインスタンスを取得するためには、多くの方法があります。 38 | 異なる引数セットで動作する多くのファクトリーメソッドがあります。 :: 39 | 40 | use Cake\Chronos\Chronos; 41 | 42 | $now = Chronos::now(); 43 | $today = Chronos::today(); 44 | $yesterday = Chronos::yesterday(); 45 | $tomorrow = Chronos::tomorrow(); 46 | 47 | // 相対式のパース 48 | $date = Chronos::parse('+2 days, +3 hours'); 49 | 50 | // 日付と時間の整数値 51 | $date = Chronos::create(2015, 12, 25, 4, 32, 58); 52 | 53 | // 日付または時間の整数値 54 | $date = Chronos::createFromDate(2015, 12, 25); 55 | $date = Chronos::createFromTime(11, 45, 10); 56 | 57 | // 整形した値にパース 58 | $date = Chronos::createFromFormat('m/d/Y', '06/15/2015'); 59 | 60 | イミュータブルオブジェクトの動作 61 | -------------------------------- 62 | 63 | もしあなたが、PHP の ``DateTime`` オブジェクトを使用したことがあるなら、 64 | *ミュータブル* オブジェクトは簡単に使用できます。 65 | Chronos はミュータブルオブジェクトを提供しますが、これは *イミュータブル* オブジェクトにもなります。 66 | イミュータブルオブジェクトはオブジェクトが変更されるたびにオブジェクトのコピーを作ります。 67 | なぜなら、日時周りの変更メソッドは必ずしも透明でないため、データが誤って、 68 | または開発者が知らない内に変更してしまうからです。 69 | イミュータブルオブジェクトはデータが誤って変更されることを防止し、 70 | 順序ベースの依存関係の問題の無いコードを作ります。 71 | 不変性は、変更時に忘れずに変数を置き換える必要があることを意味しています。 :: 72 | 73 | // このコードはイミュータブルオブジェクトでは動作しません 74 | $time->addDay(1); 75 | doSomething($time); 76 | return $time 77 | 78 | // このコードは期待通りに動作します 79 | $time = $time->addDay(1); 80 | $time = doSomething($time); 81 | return $time 82 | 83 | 各修正の戻り値をキャプチャーすることによって、コードは期待通りに動作します。 84 | イミュータブルオブジェクトを持っていて、ミュータブルオブジェクトを作りたい場合、 85 | ``toMutable()`` が使用できます。 :: 86 | 87 | $inplace = $time->toMutable(); 88 | 89 | 日付オブジェクト 90 | ------------------ 91 | 92 | PHP は単純な DateTime オブジェクトだけを提供します。このクラスのカレンダー日付の表現で、 93 | タイムゾーンおよび、本当に「日」の概念に属していないタイムコンポーネントを含むと、 94 | 少し厄介な可能性があります。 95 | Chronos は日時表現のための ``Date`` オブジェクトを提供します。 96 | これらのオブジェクトの時間とタイムゾーンは常に ``00:00:00 UTC`` に固定されており、 97 | 全ての書式/差分のメソッドは一日単位で動作します。 :: 98 | 99 | use Cake\Chronos\ChronosDate; 100 | 101 | $today = ChronosDate::today(); 102 | 103 | // 時間/タイムゾーンの変更は無視されます 104 | $today->modify('+1 hours'); 105 | 106 | // 出力 '2015-12-20' 107 | echo $today; 108 | 109 | 変更メソッド 110 | ------------ 111 | 112 | Chronos オブジェクトは細やかに値を変更できるメソッドを提供します。 :: 113 | 114 | // 日時の値のコンポーネントを設定 115 | $halloween = Chronos::create() 116 | ->year(2015) 117 | ->month(10) 118 | ->day(31) 119 | ->hour(20) 120 | ->minute(30); 121 | 122 | また、日時の部分を相対的に変更することもできます。 :: 123 | 124 | $future = Chronos::create() 125 | ->addYear(1) 126 | ->subMonth(2) 127 | ->addDays(15) 128 | ->addHours(20) 129 | ->subMinutes(2); 130 | 131 | また、ある時間の中で、定義された時点に飛ぶことも可能です。 :: 132 | 133 | $time = Chronos::create(); 134 | $time->startOfDay(); 135 | $time->endOfDay(); 136 | $time->startOfMonth(); 137 | $time->endOfMonth(); 138 | $time->startOfYear(); 139 | $time->endOfYear(); 140 | $time->startOfWeek(); 141 | $time->endOfWeek(); 142 | 143 | また、1週間中の特定の日にも飛べます。 :: 144 | 145 | $time->next(Chronos::TUESDAY); 146 | $time->previous(Chronos::MONDAY); 147 | 148 | :abbr:`DST (夏時間)` の遷移の前後で日付/時間を変更すると、 149 | あなたの操作で時間が増減するかもしれませんが、その結果、意図しない時間の値になります。 150 | これらの問題を回避するには、最初にタイムゾーンを ``UTC`` に変更し、時間を変更します。 :: 151 | 152 | // 余分な時間が追加されました 153 | $time = new Chronos('2014-03-30 00:00:00', 'Europe/London'); 154 | debug($time->modify('+24 hours')); // 2014-03-31 01:00:00 155 | 156 | // 最初に UTC に切り替え、そして更新 157 | $time = $time->setTimezone('UTC') 158 | ->modify('+24 hours'); 159 | 160 | 時間を変更すると、元のタイムゾーンを追加してローカライズされた時間を取得することができます。 161 | 162 | 比較メソッド 163 | ------------ 164 | 165 | Chronos の日付/時間オブジェクトの2つのインスタンスを様々な方法で比較することができます。 :: 166 | 167 | // 比較のフルセットが存在します 168 | // equals, notEquals, greaterThan, greaterThanOrEquals, lessThan, lessThanOrEquals 169 | $first->equals($second); 170 | $first->greaterThanOrEquals($second); 171 | 172 | // カレントオブジェクトが2つのオブジェクトの間にあるかどうかを確認します。 173 | $now->between($start, $end); 174 | 175 | // どちらの引数が最も近い (closest) か、または最も遠い (farthest) かを見つけます。 176 | $now->closest($june, $november); 177 | $now->farthest($june, $november); 178 | 179 | また、与えられた値のカレンダーに当たる場所について問い合わせできます。 :: 180 | 181 | $now->isToday(); 182 | $now->isYesterday(); 183 | $now->isFuture(); 184 | $now->isPast(); 185 | 186 | // 曜日をチェック 187 | $now->isWeekend(); 188 | 189 | // 他の曜日のメソッドも全て存在します。 190 | $now->isMonday(); 191 | 192 | また、値が相対的な期間内にあったかどうかを見つけることができます。 :: 193 | 194 | $time->wasWithinLast('3 days'); 195 | $time->isWithinNext('3 hours'); 196 | 197 | 差の生成 198 | -------- 199 | 200 | 日時比較に加えて、2つの値の差や変化の計算は一般的なタスクです。 :: 201 | 202 | // 差をあらわす DateInterval を取得 203 | $first->diff($second); 204 | 205 | // 特定の単位での差を取得 206 | $first->diffInHours($second); 207 | $first->diffInDays($second); 208 | $first->diffInWeeks($second); 209 | $first->diffInYears($second); 210 | 211 | フィードやタイムラインで使用するのに適した、人が読める形式の差を生成することができます。 :: 212 | 213 | // 現在からの差 214 | echo $date->diffForHumans(); 215 | 216 | // 別の時点からの差 217 | echo $date->diffForHumans($other); // 1時間前; 218 | 219 | フォーマットの設定 220 | ------------------ 221 | 222 | Chronos は、出力した日時オブジェクトを表示するための多くのメソッドを提供します。 :: 223 | 224 | // setToStringFormat() が制御するフォーマットを使用します 225 | echo $date; 226 | 227 | // 別の標準フォーマット 228 | echo $time->toAtomString(); // 1975-12-25T14:15:16-05:00 229 | echo $time->toCookieString(); // Thursday, 25-Dec-1975 14:15:16 EST 230 | echo $time->toIso8601String(); // 1975-12-25T14:15:16-05:00 231 | echo $time->toRfc822String(); // Thu, 25 Dec 75 14:15:16 -0500 232 | echo $time->toRfc850String(); // Thursday, 25-Dec-75 14:15:16 EST 233 | echo $time->toRfc1036String(); // Thu, 25 Dec 75 14:15:16 -0500 234 | echo $time->toRfc1123String(); // Thu, 25 Dec 1975 14:15:16 -0500 235 | echo $time->toRfc2822String(); // Thu, 25 Dec 1975 14:15:16 -0500 236 | echo $time->toRfc3339String(); // 1975-12-25T14:15:16-05:00 237 | echo $time->toRssString(); // Thu, 25 Dec 1975 14:15:16 -0500 238 | echo $time->toW3cString(); // 1975-12-25T14:15:16-05:00 239 | 240 | // クォーター/週数を取得 241 | echo $time->toQuarter(); // 4; 242 | echo $time->toWeek(); // 52 243 | 244 | // 一般的なフォーマット 245 | echo $time->toTimeString(); // 14:15:16 246 | echo $time->toDateString(); // 1975-12-25 247 | echo $time->toDateTimeString(); // 1975-12-25 14:15:16 248 | echo $time->toFormattedDateString(); // Dec 25, 1975 249 | echo $time->toDayDateTimeString(); // Thu, Dec 25, 1975 2:15 PM 250 | 251 | 日付要素の抽出 252 | -------------- 253 | 254 | 日付オブジェクトのプロパティーに直接アクセスして要素を取得することができます。 :: 255 | 256 | $time = new Chronos('2015-12-31 23:59:58'); 257 | $time->year; // 2015 258 | $time->month; // 12 259 | $time->day; // 31 260 | $time->hour // 23 261 | $time->minute // 59 262 | $time->second // 58 263 | 264 | 以下のプロパティーにもアクセスできます。 : 265 | 266 | - timezone 267 | - timezoneName 268 | - micro 269 | - dayOfWeek 270 | - dayOfMonth 271 | - dayOfYear 272 | - daysInMonth 273 | - timestamp 274 | - quarter 275 | - half 276 | 277 | テストの支援 278 | ------------ 279 | 280 | 単体テストを書いている時、現在時刻を固定すると便利です。Chronos は、 281 | 各クラスの現在時刻を修正することができます。 282 | テストスイートの bootstrap 処理に以下を含めることができます。 :: 283 | 284 | Chronos::setTestNow(Chronos::now()); 285 | MutableDateTime::setTestNow(MutableDateTime::now()); 286 | ChronosDate::setTestNow(ChronosDate::parse(Chronos::now())); 287 | MutableDate::setTestNow(MutableDate::now()); 288 | 289 | これでテストスイートが開始された時点で全てのオブジェクトの現在時刻を修正します。 290 | 291 | 例えば、 ``Chronos`` を過去のある瞬間に固定した場合、新たな ``Chronos`` 292 | のインスタンスが生成する ``now`` または相対時刻の文字列は、 293 | 固定された時刻の相対を返却します。 :: 294 | 295 | Chronos::setTestNow(new Chronos('1975-12-25 00:00:00')); 296 | 297 | $time = new Chronos(); // 1975-12-25 00:00:00 298 | $time = new Chronos('1 hour ago'); // 1975-12-24 23:00:00 299 | 300 | 固定をリセットするには、 ``setTestNow()`` をパラメーター無し、または ``null`` を設定して 301 | 再び呼び出してください。 302 | -------------------------------------------------------------------------------- /docs/pt/conf.py: -------------------------------------------------------------------------------- 1 | import sys, os 2 | 3 | # Append the top level directory of the docs, so we can import from the config dir. 4 | sys.path.insert(0, os.path.abspath('..')) 5 | 6 | # Pull in all the configuration options defined in the global config file.. 7 | from config.all import * 8 | 9 | language = 'pt' 10 | -------------------------------------------------------------------------------- /docs/pt/contents.rst: -------------------------------------------------------------------------------- 1 | .. toctree:: 2 | :maxdepth: 2 3 | :caption: CakePHP Chronos 4 | 5 | /index 6 | 7 | API -------------------------------------------------------------------------------- /docs/pt/index.rst: -------------------------------------------------------------------------------- 1 | Chronos 2 | ####### 3 | 4 | O Chronos oferece uma coleção independente de extensões para lidar com o objeto 5 | ``DateTime``. Além de métodos de conveniência, o Chronos oferece: 6 | 7 | * Objetos ``Date`` para representar datas de calendário. 8 | * Objetos *date* e *datetime* imutáveis. 9 | * Um sistema de tradução acoplável. Apenas traduções em inglês estão incluídas 10 | na biblioteca. Todavia, ``cakephp/i18n`` pode ser usado para suporte completo 11 | a idiomas. 12 | 13 | Instalação 14 | ---------- 15 | 16 | Para instalar o Chronos, você deve usar o ``composer``. A partir do diretório 17 | *ROOT* de sua aplicação (local onde o arquivo composer.json está localizado) 18 | execute o seguinte comando:: 19 | 20 | php composer.phar require cakephp/chronos "@stable" 21 | 22 | Visão geral 23 | ----------- 24 | 25 | Chronos oferece extensões para lidar com objetos *DateTime* do PHP. 5 classes 26 | cobrem variantes de data/hora mutáveis e imutáveis e uma extensão do objeto 27 | ``DateInterval``. 28 | 29 | * ``Cake\Chronos\Chronos`` é um objeto *date & time* imutável. 30 | * ``Cake\Chronos\ChronosDate`` é um objeto *date* imutável. 31 | * ``Cake\Chronos\MutableDateTime`` é um objeto *date and time* mutável. 32 | * ``Cake\Chronos\MutableDate`` é um objeto *date* mutável. 33 | * ``Cake\Chronos\ChronosInterval`` é uma extensão do objeto ``DateInterval``. 34 | 35 | Criando instâncias 36 | ------------------ 37 | 38 | Existem várias maneiras de criar instâncias do Chronos ou mesmo, do objeto Date. 39 | Um número considerável de métodos padrão que funcionam com conjuntos diferentes 40 | de argumentos:: 41 | 42 | use Cake\Chronos\Chronos; 43 | 44 | $now = Chronos::now(); 45 | $today = Chronos::today(); 46 | $yesterday = Chronos::yesterday(); 47 | $tomorrow = Chronos::tomorrow(); 48 | 49 | // Interpreta expressões relativas. 50 | $date = Chronos::parse('+2 days, +3 hours'); 51 | 52 | // Valores inteiros de Date e Time. 53 | $date = Chronos::create(2015, 12, 25, 4, 32, 58); 54 | 55 | // Valores inteiros de Date ou Time. 56 | $date = Chronos::createFromDate(2015, 12, 25); 57 | $date = Chronos::createFromTime(11, 45, 10); 58 | 59 | // Interpreta valores formatados. 60 | $date = Chronos::createFromFormat('m/d/Y', '06/15/2015'); 61 | 62 | Trabalhando com objetos imutáveis 63 | --------------------------------- 64 | 65 | Se você é familiarizado com os objetos ``DateTime`` do PHP, você se sentirá 66 | confortável com objetos *mutáveis*. Além de objetos mutáveis o Chronos também 67 | oferece objetos imutáveis que por sua vez criam cópias de objetos toda vez que 68 | um objeto é modificado. Devido ao fato de que metodos modificadores relativos 69 | a data e hora nem sempre serem transparentes, informações podem ser modificadas 70 | acidentalmente ou sem que o desenvolvedor saiba. Objetos imutáveis previnem 71 | essas alterações acidentais nos dados. Imutabilidade significa que você deverá 72 | lembrar de substituir variáveis usando modificadores:: 73 | 74 | // Esse código não funciona com objetos imutáveis 75 | $time->addDay(1); 76 | doSomething($time); 77 | return $time; 78 | 79 | // Esse funciona como o esperado 80 | $time = $time->addDay(1); 81 | $time = doSomething($time); 82 | return $time; 83 | 84 | Ao capturar o valor de retorno de cada modificação, seu código funcionará como o 85 | esperado. Se você tem um objeto imutável e quer criar um mutável a partir do 86 | mesmo, use ``toMutable()``:: 87 | 88 | $inplace = $time->toMutable(); 89 | 90 | Objetos Date 91 | ------------ 92 | 93 | O PHP disponibiliza um único objeto DateTime. Representar datas de calendário 94 | pode ser um pouco desconfortável por essa classe, uma vez que ela inclui 95 | *timezones* e componentes de hora que realmente não se encaixam no conceito de 96 | 'dia'. O Chronos oferece um objeto ``Date`` para representar datas. A hora e a 97 | zona desse objeto é sempre fixado em ``00:00:00 UTC`` e todos os métodos de 98 | formatação/diferença operam sob a resolução de dia:: 99 | 100 | use Cake\Chronos\ChronosDate; 101 | 102 | $today = ChronosDate::today(); 103 | 104 | // Mudanças na hora/timezone são ignoradas 105 | $today->modify('+1 hours'); 106 | 107 | // Exibe '2016-08-15' 108 | echo $today; 109 | 110 | Métodos modificadores 111 | --------------------- 112 | 113 | Objetos Chronos disponibilizam métodos que permitem a modificação de valores de 114 | forma granular:: 115 | 116 | // Define componentes do valor datetime 117 | $halloween = Chronos::create() 118 | ->year(2015) 119 | ->month(10) 120 | ->day(31) 121 | ->hour(20) 122 | ->minute(30); 123 | 124 | Você também pode modificar partes da data relativamente:: 125 | 126 | $future = Chronos::create() 127 | ->addYear(1) 128 | ->subMonth(2) 129 | ->addDays(15) 130 | ->addHours(20) 131 | ->subMinutes(2); 132 | 133 | Também é possível realizar grandes saltos para períodos definidos no tempo:: 134 | 135 | $time = Chronos::create(); 136 | $time->startOfDay(); 137 | $time->startOfMonth(); 138 | $time->endOfMonth(); 139 | $time->endOfYear(); 140 | $time->startOfWeek(); 141 | $time->endOfWeek(); 142 | 143 | Ou ainda para dias específicos da semana:: 144 | 145 | $time->next(Chronos::TUESDAY); 146 | $time->previous(Chronos::MONDAY); 147 | 148 | Métodos de comparação 149 | --------------------- 150 | 151 | Uma vez que você possui 2 instâncias de objetos data/hora do Chronos, é possível 152 | compará-los de várias maneiras:: 153 | 154 | // Coleção completa de comparadores 155 | // equals, notEquals, greaterThan, greaterThanOrEquals, lessThan, lessThanOrEquals 156 | $first->equals($second); 157 | $first->greaterThanOrEquals($second); 158 | 159 | // Veja se o objeto atual está entre outros 160 | $now->between($start, $end); 161 | 162 | // Encontre qual argumento está mais perto ou mais longe 163 | $now->closest($june, $november); 164 | $now->farthest($june, $november); 165 | 166 | Você também pode arguir sobre quando um determinado valor cai no calendário:: 167 | 168 | $now->isToday(); 169 | $now->isYesterday(); 170 | $now->isFuture(); 171 | $now->isPast(); 172 | 173 | // Verifica se o dia é no final de semana 174 | $now->isWeekend(); 175 | 176 | // Todos os métodos para outros dias da semana existem também 177 | $now->isMonday(); 178 | 179 | Você também pode verificar se um determinado valor está dentro de um período de 180 | tempo relativo:: 181 | 182 | $time->wasWithinLast('3 days'); 183 | $time->isWithinNext('3 hours'); 184 | 185 | Gerando diferenças 186 | ------------------ 187 | 188 | Em adição à comparação de *datetimes*, calcular diferenças ou deltas entre 189 | valores é uma tarefa simples:: 190 | 191 | // Recebe um DateInterval representando a diferença 192 | $first->diff($second); 193 | 194 | // Recebe a diferença como um contador de unidades específicas 195 | $first->diffInHours($second); 196 | $first->diffInDays($second); 197 | $first->diffInWeeks($second); 198 | $first->diffInYears($second); 199 | 200 | Você pode gerar diferenças de fácil leitura para humanos para usar em um *feed* 201 | ou *timeline*:: 202 | 203 | // Diferença em relação ao momento atual 204 | echo $date->diffForHumans(); 205 | 206 | // Diferença em relação a outro período no tempo 207 | echo $date->diffForHumans($other); // 1 hora atrás; 208 | 209 | Formatando strings 210 | ------------------ 211 | 212 | O Chronos disponibiliza métodos para exibir nossos objetos *datetime*:: 213 | 214 | // Usa o formato controlado por setToStringFormat() 215 | echo $date; 216 | 217 | // Diferentes padrões de formato 218 | echo $time->toAtomString(); // 1975-12-25T14:15:16-05:00 219 | echo $time->toCookieString(); // Thursday, 25-Dec-1975 14:15:16 EST 220 | echo $time->toIso8601String(); // 1975-12-25T14:15:16-05:00 221 | echo $time->toRfc822String(); // Thu, 25 Dec 75 14:15:16 -0500 222 | echo $time->toRfc850String(); // Thursday, 25-Dec-75 14:15:16 EST 223 | echo $time->toRfc1036String(); // Thu, 25 Dec 75 14:15:16 -0500 224 | echo $time->toRfc1123String(); // Thu, 25 Dec 1975 14:15:16 -0500 225 | echo $time->toRfc2822String(); // Thu, 25 Dec 1975 14:15:16 -0500 226 | echo $time->toRfc3339String(); // 1975-12-25T14:15:16-05:00 227 | echo $time->toRssString(); // Thu, 25 Dec 1975 14:15:16 -0500 228 | echo $time->toW3cString(); // 1975-12-25T14:15:16-05:00 229 | 230 | // Recebe o trimestre 231 | echo $time->toQuarter(); // 4; 232 | 233 | Extraindo componentes de data 234 | ----------------------------- 235 | 236 | Podemos receber partes de um objeto *date* acessando propriedades:: 237 | 238 | $time = new Chronos('2015-12-31 23:59:58'); 239 | $time->year; // 2015 240 | $time->month; // 12 241 | $time->day; // 31 242 | $time->hour // 23 243 | $time->minute // 59 244 | $time->second // 58 245 | 246 | Outras propriedades que podem ser acessadas são: 247 | 248 | - timezone 249 | - timezoneName 250 | - micro 251 | - dayOfWeek 252 | - dayOfMonth 253 | - dayOfYear 254 | - daysInMonth 255 | - timestamp 256 | - quarter 257 | - half 258 | 259 | Auxílio para testes 260 | ------------------- 261 | 262 | Ao escrever testes unitários, fixar a hora atual é bastante útil. O Chronos 263 | lhe permite fixar a hora atual para cada classe. Como parte das suas ferramentas 264 | de testes, você pode incluir o seguinte:: 265 | 266 | Chronos::setTestNow(Chronos::now()); 267 | MutableDateTime::setTestNow(MutableDateTime::now()); 268 | ChronosDate::setTestNow(ChronosDate::parse(Chronos::now())); 269 | MutableDate::setTestNow(MutableDate::now()); 270 | 271 | Isso irá corrigir a hora atual de todos os objetos para o momento em que o 272 | processo de testes foi iniciado. 273 | 274 | Por exemplo, se você fixar o ``Chronos`` em algum momento no passado, qualquer 275 | nova instância do ``Chronos`` criada com ``now`` ou uma *string* de tempo 276 | relativa, teremos um retorno referente ao tempo fixado:: 277 | 278 | Chronos::setTestNow(new Chronos('1975-12-25 00:00:00')); 279 | 280 | $time = new Chronos(); // 1975-12-25 00:00:00 281 | $time = new Chronos('1 hour ago'); // 1975-12-24 23:00:00 282 | 283 | -------------------------------------------------------------------------------- /psalm-baseline.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | array_filter 6 | date_default_timezone_get 7 | date_default_timezone_get 8 | iterator_to_array 9 | 10 | 11 | diffForHumans 12 | diffFormatter 13 | format 14 | format 15 | format 16 | format 17 | format 18 | format 19 | getTimestamp 20 | getTimezone 21 | getWeekendDays 22 | hasTestNow 23 | now 24 | now 25 | now 26 | now 27 | now 28 | now 29 | now 30 | now 31 | now 32 | now 33 | now 34 | now 35 | now 36 | now 37 | now 38 | now 39 | now 40 | now 41 | now 42 | now 43 | now 44 | now 45 | safeCreateDateTimeZone 46 | toDateString 47 | toDateString 48 | toDateString 49 | toDateString 50 | toDateString 51 | toDateString 52 | toDateString 53 | tomorrow 54 | yesterday 55 | 56 | 57 | static::$days 58 | static::$days 59 | static::$days 60 | static::$days 61 | static::$days 62 | static::$days 63 | static::$days 64 | static::$days 65 | static::$days 66 | static::$weekEndsAt 67 | static::$weekEndsAt 68 | static::$weekStartsAt 69 | static::$weekStartsAt 70 | 71 | 72 | 'Y', 74 | 'yearIso' => 'o', 75 | 'month' => 'n', 76 | 'day' => 'j', 77 | 'hour' => 'G', 78 | 'minute' => 'i', 79 | 'second' => 's', 80 | 'micro' => 'u', 81 | 'microsecond' => 'u', 82 | 'dayOfWeek' => 'N', 83 | 'dayOfYear' => 'z', 84 | 'weekOfYear' => 'W', 85 | 'daysInMonth' => 't', 86 | 'timestamp' => 'U', 87 | ];]]> 88 | 89 | 90 | f]]> 91 | 92 | 93 | public static function createFromFormat( 94 | 95 | 96 | 97 | 98 | array_filter 99 | date_default_timezone_get 100 | iterator_to_array 101 | 102 | 103 | diffForHumans 104 | diffFormatter 105 | format 106 | getTestNow 107 | getWeekEndsAt 108 | getWeekEndsAt 109 | getWeekStartsAt 110 | getWeekStartsAt 111 | getWeekendDays 112 | hasTestNow 113 | now 114 | now 115 | now 116 | now 117 | now 118 | now 119 | now 120 | now 121 | now 122 | now 123 | now 124 | now 125 | tomorrow 126 | yesterday 127 | 128 | 129 | static::$days 130 | static::$days 131 | static::$days 132 | static::$days 133 | static::$days 134 | static::$days 135 | static::$days 136 | static::$days 137 | static::$days 138 | 139 | 140 | 'Y', 142 | 'yearIso' => 'o', 143 | 'month' => 'n', 144 | 'day' => 'j', 145 | 'dayOfWeek' => 'N', 146 | 'dayOfYear' => 'z', 147 | 'weekOfYear' => 'W', 148 | 'daysInMonth' => 't', 149 | ];]]> 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /psalm.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/ChronosDate.php: -------------------------------------------------------------------------------- 1 | $month 35 | * @property-read int<1, 31> $day 36 | * @property-read int<1, 7> $dayOfWeek 1 (for Monday) through 7 (for Sunday) 37 | * @property-read int<0, 365> $dayOfYear 0 through 365 38 | * @property-read int<1, 5> $weekOfMonth 1 through 5 39 | * @property-read int<1, 53> $weekOfYear ISO-8601 week number of year, weeks starting on Monday 40 | * @property-read int<1, 31> $daysInMonth number of days in the given month 41 | * @property-read int $age does a diffInYears() with default parameters 42 | * @property-read int<1, 4> $quarter the quarter of this instance, 1 - 4 43 | * @property-read int<1, 2> $half the half of the year, with 1 for months Jan...Jun and 2 for Jul...Dec. 44 | * @psalm-immutable 45 | * @psalm-consistent-constructor 46 | */ 47 | class ChronosDate implements Stringable 48 | { 49 | use FormattingTrait; 50 | 51 | /** 52 | * Default format to use for __toString method when type juggling occurs. 53 | * 54 | * @var string 55 | */ 56 | public const DEFAULT_TO_STRING_FORMAT = 'Y-m-d'; 57 | 58 | /** 59 | * Format to use for __toString method when type juggling occurs. 60 | * 61 | * @var string 62 | */ 63 | protected static string $toStringFormat = self::DEFAULT_TO_STRING_FORMAT; 64 | 65 | /** 66 | * Names of days of the week. 67 | * 68 | * @var array 69 | */ 70 | protected static array $days = [ 71 | Chronos::MONDAY => 'Monday', 72 | Chronos::TUESDAY => 'Tuesday', 73 | Chronos::WEDNESDAY => 'Wednesday', 74 | Chronos::THURSDAY => 'Thursday', 75 | Chronos::FRIDAY => 'Friday', 76 | Chronos::SATURDAY => 'Saturday', 77 | Chronos::SUNDAY => 'Sunday', 78 | ]; 79 | 80 | /** 81 | * Instance of the diff formatting object. 82 | * 83 | * @var \Cake\Chronos\DifferenceFormatterInterface|null 84 | */ 85 | protected static ?DifferenceFormatterInterface $diffFormatter = null; 86 | 87 | /** 88 | * Errors from last time createFromFormat() was called. 89 | * 90 | * @var array|false 91 | */ 92 | protected static array|false $lastErrors = false; 93 | 94 | /** 95 | * @var \DateTimeImmutable 96 | */ 97 | protected DateTimeImmutable $native; 98 | 99 | /** 100 | * Create a new Immutable Date instance. 101 | * 102 | * Dates do not have time or timezone components exposed. Internally 103 | * ChronosDate wraps a PHP DateTimeImmutable but limits modifications 104 | * to only those that operate on day values. 105 | * 106 | * By default dates will be calculated from the server's default timezone. 107 | * You can use the `timezone` parameter to use a different timezone. Timezones 108 | * are used when parsing relative date expressions like `today` and `yesterday` 109 | * but do not participate in parsing values like `2022-01-01`. 110 | * 111 | * @param \Cake\Chronos\ChronosDate|\DateTimeInterface|string $time Fixed or relative time 112 | * @param \DateTimeZone|string|null $timezone The time zone used for 'now' 113 | */ 114 | public function __construct( 115 | ChronosDate|DateTimeInterface|string $time = 'now', 116 | DateTimeZone|string|null $timezone = null 117 | ) { 118 | $this->native = $this->createNative($time, $timezone); 119 | } 120 | 121 | /** 122 | * Initializes the PHP DateTimeImmutable object. 123 | * 124 | * @param \Cake\Chronos\ChronosDate|\DateTimeInterface|string $time Fixed or relative time 125 | * @param \DateTimeZone|string|null $timezone The time zone used for 'now' 126 | * @return \DateTimeImmutable 127 | */ 128 | protected function createNative( 129 | ChronosDate|DateTimeInterface|string $time, 130 | DateTimeZone|string|null $timezone 131 | ): DateTimeImmutable { 132 | if (!is_string($time)) { 133 | return new DateTimeImmutable($time->format('Y-m-d 00:00:00')); 134 | } 135 | 136 | $timezone ??= date_default_timezone_get(); 137 | $timezone = $timezone instanceof DateTimeZone ? $timezone : new DateTimeZone($timezone); 138 | 139 | $testNow = Chronos::getTestNow(); 140 | if ($testNow === null) { 141 | $time = new DateTimeImmutable($time, $timezone); 142 | 143 | return new DateTimeImmutable($time->format('Y-m-d 00:00:00')); 144 | } 145 | 146 | $testNow = $testNow->setTimezone($timezone); 147 | if ($time !== 'now') { 148 | $testNow = $testNow->modify($time); 149 | } 150 | 151 | return new DateTimeImmutable($testNow->format('Y-m-d 00:00:00')); 152 | } 153 | 154 | /** 155 | * Get today's date. 156 | * 157 | * @param \DateTimeZone|string|null $timezone Time zone to use for now. 158 | * @return static 159 | */ 160 | public static function now(DateTimeZone|string|null $timezone = null): static 161 | { 162 | return new static('now', $timezone); 163 | } 164 | 165 | /** 166 | * Get today's date. 167 | * 168 | * @param \DateTimeZone|string|null $timezone Time zone to use for today. 169 | * @return static 170 | */ 171 | public static function today(DateTimeZone|string|null $timezone = null): static 172 | { 173 | return static::now($timezone); 174 | } 175 | 176 | /** 177 | * Get tomorrow's date. 178 | * 179 | * @param \DateTimeZone|string|null $timezone Time zone to use for tomorrow. 180 | * @return static 181 | */ 182 | public static function tomorrow(DateTimeZone|string|null $timezone = null): static 183 | { 184 | return new static('tomorrow', $timezone); 185 | } 186 | 187 | /** 188 | * Get yesterday's date. 189 | * 190 | * @param \DateTimeZone|string|null $timezone Time zone to use for yesterday. 191 | * @return static 192 | */ 193 | public static function yesterday(DateTimeZone|string|null $timezone = null): static 194 | { 195 | return new static('yesterday', $timezone); 196 | } 197 | 198 | /** 199 | * Create an instance from a string. This is an alias for the 200 | * constructor that allows better fluent syntax as it allows you to do 201 | * Chronos::parse('Monday next week')->fn() rather than 202 | * (new Chronos('Monday next week'))->fn() 203 | * 204 | * @param \Cake\Chronos\ChronosDate|\DateTimeInterface|string $time The strtotime compatible string to parse 205 | * @return static 206 | */ 207 | public static function parse(ChronosDate|DateTimeInterface|string $time): static 208 | { 209 | return new static($time); 210 | } 211 | 212 | /** 213 | * Create an instance from a specific date. 214 | * 215 | * @param int $year The year to create an instance with. 216 | * @param int $month The month to create an instance with. 217 | * @param int $day The day to create an instance with. 218 | * @return static 219 | */ 220 | public static function create(int $year, int $month, int $day): static 221 | { 222 | $instance = static::createFromFormat( 223 | 'Y-m-d', 224 | sprintf('%s-%s-%s', 0, $month, $day), 225 | ); 226 | 227 | return $instance->addYears($year); 228 | } 229 | 230 | /** 231 | * Create an instance from a specific format 232 | * 233 | * @param string $format The date() compatible format string. 234 | * @param string $time The formatted date string to interpret. 235 | * @return static 236 | * @throws \InvalidArgumentException 237 | */ 238 | public static function createFromFormat( 239 | string $format, 240 | string $time, 241 | ): static { 242 | $dateTime = DateTimeImmutable::createFromFormat($format, $time); 243 | 244 | static::$lastErrors = DateTimeImmutable::getLastErrors(); 245 | if (!$dateTime) { 246 | $message = static::$lastErrors ? implode(PHP_EOL, static::$lastErrors['errors']) : 'Unknown error'; 247 | 248 | throw new InvalidArgumentException($message); 249 | } 250 | 251 | return new static($dateTime); 252 | } 253 | 254 | /** 255 | * Returns parse warnings and errors from the last ``createFromFormat()`` 256 | * call. 257 | * 258 | * Returns the same data as DateTimeImmutable::getLastErrors(). 259 | * 260 | * @return array|false 261 | */ 262 | public static function getLastErrors(): array|false 263 | { 264 | return static::$lastErrors; 265 | } 266 | 267 | /** 268 | * Creates an instance from an array of date values. 269 | * 270 | * Allowed values: 271 | * - year 272 | * - month 273 | * - day 274 | * 275 | * @param array $values Array of date and time values. 276 | * @return static 277 | */ 278 | public static function createFromArray(array $values): static 279 | { 280 | $formatted = sprintf('%04d-%02d-%02d ', $values['year'], $values['month'], $values['day']); 281 | 282 | return static::parse($formatted); 283 | } 284 | 285 | /** 286 | * Get the difference formatter instance or overwrite the current one. 287 | * 288 | * @param \Cake\Chronos\DifferenceFormatterInterface|null $formatter The formatter instance when setting. 289 | * @return \Cake\Chronos\DifferenceFormatterInterface The formatter instance. 290 | */ 291 | public static function diffFormatter(?DifferenceFormatterInterface $formatter = null): DifferenceFormatterInterface 292 | { 293 | if ($formatter === null) { 294 | if (static::$diffFormatter === null) { 295 | static::$diffFormatter = new DifferenceFormatter(); 296 | } 297 | 298 | return static::$diffFormatter; 299 | } 300 | 301 | return static::$diffFormatter = $formatter; 302 | } 303 | 304 | /** 305 | * Add an Interval to a Date 306 | * 307 | * Any changes to the time will be ignored and reset to 00:00:00 308 | * 309 | * @param \DateInterval $interval The interval to modify this date by. 310 | * @return static A modified Date instance 311 | */ 312 | public function add(DateInterval $interval): static 313 | { 314 | if ($interval->f > 0 || $interval->s > 0 || $interval->i > 0 || $interval->h > 0) { 315 | throw new InvalidArgumentException('Cannot add intervals with time components'); 316 | } 317 | $new = clone $this; 318 | $new->native = $new->native->add($interval)->setTime(0, 0, 0); 319 | 320 | return $new; 321 | } 322 | 323 | /** 324 | * Subtract an Interval from a Date. 325 | * 326 | * Any changes to the time will be ignored and reset to 00:00:00 327 | * 328 | * @param \DateInterval $interval The interval to modify this date by. 329 | * @return static A modified Date instance 330 | */ 331 | public function sub(DateInterval $interval): static 332 | { 333 | if ($interval->f > 0 || $interval->s > 0 || $interval->i > 0 || $interval->h > 0) { 334 | throw new InvalidArgumentException('Cannot subtract intervals with time components'); 335 | } 336 | $new = clone $this; 337 | $new->native = $new->native->sub($interval)->setTime(0, 0, 0); 338 | 339 | return $new; 340 | } 341 | 342 | /** 343 | * Creates a new instance with date modified according to DateTimeImmutable::modifier(). 344 | * 345 | * Attempting to change a time component will raise an exception 346 | * 347 | * @param string $modifier Date modifier 348 | * @return static 349 | */ 350 | public function modify(string $modifier): static 351 | { 352 | if (preg_match('/hour|minute|second/', $modifier)) { 353 | throw new InvalidArgumentException('Cannot modify date objects by time values'); 354 | } 355 | 356 | $new = clone $this; 357 | $new->native = $new->native->modify($modifier); 358 | if ($new->native === false) { 359 | throw new InvalidArgumentException(sprintf('Unable to modify date using `%s`', $modifier)); 360 | } 361 | 362 | if ($new->format('H:i:s') !== '00:00:00') { 363 | $new->native = $new->native->setTime(0, 0, 0); 364 | } 365 | 366 | return $new; 367 | } 368 | 369 | /** 370 | * Sets the date. 371 | * 372 | * @param int $year The year to set. 373 | * @param int $month The month to set. 374 | * @param int $day The day to set. 375 | * @return static 376 | */ 377 | public function setDate(int $year, int $month, int $day): static 378 | { 379 | $new = clone $this; 380 | $new->native = $new->native->setDate($year, $month, $day); 381 | 382 | return $new; 383 | } 384 | 385 | /** 386 | * Sets the date according to the ISO 8601 standard 387 | * 388 | * @param int $year Year of the date. 389 | * @param int $week Week of the date. 390 | * @param int $dayOfWeek Offset from the first day of the week. 391 | * @return static 392 | */ 393 | public function setISODate(int $year, int $week, int $dayOfWeek = 1): static 394 | { 395 | $new = clone $this; 396 | $new->native = $new->native->setISODate($year, $week, $dayOfWeek); 397 | 398 | return $new; 399 | } 400 | 401 | /** 402 | * Returns the difference between this instance and target. 403 | * 404 | * @param \Cake\Chronos\ChronosDate $target Target instance 405 | * @param bool $absolute Whether the interval is forced to be positive 406 | * @return \DateInterval 407 | */ 408 | public function diff(ChronosDate $target, bool $absolute = false): DateInterval 409 | { 410 | return $this->native->diff($target->native, $absolute); 411 | } 412 | 413 | /** 414 | * Returns formatted date string according to DateTimeImmutable::format(). 415 | * 416 | * @param string $format String format 417 | * @return string 418 | */ 419 | public function format(string $format): string 420 | { 421 | return $this->native->format($format); 422 | } 423 | 424 | /** 425 | * Set the instance's year 426 | * 427 | * @param int $value The year value. 428 | * @return static 429 | */ 430 | public function year(int $value): static 431 | { 432 | return $this->setDate($value, $this->month, $this->day); 433 | } 434 | 435 | /** 436 | * Set the instance's month 437 | * 438 | * @param int $value The month value. 439 | * @return static 440 | */ 441 | public function month(int $value): static 442 | { 443 | return $this->setDate($this->year, $value, $this->day); 444 | } 445 | 446 | /** 447 | * Set the instance's day 448 | * 449 | * @param int $value The day value. 450 | * @return static 451 | */ 452 | public function day(int $value): static 453 | { 454 | return $this->setDate($this->year, $this->month, $value); 455 | } 456 | 457 | /** 458 | * Add years to the instance. Positive $value travel forward while 459 | * negative $value travel into the past. 460 | * 461 | * If the new ChronosDate does not exist, the last day of the month is used 462 | * instead instead of overflowing into the next month. 463 | * 464 | * ### Example: 465 | * 466 | * ``` 467 | * (new Chronos('2015-01-03'))->addYears(1); // Results in 2016-01-03 468 | * 469 | * (new Chronos('2012-02-29'))->addYears(1); // Results in 2013-02-28 470 | * ``` 471 | * 472 | * @param int $value The number of years to add. 473 | * @return static 474 | */ 475 | public function addYears(int $value): static 476 | { 477 | $month = $this->month; 478 | $date = $this->modify($value . ' years'); 479 | 480 | if ($date->month !== $month) { 481 | return $date->modify('last day of previous month'); 482 | } 483 | 484 | return $date; 485 | } 486 | 487 | /** 488 | * Remove years from the instance. 489 | * 490 | * Has the same behavior as `addYears()`. 491 | * 492 | * @param int $value The number of years to remove. 493 | * @return static 494 | */ 495 | public function subYears(int $value): static 496 | { 497 | return $this->addYears(-$value); 498 | } 499 | 500 | /** 501 | * Add years with overflowing to the instance. Positive $value 502 | * travels forward while negative $value travels into the past. 503 | * 504 | * If the new ChronosDate does not exist, the days overflow into the next month. 505 | * 506 | * ### Example: 507 | * 508 | * ``` 509 | * (new Chronos('2012-02-29'))->addYearsWithOverflow(1); // Results in 2013-03-01 510 | * ``` 511 | * 512 | * @param int $value The number of years to add. 513 | * @return static 514 | */ 515 | public function addYearsWithOverflow(int $value): static 516 | { 517 | return $this->modify($value . ' year'); 518 | } 519 | 520 | /** 521 | * Remove years with overflow from the instance 522 | * 523 | * Has the same behavior as `addYeasrWithOverflow()`. 524 | * 525 | * @param int $value The number of years to remove. 526 | * @return static 527 | */ 528 | public function subYearsWithOverflow(int $value): static 529 | { 530 | return $this->addYearsWithOverflow(-1 * $value); 531 | } 532 | 533 | /** 534 | * Add months to the instance. Positive $value travels forward while 535 | * negative $value travels into the past. 536 | * 537 | * When adding or subtracting months, if the resulting time is a date 538 | * that does not exist, the result of this operation will always be the 539 | * last day of the intended month. 540 | * 541 | * ### Example: 542 | * 543 | * ``` 544 | * (new Chronos('2015-01-03'))->addMonths(1); // Results in 2015-02-03 545 | * 546 | * (new Chronos('2015-01-31'))->addMonths(1); // Results in 2015-02-28 547 | * ``` 548 | * 549 | * @param int $value The number of months to add. 550 | * @return static 551 | */ 552 | public function addMonths(int $value): static 553 | { 554 | $day = $this->day; 555 | $date = $this->modify($value . ' months'); 556 | 557 | if ($date->day !== $day) { 558 | return $date->modify('last day of previous month'); 559 | } 560 | 561 | return $date; 562 | } 563 | 564 | /** 565 | * Remove months from the instance 566 | * 567 | * Has the same behavior as `addMonths()`. 568 | * 569 | * @param int $value The number of months to remove. 570 | * @return static 571 | */ 572 | public function subMonths(int $value): static 573 | { 574 | return $this->addMonths(-$value); 575 | } 576 | 577 | /** 578 | * Add months with overflowing to the instance. Positive $value 579 | * travels forward while negative $value travels into the past. 580 | * 581 | * If the new ChronosDate does not exist, the days overflow into the next month. 582 | * 583 | * ### Example: 584 | * 585 | * ``` 586 | * (new Chronos('2012-01-30'))->addMonthsWithOverflow(1); // Results in 2013-03-01 587 | * ``` 588 | * 589 | * @param int $value The number of months to add. 590 | * @return static 591 | */ 592 | public function addMonthsWithOverflow(int $value): static 593 | { 594 | return $this->modify($value . ' months'); 595 | } 596 | 597 | /** 598 | * Add months with overflowing to the instance. Positive $value 599 | * travels forward while negative $value travels into the past. 600 | * 601 | * If the new ChronosDate does not exist, the days overflow into the next month. 602 | * 603 | * ### Example: 604 | * 605 | * ``` 606 | * (new Chronos('2012-01-30'))->addMonthsWithOverflow(1); // Results in 2013-03-01 607 | * ``` 608 | * 609 | * @param int $value The number of months to remove. 610 | * @return static 611 | */ 612 | public function subMonthsWithOverflow(int $value): static 613 | { 614 | return $this->addMonthsWithOverflow(-1 * $value); 615 | } 616 | 617 | /** 618 | * Add days to the instance. Positive $value travels forward while 619 | * negative $value travels into the past. 620 | * 621 | * @param int $value The number of days to add. 622 | * @return static 623 | */ 624 | public function addDays(int $value): static 625 | { 626 | return $this->modify("$value days"); 627 | } 628 | 629 | /** 630 | * Remove days from the instance 631 | * 632 | * @param int $value The number of days to remove. 633 | * @return static 634 | */ 635 | public function subDays(int $value): static 636 | { 637 | return $this->addDays(-$value); 638 | } 639 | 640 | /** 641 | * Add weekdays to the instance. Positive $value travels forward while 642 | * negative $value travels into the past. 643 | * 644 | * @param int $value The number of weekdays to add. 645 | * @return static 646 | */ 647 | public function addWeekdays(int $value): static 648 | { 649 | return $this->modify($value . ' weekdays, ' . $this->format('H:i:s')); 650 | } 651 | 652 | /** 653 | * Remove weekdays from the instance 654 | * 655 | * @param int $value The number of weekdays to remove. 656 | * @return static 657 | */ 658 | public function subWeekdays(int $value): static 659 | { 660 | return $this->addWeekdays(-$value); 661 | } 662 | 663 | /** 664 | * Add weeks to the instance. Positive $value travels forward while 665 | * negative $value travels into the past. 666 | * 667 | * @param int $value The number of weeks to add. 668 | * @return static 669 | */ 670 | public function addWeeks(int $value): static 671 | { 672 | return $this->modify("$value week"); 673 | } 674 | 675 | /** 676 | * Remove weeks to the instance 677 | * 678 | * @param int $value The number of weeks to remove. 679 | * @return static 680 | */ 681 | public function subWeeks(int $value): static 682 | { 683 | return $this->addWeeks(-$value); 684 | } 685 | 686 | /** 687 | * Resets the date to the first day of the month 688 | * 689 | * @return static 690 | */ 691 | public function startOfMonth(): static 692 | { 693 | return $this->modify('first day of this month'); 694 | } 695 | 696 | /** 697 | * Resets the date to end of the month 698 | * 699 | * @return static 700 | */ 701 | public function endOfMonth(): static 702 | { 703 | return $this->modify('last day of this month'); 704 | } 705 | 706 | /** 707 | * Resets the date to the first day of the year 708 | * 709 | * @return static 710 | */ 711 | public function startOfYear(): static 712 | { 713 | return $this->modify('first day of january'); 714 | } 715 | 716 | /** 717 | * Resets the date to end of the year 718 | * 719 | * @return static 720 | */ 721 | public function endOfYear(): static 722 | { 723 | return $this->modify('last day of december'); 724 | } 725 | 726 | /** 727 | * Resets the date to the first day of the decade 728 | * 729 | * @return static 730 | */ 731 | public function startOfDecade(): static 732 | { 733 | $year = $this->year - $this->year % Chronos::YEARS_PER_DECADE; 734 | 735 | return $this->modify("first day of january $year"); 736 | } 737 | 738 | /** 739 | * Resets the date to end of the decade 740 | * 741 | * @return static 742 | */ 743 | public function endOfDecade(): static 744 | { 745 | $year = $this->year - $this->year % Chronos::YEARS_PER_DECADE + Chronos::YEARS_PER_DECADE - 1; 746 | 747 | return $this->modify("last day of december $year"); 748 | } 749 | 750 | /** 751 | * Resets the date to the first day of the century 752 | * 753 | * @return static 754 | */ 755 | public function startOfCentury(): static 756 | { 757 | $year = $this->startOfYear() 758 | ->year($this->year - 1 - ($this->year - 1) % Chronos::YEARS_PER_CENTURY + 1) 759 | ->year; 760 | 761 | return $this->modify("first day of january $year"); 762 | } 763 | 764 | /** 765 | * Resets the date to end of the century and time to 23:59:59 766 | * 767 | * @return static 768 | */ 769 | public function endOfCentury(): static 770 | { 771 | $y = $this->year - 1 772 | - ($this->year - 1) 773 | % Chronos::YEARS_PER_CENTURY 774 | + Chronos::YEARS_PER_CENTURY; 775 | 776 | $year = $this->endOfYear() 777 | ->year($y) 778 | ->year; 779 | 780 | return $this->modify("last day of december $year"); 781 | } 782 | 783 | /** 784 | * Resets the date to the first day of week (defined in $weekStartsAt) 785 | * 786 | * @return static 787 | */ 788 | public function startOfWeek(): static 789 | { 790 | $dateTime = $this; 791 | if ($dateTime->dayOfWeek !== Chronos::getWeekStartsAt()) { 792 | $dateTime = $dateTime->previous(Chronos::getWeekStartsAt()); 793 | } 794 | 795 | return $dateTime; 796 | } 797 | 798 | /** 799 | * Resets the date to end of week (defined in $weekEndsAt) and time to 23:59:59 800 | * 801 | * @return static 802 | */ 803 | public function endOfWeek(): static 804 | { 805 | $dateTime = $this; 806 | if ($dateTime->dayOfWeek !== Chronos::getWeekEndsAt()) { 807 | $dateTime = $dateTime->next(Chronos::getWeekEndsAt()); 808 | } 809 | 810 | return $dateTime; 811 | } 812 | 813 | /** 814 | * Modify to the next occurrence of a given day of the week. 815 | * If no dayOfWeek is provided, modify to the next occurrence 816 | * of the current day of the week. Use the supplied consts 817 | * to indicate the desired dayOfWeek, ex. Chronos::MONDAY. 818 | * 819 | * @param int|null $dayOfWeek The day of the week to move to. 820 | * @return static 821 | */ 822 | public function next(?int $dayOfWeek = null): static 823 | { 824 | if ($dayOfWeek === null) { 825 | $dayOfWeek = $this->dayOfWeek; 826 | } 827 | 828 | $day = static::$days[$dayOfWeek]; 829 | 830 | return $this->modify("next $day"); 831 | } 832 | 833 | /** 834 | * Modify to the previous occurrence of a given day of the week. 835 | * If no dayOfWeek is provided, modify to the previous occurrence 836 | * of the current day of the week. Use the supplied consts 837 | * to indicate the desired dayOfWeek, ex. Chronos::MONDAY. 838 | * 839 | * @param int|null $dayOfWeek The day of the week to move to. 840 | * @return static 841 | */ 842 | public function previous(?int $dayOfWeek = null): static 843 | { 844 | if ($dayOfWeek === null) { 845 | $dayOfWeek = $this->dayOfWeek; 846 | } 847 | 848 | $day = static::$days[$dayOfWeek]; 849 | 850 | return $this->modify("last $day"); 851 | } 852 | 853 | /** 854 | * Modify to the first occurrence of a given day of the week 855 | * in the current month. If no dayOfWeek is provided, modify to the 856 | * first day of the current month. Use the supplied consts 857 | * to indicate the desired dayOfWeek, ex. Chronos::MONDAY. 858 | * 859 | * @param int|null $dayOfWeek The day of the week to move to. 860 | * @return static 861 | */ 862 | public function firstOfMonth(?int $dayOfWeek = null): static 863 | { 864 | $day = $dayOfWeek === null ? 'day' : static::$days[$dayOfWeek]; 865 | 866 | return $this->modify("first $day of this month"); 867 | } 868 | 869 | /** 870 | * Modify to the last occurrence of a given day of the week 871 | * in the current month. If no dayOfWeek is provided, modify to the 872 | * last day of the current month. Use the supplied consts 873 | * to indicate the desired dayOfWeek, ex. Chronos::MONDAY. 874 | * 875 | * @param int|null $dayOfWeek The day of the week to move to. 876 | * @return static 877 | */ 878 | public function lastOfMonth(?int $dayOfWeek = null): static 879 | { 880 | $day = $dayOfWeek === null ? 'day' : static::$days[$dayOfWeek]; 881 | 882 | return $this->modify("last $day of this month"); 883 | } 884 | 885 | /** 886 | * Modify to the given occurrence of a given day of the week 887 | * in the current month. If the calculated occurrence is outside the scope 888 | * of the current month, then return false and no modifications are made. 889 | * Use the supplied consts to indicate the desired dayOfWeek, ex. Chronos::MONDAY. 890 | * 891 | * @param int $nth The offset to use. 892 | * @param int $dayOfWeek The day of the week to move to. 893 | * @return static|false 894 | */ 895 | public function nthOfMonth(int $nth, int $dayOfWeek): static|false 896 | { 897 | $dateTime = $this->firstOfMonth(); 898 | $check = $dateTime->format('Y-m'); 899 | $dateTime = $dateTime->modify("+$nth " . static::$days[$dayOfWeek]); 900 | 901 | return $dateTime->format('Y-m') === $check ? $dateTime : false; 902 | } 903 | 904 | /** 905 | * Modify to the first occurrence of a given day of the week 906 | * in the current quarter. If no dayOfWeek is provided, modify to the 907 | * first day of the current quarter. Use the supplied consts 908 | * to indicate the desired dayOfWeek, ex. Chronos::MONDAY. 909 | * 910 | * @param int|null $dayOfWeek The day of the week to move to. 911 | * @return static 912 | */ 913 | public function firstOfQuarter(?int $dayOfWeek = null): static 914 | { 915 | return $this 916 | ->day(1) 917 | ->month($this->quarter * Chronos::MONTHS_PER_QUARTER - 2) 918 | ->firstOfMonth($dayOfWeek); 919 | } 920 | 921 | /** 922 | * Modify to the last occurrence of a given day of the week 923 | * in the current quarter. If no dayOfWeek is provided, modify to the 924 | * last day of the current quarter. Use the supplied consts 925 | * to indicate the desired dayOfWeek, ex. Chronos::MONDAY. 926 | * 927 | * @param int|null $dayOfWeek The day of the week to move to. 928 | * @return static 929 | */ 930 | public function lastOfQuarter(?int $dayOfWeek = null): static 931 | { 932 | return $this 933 | ->day(1) 934 | ->month($this->quarter * Chronos::MONTHS_PER_QUARTER) 935 | ->lastOfMonth($dayOfWeek); 936 | } 937 | 938 | /** 939 | * Modify to the given occurrence of a given day of the week 940 | * in the current quarter. If the calculated occurrence is outside the scope 941 | * of the current quarter, then return false and no modifications are made. 942 | * Use the supplied consts to indicate the desired dayOfWeek, ex. Chronos::MONDAY. 943 | * 944 | * @param int $nth The offset to use. 945 | * @param int $dayOfWeek The day of the week to move to. 946 | * @return static|false 947 | */ 948 | public function nthOfQuarter(int $nth, int $dayOfWeek): static|false 949 | { 950 | $dateTime = $this->day(1)->month($this->quarter * Chronos::MONTHS_PER_QUARTER); 951 | $lastMonth = $dateTime->month; 952 | $year = $dateTime->year; 953 | $dateTime = $dateTime->firstOfQuarter()->modify("+$nth" . static::$days[$dayOfWeek]); 954 | 955 | return $lastMonth < $dateTime->month || $year !== $dateTime->year ? false : $dateTime; 956 | } 957 | 958 | /** 959 | * Modify to the first occurrence of a given day of the week 960 | * in the current year. If no dayOfWeek is provided, modify to the 961 | * first day of the current year. Use the supplied consts 962 | * to indicate the desired dayOfWeek, ex. Chronos::MONDAY. 963 | * 964 | * @param int|null $dayOfWeek The day of the week to move to. 965 | * @return static 966 | */ 967 | public function firstOfYear(?int $dayOfWeek = null): static 968 | { 969 | $day = $dayOfWeek === null ? 'day' : static::$days[$dayOfWeek]; 970 | 971 | return $this->modify("first $day of january"); 972 | } 973 | 974 | /** 975 | * Modify to the last occurrence of a given day of the week 976 | * in the current year. If no dayOfWeek is provided, modify to the 977 | * last day of the current year. Use the supplied consts 978 | * to indicate the desired dayOfWeek, ex. Chronos::MONDAY. 979 | * 980 | * @param int|null $dayOfWeek The day of the week to move to. 981 | * @return static 982 | */ 983 | public function lastOfYear(?int $dayOfWeek = null): static 984 | { 985 | $day = $dayOfWeek === null ? 'day' : static::$days[$dayOfWeek]; 986 | 987 | return $this->modify("last $day of december"); 988 | } 989 | 990 | /** 991 | * Modify to the given occurrence of a given day of the week 992 | * in the current year. If the calculated occurrence is outside the scope 993 | * of the current year, then return false and no modifications are made. 994 | * Use the supplied consts to indicate the desired dayOfWeek, ex. Chronos::MONDAY. 995 | * 996 | * @param int $nth The offset to use. 997 | * @param int $dayOfWeek The day of the week to move to. 998 | * @return static|false 999 | */ 1000 | public function nthOfYear(int $nth, int $dayOfWeek): static|false 1001 | { 1002 | $dateTime = $this->firstOfYear()->modify("+$nth " . static::$days[$dayOfWeek]); 1003 | 1004 | return $this->year === $dateTime->year ? $dateTime : false; 1005 | } 1006 | 1007 | /** 1008 | * Determines if the instance is equal to another 1009 | * 1010 | * @param \Cake\Chronos\ChronosDate $other The instance to compare with. 1011 | * @return bool 1012 | */ 1013 | public function equals(ChronosDate $other): bool 1014 | { 1015 | return $this->native == $other->native; 1016 | } 1017 | 1018 | /** 1019 | * Determines if the instance is not equal to another 1020 | * 1021 | * @param \Cake\Chronos\ChronosDate $other The instance to compare with. 1022 | * @return bool 1023 | */ 1024 | public function notEquals(ChronosDate $other): bool 1025 | { 1026 | return !$this->equals($other); 1027 | } 1028 | 1029 | /** 1030 | * Determines if the instance is greater (after) than another 1031 | * 1032 | * @param \Cake\Chronos\ChronosDate $other The instance to compare with. 1033 | * @return bool 1034 | */ 1035 | public function greaterThan(ChronosDate $other): bool 1036 | { 1037 | return $this->native > $other->native; 1038 | } 1039 | 1040 | /** 1041 | * Determines if the instance is greater (after) than or equal to another 1042 | * 1043 | * @param \Cake\Chronos\ChronosDate $other The instance to compare with. 1044 | * @return bool 1045 | */ 1046 | public function greaterThanOrEquals(ChronosDate $other): bool 1047 | { 1048 | return $this->native >= $other->native; 1049 | } 1050 | 1051 | /** 1052 | * Determines if the instance is less (before) than another 1053 | * 1054 | * @param \Cake\Chronos\ChronosDate $other The instance to compare with. 1055 | * @return bool 1056 | */ 1057 | public function lessThan(ChronosDate $other): bool 1058 | { 1059 | return $this->native < $other->native; 1060 | } 1061 | 1062 | /** 1063 | * Determines if the instance is less (before) or equal to another 1064 | * 1065 | * @param \Cake\Chronos\ChronosDate $other The instance to compare with. 1066 | * @return bool 1067 | */ 1068 | public function lessThanOrEquals(ChronosDate $other): bool 1069 | { 1070 | return $this->native <= $other->native; 1071 | } 1072 | 1073 | /** 1074 | * Determines if the instance is between two others 1075 | * 1076 | * @param \Cake\Chronos\ChronosDate $start Start of target range 1077 | * @param \Cake\Chronos\ChronosDate $end End of target range 1078 | * @param bool $equals Whether to include the beginning and end of range 1079 | * @return bool 1080 | */ 1081 | public function between(ChronosDate $start, ChronosDate $end, bool $equals = true): bool 1082 | { 1083 | if ($start->greaterThan($end)) { 1084 | [$start, $end] = [$end, $start]; 1085 | } 1086 | 1087 | if ($equals) { 1088 | return $this->greaterThanOrEquals($start) && $this->lessThanOrEquals($end); 1089 | } 1090 | 1091 | return $this->greaterThan($start) && $this->lessThan($end); 1092 | } 1093 | 1094 | /** 1095 | * Get the closest date from the instance. 1096 | * 1097 | * @param \Cake\Chronos\ChronosDate $first The instance to compare with. 1098 | * @param \Cake\Chronos\ChronosDate $second The instance to compare with. 1099 | * @param \Cake\Chronos\ChronosDate ...$others Others instance to compare with. 1100 | * @return self 1101 | */ 1102 | public function closest(ChronosDate $first, ChronosDate $second, ChronosDate ...$others): ChronosDate 1103 | { 1104 | $closest = $first; 1105 | $closestDiffInDays = $this->diffInDays($first); 1106 | foreach ([$second, ...$others] as $other) { 1107 | $otherDiffInDays = $this->diffInDays($other); 1108 | if ($otherDiffInDays < $closestDiffInDays) { 1109 | $closest = $other; 1110 | $closestDiffInDays = $otherDiffInDays; 1111 | } 1112 | } 1113 | 1114 | return $closest; 1115 | } 1116 | 1117 | /** 1118 | * Get the farthest date from the instance. 1119 | * 1120 | * @param \Cake\Chronos\ChronosDate $first The instance to compare with. 1121 | * @param \Cake\Chronos\ChronosDate $second The instance to compare with. 1122 | * @param \Cake\Chronos\ChronosDate ...$others Others instance to compare with. 1123 | * @return self 1124 | */ 1125 | public function farthest(ChronosDate $first, ChronosDate $second, ChronosDate ...$others): ChronosDate 1126 | { 1127 | $farthest = $first; 1128 | $farthestDiffInDays = $this->diffInDays($first); 1129 | foreach ([$second, ...$others] as $other) { 1130 | $otherDiffInDays = $this->diffInDays($other); 1131 | if ($otherDiffInDays > $farthestDiffInDays) { 1132 | $farthest = $other; 1133 | $farthestDiffInDays = $otherDiffInDays; 1134 | } 1135 | } 1136 | 1137 | return $farthest; 1138 | } 1139 | 1140 | /** 1141 | * Determines if the instance is a weekday 1142 | * 1143 | * @return bool 1144 | */ 1145 | public function isWeekday(): bool 1146 | { 1147 | return !$this->isWeekend(); 1148 | } 1149 | 1150 | /** 1151 | * Determines if the instance is a weekend day 1152 | * 1153 | * @return bool 1154 | */ 1155 | public function isWeekend(): bool 1156 | { 1157 | return in_array($this->dayOfWeek, Chronos::getWeekendDays(), true); 1158 | } 1159 | 1160 | /** 1161 | * Determines if the instance is yesterday 1162 | * 1163 | * @param \DateTimeZone|string|null $timezone Time zone to use for now. 1164 | * @return bool 1165 | */ 1166 | public function isYesterday(DateTimeZone|string|null $timezone = null): bool 1167 | { 1168 | return $this->equals(static::yesterday($timezone)); 1169 | } 1170 | 1171 | /** 1172 | * Determines if the instance is today 1173 | * 1174 | * @param \DateTimeZone|string|null $timezone Time zone to use for now. 1175 | * @return bool 1176 | */ 1177 | public function isToday(DateTimeZone|string|null $timezone = null): bool 1178 | { 1179 | return $this->equals(static::now($timezone)); 1180 | } 1181 | 1182 | /** 1183 | * Determines if the instance is tomorrow 1184 | * 1185 | * @param \DateTimeZone|string|null $timezone Time zone to use for now. 1186 | * @return bool 1187 | */ 1188 | public function isTomorrow(DateTimeZone|string|null $timezone = null): bool 1189 | { 1190 | return $this->equals(static::tomorrow($timezone)); 1191 | } 1192 | 1193 | /** 1194 | * Determines if the instance is within the next week 1195 | * 1196 | * @param \DateTimeZone|string|null $timezone Time zone to use for now. 1197 | * @return bool 1198 | */ 1199 | public function isNextWeek(DateTimeZone|string|null $timezone = null): bool 1200 | { 1201 | return $this->format('W o') === static::now($timezone)->addWeeks(1)->format('W o'); 1202 | } 1203 | 1204 | /** 1205 | * Determines if the instance is within the last week 1206 | * 1207 | * @param \DateTimeZone|string|null $timezone Time zone to use for now. 1208 | * @return bool 1209 | */ 1210 | public function isLastWeek(DateTimeZone|string|null $timezone = null): bool 1211 | { 1212 | return $this->format('W o') === static::now($timezone)->subWeeks(1)->format('W o'); 1213 | } 1214 | 1215 | /** 1216 | * Determines if the instance is within the next month 1217 | * 1218 | * @param \DateTimeZone|string|null $timezone Time zone to use for now. 1219 | * @return bool 1220 | */ 1221 | public function isNextMonth(DateTimeZone|string|null $timezone = null): bool 1222 | { 1223 | return $this->format('m Y') === static::now($timezone)->addMonths(1)->format('m Y'); 1224 | } 1225 | 1226 | /** 1227 | * Determines if the instance is within the last month 1228 | * 1229 | * @param \DateTimeZone|string|null $timezone Time zone to use for now. 1230 | * @return bool 1231 | */ 1232 | public function isLastMonth(DateTimeZone|string|null $timezone = null): bool 1233 | { 1234 | return $this->format('m Y') === static::now($timezone)->subMonths(1)->format('m Y'); 1235 | } 1236 | 1237 | /** 1238 | * Determines if the instance is within the next year 1239 | * 1240 | * @param \DateTimeZone|string|null $timezone Time zone to use for now. 1241 | * @return bool 1242 | */ 1243 | public function isNextYear(DateTimeZone|string|null $timezone = null): bool 1244 | { 1245 | return $this->year === static::now($timezone)->addYears(1)->year; 1246 | } 1247 | 1248 | /** 1249 | * Determines if the instance is within the last year 1250 | * 1251 | * @param \DateTimeZone|string|null $timezone Time zone to use for now. 1252 | * @return bool 1253 | */ 1254 | public function isLastYear(DateTimeZone|string|null $timezone = null): bool 1255 | { 1256 | return $this->year === static::now($timezone)->subYears(1)->year; 1257 | } 1258 | 1259 | /** 1260 | * Determines if the instance is within the first half of year 1261 | * 1262 | * @return bool 1263 | */ 1264 | public function isFirstHalf(): bool 1265 | { 1266 | return $this->half === 1; 1267 | } 1268 | 1269 | /** 1270 | * Determines if the instance is within the second half of year 1271 | * 1272 | * @return bool 1273 | */ 1274 | public function isSecondHalf(): bool 1275 | { 1276 | return $this->half === 2; 1277 | } 1278 | 1279 | /** 1280 | * Determines if the instance is in the future, ie. greater (after) than now 1281 | * 1282 | * @param \DateTimeZone|string|null $timezone Time zone to use for now. 1283 | * @return bool 1284 | */ 1285 | public function isFuture(DateTimeZone|string|null $timezone = null): bool 1286 | { 1287 | return $this->greaterThan(static::now($timezone)); 1288 | } 1289 | 1290 | /** 1291 | * Determines if the instance is in the past, ie. less (before) than now 1292 | * 1293 | * @param \DateTimeZone|string|null $timezone Time zone to use for now. 1294 | * @return bool 1295 | */ 1296 | public function isPast(DateTimeZone|string|null $timezone = null): bool 1297 | { 1298 | return $this->lessThan(static::now($timezone)); 1299 | } 1300 | 1301 | /** 1302 | * Determines if the instance is a leap year 1303 | * 1304 | * @return bool 1305 | */ 1306 | public function isLeapYear(): bool 1307 | { 1308 | return $this->format('L') === '1'; 1309 | } 1310 | 1311 | /** 1312 | * Checks if this day is a Sunday. 1313 | * 1314 | * @return bool 1315 | */ 1316 | public function isSunday(): bool 1317 | { 1318 | return $this->dayOfWeek === Chronos::SUNDAY; 1319 | } 1320 | 1321 | /** 1322 | * Checks if this day is a Monday. 1323 | * 1324 | * @return bool 1325 | */ 1326 | public function isMonday(): bool 1327 | { 1328 | return $this->dayOfWeek === Chronos::MONDAY; 1329 | } 1330 | 1331 | /** 1332 | * Checks if this day is a Tuesday. 1333 | * 1334 | * @return bool 1335 | */ 1336 | public function isTuesday(): bool 1337 | { 1338 | return $this->dayOfWeek === Chronos::TUESDAY; 1339 | } 1340 | 1341 | /** 1342 | * Checks if this day is a Wednesday. 1343 | * 1344 | * @return bool 1345 | */ 1346 | public function isWednesday(): bool 1347 | { 1348 | return $this->dayOfWeek === Chronos::WEDNESDAY; 1349 | } 1350 | 1351 | /** 1352 | * Checks if this day is a Thursday. 1353 | * 1354 | * @return bool 1355 | */ 1356 | public function isThursday(): bool 1357 | { 1358 | return $this->dayOfWeek === Chronos::THURSDAY; 1359 | } 1360 | 1361 | /** 1362 | * Checks if this day is a Friday. 1363 | * 1364 | * @return bool 1365 | */ 1366 | public function isFriday(): bool 1367 | { 1368 | return $this->dayOfWeek === Chronos::FRIDAY; 1369 | } 1370 | 1371 | /** 1372 | * Checks if this day is a Saturday. 1373 | * 1374 | * @return bool 1375 | */ 1376 | public function isSaturday(): bool 1377 | { 1378 | return $this->dayOfWeek === Chronos::SATURDAY; 1379 | } 1380 | 1381 | /** 1382 | * Returns true this instance happened within the specified interval 1383 | * 1384 | * @param string|int $timeInterval the numeric value with space then time type. 1385 | * Example of valid types: 6 hours, 2 days, 1 minute. 1386 | * @return bool 1387 | */ 1388 | public function wasWithinLast(string|int $timeInterval): bool 1389 | { 1390 | $now = new static(new Chronos()); 1391 | $interval = $now->modify('-' . $timeInterval); 1392 | $thisTime = $this->format('U'); 1393 | 1394 | return $thisTime >= $interval->format('U') && $thisTime <= $now->format('U'); 1395 | } 1396 | 1397 | /** 1398 | * Returns true this instance will happen within the specified interval 1399 | * 1400 | * @param string|int $timeInterval the numeric value with space then time type. 1401 | * Example of valid types: 6 hours, 2 days, 1 minute. 1402 | * @return bool 1403 | */ 1404 | public function isWithinNext(string|int $timeInterval): bool 1405 | { 1406 | $now = new static(new Chronos()); 1407 | $interval = $now->modify('+' . $timeInterval); 1408 | $thisTime = $this->format('U'); 1409 | 1410 | return $thisTime <= $interval->format('U') && $thisTime >= $now->format('U'); 1411 | } 1412 | 1413 | /** 1414 | * Get the difference by the given interval using a filter callable 1415 | * 1416 | * @param \DateInterval $interval An interval to traverse by 1417 | * @param callable $callback The callback to use for filtering. 1418 | * @param \Cake\Chronos\ChronosDate|null $other The instance to difference from. 1419 | * @param bool $absolute Get the absolute of the difference 1420 | * @param int $options DatePeriod options, {@see https://www.php.net/manual/en/class.dateperiod.php} 1421 | * @return int 1422 | */ 1423 | public function diffFiltered( 1424 | DateInterval $interval, 1425 | callable $callback, 1426 | ?ChronosDate $other = null, 1427 | bool $absolute = true, 1428 | int $options = 0 1429 | ): int { 1430 | $start = $this; 1431 | $end = $other ?? new ChronosDate(Chronos::now()); 1432 | $inverse = false; 1433 | 1434 | if ($end < $start) { 1435 | $start = $end; 1436 | $end = $this; 1437 | $inverse = true; 1438 | } 1439 | // Hack around PHP's DatePeriod not counting equal dates at midnight as 1440 | // within the range. Sadly INCLUDE_END_DATE doesn't land until 8.2 1441 | $endTime = $end->native->modify('+1 second'); 1442 | 1443 | $period = new DatePeriod($start->native, $interval, $endTime, $options); 1444 | $vals = array_filter(iterator_to_array($period), function (DateTimeInterface $date) use ($callback) { 1445 | return $callback(static::parse($date)); 1446 | }); 1447 | 1448 | $diff = count($vals); 1449 | 1450 | return $inverse && !$absolute ? -$diff : $diff; 1451 | } 1452 | 1453 | /** 1454 | * Get the difference in years 1455 | * 1456 | * @param \Cake\Chronos\ChronosDate|null $other The instance to difference from. 1457 | * @param bool $absolute Get the absolute of the difference 1458 | * @return int 1459 | */ 1460 | public function diffInYears(?ChronosDate $other = null, bool $absolute = true): int 1461 | { 1462 | $diff = $this->diff($other ?? new static(new Chronos()), $absolute); 1463 | 1464 | return $diff->invert ? -$diff->y : $diff->y; 1465 | } 1466 | 1467 | /** 1468 | * Get the difference in months 1469 | * 1470 | * @param \Cake\Chronos\ChronosDate|null $other The instance to difference from. 1471 | * @param bool $absolute Get the absolute of the difference 1472 | * @return int 1473 | */ 1474 | public function diffInMonths(?ChronosDate $other = null, bool $absolute = true): int 1475 | { 1476 | $diff = $this->diff($other ?? new static(Chronos::now()), $absolute); 1477 | $months = $diff->y * Chronos::MONTHS_PER_YEAR + $diff->m; 1478 | 1479 | return $diff->invert ? -$months : $months; 1480 | } 1481 | 1482 | /** 1483 | * Get the difference in weeks 1484 | * 1485 | * @param \Cake\Chronos\ChronosDate|null $other The instance to difference from. 1486 | * @param bool $absolute Get the absolute of the difference 1487 | * @return int 1488 | */ 1489 | public function diffInWeeks(?ChronosDate $other = null, bool $absolute = true): int 1490 | { 1491 | return (int)($this->diffInDays($other, $absolute) / Chronos::DAYS_PER_WEEK); 1492 | } 1493 | 1494 | /** 1495 | * Get the difference in days 1496 | * 1497 | * @param \Cake\Chronos\ChronosDate|null $other The instance to difference from. 1498 | * @param bool $absolute Get the absolute of the difference 1499 | * @return int 1500 | */ 1501 | public function diffInDays(?ChronosDate $other = null, bool $absolute = true): int 1502 | { 1503 | $diff = $this->diff($other ?? new static(Chronos::now()), $absolute); 1504 | 1505 | return $diff->invert ? -(int)$diff->days : (int)$diff->days; 1506 | } 1507 | 1508 | /** 1509 | * Get the difference in days using a filter callable 1510 | * 1511 | * @param callable $callback The callback to use for filtering. 1512 | * @param \Cake\Chronos\ChronosDate|null $other The instance to difference from. 1513 | * @param bool $absolute Get the absolute of the difference 1514 | * @param int $options DatePeriod options, {@see https://www.php.net/manual/en/class.dateperiod.php} 1515 | * @return int 1516 | */ 1517 | public function diffInDaysFiltered( 1518 | callable $callback, 1519 | ?ChronosDate $other = null, 1520 | bool $absolute = true, 1521 | int $options = 0 1522 | ): int { 1523 | return $this->diffFiltered(new DateInterval('P1D'), $callback, $other, $absolute, $options); 1524 | } 1525 | 1526 | /** 1527 | * Get the difference in weekdays 1528 | * 1529 | * @param \Cake\Chronos\ChronosDate|null $other The instance to difference from. 1530 | * @param bool $absolute Get the absolute of the difference 1531 | * @param int $options DatePeriod options, {@see https://www.php.net/manual/en/class.dateperiod.php} 1532 | * @return int 1533 | */ 1534 | public function diffInWeekdays(?ChronosDate $other = null, bool $absolute = true, int $options = 0): int 1535 | { 1536 | return $this->diffInDaysFiltered(function (ChronosDate $date) { 1537 | return $date->isWeekday(); 1538 | }, $other, $absolute, $options); 1539 | } 1540 | 1541 | /** 1542 | * Get the difference in weekend days using a filter 1543 | * 1544 | * @param \Cake\Chronos\ChronosDate|null $other The instance to difference from. 1545 | * @param bool $absolute Get the absolute of the difference 1546 | * @param int $options DatePeriod options, {@see https://www.php.net/manual/en/class.dateperiod.php} 1547 | * @return int 1548 | */ 1549 | public function diffInWeekendDays(?ChronosDate $other = null, bool $absolute = true, int $options = 0): int 1550 | { 1551 | return $this->diffInDaysFiltered(function (ChronosDate $date) { 1552 | return $date->isWeekend(); 1553 | }, $other, $absolute, $options); 1554 | } 1555 | 1556 | /** 1557 | * Get the difference in a human readable format. 1558 | * 1559 | * When comparing a value in the past to default now: 1560 | * 5 months ago 1561 | * 1562 | * When comparing a value in the future to default now: 1563 | * 5 months from now 1564 | * 1565 | * When comparing a value in the past to another value: 1566 | * 5 months before 1567 | * 1568 | * When comparing a value in the future to another value: 1569 | * 5 months after 1570 | * 1571 | * @param \Cake\Chronos\ChronosDate|null $other The datetime to compare with. 1572 | * @param bool $absolute removes difference modifiers ago, after, etc 1573 | * @return string 1574 | */ 1575 | public function diffForHumans(?ChronosDate $other = null, bool $absolute = false): string 1576 | { 1577 | return static::diffFormatter()->diffForHumans($this, $other, $absolute); 1578 | } 1579 | 1580 | /** 1581 | * Returns the date as a `DateTimeImmutable` instance at midnight. 1582 | * 1583 | * @param \DateTimeZone|string|null $timezone Time zone the DateTimeImmutable instance will be in 1584 | * @return \DateTimeImmutable 1585 | */ 1586 | public function toDateTimeImmutable(DateTimeZone|string|null $timezone = null): DateTimeImmutable 1587 | { 1588 | if ($timezone === null) { 1589 | return $this->native; 1590 | } 1591 | 1592 | $timezone = is_string($timezone) ? new DateTimeZone($timezone) : $timezone; 1593 | 1594 | return new DateTimeImmutable($this->native->format('Y-m-d H:i:s.u'), $timezone); 1595 | } 1596 | 1597 | /** 1598 | * Returns the date as a `DateTimeImmutable` instance at midnight. 1599 | * 1600 | * Alias of `toDateTimeImmutable()`. 1601 | * 1602 | * @param \DateTimeZone|string|null $timezone Time zone the DateTimeImmutable instance will be in 1603 | * @return \DateTimeImmutable 1604 | */ 1605 | public function toNative(DateTimeZone|string|null $timezone = null): DateTimeImmutable 1606 | { 1607 | return $this->toDateTimeImmutable($timezone); 1608 | } 1609 | 1610 | /** 1611 | * Get a part of the object 1612 | * 1613 | * @param string $name The property name to read. 1614 | * @return string|float|int|bool The property value. 1615 | * @throws \InvalidArgumentException 1616 | */ 1617 | public function __get(string $name): string|float|int|bool 1618 | { 1619 | static $formats = [ 1620 | 'year' => 'Y', 1621 | 'yearIso' => 'o', 1622 | 'month' => 'n', 1623 | 'day' => 'j', 1624 | 'dayOfWeek' => 'N', 1625 | 'dayOfYear' => 'z', 1626 | 'weekOfYear' => 'W', 1627 | 'daysInMonth' => 't', 1628 | ]; 1629 | 1630 | switch (true) { 1631 | case isset($formats[$name]): 1632 | return (int)$this->format($formats[$name]); 1633 | 1634 | case $name === 'dayOfWeekName': 1635 | return $this->format('l'); 1636 | 1637 | case $name === 'weekOfMonth': 1638 | return (int)ceil($this->day / Chronos::DAYS_PER_WEEK); 1639 | 1640 | case $name === 'age': 1641 | return $this->diffInYears(); 1642 | 1643 | case $name === 'quarter': 1644 | return (int)ceil($this->month / 3); 1645 | 1646 | case $name === 'half': 1647 | return $this->month <= 6 ? 1 : 2; 1648 | 1649 | default: 1650 | throw new InvalidArgumentException(sprintf('Unknown getter `%s`', $name)); 1651 | } 1652 | } 1653 | 1654 | /** 1655 | * Check if an attribute exists on the object 1656 | * 1657 | * @param string $name The property name to check. 1658 | * @return bool Whether the property exists. 1659 | */ 1660 | public function __isset(string $name): bool 1661 | { 1662 | try { 1663 | $this->__get($name); 1664 | } catch (InvalidArgumentException $e) { 1665 | return false; 1666 | } 1667 | 1668 | return true; 1669 | } 1670 | 1671 | /** 1672 | * Return properties for debugging. 1673 | * 1674 | * @return array 1675 | */ 1676 | public function __debugInfo(): array 1677 | { 1678 | $properties = [ 1679 | 'hasFixedNow' => Chronos::hasTestNow(), 1680 | 'date' => $this->format('Y-m-d'), 1681 | ]; 1682 | 1683 | return $properties; 1684 | } 1685 | } 1686 | -------------------------------------------------------------------------------- /src/ChronosTime.php: -------------------------------------------------------------------------------- 1 | setTimezone($timezone); 88 | } 89 | $this->ticks = static::parseString($time->format('H:i:s.u')); 90 | } elseif (is_string($time)) { 91 | $this->ticks = static::parseString($time); 92 | } elseif ($time instanceof ChronosTime) { 93 | $this->ticks = $time->ticks; 94 | } else { 95 | $this->ticks = static::parseString($time->format('H:i:s.u')); 96 | } 97 | } 98 | 99 | /** 100 | * Copies time from onther instance or from string in the format HH[:.]mm or HH[:.]mm[:.]ss.u 101 | * 102 | * Defaults to server time. 103 | * 104 | * @param \Cake\Chronos\ChronosTime|\DateTimeInterface|string $time Time 105 | * @param \DateTimeZone|string|null $timezone The timezone to use for now 106 | * @return static 107 | */ 108 | public static function parse( 109 | ChronosTime|DateTimeInterface|string|null $time = null, 110 | DateTimeZone|string|null $timezone = null 111 | ): static { 112 | return new static($time, $timezone); 113 | } 114 | 115 | /** 116 | * @param string $time Time string in the format HH[:.]mm or HH[:.]mm[:.]ss.u 117 | * @return int 118 | */ 119 | protected static function parseString(string $time): int 120 | { 121 | if (!preg_match('/^\s*(\d{1,2})[:.](\d{1,2})(?|[:.](\d{1,2})[.](\d+)|[:.](\d{1,2}))?\s*$/', $time, $matches)) { 122 | throw new InvalidArgumentException( 123 | sprintf('Time string `%s` is not in expected format `HH[:.]mm` or `HH[:.]mm[:.]ss.u`.', $time) 124 | ); 125 | } 126 | 127 | $hours = (int)$matches[1]; 128 | $minutes = (int)$matches[2]; 129 | $seconds = (int)($matches[3] ?? 0); 130 | $microseconds = (int)substr($matches[4] ?? '', 0, 6); 131 | 132 | if ($hours > 24 || $minutes > 59 || $seconds > 59 || $microseconds > 999_999) { 133 | throw new InvalidArgumentException(sprintf('Time string `%s` contains invalid values.', $time)); 134 | } 135 | 136 | $ticks = $hours * self::TICKS_PER_HOUR; 137 | $ticks += $minutes * self::TICKS_PER_MINUTE; 138 | $ticks += $seconds * self::TICKS_PER_SECOND; 139 | $ticks += $microseconds * self::TICKS_PER_MICROSECOND; 140 | 141 | return $ticks % self::TICKS_PER_DAY; 142 | } 143 | 144 | /** 145 | * Returns instance set to server time. 146 | * 147 | * @param \DateTimeZone|string|null $timezone The timezone to use for now 148 | * @return static 149 | */ 150 | public static function now(DateTimeZone|string|null $timezone = null): static 151 | { 152 | return new static(null, $timezone); 153 | } 154 | 155 | /** 156 | * Returns instance set to midnight. 157 | * 158 | * @return static 159 | */ 160 | public static function midnight(): static 161 | { 162 | return new static('00:00:00'); 163 | } 164 | 165 | /** 166 | * Returns instance set to noon. 167 | * 168 | * @return static 169 | */ 170 | public static function noon(): static 171 | { 172 | return new static('12:00:00'); 173 | } 174 | 175 | /** 176 | * Returns instance set to end of day - either 177 | * 23:59:59 or 23:59:59.999999 if `$microseconds` is true 178 | * 179 | * @param bool $microseconds Whether to set microseconds or not 180 | * @return static 181 | */ 182 | public static function endOfDay(bool $microseconds = false): static 183 | { 184 | if ($microseconds) { 185 | return new static('23:59:59.999999'); 186 | } 187 | 188 | return new static('23:59:59'); 189 | } 190 | 191 | /** 192 | * Returns clock microseconds. 193 | * 194 | * @return int 195 | */ 196 | public function getMicroseconds(): int 197 | { 198 | return intdiv($this->ticks % self::TICKS_PER_SECOND, self::TICKS_PER_MICROSECOND); 199 | } 200 | 201 | /** 202 | * Sets clock microseconds. 203 | * 204 | * @param int $microseconds Clock microseconds 205 | * @return static 206 | */ 207 | public function setMicroseconds(int $microseconds): static 208 | { 209 | $baseTicks = $this->ticks - $this->ticks % self::TICKS_PER_SECOND; 210 | $newTicks = static::mod($baseTicks + $microseconds * self::TICKS_PER_MICROSECOND, self::TICKS_PER_DAY); 211 | 212 | $clone = clone $this; 213 | $clone->ticks = $newTicks; 214 | 215 | return $clone; 216 | } 217 | 218 | /** 219 | * Return clock seconds. 220 | * 221 | * @return int 222 | */ 223 | public function getSeconds(): int 224 | { 225 | $secondsTicks = $this->ticks % self::TICKS_PER_MINUTE - $this->ticks % self::TICKS_PER_SECOND; 226 | 227 | return intdiv($secondsTicks, self::TICKS_PER_SECOND); 228 | } 229 | 230 | /** 231 | * Set clock seconds. 232 | * 233 | * @param int $seconds Clock seconds 234 | * @return static 235 | */ 236 | public function setSeconds(int $seconds): static 237 | { 238 | $baseTicks = $this->ticks - ($this->ticks % self::TICKS_PER_MINUTE - $this->ticks % self::TICKS_PER_SECOND); 239 | $newTicks = static::mod($baseTicks + $seconds * self::TICKS_PER_SECOND, self::TICKS_PER_DAY); 240 | 241 | $clone = clone $this; 242 | $clone->ticks = $newTicks; 243 | 244 | return $clone; 245 | } 246 | 247 | /** 248 | * Returns clock minutes. 249 | * 250 | * @return int 251 | */ 252 | public function getMinutes(): int 253 | { 254 | $minutesTicks = $this->ticks % self::TICKS_PER_HOUR - $this->ticks % self::TICKS_PER_MINUTE; 255 | 256 | return intdiv($minutesTicks, self::TICKS_PER_MINUTE); 257 | } 258 | 259 | /** 260 | * Set clock minutes. 261 | * 262 | * @param int $minutes Clock minutes 263 | * @return static 264 | */ 265 | public function setMinutes(int $minutes): static 266 | { 267 | $baseTicks = $this->ticks - ($this->ticks % self::TICKS_PER_HOUR - $this->ticks % self::TICKS_PER_MINUTE); 268 | $newTicks = static::mod($baseTicks + $minutes * self::TICKS_PER_MINUTE, self::TICKS_PER_DAY); 269 | 270 | $clone = clone $this; 271 | $clone->ticks = $newTicks; 272 | 273 | return $clone; 274 | } 275 | 276 | /** 277 | * Returns clock hours. 278 | * 279 | * @return int 280 | */ 281 | public function getHours(): int 282 | { 283 | $hoursInTicks = $this->ticks - $this->ticks % self::TICKS_PER_HOUR; 284 | 285 | return intdiv($hoursInTicks, self::TICKS_PER_HOUR); 286 | } 287 | 288 | /** 289 | * Set clock hours. 290 | * 291 | * @param int $hours Clock hours 292 | * @return static 293 | */ 294 | public function setHours(int $hours): static 295 | { 296 | $baseTicks = $this->ticks - ($this->ticks - $this->ticks % self::TICKS_PER_HOUR); 297 | $newTicks = static::mod($baseTicks + $hours * self::TICKS_PER_HOUR, self::TICKS_PER_DAY); 298 | 299 | $clone = clone $this; 300 | $clone->ticks = $newTicks; 301 | 302 | return $clone; 303 | } 304 | 305 | /** 306 | * Sets clock time. 307 | * 308 | * @param int $hours Clock hours 309 | * @param int $minutes Clock minutes 310 | * @param int $seconds Clock seconds 311 | * @param int $microseconds Clock microseconds 312 | * @return static 313 | */ 314 | public function setTime(int $hours = 0, int $minutes = 0, int $seconds = 0, int $microseconds = 0): static 315 | { 316 | $ticks = $hours * self::TICKS_PER_HOUR + 317 | $minutes * self::TICKS_PER_MINUTE + 318 | $seconds * self::TICKS_PER_SECOND + 319 | $microseconds * self::TICKS_PER_MICROSECOND; 320 | $ticks = static::mod($ticks, self::TICKS_PER_DAY); 321 | 322 | $clone = clone $this; 323 | $clone->ticks = $ticks; 324 | 325 | return $clone; 326 | } 327 | 328 | /** 329 | * @param int $a Left side 330 | * @param int $a Right side 331 | * @return int 332 | */ 333 | protected static function mod(int $a, int $b): int 334 | { 335 | if ($a < 0) { 336 | return $a % $b + $b; 337 | } 338 | 339 | return $a % $b; 340 | } 341 | 342 | /** 343 | * Formats string using the same syntax as `DateTimeImmutable::format()`. 344 | * 345 | * As this uses DateTimeImmutable::format() to format the string, non-time formatters 346 | * will still be interpreted. Be sure to escape those characters first. 347 | * 348 | * @param string $format Format string 349 | * @return string 350 | */ 351 | public function format(string $format): string 352 | { 353 | return $this->toDateTimeImmutable()->format($format); 354 | } 355 | 356 | /** 357 | * Reset the format used to the default when converting to a string 358 | * 359 | * @return void 360 | */ 361 | public static function resetToStringFormat(): void 362 | { 363 | static::setToStringFormat(static::DEFAULT_TO_STRING_FORMAT); 364 | } 365 | 366 | /** 367 | * Set the default format used when converting to a string 368 | * 369 | * @param string $format The format to use in future __toString() calls. 370 | * @return void 371 | */ 372 | public static function setToStringFormat(string $format): void 373 | { 374 | static::$toStringFormat = $format; 375 | } 376 | 377 | /** 378 | * Format the instance as a string using the set format 379 | * 380 | * @return string 381 | */ 382 | public function __toString(): string 383 | { 384 | return $this->format(static::$toStringFormat); 385 | } 386 | 387 | /** 388 | * Returns whether time is equal to target time. 389 | * 390 | * @param \Cake\Chronos\ChronosTime $target Target time 391 | * @return bool 392 | */ 393 | public function equals(ChronosTime $target): bool 394 | { 395 | return $this->ticks === $target->ticks; 396 | } 397 | 398 | /** 399 | * Returns whether time is greater than target time. 400 | * 401 | * @param \Cake\Chronos\ChronosTime $target Target time 402 | * @return bool 403 | */ 404 | public function greaterThan(ChronosTime $target): bool 405 | { 406 | return $this->ticks > $target->ticks; 407 | } 408 | 409 | /** 410 | * Returns whether time is greater than or equal to target time. 411 | * 412 | * @param \Cake\Chronos\ChronosTime $target Target time 413 | * @return bool 414 | */ 415 | public function greaterThanOrEquals(ChronosTime $target): bool 416 | { 417 | return $this->ticks >= $target->ticks; 418 | } 419 | 420 | /** 421 | * Returns whether time is less than target time. 422 | * 423 | * @param \Cake\Chronos\ChronosTime $target Target time 424 | * @return bool 425 | */ 426 | public function lessThan(ChronosTime $target): bool 427 | { 428 | return $this->ticks < $target->ticks; 429 | } 430 | 431 | /** 432 | * Returns whether time is less than or equal to target time. 433 | * 434 | * @param \Cake\Chronos\ChronosTime $target Target time 435 | * @return bool 436 | */ 437 | public function lessThanOrEquals(ChronosTime $target): bool 438 | { 439 | return $this->ticks <= $target->ticks; 440 | } 441 | 442 | /** 443 | * Returns whether time is between time range. 444 | * 445 | * @param \Cake\Chronos\ChronosTime $start Start of target range 446 | * @param \Cake\Chronos\ChronosTime $end End of target range 447 | * @param bool $equals Whether to include the beginning and end of range 448 | * @return bool 449 | */ 450 | public function between(ChronosTime $start, ChronosTime $end, bool $equals = true): bool 451 | { 452 | if ($start->greaterThan($end)) { 453 | [$start, $end] = [$end, $start]; 454 | } 455 | 456 | if ($equals) { 457 | return $this->greaterThanOrEquals($start) && $this->lessThanOrEquals($end); 458 | } 459 | 460 | return $this->greaterThan($start) && $this->lessThan($end); 461 | } 462 | 463 | /** 464 | * Returns an `DateTimeImmutable` instance set to this clock time. 465 | * 466 | * @param \DateTimeZone|string|null $timezone Time zone the DateTimeImmutable instance will be in 467 | * @return \DateTimeImmutable 468 | */ 469 | public function toDateTimeImmutable(DateTimeZone|string|null $timezone = null): DateTimeImmutable 470 | { 471 | $timezone = is_string($timezone) ? new DateTimeZone($timezone) : $timezone; 472 | 473 | return (new DateTimeImmutable(timezone: $timezone))->setTime( 474 | $this->getHours(), 475 | $this->getMinutes(), 476 | $this->getSeconds(), 477 | $this->getMicroseconds() 478 | ); 479 | } 480 | 481 | /** 482 | * Returns an `DateTimeImmutable` instance set to this clock time. 483 | * 484 | * Alias of `toDateTimeImmutable()`. 485 | * 486 | * @param \DateTimeZone|string|null $timezone Time zone the DateTimeImmutable instance will be in 487 | * @return \DateTimeImmutable 488 | */ 489 | public function toNative(DateTimeZone|string|null $timezone = null): DateTimeImmutable 490 | { 491 | return $this->toDateTimeImmutable($timezone); 492 | } 493 | } 494 | -------------------------------------------------------------------------------- /src/ClockFactory.php: -------------------------------------------------------------------------------- 1 | timezone = $timezone; 36 | } 37 | 38 | /** 39 | * Returns the current time object. 40 | * 41 | * @return \Cake\Chronos\Chronos The current time 42 | */ 43 | public function now(): DateTimeImmutable 44 | { 45 | return Chronos::now($this->timezone); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/DifferenceFormatter.php: -------------------------------------------------------------------------------- 1 | 12 | * @link https://cakephp.org CakePHP(tm) Project 13 | * @license https://www.opensource.org/licenses/mit-license.php MIT License 14 | */ 15 | namespace Cake\Chronos; 16 | 17 | use DateTimeInterface; 18 | 19 | /** 20 | * Handles formatting differences in text. 21 | * 22 | * Provides a swappable component for other libraries to leverage. 23 | * when localizing or customizing the difference output. 24 | * 25 | * @internal 26 | */ 27 | class DifferenceFormatter implements DifferenceFormatterInterface 28 | { 29 | /** 30 | * The text translator object 31 | * 32 | * @var \Cake\Chronos\Translator 33 | */ 34 | protected Translator $translate; 35 | 36 | /** 37 | * Constructor. 38 | * 39 | * @param \Cake\Chronos\Translator|null $translate The text translator object. 40 | */ 41 | public function __construct(?Translator $translate = null) 42 | { 43 | $this->translate = $translate ?: new Translator(); 44 | } 45 | 46 | /** 47 | * @inheritDoc 48 | */ 49 | public function diffForHumans( 50 | ChronosDate|DateTimeInterface $first, 51 | ChronosDate|DateTimeInterface|null $second = null, 52 | bool $absolute = false 53 | ): string { 54 | $isNow = $second === null; 55 | if ($second === null) { 56 | if ($first instanceof ChronosDate) { 57 | $second = new ChronosDate(Chronos::now()); 58 | } else { 59 | $second = Chronos::now($first->getTimezone()); 60 | } 61 | } 62 | assert( 63 | ($first instanceof ChronosDate && $second instanceof ChronosDate) || 64 | ($first instanceof DateTimeInterface && $second instanceof DateTimeInterface) 65 | ); 66 | 67 | $diffInterval = $first->diff($second); 68 | 69 | switch (true) { 70 | case $diffInterval->y > 0: 71 | $unit = 'year'; 72 | $count = $diffInterval->y; 73 | break; 74 | case $diffInterval->m >= 2: 75 | $unit = 'month'; 76 | $count = $diffInterval->m; 77 | break; 78 | case $diffInterval->days >= Chronos::DAYS_PER_WEEK * 3: 79 | $unit = 'week'; 80 | $count = (int)($diffInterval->days / Chronos::DAYS_PER_WEEK); 81 | break; 82 | case $diffInterval->d > 0: 83 | $unit = 'day'; 84 | $count = $diffInterval->d; 85 | break; 86 | case $diffInterval->h > 0: 87 | $unit = 'hour'; 88 | $count = $diffInterval->h; 89 | break; 90 | case $diffInterval->i > 0: 91 | $unit = 'minute'; 92 | $count = $diffInterval->i; 93 | break; 94 | default: 95 | $count = $diffInterval->s; 96 | $unit = 'second'; 97 | break; 98 | } 99 | $time = $this->translate->plural($unit, $count, ['count' => $count]); 100 | if ($absolute) { 101 | return $time; 102 | } 103 | $isFuture = $diffInterval->invert === 1; 104 | $transId = $isNow ? ($isFuture ? 'from_now' : 'ago') : ($isFuture ? 'after' : 'before'); 105 | 106 | // Some langs have special pluralization for past and future tense. 107 | $tryKeyExists = $unit . '_' . $transId; 108 | if ($this->translate->exists($tryKeyExists)) { 109 | $time = $this->translate->plural($tryKeyExists, $count, ['count' => $count]); 110 | } 111 | 112 | return $this->translate->singular($transId, ['time' => $time]); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/DifferenceFormatterInterface.php: -------------------------------------------------------------------------------- 1 | 12 | * @link https://cakephp.org CakePHP(tm) Project 13 | * @license https://www.opensource.org/licenses/mit-license.php MIT License 14 | */ 15 | namespace Cake\Chronos; 16 | 17 | use DateTime; 18 | 19 | /** 20 | * Provides string formatting methods for datetime instances. 21 | * 22 | * Expects implementing classes to define static::$toStringFormat 23 | * 24 | * @internal 25 | */ 26 | trait FormattingTrait 27 | { 28 | /** 29 | * Resets the __toString() format to ``DEFAULT_TO_STRING_FORMAT``. 30 | * 31 | * @return void 32 | */ 33 | public static function resetToStringFormat(): void 34 | { 35 | static::setToStringFormat(static::DEFAULT_TO_STRING_FORMAT); 36 | } 37 | 38 | /** 39 | * Sets the __toString() format. 40 | * 41 | * @param string $format See ``format()`` for accepted specifiers. 42 | * @return void 43 | */ 44 | public static function setToStringFormat(string $format): void 45 | { 46 | static::$toStringFormat = $format; 47 | } 48 | 49 | /** 50 | * Returns a formatted string specified by ``setToStringFormat()`` 51 | * or the default ``DEFAULT_TO_STRING_FORMAT`` format. 52 | * 53 | * @return string 54 | */ 55 | public function __toString(): string 56 | { 57 | return $this->format(static::$toStringFormat); 58 | } 59 | 60 | /** 61 | * Format the instance as date 62 | * 63 | * @return string 64 | */ 65 | public function toDateString(): string 66 | { 67 | return $this->format('Y-m-d'); 68 | } 69 | 70 | /** 71 | * Format the instance as a readable date 72 | * 73 | * @return string 74 | */ 75 | public function toFormattedDateString(): string 76 | { 77 | return $this->format('M j, Y'); 78 | } 79 | 80 | /** 81 | * Format the instance as time 82 | * 83 | * @return string 84 | */ 85 | public function toTimeString(): string 86 | { 87 | return $this->format('H:i:s'); 88 | } 89 | 90 | /** 91 | * Format the instance as date and time 92 | * 93 | * @return string 94 | */ 95 | public function toDateTimeString(): string 96 | { 97 | return $this->format('Y-m-d H:i:s'); 98 | } 99 | 100 | /** 101 | * Format the instance with day, date and time 102 | * 103 | * @return string 104 | */ 105 | public function toDayDateTimeString(): string 106 | { 107 | return $this->format('D, M j, Y g:i A'); 108 | } 109 | 110 | /** 111 | * Format the instance as ATOM 112 | * 113 | * @return string 114 | */ 115 | public function toAtomString(): string 116 | { 117 | return $this->format(DateTime::ATOM); 118 | } 119 | 120 | /** 121 | * Format the instance as COOKIE 122 | * 123 | * @return string 124 | */ 125 | public function toCookieString(): string 126 | { 127 | return $this->format(DateTime::COOKIE); 128 | } 129 | 130 | /** 131 | * Format the instance as ISO8601 132 | * 133 | * @return string 134 | */ 135 | public function toIso8601String(): string 136 | { 137 | return $this->format(DateTime::ATOM); 138 | } 139 | 140 | /** 141 | * Format the instance as RFC822 142 | * 143 | * @return string 144 | * @link https://tools.ietf.org/html/rfc822 145 | */ 146 | public function toRfc822String(): string 147 | { 148 | return $this->format(DateTime::RFC822); 149 | } 150 | 151 | /** 152 | * Format the instance as RFC850 153 | * 154 | * @return string 155 | * @link https://tools.ietf.org/html/rfc850 156 | */ 157 | public function toRfc850String(): string 158 | { 159 | return $this->format(DateTime::RFC850); 160 | } 161 | 162 | /** 163 | * Format the instance as RFC1036 164 | * 165 | * @return string 166 | * @link https://tools.ietf.org/html/rfc1036 167 | */ 168 | public function toRfc1036String(): string 169 | { 170 | return $this->format(DateTime::RFC1036); 171 | } 172 | 173 | /** 174 | * Format the instance as RFC1123 175 | * 176 | * @return string 177 | * @link https://tools.ietf.org/html/rfc1123 178 | */ 179 | public function toRfc1123String(): string 180 | { 181 | return $this->format(DateTime::RFC1123); 182 | } 183 | 184 | /** 185 | * Format the instance as RFC2822 186 | * 187 | * @return string 188 | * @link https://tools.ietf.org/html/rfc2822 189 | */ 190 | public function toRfc2822String(): string 191 | { 192 | return $this->format(DateTime::RFC2822); 193 | } 194 | 195 | /** 196 | * Format the instance as RFC3339 197 | * 198 | * @return string 199 | * @link https://tools.ietf.org/html/rfc3339 200 | */ 201 | public function toRfc3339String(): string 202 | { 203 | return $this->format(DateTime::RFC3339); 204 | } 205 | 206 | /** 207 | * Format the instance as RSS 208 | * 209 | * @return string 210 | */ 211 | public function toRssString(): string 212 | { 213 | return $this->format(DateTime::RSS); 214 | } 215 | 216 | /** 217 | * Format the instance as W3C 218 | * 219 | * @return string 220 | */ 221 | public function toW3cString(): string 222 | { 223 | return $this->format(DateTime::W3C); 224 | } 225 | 226 | /** 227 | * Returns a UNIX timestamp. 228 | * 229 | * @return string UNIX timestamp 230 | */ 231 | public function toUnixString(): string 232 | { 233 | return $this->format('U'); 234 | } 235 | 236 | /** 237 | * Returns the quarter 238 | * 239 | * @param bool $range Range. 240 | * @return array|int 1, 2, 3, or 4 quarter of year or array if $range true 241 | */ 242 | public function toQuarter(bool $range = false): int|array 243 | { 244 | $quarter = (int)ceil((int)$this->format('m') / 3); 245 | if ($range === false) { 246 | return $quarter; 247 | } 248 | 249 | $year = $this->format('Y'); 250 | switch ($quarter) { 251 | case 1: 252 | return [$year . '-01-01', $year . '-03-31']; 253 | case 2: 254 | return [$year . '-04-01', $year . '-06-30']; 255 | case 3: 256 | return [$year . '-07-01', $year . '-09-30']; 257 | default: 258 | return [$year . '-10-01', $year . '-12-31']; 259 | } 260 | } 261 | 262 | /** 263 | * Returns ISO 8601 week number of year, weeks starting on Monday 264 | * 265 | * @return int ISO 8601 week number of year 266 | */ 267 | public function toWeek(): int 268 | { 269 | return (int)$this->format('W'); 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /src/Translator.php: -------------------------------------------------------------------------------- 1 | '1 year', 30 | 'year_plural' => '{count} years', 31 | 'month' => '1 month', 32 | 'month_plural' => '{count} months', 33 | 'week' => '1 week', 34 | 'week_plural' => '{count} weeks', 35 | 'day' => '1 day', 36 | 'day_plural' => '{count} days', 37 | 'hour' => '1 hour', 38 | 'hour_plural' => '{count} hours', 39 | 'minute' => '1 minute', 40 | 'minute_plural' => '{count} minutes', 41 | 'second' => '1 second', 42 | 'second_plural' => '{count} seconds', 43 | 'ago' => '{time} ago', 44 | 'from_now' => '{time} from now', 45 | 'after' => '{time} after', 46 | 'before' => '{time} before', 47 | ]; 48 | 49 | /** 50 | * Check if a translation key exists. 51 | * 52 | * @param string $key The key to check. 53 | * @return bool Whether the key exists. 54 | */ 55 | public function exists(string $key): bool 56 | { 57 | return isset(static::$strings[$key]); 58 | } 59 | 60 | /** 61 | * Get a plural message. 62 | * 63 | * @param string $key The key to use. 64 | * @param int $count The number of items in the translation. 65 | * @param array $vars Additional context variables. 66 | * @return string The translated message or ''. 67 | */ 68 | public function plural(string $key, int $count, array $vars = []): string 69 | { 70 | if ($count === 1) { 71 | return $this->singular($key, $vars); 72 | } 73 | 74 | return $this->singular($key . '_plural', ['count' => $count] + $vars); 75 | } 76 | 77 | /** 78 | * Get a singular message. 79 | * 80 | * @param string $key The key to use. 81 | * @param array $vars Additional context variables. 82 | * @return string The translated message or ''. 83 | */ 84 | public function singular(string $key, array $vars = []): string 85 | { 86 | if (isset(static::$strings[$key])) { 87 | $varKeys = array_keys($vars); 88 | foreach ($varKeys as $i => $k) { 89 | $varKeys[$i] = '{' . $k . '}'; 90 | } 91 | 92 | return str_replace($varKeys, $vars, static::$strings[$key]); 93 | } 94 | 95 | return ''; 96 | } 97 | } 98 | --------------------------------------------------------------------------------