diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..bdd2c9b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +root = true + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = tab +indent_size = 4 + +[{*.yaml,*.yml,*.neon,*.json}] +indent_size = 2 +indent_style = space + +[*.md] +indent_style = space +max_line_length = 100 diff --git a/.gitattributes b/.gitattributes index 7a6aaab..2935763 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,6 @@ -.github +* text=auto + +.editorconfig export-ignore .gitattributes export-ignore .github export-ignore .gitignore export-ignore diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 96968b1..aef62d4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,15 +7,15 @@ on: jobs: phpunit: name: phpunit - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest strategy: matrix: php-version: - - "7.3" - - "8.1" + - "8.2" + - "8.3" steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Install PHP uses: shivammathur/setup-php@v2 with: @@ -24,7 +24,7 @@ jobs: ini-values: memory_limit=-1 tools: composer:v2 - name: Cache dependencies - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: | ~/.composer/cache @@ -39,7 +39,7 @@ jobs: run: make test-coveralls - name: Upload code coverage - if: ${{ matrix.php-version == '7.3' }} + if: ${{ matrix.php-version == '8.3' }} env: COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | diff --git a/.scrutinizer.yml b/.scrutinizer.yml new file mode 100644 index 0000000..dd7f4e0 --- /dev/null +++ b/.scrutinizer.yml @@ -0,0 +1,12 @@ +build: + nodes: + analysis: + tests: + override: + - php-scrutinizer-run + environment: + php: + version: "8.2" +filter: + excluded_paths: + - 'tests/*' diff --git a/MIGRATION.md b/CHANGELOG.md similarity index 59% rename from MIGRATION.md rename to CHANGELOG.md index 403610e..1aadc7b 100644 --- a/MIGRATION.md +++ b/CHANGELOG.md @@ -1,6 +1,30 @@ -# Migration +# CHANGELOG -## v1.x to v2.x +## v2.x to v3.0 + +### New Requirements + +Requires PHP 8.2+ + +### New features + +None + +### Backward Incompatible Changes + +None + +### Deprecated Features + +None + +### Other Changes + +None + + + +## v1.x to v2.0 ### New Requirements diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..5ef93b0 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,132 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available +at [https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ed6eaf4..33bc016 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,11 +2,13 @@ Contributions are **welcome** and will be fully **credited**. -We accept contributions via Pull Requests on [Github](https://github.com/ICanBoogie/Inflector). +We accept contributions via Pull Requests. ## Pull Requests -- **Add tests!** — Your contribution won't be accepted if it doesn't have tests. +- **Code style** — We're following a [Coding Standard][]. Check the code style with `make lint`. +- **Code health** — We're using [PHPStan][] to analyse the code, with maximum scrutiny. Check the code with `make lint`. +- **Add tests!** — Your contribution won't be accepted if it does not have tests. - **Document any change in behaviour** — Make sure the `README.md` and any other relevant documentation are kept up-to-date. - **Consider our release cycle** — We follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not @@ -25,4 +27,6 @@ coverage. The coverage report is available in `build/coverage/index.html`. **Thanks for your contribution**! +[Coding Standard]: phpcs.xml [git-squash]: http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages +[PHPStan]: https://phpstan.org/user-guide/getting-started diff --git a/Dockerfile b/Dockerfile index f2e62ad..eccf687 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,38 @@ -ARG PHP_VERSION -FROM php:${PHP_VERSION}-cli-alpine +ARG PHP_VERSION=8.2 +FROM php:${PHP_VERSION}-cli-bookworm -RUN apk add --no-cache make && \ - docker-php-ext-enable opcache && \ - docker-php-source delete +RUN <<-EOF + apt-get update + apt-get install -y autoconf pkg-config + pecl channel-update pecl.php.net + pecl install xdebug + docker-php-ext-enable opcache xdebug +EOF -RUN echo $'\ -display_errors=On\n\ -error_reporting=E_ALL\n\ -date.timezone=UTC\n\ -' >> /usr/local/etc/php/conf.d/php.ini +RUN <<-EOF + cat <<-SHELL >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini + xdebug.client_host=host.docker.internal + xdebug.mode=develop + xdebug.start_with_request=yes + SHELL + + cat <<-SHELL >> /usr/local/etc/php/conf.d/php.ini + display_errors=On + error_reporting=E_ALL + date.timezone=UTC + SHELL +EOF ENV COMPOSER_ALLOW_SUPERUSER 1 -RUN curl -s https://raw.githubusercontent.com/composer/getcomposer.org/76a7060ccb93902cd7576b67264ad91c8a2700e2/web/installer | php -- --quiet && \ - mv composer.phar /usr/local/bin/composer && \ - echo $'export PATH="$HOME/.composer/vendor/bin:$PATH"\n' >> /root/.profile +RUN <<-EOF + apt-get update + apt-get install unzip + curl -s https://raw.githubusercontent.com/composer/getcomposer.org/76a7060ccb93902cd7576b67264ad91c8a2700e2/web/installer | php -- --quiet + mv composer.phar /usr/local/bin/composer + cat <<-SHELL >> /root/.bashrc + export PATH="$HOME/.composer/vendor/bin:$PATH" + SHELL +EOF + +RUN composer global require squizlabs/php_codesniffer diff --git a/LICENSE b/LICENSE index 19db9f1..edfca52 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ The ICanBoogie/DateTime package is free software. It is released under the terms of the following BSD License. -Copyright (c) 2013-2022 by Olivier Laviale +Copyright (c) 2013 by Olivier Laviale All rights reserved. Redistribution and use in source and binary forms, with or without modification, diff --git a/Makefile b/Makefile index 8d148f4..17fb4ce 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,5 @@ # customization -PACKAGE_NAME = ICanBoogie/DateTime PHPUNIT = vendor/bin/phpunit # do not edit the following lines @@ -15,7 +14,7 @@ test-dependencies: vendor .PHONY: test test: test-dependencies - @$(PHPUNIT) + @$(PHPUNIT) $(ARGS) .PHONY: test-coverage test-coverage: test-dependencies @@ -27,12 +26,15 @@ test-coveralls: test-dependencies @mkdir -p build/logs @XDEBUG_MODE=coverage $(PHPUNIT) --coverage-clover build/logs/clover.xml -.PHONY: test-container-73 -test-container-73: - @docker-compose run --rm app73 sh +.PHONY: test-container +test-container: test-container-82 + +.PHONY: test-container-82 +test-container-82: + @docker-compose run --rm app82 bash @docker-compose down -.PHONY: test-container-81 -test-container-81: - @docker-compose run --rm app81 sh +.PHONY: test-container-83 +test-container-83: + @docker-compose run --rm app83 bash @docker-compose down diff --git a/README.md b/README.md index d7b2b9f..0be1868 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # DateTime [![Release](https://img.shields.io/packagist/v/ICanBoogie/DateTime.svg)](https://packagist.org/packages/icanboogie/datetime) -[![Coverage Status](https://coveralls.io/repos/github/ICanBoogie/DateTime/badge.svg?branch=2.0)](https://coveralls.io/github/ICanBoogie/DateTime?branch=2.0) -[![Packagist](https://img.shields.io/packagist/dm/icanboogie/datetime.svg)](https://packagist.org/packages/icanboogie/datetime) +[![Code Coverage](https://coveralls.io/repos/github/ICanBoogie/DateTime/badge.svg?branch=3.0)](https://coveralls.io/github/ICanBoogie/DateTime?branch=3.0) +[![Downloads](https://img.shields.io/packagist/dm/icanboogie/datetime.svg)](https://packagist.org/packages/icanboogie/datetime) This package extends the features of PHP [DateTime](http://www.php.net/manual/en/class.datetime.php) and [DateTimeZone](http://www.php.net/manual/en/class.datetimezone.php) classes to ease the @@ -353,13 +353,20 @@ echo $date->localize('fr')->as_medium; // 5 mai 2015 23:13:05 The project is continuously tested by [GitHub actions](https://github.com/ICanBoogie/DateTime/actions). -[![Tests](https://github.com/ICanBoogie/DateTime/actions/workflows/test.yml/badge.svg?branch=2.0)](https://github.com/ICanBoogie/DateTime/actions/workflows/test.yml) +[![Tests](https://github.com/ICanBoogie/DateTime/actions/workflows/test.yml/badge.svg?branch=3.0)](https://github.com/ICanBoogie/DateTime/actions/workflows/test.yml) + + + +## Code of Conduct + +This project adheres to a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in +this project and its community, you're expected to uphold this code. ## Contributing -Please see [CONTRIBUTING](CONTRIBUTING.md) for details. +See [CONTRIBUTING](CONTRIBUTING.md) for details. diff --git a/composer.json b/composer.json index 5c19f46..798399e 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,11 @@ "name": "icanboogie/datetime", "type": "library", "description": "Extends the features of PHP DateTime and DateTimeZone", - "keywords": ["time", "date", "timezone"], + "keywords": [ + "time", + "date", + "timezone" + ], "homepage": "https://icanboogie.org/", "license": "BSD-3-Clause", "authors": [ @@ -10,7 +14,7 @@ "name": "Olivier Laviale", "email": "olivier.laviale@gmail.com", "homepage": "https://olvlvl.com/", - "role" : "Developer" + "role": "Developer" } ], "support": { @@ -21,11 +25,11 @@ "sort-packages": true }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { "icanboogie/common": "^2.0", - "phpunit/phpunit": "^9.5" + "phpunit/phpunit": "^11.4" }, "suggest": { "icanboogie/common": "Allows finer exceptions to be thrown" diff --git a/docker-compose.yaml b/docker-compose.yaml index 953abf0..54dc2f5 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,22 +1,21 @@ --- -version: "3.0" services: - app73: + app82: build: context: . args: - PHP_VERSION: 7.3 + PHP_VERSION: "8.2" environment: PHP_IDE_CONFIG: "serverName=icanboogie-datetime" volumes: - .:/app:delegated - ~/.composer:/root/.composer:delegated working_dir: /app - app81: + app83: build: context: . args: - PHP_VERSION: 8.1 + PHP_VERSION: "8.3" environment: PHP_IDE_CONFIG: "serverName=icanboogie-datetime" volumes: diff --git a/lib/DateTime.php b/lib/DateTime.php index f194158..0c52619 100644 --- a/lib/DateTime.php +++ b/lib/DateTime.php @@ -1,14 +1,5 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - namespace ICanBoogie; use DateTimeZone; @@ -125,21 +116,21 @@ * @property-read DateTime $saturday A new instance representing Saturday of the week. Time is reset to 00:00:00. * @property-read DateTime $sunday A new instance representing Sunday of the week. Time is reset to 00:00:00. * - * @property-read string $as_atom The instance formatted according to {@link ATOM}. - * @property-read string $as_cookie The instance formatted according to {@link COOKIE}. - * @property-read string $as_iso8601 The instance formatted according to {@link ISO8601}. - * @property-read string $as_rfc822 The instance formatted according to {@link RFC822}. - * @property-read string $as_rfc850 The instance formatted according to {@link RFC850}. - * @property-read string $as_rfc1036 The instance formatted according to {@link RFC1036}. - * @property-read string $as_rfc1123 The instance formatted according to {@link RFC1123}. - * @property-read string $as_rfc2822 The instance formatted according to {@link RFC2822}. - * @property-read string $as_rfc3339 The instance formatted according to {@link RFC3339}. - * @property-read string $as_rss The instance formatted according to {@link RSS}. - * @property-read string $as_w3c The instance formatted according to {@link W3C}. - * @property-read string $as_db The instance formatted according to {@link DB}. - * @property-read string $as_number The instance formatted according to {@link NUMBER}. - * @property-read string $as_date The instance formatted according to {@link DATE}. - * @property-read string $as_time The instance formatted according to {@link TIME}. + * @property-read string $as_atom The instance formatted according to {@see ATOM}. + * @property-read string $as_cookie The instance formatted according to {@see COOKIE}. + * @property-read string $as_iso8601 The instance formatted according to {@see ISO8601}. + * @property-read string $as_rfc822 The instance formatted according to {@see RFC822}. + * @property-read string $as_rfc850 The instance formatted according to {@see RFC850}. + * @property-read string $as_rfc1036 The instance formatted according to {@see RFC1036}. + * @property-read string $as_rfc1123 The instance formatted according to {@see RFC1123}. + * @property-read string $as_rfc2822 The instance formatted according to {@see RFC2822}. + * @property-read string $as_rfc3339 The instance formatted according to {@see RFC3339}. + * @property-read string $as_rss The instance formatted according to {@see RSS}. + * @property-read string $as_w3c The instance formatted according to {@see W3C}. + * @property-read string $as_db The instance formatted according to {@see DB}. + * @property-read string $as_number The instance formatted according to {@see NUMBER}. + * @property-read string $as_date The instance formatted according to {@see DATE}. + * @property-read string $as_time The instance formatted according to {@see TIME}. * * @property TimeZone $zone The timezone of the instance. * @property-read DateTime $utc A new instance in the UTC timezone. @@ -148,30 +139,30 @@ * @property-read bool $is_local `true` if the instance is in the local timezone. * @property-read bool $is_dst `true` if time occurs during Daylight Saving Time in its time zone. * - * @method string format_as_atom() format_as_atom() Formats the instance according to {@link ATOM}. - * @method string format_as_cookie() format_as_cookie() Formats the instance according to {@link COOKIE}. - * @method string format_as_iso8601() format_as_iso8601() Formats the instance according to {@link ISO8601}. - * @method string format_as_rfc822() format_as_rfc822() Formats the instance according to {@link RFC822}. - * @method string format_as_rfc850() format_as_rfc850() Formats the instance according to {@link RFC850}. - * @method string format_as_rfc1036() format_as_rfc1036() Formats the instance according to {@link RFC1036}. - * @method string format_as_rfc1123() format_as_rfc1123() Formats the instance according to {@link RFC1123}. - * @method string format_as_rfc2822() format_as_rfc2822() Formats the instance according to {@link RFC2822}. - * @method string format_as_rfc3339() format_as_rfc3339() Formats the instance according to {@link RFC3339}. - * @method string format_as_rss() format_as_rss() Formats the instance according to {@link RSS}. - * @method string format_as_w3c() format_as_w3c() Formats the instance according to {@link W3C}. - * @method string format_as_db() format_as_db() Formats the instance according to {@link DB}. - * @method string format_as_number() format_as_number() Formats the instance according to {@link NUMBER}. - * @method string format_as_date() format_as_date() Formats the instance according to {@link DATE}. - * @method string format_as_time() format_as_time() Formats the instance according to {@link TIME}. + * @method string format_as_atom() Formats the instance according to {@see ATOM}. + * @method string format_as_cookie() Formats the instance according to {@see COOKIE}. + * @method string format_as_iso8601() Formats the instance according to {@see ISO8601}. + * @method string format_as_rfc822() Formats the instance according to {@see RFC822}. + * @method string format_as_rfc850() Formats the instance according to {@see RFC850}. + * @method string format_as_rfc1036() Formats the instance according to {@see RFC1036}. + * @method string format_as_rfc1123() Formats the instance according to {@see RFC1123}. + * @method string format_as_rfc2822() Formats the instance according to {@see RFC2822}. + * @method string format_as_rfc3339() Formats the instance according to {@see RFC3339}. + * @method string format_as_rss() Formats the instance according to {@see RSS}. + * @method string format_as_w3c() Formats the instance according to {@see W3C}. + * @method string format_as_db() Formats the instance according to {@see DB}. + * @method string format_as_number() Formats the instance according to {@see NUMBER}. + * @method string format_as_date() Formats the instance according to {@see DATE}. + * @method string format_as_time() Formats the instance according to {@see TIME}. * - * @see http://en.wikipedia.org/wiki/ISO_8601 + * @link http://en.wikipedia.org/wiki/ISO_8601 */ class DateTime extends \DateTime implements \JsonSerializable { /** * We redefine the constant to make sure that the cookie uses a valid pattern. * - * @see http://grokbase.com/t/php/php-bugs/111xynxd6m/php-bug-bug-53879-new-datetime-createfromformat-fails-to-parse-cookie-expiration-date + * @link http://grokbase.com/t/php/php-bugs/111xynxd6m/php-bug-bug-53879-new-datetime-createfromformat-fails-to-parse-cookie-expiration-date */ public const COOKIE = 'l, d-M-Y H:i:s T'; @@ -203,7 +194,7 @@ class DateTime extends \DateTime implements \JsonSerializable static public $localizer = null; /** - * Creates a {@link DateTime} instance from a source. + * Creates a {@see DateTime} instance from a source. * *
 	 * 
 	 *
