From 8cb2355c1472f942f770c4a453779af93a8c68a5 Mon Sep 17 00:00:00 2001 From: Toon Verwerft Date: Fri, 30 Aug 2024 15:03:02 +0200 Subject: [PATCH] Fix DateTime mutation tests --- composer.lock | 198 +++++++++--------- config/infection.json.dist | 7 +- .../DateTimeConvenienceMethodsTrait.php | 21 +- src/Psl/DateTime/Duration.php | 29 ++- tests/unit/DateTime/DateTimeTest.php | 140 +++++++++++-- tests/unit/DateTime/DurationTest.php | 16 ++ .../InvalidArgumentExceptionTest.php | 102 +++++++++ tests/unit/DateTime/TimestampTest.php | 26 +++ tests/unit/DateTime/TimezoneTest.php | 24 +++ 9 files changed, 418 insertions(+), 145 deletions(-) create mode 100644 tests/unit/DateTime/Exception/InvalidArgumentExceptionTest.php diff --git a/composer.lock b/composer.lock index 8598a993..a41893b1 100644 --- a/composer.lock +++ b/composer.lock @@ -1131,16 +1131,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.58.0", + "version": "v3.58.1", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "55d3483c80c09f91d876aa4e2971ce349d07310c" + "reference": "04e9424025677a86914b9a4944dbbf4060bb0aff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/55d3483c80c09f91d876aa4e2971ce349d07310c", - "reference": "55d3483c80c09f91d876aa4e2971ce349d07310c", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/04e9424025677a86914b9a4944dbbf4060bb0aff", + "reference": "04e9424025677a86914b9a4944dbbf4060bb0aff", "shasum": "" }, "require": { @@ -1219,7 +1219,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.58.0" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.58.1" }, "funding": [ { @@ -1227,7 +1227,7 @@ "type": "github" } ], - "time": "2024-05-28T16:55:30+00:00" + "time": "2024-05-29T16:39:07+00:00" }, { "name": "guzzlehttp/guzzle", @@ -1935,16 +1935,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.11.1", + "version": "1.12.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", "shasum": "" }, "require": { @@ -1952,11 +1952,12 @@ }, "conflict": { "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3,<3.2.2" + "doctrine/common": "<2.13.3 || >=3 <3.2.2" }, "require-dev": { "doctrine/collections": "^1.6.8", "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", @@ -1982,7 +1983,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" }, "funding": [ { @@ -1990,7 +1991,7 @@ "type": "tidelift" } ], - "time": "2023-03-08T13:26:56+00:00" + "time": "2024-06-12T14:39:25+00:00" }, { "name": "netresearch/jsonmapper", @@ -2809,16 +2810,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.29.0", + "version": "1.29.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "536889f2b340489d328f5ffb7b02bb6b183ddedc" + "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/536889f2b340489d328f5ffb7b02bb6b183ddedc", - "reference": "536889f2b340489d328f5ffb7b02bb6b183ddedc", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fcaefacf2d5c417e928405b71b400d4ce10daaf4", + "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4", "shasum": "" }, "require": { @@ -2850,9 +2851,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.29.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.29.1" }, - "time": "2024-05-06T12:04:23+00:00" + "time": "2024-05-31T08:52:43+00:00" }, { "name": "phpunit/php-code-coverage", @@ -3835,28 +3836,28 @@ }, { "name": "react/dns", - "version": "v1.12.0", + "version": "v1.13.0", "source": { "type": "git", "url": "https://github.com/reactphp/dns.git", - "reference": "c134600642fa615b46b41237ef243daa65bb64ec" + "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/dns/zipball/c134600642fa615b46b41237ef243daa65bb64ec", - "reference": "c134600642fa615b46b41237ef243daa65bb64ec", + "url": "https://api.github.com/repos/reactphp/dns/zipball/eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", + "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", "shasum": "" }, "require": { "php": ">=5.3.0", "react/cache": "^1.0 || ^0.6 || ^0.5", "react/event-loop": "^1.2", - "react/promise": "^3.0 || ^2.7 || ^1.2.1" + "react/promise": "^3.2 || ^2.7 || ^1.2.1" }, "require-dev": { "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", - "react/async": "^4 || ^3 || ^2", - "react/promise-timer": "^1.9" + "react/async": "^4.3 || ^3 || ^2", + "react/promise-timer": "^1.11" }, "type": "library", "autoload": { @@ -3899,7 +3900,7 @@ ], "support": { "issues": "https://github.com/reactphp/dns/issues", - "source": "https://github.com/reactphp/dns/tree/v1.12.0" + "source": "https://github.com/reactphp/dns/tree/v1.13.0" }, "funding": [ { @@ -3907,7 +3908,7 @@ "type": "open_collective" } ], - "time": "2023-11-29T12:41:06+00:00" + "time": "2024-06-13T14:18:03+00:00" }, { "name": "react/event-loop", @@ -4136,16 +4137,16 @@ }, { "name": "react/stream", - "version": "v1.3.0", + "version": "v1.4.0", "source": { "type": "git", "url": "https://github.com/reactphp/stream.git", - "reference": "6fbc9672905c7d5a885f2da2fc696f65840f4a66" + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/stream/zipball/6fbc9672905c7d5a885f2da2fc696f65840f4a66", - "reference": "6fbc9672905c7d5a885f2da2fc696f65840f4a66", + "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d", "shasum": "" }, "require": { @@ -4155,7 +4156,7 @@ }, "require-dev": { "clue/stream-filter": "~1.2", - "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" }, "type": "library", "autoload": { @@ -4202,7 +4203,7 @@ ], "support": { "issues": "https://github.com/reactphp/stream/issues", - "source": "https://github.com/reactphp/stream/tree/v1.3.0" + "source": "https://github.com/reactphp/stream/tree/v1.4.0" }, "funding": [ { @@ -4210,7 +4211,7 @@ "type": "open_collective" } ], - "time": "2023-06-16T10:52:11+00:00" + "time": "2024-06-11T12:45:25+00:00" }, { "name": "roave/infection-static-analysis-plugin", @@ -5569,22 +5570,22 @@ }, { "name": "symfony/config", - "version": "v7.0.7", + "version": "v7.1.1", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "f66f908a975500aa4594258bf454dc66e3939eac" + "reference": "2210fc99fa42a259eb6c89d1f724ce0c4d62d5d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/f66f908a975500aa4594258bf454dc66e3939eac", - "reference": "f66f908a975500aa4594258bf454dc66e3939eac", + "url": "https://api.github.com/repos/symfony/config/zipball/2210fc99fa42a259eb6c89d1f724ce0c4d62d5d2", + "reference": "2210fc99fa42a259eb6c89d1f724ce0c4d62d5d2", "shasum": "" }, "require": { "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/filesystem": "^6.4|^7.0", + "symfony/filesystem": "^7.1", "symfony/polyfill-ctype": "~1.8" }, "conflict": { @@ -5624,7 +5625,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v7.0.7" + "source": "https://github.com/symfony/config/tree/v7.1.1" }, "funding": [ { @@ -5640,20 +5641,20 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:29:19+00:00" + "time": "2024-05-31T14:57:53+00:00" }, { "name": "symfony/console", - "version": "v7.0.7", + "version": "v7.1.1", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "c981e0e9380ce9f146416bde3150c79197ce9986" + "reference": "9b008f2d7b21c74ef4d0c3de6077a642bc55ece3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/c981e0e9380ce9f146416bde3150c79197ce9986", - "reference": "c981e0e9380ce9f146416bde3150c79197ce9986", + "url": "https://api.github.com/repos/symfony/console/zipball/9b008f2d7b21c74ef4d0c3de6077a642bc55ece3", + "reference": "9b008f2d7b21c74ef4d0c3de6077a642bc55ece3", "shasum": "" }, "require": { @@ -5717,7 +5718,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.0.7" + "source": "https://github.com/symfony/console/tree/v7.1.1" }, "funding": [ { @@ -5733,7 +5734,7 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:29:19+00:00" + "time": "2024-05-31T14:57:53+00:00" }, { "name": "symfony/deprecation-contracts", @@ -5804,16 +5805,16 @@ }, { "name": "symfony/event-dispatcher", - "version": "v7.0.7", + "version": "v7.1.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "db2a7fab994d67d92356bb39c367db115d9d30f9" + "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/db2a7fab994d67d92356bb39c367db115d9d30f9", - "reference": "db2a7fab994d67d92356bb39c367db115d9d30f9", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7", + "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7", "shasum": "" }, "require": { @@ -5864,7 +5865,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.0.7" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.1.1" }, "funding": [ { @@ -5880,7 +5881,7 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:29:19+00:00" + "time": "2024-05-31T14:57:53+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -5960,22 +5961,24 @@ }, { "name": "symfony/filesystem", - "version": "v7.0.7", + "version": "v7.1.1", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "cc168be6fbdcdf3401f50ae863ee3818ed4338f5" + "reference": "802e87002f919296c9f606457d9fa327a0b3d6b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/cc168be6fbdcdf3401f50ae863ee3818ed4338f5", - "reference": "cc168be6fbdcdf3401f50ae863ee3818ed4338f5", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/802e87002f919296c9f606457d9fa327a0b3d6b2", + "reference": "802e87002f919296c9f606457d9fa327a0b3d6b2", "shasum": "" }, "require": { "php": ">=8.2", "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { "symfony/process": "^6.4|^7.0" }, "type": "library", @@ -6004,7 +6007,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.0.7" + "source": "https://github.com/symfony/filesystem/tree/v7.1.1" }, "funding": [ { @@ -6020,20 +6023,20 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:29:19+00:00" + "time": "2024-05-31T14:57:53+00:00" }, { "name": "symfony/finder", - "version": "v7.0.7", + "version": "v7.1.1", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "4d58f0f4fe95a30d7b538d71197135483560b97c" + "reference": "fbb0ba67688b780efbc886c1a0a0948dcf7205d6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/4d58f0f4fe95a30d7b538d71197135483560b97c", - "reference": "4d58f0f4fe95a30d7b538d71197135483560b97c", + "url": "https://api.github.com/repos/symfony/finder/zipball/fbb0ba67688b780efbc886c1a0a0948dcf7205d6", + "reference": "fbb0ba67688b780efbc886c1a0a0948dcf7205d6", "shasum": "" }, "require": { @@ -6068,7 +6071,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.0.7" + "source": "https://github.com/symfony/finder/tree/v7.1.1" }, "funding": [ { @@ -6084,20 +6087,20 @@ "type": "tidelift" } ], - "time": "2024-04-28T11:44:19+00:00" + "time": "2024-05-31T14:57:53+00:00" }, { "name": "symfony/options-resolver", - "version": "v7.0.7", + "version": "v7.1.1", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "23cc173858776ad451e31f053b1c9f47840b2cfa" + "reference": "47aa818121ed3950acd2b58d1d37d08a94f9bf55" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/23cc173858776ad451e31f053b1c9f47840b2cfa", - "reference": "23cc173858776ad451e31f053b1c9f47840b2cfa", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/47aa818121ed3950acd2b58d1d37d08a94f9bf55", + "reference": "47aa818121ed3950acd2b58d1d37d08a94f9bf55", "shasum": "" }, "require": { @@ -6135,7 +6138,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.0.7" + "source": "https://github.com/symfony/options-resolver/tree/v7.1.1" }, "funding": [ { @@ -6151,7 +6154,7 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:29:19+00:00" + "time": "2024-05-31T14:57:53+00:00" }, { "name": "symfony/polyfill-ctype", @@ -6629,16 +6632,16 @@ }, { "name": "symfony/process", - "version": "v7.0.7", + "version": "v7.1.1", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "3839e56b94dd1dbd13235d27504e66baf23faba0" + "reference": "febf90124323a093c7ee06fdb30e765ca3c20028" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/3839e56b94dd1dbd13235d27504e66baf23faba0", - "reference": "3839e56b94dd1dbd13235d27504e66baf23faba0", + "url": "https://api.github.com/repos/symfony/process/zipball/febf90124323a093c7ee06fdb30e765ca3c20028", + "reference": "febf90124323a093c7ee06fdb30e765ca3c20028", "shasum": "" }, "require": { @@ -6670,7 +6673,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.0.7" + "source": "https://github.com/symfony/process/tree/v7.1.1" }, "funding": [ { @@ -6686,7 +6689,7 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:29:19+00:00" + "time": "2024-05-31T14:57:53+00:00" }, { "name": "symfony/service-contracts", @@ -6773,16 +6776,16 @@ }, { "name": "symfony/stopwatch", - "version": "v7.0.7", + "version": "v7.1.1", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "41a7a24aa1dc82adf46a06bc292d1923acfe6b84" + "reference": "5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/41a7a24aa1dc82adf46a06bc292d1923acfe6b84", - "reference": "41a7a24aa1dc82adf46a06bc292d1923acfe6b84", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d", + "reference": "5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d", "shasum": "" }, "require": { @@ -6815,7 +6818,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v7.0.7" + "source": "https://github.com/symfony/stopwatch/tree/v7.1.1" }, "funding": [ { @@ -6831,20 +6834,20 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:29:19+00:00" + "time": "2024-05-31T14:57:53+00:00" }, { "name": "symfony/string", - "version": "v7.0.7", + "version": "v7.1.1", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "e405b5424dc2528e02e31ba26b83a79fd4eb8f63" + "reference": "60bc311c74e0af215101235aa6f471bcbc032df2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/e405b5424dc2528e02e31ba26b83a79fd4eb8f63", - "reference": "e405b5424dc2528e02e31ba26b83a79fd4eb8f63", + "url": "https://api.github.com/repos/symfony/string/zipball/60bc311c74e0af215101235aa6f471bcbc032df2", + "reference": "60bc311c74e0af215101235aa6f471bcbc032df2", "shasum": "" }, "require": { @@ -6858,6 +6861,7 @@ "symfony/translation-contracts": "<2.5" }, "require-dev": { + "symfony/emoji": "^7.1", "symfony/error-handler": "^6.4|^7.0", "symfony/http-client": "^6.4|^7.0", "symfony/intl": "^6.4|^7.0", @@ -6901,7 +6905,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.0.7" + "source": "https://github.com/symfony/string/tree/v7.1.1" }, "funding": [ { @@ -6917,20 +6921,20 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:29:19+00:00" + "time": "2024-06-04T06:40:14+00:00" }, { "name": "symfony/yaml", - "version": "v7.0.7", + "version": "v7.1.1", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "0d3916ae69ea28b59d94b60c4f2b50f4e25adb5c" + "reference": "fa34c77015aa6720469db7003567b9f772492bf2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/0d3916ae69ea28b59d94b60c4f2b50f4e25adb5c", - "reference": "0d3916ae69ea28b59d94b60c4f2b50f4e25adb5c", + "url": "https://api.github.com/repos/symfony/yaml/zipball/fa34c77015aa6720469db7003567b9f772492bf2", + "reference": "fa34c77015aa6720469db7003567b9f772492bf2", "shasum": "" }, "require": { @@ -6972,7 +6976,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.0.7" + "source": "https://github.com/symfony/yaml/tree/v7.1.1" }, "funding": [ { @@ -6988,7 +6992,7 @@ "type": "tidelift" } ], - "time": "2024-04-28T11:44:19+00:00" + "time": "2024-05-31T14:57:53+00:00" }, { "name": "thecodingmachine/safe", diff --git a/config/infection.json.dist b/config/infection.json.dist index c2ee2166..1e6aeee1 100644 --- a/config/infection.json.dist +++ b/config/infection.json.dist @@ -32,7 +32,8 @@ }, "DecrementInteger": { "ignore": [ - "Psl\\DataStructure\\PriorityQueue::peek" + "Psl\\DataStructure\\PriorityQueue::peek", + "Psl\\DateTime\\TemporalConvenienceMethodsTrait::since" ] }, "FunctionCallRemoval": { @@ -42,7 +43,8 @@ }, "IncrementInteger": { "ignore": [ - "Psl\\DataStructure\\PriorityQueue::peek" + "Psl\\DataStructure\\PriorityQueue::peek", + "Psl\\DateTime\\TemporalConvenienceMethodsTrait::since" ] }, "LogicalNot": { @@ -58,6 +60,7 @@ }, "Throw_": { "ignore": [ + "Psl\\DateTime\\DateTime::__construct", "Psl\\File\\ReadHandle::__construct", "Psl\\File\\WriteHandle::__construct", "Psl\\File\\ReadWriteHandle::__construct", diff --git a/src/Psl/DateTime/DateTimeConvenienceMethodsTrait.php b/src/Psl/DateTime/DateTimeConvenienceMethodsTrait.php index 72bfc1b6..383b7e08 100644 --- a/src/Psl/DateTime/DateTimeConvenienceMethodsTrait.php +++ b/src/Psl/DateTime/DateTimeConvenienceMethodsTrait.php @@ -398,17 +398,17 @@ public function plusMonths(int $months): static return $this; } - if (0 > $months) { + if ($months < 1) { return $this->minusMonths(-$months); } - $plus_years = Math\div($months, 12); - $months_left = $months - ($plus_years * 12); + $plus_years = Math\div($months, MONTHS_PER_YEAR); + $months_left = $months - ($plus_years * MONTHS_PER_YEAR); $target_month = $this->getMonth() + $months_left; - if ($target_month > 12) { + if ($target_month > MONTHS_PER_YEAR) { $plus_years++; - $target_month = $target_month - 12; + $target_month = $target_month - MONTHS_PER_YEAR; } $target_month_enum = Month::from($target_month); @@ -438,17 +438,17 @@ public function minusMonths(int $months): static return $this; } - if (0 > $months) { + if ($months < 1) { return $this->plusMonths(-$months); } - $minus_years = Math\div($months, 12); - $months_left = $months - ($minus_years * 12); + $minus_years = Math\div($months, MONTHS_PER_YEAR); + $months_left = $months - ($minus_years * MONTHS_PER_YEAR); $target_month = $this->getMonth() - $months_left; if ($target_month <= 0) { $minus_years++; - $target_month = 12 - Math\abs($target_month); + $target_month = MONTHS_PER_YEAR - Math\abs($target_month); } $target_month_enum = Month::from($target_month); @@ -616,10 +616,9 @@ public function toString(null|DateStyle $date_style = null, null|TimeStyle $time $timestamp = $this->getTimestamp(); /** - * @psalm-suppress InvalidOperand * @psalm-suppress ImpureMethodCall */ return Internal\create_intl_date_formatter($date_style, $time_style, null, $timezone ?? $this->getTimezone(), $locale) - ->format($timestamp->getSeconds() + ($timestamp->getNanoseconds() / NANOSECONDS_PER_SECOND)); + ->format($timestamp->getSeconds()); } } diff --git a/src/Psl/DateTime/Duration.php b/src/Psl/DateTime/Duration.php index e5dd7270..5a5b54cd 100644 --- a/src/Psl/DateTime/Duration.php +++ b/src/Psl/DateTime/Duration.php @@ -674,27 +674,24 @@ public function toString(int $max_decimals = 3): string $sec_sign = $this->seconds < 0 || $this->nanoseconds < 0 ? '-' : ''; $sec = Math\abs($this->seconds); - /** @var list $values */ - $values = [ - [((string) $this->hours), 'hour(s)'], - [((string) $this->minutes), 'minute(s)'], - [$sec_sign . ((string) $sec) . $decimal_part, 'second(s)'], - ]; - // $end is the sizeof($values), use static value for better performance. - $end = 3; - while ($end > 0 && $values[$end - 1][0] === '0') { - --$end; + $containsHours = $this->hours !== 0; + $containsMinutes = $this->minutes !== 0; + $concatenatedSeconds = $sec_sign . ((string) $sec) . $decimal_part; + $containsSeconds = $concatenatedSeconds !== '0'; + + /** @var list $output */ + $output = []; + if ($containsHours) { + $output[] = ((string) $this->hours) . ' hour(s)'; } - $start = 0; - while ($start < $end && $values[$start][0] === '0') { - ++$start; + if ($containsMinutes || ($containsHours && $containsSeconds)) { + $output[] = ((string) $this->minutes) . ' minute(s)'; } - $output = []; - for ($i = $start; $i < $end; ++$i) { - $output[] = $values[$i][0] . ' ' . $values[$i][1]; + if ($containsSeconds) { + $output[] = $concatenatedSeconds . ' second(s)'; } return ([] === $output) ? '0 second(s)' : Str\join($output, ', '); diff --git a/tests/unit/DateTime/DateTimeTest.php b/tests/unit/DateTime/DateTimeTest.php index 4ea371bf..a0fd2c45 100644 --- a/tests/unit/DateTime/DateTimeTest.php +++ b/tests/unit/DateTime/DateTimeTest.php @@ -5,13 +5,17 @@ namespace Psl\Tests\Unit\DateTime; use PHPUnit\Framework\TestCase; +use Psl\DateTime\DateStyle; use Psl\DateTime\DateTime; use Psl\DateTime\Exception\UnexpectedValueException; +use Psl\DateTime\FormatPattern; use Psl\DateTime\Meridiem; use Psl\DateTime\Month; +use Psl\DateTime\TimeStyle; use Psl\DateTime\Timezone; use Psl\DateTime\Weekday; use Psl\Json; +use Psl\Locale\Locale; use function time; @@ -59,6 +63,7 @@ public function testFromParts(): void static::assertSame(Timezone::UTC, $datetime->getTimezone()); static::assertSame(2024, $datetime->getYear()); + static::assertSame(24, $datetime->getYearShort()); static::assertSame(2, $datetime->getMonth()); static::assertSame(4, $datetime->getDay()); static::assertSame(Weekday::Sunday, $datetime->getWeekday()); @@ -66,11 +71,12 @@ public function testFromParts(): void static::assertSame(0, $datetime->getMinutes()); static::assertSame(0, $datetime->getSeconds()); static::assertSame(1, $datetime->getNanoseconds()); + static::assertSame([2024, 2, 4, 14, 0, 0, 1,], $datetime->getParts()); } public function testFromPartsWithDefaults(): void { - $datetime = DateTime::fromParts(Timezone::UTC, 2024, Month::February, 4, ); + $datetime = DateTime::fromParts(Timezone::UTC, 2024, Month::February, 4,); static::assertSame(Timezone::UTC, $datetime->getTimezone()); static::assertSame(2024, $datetime->getYear()); @@ -83,22 +89,71 @@ public function testFromPartsWithDefaults(): void static::assertSame(0, $datetime->getNanoseconds()); } - public function testFromPartsWithInvalidComponent(): void - { + + /** + * @dataProvider provideInvalidComponentParts + */ + public function testFromPartsWithInvalidComponent( + string $expectedMessage, + int $year, + int $month, + int $day, + int $hours, + int $minutes, + int $seconds, + int $nanoseconds + ): void { $this->expectException(UnexpectedValueException::class); - $this->expectExceptionMessage('Unexpected hours value encountered. Provided "999", but the calendar expects "15". Ensure the hour falls within a 24-hour day.'); + $this->expectExceptionMessage($expectedMessage); - DateTime::fromParts(Timezone::UTC, 2024, Month::February, 4, 999, 0, 0, 1); + DateTime::fromParts(Timezone::UTC, $year, $month, $day, $hours, $minutes, $seconds, $nanoseconds); } - public function fromString(): void + public static function provideInvalidComponentParts(): array { - $datetime = DateTime::fromParts(Timezone::default(), 2024, Month::February, 4, 14, 0, 0, 0); + return [ + ['Unexpected year value encountered. Provided "0", but the calendar expects "1". Check the year for accuracy and ensure it\'s within the supported range.', 0, 1, 1, 0, 0, 0, 0], + ['Unexpected month value encountered. Provided "0", but the calendar expects "12". Ensure the month is within the 1-12 range and matches the specific year context.', 2024, 0, 1, 0, 0, 0, 0], + ['Unexpected day value encountered. Provided "0", but the calendar expects "31". Ensure the day is valid for the given month and year, considering variations like leap years.', 2024, 1, 0, 0, 0, 0, 0], + ['Unexpected hours value encountered. Provided "-1", but the calendar expects "23". Ensure the hour falls within a 24-hour day.', 2024, 1, 1, -1, 0, 0, 0], + ['Unexpected minutes value encountered. Provided "-1", but the calendar expects "59". Check the minutes value for errors and ensure it\'s within the 0-59 range.', 2024, 1, 1, 0, -1, 0, 0], + ['Unexpected seconds value encountered. Provided "59", but the calendar expects "-1". Ensure the seconds are correct and within the 0-59 range.', 2024, 1, 1, 0, 0, -1, 0], + ]; + } + + public function testFromString(): void + { + $timezone = Timezone::EuropeBrussels; + $datetime = DateTime::fromParts($timezone, 2024, Month::February, 4, 14, 0, 0, 0); $string = $datetime->toString(); - $parsed = DateTime::fromString($string); + $parsed = DateTime::fromString($string, timezone: $timezone); static::assertEquals($datetime->getTimestamp(), $parsed->getTimestamp()); + static::assertSame($datetime->getTimezone(), $parsed->getTimezone()); + static::assertSame($string, $parsed->toString()); + } + + public function testToString(): void + { + $datetime = DateTime::fromParts(Timezone::default(), 2024, Month::February, 4, 14, 0, 0, 0); + + static::assertSame('4 Feb 2024, 14:00:00', $datetime->toString()); + static::assertSame('04/02/2024, 14:00:00', $datetime->toString(date_style: DateStyle::Short)); + static::assertSame('4 Feb 2024, 14:00:00 Greenwich Mean Time', $datetime->toString(time_style: TimeStyle::Full)); + static::assertSame('4 Feb 2024, 15:00:00', $datetime->toString(timezone: TimeZone::EuropeBrussels)); + static::assertSame('4 feb. 2024, 14:00:00', $datetime->toString(locale: Locale::DutchBelgium)); + } + + public function testFormat(): void + { + $datetime = DateTime::fromParts(Timezone::default(), 2024, Month::February, 4, 14, 0, 0, 0); + + static::assertSame('4 Feb 2024, 14:00:00', $datetime->format()); + static::assertSame('02/04/2024', $datetime->format(pattern: FormatPattern::American)); + static::assertSame('02/04/2024', $datetime->format(pattern: FormatPattern::American->value)); + static::assertSame('4 Feb 2024, 15:00:00', $datetime->format(timezone: TimeZone::EuropeBrussels)); + static::assertSame('4 feb. 2024, 14:00:00', $datetime->format(locale: Locale::DutchBelgium)); } public function testParse(): void @@ -121,7 +176,6 @@ public function testParseWithTimezone(): void static::assertEquals($datetime->getTimestamp(), $parsed->getTimestamp()); static::assertSame($datetime->getTimezone(), $parsed->getTimezone()); - } public function testWithDate(): void @@ -173,18 +227,32 @@ public function testGetEra() public function testGetCentury() { - $datetime = DateTime::fromParts(Timezone::default(), 2024, Month::February, 4, 14, 0, 0, 0); + static::assertSame(20, DateTime::fromParts(Timezone::default(), 1999, Month::February, 4, 14)->getCentury()); + static::assertSame(21, DateTime::fromParts(Timezone::default(), 2000, Month::February, 4, 14)->getCentury()); + } - static::assertSame(21, $datetime->getCentury()); + public static function provideTwelveHours() + { + yield [0, 12, Meridiem::AnteMeridiem]; + yield [1, 1, Meridiem::AnteMeridiem]; + yield [2, 2, Meridiem::AnteMeridiem]; + yield [11, 11, Meridiem::AnteMeridiem]; + yield [12, 12, Meridiem::PostMeridiem]; + yield [13, 1, Meridiem::PostMeridiem]; + yield [14, 2, Meridiem::PostMeridiem]; + yield [23, 11, Meridiem::PostMeridiem]; } - public function testGetTwelveHours() + /** + * @dataProvider provideTwelveHours + */ + public function testGetTwelveHours(int $hour, $expectedTwelveHour, $expectedMeridiem) { - $datetime = DateTime::fromParts(Timezone::default(), 2024, Month::February, 4, 14, 0, 0, 0); + $datetime = DateTime::fromParts(Timezone::default(), 2024, Month::February, 4, $hour, 0, 0, 0); [$hours, $meridiem] = $datetime->getTwelveHours(); - static::assertSame(2, $hours); - static::assertSame(Meridiem::PostMeridiem, $meridiem); + static::assertSame($expectedTwelveHour, $hours); + static::assertSame($expectedMeridiem, $meridiem); } public function testGetIsoWeek(): void @@ -221,6 +289,12 @@ public function testPlusMethods(): void $new = $datetime->plusMonths(1); static::assertSame(3, $new->getMonth()); + $new = $datetime->plusMonths(0); + static::assertSame($datetime, $new); + + $new = $datetime->plusMonths(-1); + static::assertSame(1, $new->getMonth()); + $new = $datetime->plusDays(1); static::assertSame(5, $new->getDay()); @@ -285,6 +359,12 @@ public function testMinusMethods(): void $new = $datetime->minusMonths(1); static::assertSame(1, $new->getMonth()); + $new = $datetime->minusMonths(0); + static::assertSame($datetime, $new); + + $new = $datetime->minusMonths(-1); + static::assertSame(3, $new->getMonth()); + $new = $datetime->minusDays(1); static::assertSame(3, $new->getDay()); @@ -412,9 +492,31 @@ public function testWithTime() $date = DateTime::todayAt(14, 0); $new = $date->withTime(15, 0); - self::assertSame(15, $new->getHours()); - self::assertSame(0, $new->getMinutes()); - self::assertSame(0, $new->getSeconds()); - self::assertSame(0, $new->getNanoseconds()); + static::assertSame(15, $new->getHours()); + static::assertSame(0, $new->getMinutes()); + static::assertSame(0, $new->getSeconds()); + static::assertSame(0, $new->getNanoseconds()); + } + + public function testTimezoneInfo() + { + $timeZone = Timezone::EuropeBrussels; + $date = DateTime::fromParts($timeZone, 2024, 01, 01); + + static::assertSame(!$timeZone->getDaylightSavingTimeOffset($date)->isZero(), $date->isDaylightSavingTime()); + static::assertEquals($timeZone->getOffset($date), $date->getTimezoneOffset()); + } + + public function testConvertTimeZone() + { + $date = DateTime::fromParts(Timezone::EuropeBrussels, 2024, 01, 01, 1); + $converted = $date->convertToTimezone($london = Timezone::EuropeLondon); + + static::assertSame($london, $converted->getTimezone()); + static::assertSame($date->getTimestamp(), $converted->getTimestamp()); + static::assertSame($date->getYear(), $converted->getYear()); + static::assertSame($date->getMonth(), $converted->getMonth()); + static::assertSame($date->getDay(), $converted->getDay()); + static::assertSame(0, $converted->getHours()); } } diff --git a/tests/unit/DateTime/DurationTest.php b/tests/unit/DateTime/DurationTest.php index 0e76acc5..1274ef2c 100644 --- a/tests/unit/DateTime/DurationTest.php +++ b/tests/unit/DateTime/DurationTest.php @@ -27,6 +27,19 @@ public function testGetters(): void static::assertEquals([1, 2, 3, 4], $t->getParts()); } + public function testNamedConstructors() + { + static::assertSame(168.0, DateTime\Duration::weeks(1)->getTotalHours()); + static::assertSame(24.0, DateTime\Duration::days(1)->getTotalHours()); + static::assertSame(1.0, DateTime\Duration::hours(1)->getTotalHours()); + static::assertSame(1.0, DateTime\Duration::minutes(1)->getTotalMinutes()); + static::assertSame(1.0, DateTime\Duration::seconds(1)->getTotalSeconds()); + static::assertSame(1.0, DateTime\Duration::milliseconds(1)->getTotalMilliseconds()); + static::assertSame(1.0, DateTime\Duration::microseconds(1)->getTotalMicroseconds()); + static::assertSame(1, DateTime\Duration::nanoseconds(1)->getNanoseconds()); + static::assertSame(0.0, DateTime\Duration::zero(1)->getTotalSeconds()); + } + public function provideGetTotalHours(): array { return [ @@ -237,6 +250,9 @@ public function testPositiveNegative(int $h, int $m, int $s, int $ns, int $expec public static function provideCompare(): array { return [ + [DateTime\Duration::seconds(20), DateTime\Duration::seconds(10), Order::Greater], + [DateTime\Duration::seconds(10), DateTime\Duration::seconds(20), Order::Less], + [DateTime\Duration::seconds(10), DateTime\Duration::seconds(10), Order::Equal], [DateTime\Duration::hours(1), DateTime\Duration::minutes(42), Order::Greater], [DateTime\Duration::minutes(2), DateTime\Duration::seconds(120), Order::Equal], [DateTime\Duration::zero(), DateTime\Duration::nanoseconds(1), Order::Less], diff --git a/tests/unit/DateTime/Exception/InvalidArgumentExceptionTest.php b/tests/unit/DateTime/Exception/InvalidArgumentExceptionTest.php new file mode 100644 index 00000000..c30e321f --- /dev/null +++ b/tests/unit/DateTime/Exception/InvalidArgumentExceptionTest.php @@ -0,0 +1,102 @@ +getMessage() + ); + + $this->expectExceptionObject($exception); + throw $exception; + } + + public function testForMonth(): void + { + $exception = InvalidArgumentException::forMonth(13); + + static::assertSame( + 'The month \'13\' falls outside the acceptable range of \'1\' to \'12\'.', + $exception->getMessage() + ); + + $this->expectExceptionObject($exception); + throw $exception; + } + + public function testForDay(): void + { + $exception = InvalidArgumentException::forDay(32, 1, 2021); + + static::assertSame( + 'The day \'32\', for month \'1\' and year \'2021\', does not align with the expected range of \'1\' to \'31\'.', + $exception->getMessage() + ); + + $this->expectExceptionObject($exception); + throw $exception; + } + + public function testForHours(): void + { + $exception = InvalidArgumentException::forHours(24); + + static::assertSame( + 'The hour \'24\' exceeds the expected range of \'0\' to \'23\'.', + $exception->getMessage() + ); + + $this->expectExceptionObject($exception); + throw $exception; + } + + public function testForMinutes(): void + { + $exception = InvalidArgumentException::forMinutes(60); + + static::assertSame( + 'The minute \'60\' steps beyond the bounds of \'0\' to \'59\'.', + $exception->getMessage() + ); + + $this->expectExceptionObject($exception); + throw $exception; + } + + public function testForSeconds(): void + { + $exception = InvalidArgumentException::forSeconds(61); + + static::assertSame( + 'The seconds \'61\' stretch outside the acceptable range of \'0\' to \'59\'.', + $exception->getMessage() + ); + + $this->expectExceptionObject($exception); + throw $exception; + } + + public function testForNanoseconds(): void + { + $exception = InvalidArgumentException::forNanoseconds(1_000_000_000); + + static::assertSame( + 'The nanoseconds \'1000000000\' exceed the foreseen limit of \'0\' to \'999999999\'.', + $exception->getMessage() + ); + + $this->expectExceptionObject($exception); + throw $exception; + } +} diff --git a/tests/unit/DateTime/TimestampTest.php b/tests/unit/DateTime/TimestampTest.php index 716cedeb..4ab68b15 100644 --- a/tests/unit/DateTime/TimestampTest.php +++ b/tests/unit/DateTime/TimestampTest.php @@ -13,6 +13,7 @@ use Psl\DateTime\Exception\ParserException; use Psl\DateTime\Exception\UnderflowException; use Psl\DateTime\FormatPattern; +use Psl\DateTime\SecondsStyle; use Psl\DateTime\Timestamp; use Psl\DateTime\Timezone; use Psl\Locale\Locale; @@ -53,6 +54,17 @@ public function testMonotonicIsPrecise(): void static::assertGreaterThan(100.0, $difference->getTotalMilliseconds()); } + public function testSince() + { + $a = Timestamp::fromParts(20, 1); + $b = Timestamp::fromParts(30, 2); + + $duration = $b->since($a); + + static::assertSame(10, $duration->getSeconds()); + static::assertSame(1, $duration->getNanoseconds()); + } + public function testFromRowOverflow(): void { $this->expectException(OverflowException::class); @@ -189,6 +201,11 @@ public static function provideCompare(): array [Timestamp::fromParts(100), Timestamp::fromParts(42), Order::Greater], [Timestamp::fromParts(42), Timestamp::fromParts(42), Order::Equal], [Timestamp::fromParts(42), Timestamp::fromParts(100), Order::Less], + + // Nanoseconds + [Timestamp::fromParts(42, 100), Timestamp::fromParts(42, 42), Order::Greater], + [Timestamp::fromParts(42, 42), Timestamp::fromParts(42, 42), Order::Equal], + [Timestamp::fromParts(42, 42), Timestamp::fromParts(42, 100), Order::Less], ]; } /** @@ -403,4 +420,13 @@ public function testJsonSerialization(): void static::assertSame(1711917232, $serialized['seconds']); static::assertSame(12, $serialized['nanoseconds']); } + + public function testToRfc3999(): void + { + $timestamp = Timestamp::fromParts(1711917232, 12); + + static::assertSame('2024-03-31T20:33:52.12+00:00', $timestamp->toRfc3339()); + static::assertSame('2024-03-31T20:33:52+00:00', $timestamp->toRfc3339(seconds_style: SecondsStyle::Seconds)); + static::assertSame('2024-03-31T20:33:52.12Z', $timestamp->toRfc3339(use_z: true)); + } } diff --git a/tests/unit/DateTime/TimezoneTest.php b/tests/unit/DateTime/TimezoneTest.php index 991843ff..d3d1c7e7 100644 --- a/tests/unit/DateTime/TimezoneTest.php +++ b/tests/unit/DateTime/TimezoneTest.php @@ -5,6 +5,7 @@ namespace Psl\Tests\Unit\DateTime; use PHPUnit\Framework\TestCase; +use Psl\DateTime\DateTime; use Psl\DateTime\Timestamp; use Psl\DateTime\Timezone; @@ -31,6 +32,15 @@ public function testGetOffset(): void static::assertSame(-12600., Timezone::Minus0330->getOffset($temporal)->getTotalSeconds()); static::assertSame(3600., Timezone::Plus0100->getOffset($temporal)->getTotalSeconds()); static::assertSame(-3600., Timezone::Minus0100->getOffset($temporal)->getTotalSeconds()); + + // Local + $brussels = Timezone::EuropeBrussels; + date_default_timezone_set($brussels->value); + + $summer = DateTime::fromParts($brussels, 2024, 3, 31, 3); + + static::assertSame(2., $brussels->getOffset($summer)->getTotalHours()); + static::assertSame(1., $brussels->getOffset($summer, local: true)->getTotalHours()); } /** @@ -61,6 +71,20 @@ public function testHasTheSameRulesAs(): void static::assertFalse(Timezone::AmericaNewYork->hasTheSameRulesAs(Timezone::EuropeLondon)); } + public function testGetDaylightSavingTimeOffset(): void + { + $brussels = Timezone::EuropeBrussels; + date_default_timezone_set($brussels->value); + + $summer = DateTime::fromParts($brussels, 2024, 3, 31, 3); + $winter = DateTime::fromParts($brussels, 2024, 10, 27, 2); + + static::assertSame(0., $brussels->getDaylightSavingTimeOffset($winter)->getTotalHours()); + static::assertSame(1., $brussels->getDaylightSavingTimeOffset($winter, local: true)->getTotalHours()); + static::assertSame(1., $brussels->getDaylightSavingTimeOffset($summer)->getTotalHours()); + static::assertSame(0., $brussels->getDaylightSavingTimeOffset($summer, local: true)->getTotalHours()); + } + public static function provideRawOffsetData(): iterable { yield [Timezone::EuropeLondon, 0];