-	 * @param mixed $source
-	 * @param mixed $timezone The time zone to use to create the time. The value is ignored if the
-	 * source is an instance of {@link \DateTime}.
+	 * @param self|\DateTime|string $source
+	 * @param DateTimeZone|string|null $timezone The time zone to use to create the time.
+	 * The value is ignored if the source is an instance of {@see \DateTime}.
+	 *
+	 * @throws \DateInvalidTimeZoneException
+	 * @throws \DateMalformedStringException
 	 */
-	static public function from($source, $timezone = null): self
+	static public function from(
+		self|\DateTime|string $source,
+		DateTimeZone|string|null $timezone = null
+	): static
 	{
 		if ($source instanceof static)
 		{
@@ -237,17 +234,22 @@ static public function from($source, $timezone = null): self
 	/**
 	 * Returns an instance with the current local time and the local time zone.
 	 *
-	 * **Note:** Subsequent calls return equal times, event if they are minutes apart. _now_
+	 * **Note:** Subsequent calls return equal times, event if they're minutes apart. _now_
 	 * actually refers to the `REQUEST_TIME` or, if it is now available, to the first time
 	 * the method was invoked.
+	 *
+	 * @throws \DateInvalidTimeZoneException
+	 * @throws \DateMalformedStringException
 	 */
-	static public function now(): self
+	static public function now(): static
 	{
 		static $now;
 
 		if (!$now)
 		{
-			$now = empty($_SERVER['REQUEST_TIME']) ? new static : (new static('@' . $_SERVER['REQUEST_TIME']))->local;
+			$now = empty($_SERVER['REQUEST_TIME'])
+				? new static()
+				: (new static('@' . $_SERVER['REQUEST_TIME']))->local;
 		}
 
 		return clone $now;
@@ -283,15 +285,16 @@ static public function right_now(): self
 	 * @param DateTimeZone|string $timezone The time zone in which the empty date is created.
 	 * Defaults to "UTC".
 	 *
-	 * @return DateTime
+	 * @throws \DateInvalidTimeZoneException
+	 * @throws \DateMalformedStringException
 	 */
-	static public function none($timezone = 'utc'): self
+	static public function none(DateTimeZone|string $timezone = 'utc'): static
 	{
 		return new static('0000-00-00', $timezone);
 	}
 
 	/**
-	 * If the time zone is specified as a string a {@link \DateTimeZone} instance is created and
+	 * If the time zone is specified as a string a {@see \DateTimeZone} instance is created and
 	 * used instead.
 	 *
 	 * 
@@ -304,9 +307,10 @@ static public function none($timezone = 'utc'): self
 	 * new DateTime;
 	 * 
* - * @param DateTimeZone|string|null $timezone + * @throws \DateInvalidTimeZoneException + * @throws \DateMalformedStringException */ - public function __construct(string $time = 'now', $timezone = null) + public function __construct(string $time = 'now', DateTimeZone|string|null $timezone = null) { if (is_string($timezone)) { @@ -316,12 +320,9 @@ public function __construct(string $time = 'now', $timezone = null) parent::__construct($time, $timezone); } - /** - * @inheritdoc - */ public function __get($property) { - if (strpos($property, 'as_') === 0) + if (str_starts_with($property, 'as_')) { return $this->{ 'format_' . $property }(); } @@ -435,6 +436,8 @@ public function __get($property) /** * Returns Monday of the week. + * + * @throws \DateMalformedStringException */ private function get_monday(): self { @@ -453,6 +456,8 @@ private function get_monday(): self /** * Returns Tuesday of the week. + * + * @throws \DateMalformedStringException */ private function get_tuesday(): self { @@ -461,6 +466,8 @@ private function get_tuesday(): self /** * Returns Wednesday of the week. + * + * @throws \DateMalformedStringException */ private function get_wednesday(): self { @@ -469,6 +476,8 @@ private function get_wednesday(): self /** * Returns Thursday of the week. + * + * @throws \DateMalformedStringException */ private function get_thursday(): self { @@ -477,6 +486,8 @@ private function get_thursday(): self /** * Returns Friday of the week. + * + * @throws \DateMalformedStringException */ private function get_friday(): self { @@ -485,6 +496,8 @@ private function get_friday(): self /** * Returns Saturday of the week. + * + * @throws \DateMalformedStringException */ private function get_saturday(): self { @@ -493,6 +506,8 @@ private function get_saturday(): self /** * Returns Sunday of the week. + * + * @throws \DateMalformedStringException */ private function get_sunday(): self { @@ -509,22 +524,21 @@ private function get_sunday(): self return $time; } + private const READONLY_PROPERTIES = [ + 'quarter', 'week', 'year_day', 'weekday', + 'tomorrow', 'yesterday', 'utc', 'local' + ]; + /** - * Sets the {@link $year}, {@link $month}, {@link $day}, {@link $hour}, {@link $minute}, - * {@link $second}, {@link $timestamp} and {@link $zone} properties. - * - * @throws PropertyNotWritable in attempt to set a read-only property. - * @throws PropertyNotDefined in attempt to set an unsupported property. + * Sets the {@see $year}, {@see $month}, {@see $day}, {@see $hour}, {@see $minute}, + * {@see $second}, {@see $timestamp} and {@see $zone} properties. * - * @inheritdoc + * @throws PropertyNotWritable in an attempt to set a read-only property. + * @throws PropertyNotDefined in an attempt to set an unsupported property. + * @throws \DateInvalidTimeZoneException */ public function __set($property, $value): void { - static $readonly = [ - 'quarter', 'week', 'year_day', 'weekday', - 'tomorrow', 'yesterday', 'utc', 'local' - ]; - switch ($property) { case 'year': @@ -545,7 +559,11 @@ public function __set($property, $value): void return; } - if (strpos($property, 'is_') === 0 || strpos($property, 'as_') === 0 || in_array($property, $readonly) || method_exists($this, 'get_' . $property)) + if (str_starts_with($property, 'is_') + || str_starts_with($property, 'as_') + || in_array($property, self::READONLY_PROPERTIES) + || method_exists($this, 'get_' . $property) + ) { if (class_exists(PropertyNotWritable::class)) { @@ -570,19 +588,17 @@ public function __set($property, $value): void /** * Handles the `format_as_*` methods. * - * If the format is {@link RFC822} or {@link RFC1123} and the time zone is equivalent to GMT, + * If the format is {@see RFC822} or {@see RFC1123} and the time zone is equivalent to GMT, * the offset `+0000` is replaced by `GMT` according to the specs. * - * If the format is {@link ISO8601} and the time zone is equivalent to UTC, the offset `+0000` + * If the format is {@see ISO8601} and the time zone is equivalent to UTC, the offset `+0000` * is replaced by `Z` according to the specs. * * @throws \BadMethodCallException in attempt to call an unsupported method. - * - * @inheritdoc */ public function __call($method, $arguments) { - if (strpos($method, 'format_as_') !== 0) + if (!str_starts_with($method, 'format_as_')) { throw new \BadMethodCallException("Unsupported method: $method."); } @@ -591,24 +607,18 @@ public function __call($method, $arguments) $format = constant(__CLASS__ . '::' . $as); $value = $this->format($format); - switch ($as) + return match ($as) { - case 'RFC822': - case 'RFC1123': - return str_replace('+0000', 'GMT', $value); - - case 'ISO8601': - return str_replace('+0000', 'Z', $value); - - default: - return $value; - } + 'RFC822', 'RFC1123' => str_replace('+0000', 'GMT', $value), + 'ISO8601' => str_replace('+0000', 'Z', $value), + default => $value, + }; } /** - * Returns the datetime formatted as {@link ISO8601}. + * Returns the datetime formatted as {@see ISO8601}. * - * @return string The instance rendered as an {@link ISO8601} string, or an empty string if the + * @return string The instance rendered as an {@see ISO8601} string, or an empty string if the * datetime is empty. */ public function __toString(): string @@ -617,7 +627,7 @@ public function __toString(): string } /** - * Returns a {@link ISO8601} representation of the instance. + * Returns a {@see ISO8601} representation of the instance. */ public function jsonSerialize(): string { @@ -625,12 +635,14 @@ public function jsonSerialize(): string } /** + * @inheritdoc + * * The timezone can be specified as a string. * - * If the timezone is `local` the timezone returned by {@link date_default_timezone_get()} is + * If the timezone is `local` the timezone returned by {@see date_default_timezone_get()} is * used instead. * - * @inheritdoc + * @throws \DateInvalidTimeZoneException */ public function setTimezone($timezone): self { @@ -650,8 +662,8 @@ public function setTimezone($timezone): self /** * Modifies the properties of the instance according to the options. * - * The following properties can be updated: {@link $year}, {@link $month}, {@link $day}, - * {@link $hour}, {@link $minute} and {@link $second}. + * The following properties can be updated: {@see $year}, {@see $month}, {@see $day}, + * {@see $hour}, {@see $minute} and {@see $second}. * * Note: Values exceeding ranges are added to their parent values. * @@ -664,10 +676,13 @@ public function setTimezone($timezone): self * $time->change([ 'year' => 2000, 'second' => 0 ]); *
* - * @param array{ year: int, month: int, day: int, hour: int, minute: int, second: int, timezone: string } $options + * @param array{ year: int, month: int, day: int, hour: int, minute: int, second: int, + * timezone: string } $options * @param bool $cascade If `true`, time options (`hour`, `minute`, `second`) reset - * cascading, so if only the hour is passed, then minute and second is set to 0. If the hour - * and minute is passed, then second is set to 0. + * cascading, so if only the hour is passed, then minute and second are set to 0. If the hour + * and minute are passed, the second is set to 0. + * + * @throws \DateInvalidTimeZoneException */ public function change(array $options, bool $cascade = false): self { @@ -739,10 +754,13 @@ public function change(array $options, bool $cascade = false): self /** * Instantiate a new instance with changes properties. * - * @param array{ year: int, month: int, day: int, hour: int, minute: int, second: int, timezone: string } $options + * @param array{ year: int, month: int, day: int, hour: int, minute: int, second: int, + * timezone: string } $options * @param bool $cascade If `true`, time options (`hour`, `minute`, `second`) reset - * cascading, so if only the hour is passed, then minute and second is set to 0. If the hour - * and minute is passed, then second is set to 0. + * cascading, so if only the hour is passed, then minute and second are set to 0. If the hour + * and minute are passed, the second is set to 0. + * + * @throws \DateInvalidTimeZoneException */ public function with(array $options, bool $cascade = false): self { @@ -752,10 +770,10 @@ public function with(array $options, bool $cascade = false): self } /** - * If the instance represents an empty date and the format is {@link DATE} or {@link DB}, + * If the instance represents an empty date and the format is {@see DATE} or {@see DB}, * an empty date is returned, respectively "0000-00-00" and "0000-00-00 00:00:00". Note that - * the time information is discarded for {@link DB}. This only apply to {@link DATE} and - * {@link DB} formats. For instance {@link RSS} will return the following string: + * the time information is discarded for {@see DB}. This only applies to {@see DATE} and + * {@see DB} formats. For instance {@see RSS} will return the following string: * "Wed, 30 Nov -0001 00:00:00 +0000". * * @inheritdoc @@ -775,7 +793,7 @@ public function format($format): string * * @return mixed * - * @throws RuntimeException if {@link $localizer} is not defined. + * @throws RuntimeException if {@see $localizer} is not defined. */ public function localize(string $locale = 'en') { diff --git a/lib/TimeZone.php b/lib/TimeZone.php index 62a3877..20835b9 100644 --- a/lib/TimeZone.php +++ b/lib/TimeZone.php @@ -1,14 +1,5 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - namespace ICanBoogie; /** @@ -33,8 +24,12 @@ */ class TimeZone extends \DateTimeZone { - static private $utc_time; - static private $cache; + static private \DateTime $utc_time; + + /** + * @var array + */ + static private array $cache = []; /** * Returns a timezone according to the specified source. @@ -45,46 +40,42 @@ class TimeZone extends \DateTimeZone * the same instance. * * @param mixed $source Source of the timezone. + * + * @throws \DateInvalidTimeZoneException */ - static public function from($source): self + static public function from(self|\DateTimeZone|\Stringable|string $source): self { if ($source instanceof self) { return $source; } - else if ($source instanceof \DateTimeZone) + + if ($source instanceof \DateTimeZone) { $source = $source->getName(); } $source = (string) $source; - if (empty(self::$cache[$source])) - { - self::$cache[$source] = new static($source); - } - - return self::$cache[$source]; + return self::$cache[$source] ??= new self($source); } /** * The name of the timezone. * * Note: This variable is only used to provide information during debugging. - * - * @var string */ - private $name; + private string $name; /** * Location of the timezone. - * - * @var TimeZoneLocation */ - private $location; + private TimeZoneLocation $location; /** - * Initializes the {@link $name} property. + * Initializes the {@see $name} property. + * + * @throws \DateInvalidTimeZoneException */ public function __construct(string $timezone) { @@ -92,7 +83,7 @@ public function __construct(string $timezone) $name = $this->getName(); - if ($name == 'utc') + if ($name === 'utc') { $name = 'UTC'; } @@ -101,11 +92,10 @@ public function __construct(string $timezone) } /** - * Returns the {@link $location}, {@link $name} and {@link $offset} properties. - * - * @throws PropertyNotDefined in attempt to get an unsupported property. + * Returns the {@see $location}, {@see $name} and {@see $offset} properties. * - * @inheritdoc + * @throws PropertyNotDefined in an attempt to get an unsupported property. + * @throws \DateMalformedStringException */ public function __get(string $property) { @@ -113,12 +103,7 @@ public function __get(string $property) { case 'location': - if (!$this->location) - { - $this->location = TimeZoneLocation::from($this); - } - - return $this->location; + return $this->location ??= TimeZoneLocation::from($this); case 'name': @@ -126,12 +111,7 @@ public function __get(string $property) case 'offset': - $utc_time = self::$utc_time; - - if (!$utc_time) - { - self::$utc_time = $utc_time = new \DateTime('now', new \DateTimeZone('utc')); - } + $utc_time = self::$utc_time ??= new \DateTime('now', new \DateTimeZone('utc')); return $this->getOffset($utc_time); } diff --git a/phpunit.xml b/phpunit.xml index 7b5140a..9d19d39 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,15 +1,24 @@ - - - ./lib - - + xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/11.4/phpunit.xsd" + beStrictAboutCoverageMetadata="true" + beStrictAboutOutputDuringTests="true" + bootstrap="./vendor/autoload.php" + colors="true" + displayDetailsOnTestsThatTriggerDeprecations="true" + displayDetailsOnTestsThatTriggerNotices="true" + displayDetailsOnTestsThatTriggerWarnings="true" + displayDetailsOnPhpunitDeprecations="true" + executionOrder="depends,defects" +> - + ./tests + + + lib + + diff --git a/tests/DateTimeTest.php b/tests/DateTimeTest.php index 7b13ae9..cb86a10 100644 --- a/tests/DateTimeTest.php +++ b/tests/DateTimeTest.php @@ -1,19 +1,11 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - namespace Test\ICanBoogie; use ICanBoogie\DateTime; use ICanBoogie\PropertyNotDefined; use ICanBoogie\PropertyNotWritable; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Test\ICanBoogie\DateTimeTest\MyDateTime; use function array_map; @@ -27,9 +19,7 @@ protected function setUp(): void date_default_timezone_set('Europe/Paris'); } - /** - * @dataProvider provide_readonly - */ + #[DataProvider("provide_readonly")] public function test_readonly(string $property): void { $d = DateTime::now(); @@ -39,13 +29,13 @@ public function test_readonly(string $property): void $d->{ $property } = null; } - public function provide_readonly(): array + public static function provide_readonly(): array { $properties = <<assertEquals('2001-01-01 01:01:01', $d->as_db); } - public function provider_test_change_cascade(): array + public static function provide_test_change_cascade(): array { return [ ['2001-01-01 01:02:00', [ 'minute' => 2 ]], @@ -198,9 +188,7 @@ public function test_change_on_timezone_option(): void $this->assertSame($expectedTimezone, $resultTimezone->getName()); } - /** - * @dataProvider provider_test_change_cascade - */ + #[DataProvider('provide_test_change_cascade')] public function test_change_cascade($expected_datetime, $change_format): void { $datetime = '2001-01-01 01:01:01'; @@ -221,7 +209,7 @@ public function test_with(): void $this->assertEquals('2015-05-05 01:01:01', $e->as_db); } - public function provider_test_get_year(): array + public static function provide_test_get_year(): array { return [ ['2012-12-16 15:00:00', 2012], @@ -230,9 +218,7 @@ public function provider_test_get_year(): array ]; } - /** - * @dataProvider provider_test_get_year - */ + #[DataProvider('provide_test_get_year')] public function test_get_year($datetime, $expected): void { $d = new DateTime($datetime); @@ -246,7 +232,7 @@ public function test_set_year(): void $this->assertEquals('2009-01-01 01:01:01', $d->as_db); } - public function provider_test_get_quarter(): array + public static function provide_test_get_quarter(): array { return [ ['2012-01-16 15:00:00', 1], @@ -264,16 +250,14 @@ public function provider_test_get_quarter(): array ]; } - /** - * @dataProvider provider_test_get_quarter - */ + #[DataProvider('provide_test_get_quarter')] public function test_get_quarter($datetime, $expected): void { $d = new DateTime($datetime); $this->assertEquals($expected, $d->quarter); } - public function provider_test_get_month(): array + public static function provide_test_get_month(): array { return [ ['2012-01-16 15:00:00', 1], @@ -282,9 +266,7 @@ public function provider_test_get_month(): array ]; } - /** - * @dataProvider provider_test_get_month - */ + #[DataProvider('provide_test_get_month')] public function test_get_month($datetime, $expected): void { $d = new DateTime($datetime); @@ -298,7 +280,7 @@ public function test_set_month(): void $this->assertEquals('2001-09-01 01:01:01', $d->as_db); } - public function provider_test_get_week(): array + public static function provide_test_get_week(): array { return [ ['2012-01-01 15:00:00', 52], @@ -306,16 +288,14 @@ public function provider_test_get_week(): array ]; } - /** - * @dataProvider provider_test_get_week - */ + #[DataProvider('provide_test_get_week')] public function test_get_week($datetime, $expected): void { $d = new DateTime($datetime); $this->assertEquals($expected, $d->week); } - public function provider_test_get_year_day(): array + public static function provide_test_get_year_day(): array { return [ ['2012-01-01 15:00:00', 1], @@ -323,16 +303,14 @@ public function provider_test_get_year_day(): array ]; } - /** - * @dataProvider provider_test_get_year_day - */ + #[DataProvider('provide_test_get_year_day')] public function test_get_year_day($datetime, $expected): void { $d = new DateTime($datetime); $this->assertEquals($expected, $d->year_day); } - public function provider_test_get_weekday(): array + public static function provide_test_get_weekday(): array { return [ ['2012-12-17 15:00:00', 1], @@ -347,15 +325,15 @@ public function provider_test_get_weekday(): array /** * Sunday must be 7, Monday must be 1. - * @dataProvider provider_test_get_weekday */ + #[DataProvider('provide_test_get_weekday')] public function test_get_weekday($datetime, $expected): void { $d = new DateTime($datetime); $this->assertEquals($expected, $d->weekday); } - public function provider_test_get_day(): array + public static function provide_test_get_day(): array { return [ ['2012-12-16 15:00:00', 16], @@ -364,9 +342,7 @@ public function provider_test_get_day(): array ]; } - /** - * @dataProvider provider_test_get_day - */ + #[DataProvider('provide_test_get_day')] public function test_get_day($datetime, $expected): void { $d = new DateTime($datetime); @@ -558,16 +534,14 @@ public function test_get_yesterday() $this->assertEquals('2013-02-09 00:00:00', $d->yesterday->as_db); } - /** - * @dataProvider provide_test_day_instance - */ + #[DataProvider('provide_test_day_instance')] public function test_day_instance($date, $expected, $day): void { $d = new DateTime($date); $this->assertEquals($expected, $d->$day->as_db); } - public function provide_test_day_instance(): array + public static function provide_test_day_instance(): array { return [ diff --git a/tests/TimeZoneLocationTest.php b/tests/TimeZoneLocationTest.php index d5ad980..ee3c33e 100644 --- a/tests/TimeZoneLocationTest.php +++ b/tests/TimeZoneLocationTest.php @@ -1,14 +1,5 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - namespace Test\ICanBoogie; use DateTimeZone; @@ -16,6 +7,7 @@ use ICanBoogie\PropertyNotDefined; use ICanBoogie\PropertyNotWritable; use ICanBoogie\TimeZoneLocation; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use function array_map; use function preg_split; @@ -24,9 +16,7 @@ class TimeZoneLocationTest extends TestCase { - /** - * @dataProvider provide_readonly - */ + #[DataProvider('provide_readonly')] public function test_readonly(string $property): void { $zone = new DateTimeZone('Europe/Paris'); @@ -38,7 +28,7 @@ public function test_readonly(string $property): void $instance->$property = null; } - public function provide_readonly(): array + public static function provide_readonly(): array { $properties = <<