diff --git a/packages/pronamic/wp-datetime/CHANGELOG.md b/packages/pronamic/wp-datetime/CHANGELOG.md new file mode 100644 index 0000000..015701f --- /dev/null +++ b/packages/pronamic/wp-datetime/CHANGELOG.md @@ -0,0 +1,152 @@ +# Change Log + +All notable changes to this project will be documented in this file. + +This projects adheres to [Semantic Versioning](http://semver.org/) and [Keep a CHANGELOG](http://keepachangelog.com/). + +## [Unreleased][unreleased] +- + +## [2.1.7] - 2023-10-30 + +### Commits + +- Updated .gitattributes ([8fbdefa](https://github.com/pronamic/wp-datetime/commit/8fbdefa989366b3965afb4565574a8e9e88eab25)) + +Full set of changes: [`2.1.6...2.1.7`][2.1.7] + +[2.1.7]: https://github.com/pronamic/wp-datetime/compare/v2.1.6...v2.1.7 + +## [2.1.6] - 2023-10-30 + +### Commits + +- Updated composer.json ([7ab2db5](https://github.com/pronamic/wp-datetime/commit/7ab2db5184322a31fd5b24f2d6e720fed24d3e6d)) +- Removed Grunt. ([1e3bf5c](https://github.com/pronamic/wp-datetime/commit/1e3bf5c213d14b4eae36e49b9bc785d7f5058086)) +- Added `if ( ! defined( 'ABSPATH' ) )`. ([81c818a](https://github.com/pronamic/wp-datetime/commit/81c818ae98a63222bf3f0f89f275b0317eb85caf)) + +Full set of changes: [`2.1.5...2.1.6`][2.1.6] + +[2.1.6]: https://github.com/pronamic/wp-datetime/compare/v2.1.5...v2.1.6 + +## [2.1.5] - 2023-10-13 + +### Commits + +- It is recommended not to use reserved keyword "object". ([b5dcf67](https://github.com/pronamic/wp-datetime/commit/b5dcf67de0584953fbb855cb7feec8589b2faf8d)) +- Stand-alone post-increment statement found. ([a5a1a81](https://github.com/pronamic/wp-datetime/commit/a5a1a81bb8dfe5c33c49273ac4a81fb6af051968)) + +Full set of changes: [`2.1.4...2.1.5`][2.1.5] + +[2.1.5]: https://github.com/pronamic/wp-datetime/compare/v2.1.4...v2.1.5 + +## [2.1.4] - 2023-03-27 + +### Commits + +- Set Composer type to `wordpress-plugin`. ([9563b8e](https://github.com/pronamic/wp-datetime/commit/9563b8e4f85c3fd935cff82cd7f94ee16e021870)) + +Full set of changes: [`2.1.3...2.1.4`][2.1.4] + +[2.1.4]: https://github.com/pronamic/wp-datetime/compare/v2.1.3...v2.1.4 + +## [2.1.3] - 2023-03-02 +### Added + +- Add .gitattributes. + +Full set of changes: [`2.1.2...2.1.3`][2.1.3] + +[2.1.3]: https://github.com/pronamic/wp-datetime/compare/v2.1.2...v2.1.3 + +## [2.1.2] - 2023-01-31 +### Composer + +- Changed `php` from `>=8.0` to `>=7.4`. + +Full set of changes: [`2.1.1...2.1.2`][2.1.2] + +[2.1.2]: https://github.com/pronamic/wp-datetime/compare/v2.1.1...v2.1.2 + +## [2.1.1] - 2022-12-28 + +### Commits + +- Improve compatibility with PHP versions < 8.0. ([7842a7f](https://github.com/pronamic/wp-datetime/commit/7842a7f4978595b8341311d315c742ae66e569b8)) + +Full set of changes: [`2.1.0...2.1.1`][2.1.1] + +[2.1.1]: https://github.com/pronamic/wp-datetime/compare/v2.1.0...v2.1.1 + +## [2.1.0] - 2022-12-19 +- Increased minimum PHP version to version `8` or higher. +- Improved support for PHP `8.1` and `8.2`. +- Removed usage of deprecated constant `FILTER_SANITIZE_STRING`. + +Full set of changes: [`2.0.3...2.1.0`][2.1.0] + +[2.1.0]: https://github.com/pronamic/wp-datetime/compare/2.0.3...2.1.0 + +## [2.0.3] - 2022-09-27 +- Update plugin version. + +## [2.0.2] - 2022-09-23 +- Coding standards. + +## [2.0.1] - 2022-04-11 +### Changed +- Coding standards. + +## [2.0.0] - 2022-01-10 +### Added +- Added `DateTimeTrait::create_from_interface`. + +### Removed +- Removed `DateTime::create_from_mutable`. +- Removed `DateTimeImmutable::create_from_mutable`. + +## [1.2.2] - 2021-08-26 +- Added the character `p` to the date format characters list which was added in PHP 8. + +## [1.2.1] - 2021-04-26 +- Happy 2021. + +## [1.2.0] - 2020-10-08 +- Added DateTimeImmutable class. +- Added `DateTime::create_from_immutable( \DateTimeImmutable $object )` method. +- Added `DateTimeImmutable::create_from_mutable( \DateTime $object )` method. +- Override upstream `DateTime::createFromImmutable( $object )` method. +- Override upstream `DateTimeImmutable::createFromMutable( $object )` method. +- Override upstream `DateTimeInterface::createFromFormat( $format, $time, $timezone = null )` method. +- Updated copyright. + +## [1.1.1] - 2019-12-17 +- Fix for WordPress core trac ticket 48319 (https://core.trac.wordpress.org/ticket/48319). +- Updated PHP compatibility test version to PHP 5.6. +- Updated tests. + +## [1.1.0] - 2019-08-26 +- Introduced a format translate function, will not switch to local timezone. + +## [1.0.2] - 2018-09-12 +- Fixed issue on PHP 5.6 or lower. + +## [1.0.1] - 2018-08-16 +- Override `createFromFormat` method to return WordPress DateTime object. +- Use new `create_from_format` method instead of override, due to method signature Travis errors for different PHP versions. +- Improved support for timezones. + +## 1.0.0 +- First release. + +[unreleased]: https://github.com/pronamic/wp-datetime/compare/2.0.0...HEAD +[2.0.3]: https://github.com/pronamic/wp-datetime/compare/2.0.2...2.0.3 +[2.0.2]: https://github.com/pronamic/wp-datetime/compare/2.0.1...2.0.2 +[2.0.0]: https://github.com/pronamic/wp-datetime/compare/1.2.2...2.0.0 +[1.2.2]: https://github.com/pronamic/wp-datetime/compare/1.2.1...1.2.2 +[1.2.1]: https://github.com/pronamic/wp-datetime/compare/1.2.0...1.2.1 +[1.2.0]: https://github.com/pronamic/wp-datetime/compare/1.1.1...1.2.0 +[1.1.1]: https://github.com/pronamic/wp-datetime/compare/1.1.0...1.1.1 +[1.1.0]: https://github.com/pronamic/wp-datetime/compare/1.0.2...1.1.0 +[1.0.2]: https://github.com/pronamic/wp-datetime/compare/1.0.1...1.0.2 +[1.0.1]: https://github.com/pronamic/wp-datetime/compare/1.0.0...1.0.1 diff --git a/packages/pronamic/wp-datetime/README.md b/packages/pronamic/wp-datetime/README.md new file mode 100644 index 0000000..2010083 --- /dev/null +++ b/packages/pronamic/wp-datetime/README.md @@ -0,0 +1,33 @@ +# WordPress DateTime + +## WordPress Filters + +### pronamic_datetime_default_format + +```php +function prefix_pronamic_datetime_default_format( $format ) { + return _x( 'D j M Y \a\t H:i', 'default datetime format', 'pronamic-ideal' ); +} + +add_filter( 'pronamic_datetime_default_format', 'prefix_pronamic_datetime_default_format' ); +``` + +## Note `date_i18n` + +It is important to note that `date_i18n()`: + +1. does not have full feature parity with `date()`, not all formats are supported (such as shorthands); +2. does not accept Unix timestamp (despite documented to), the expected value is “WordPress timestamp” (offset by time zone); +3. has issues with certain timezone settings, such as numerical ones; +4. does _nothing_ with `$gmt` argument under normal circumstances; + +Any use of this function must be carefully audited for correctness, _especially_ in regards to output of time zones. + +Source: https://developer.wordpress.org/reference/functions/date_i18n/#comment-2403 + +## Inspiration + +* https://github.com/woocommerce/woocommerce/blob/3.3.5/includes/class-wc-datetime.php +* https://github.com/Rarst/wpdatetime + +[![Pronamic - Work with us](https://github.com/pronamic/brand-resources/blob/main/banners/pronamic-work-with-us-leaderboard-728x90%404x.png)](https://www.pronamic.eu/contact/) diff --git a/packages/pronamic/wp-datetime/composer.json b/packages/pronamic/wp-datetime/composer.json new file mode 100644 index 0000000..b4dfabd --- /dev/null +++ b/packages/pronamic/wp-datetime/composer.json @@ -0,0 +1,77 @@ +{ + "name": "pronamic/wp-datetime", + "description": "WordPress DateTime library.", + "license": "GPL-3.0-or-later", + "type": "wordpress-plugin", + "autoload": { + "psr-4": { + "Pronamic\\WordPress\\DateTime\\": "src" + } + }, + "config": { + "sort-packages": true, + "allow-plugins": { + "koodimonni/composer-dropin-installer": true, + "dealerdirect/phpcodesniffer-composer-installer": true, + "roots/wordpress-core-installer": true, + "bamarni/composer-bin-plugin": true + } + }, + "repositories": [ + { + "type": "composer", + "url": "https://wp-languages.github.io" + } + ], + "require": { + "php": ">=7.4" + }, + "require-dev": { + "automattic/wordbless": "^0.4.1", + "bamarni/composer-bin-plugin": "^1.4", + "koodimonni-language/de_de": "*", + "koodimonni-language/en_gb": "*", + "koodimonni-language/fr_fr": "*", + "koodimonni-language/ja": "*", + "koodimonni-language/nl_nl": "*", + "overtrue/phplint": "^9.0", + "php-coveralls/php-coveralls": "^2.4", + "php-stubs/wordpress-globals": "^0.2.0", + "phpmd/phpmd": "^2.9", + "pronamic/pronamic-cli": "^1.1", + "pronamic/wp-coding-standards": "^2.0", + "roots/wordpress": "^6.0", + "yoast/phpunit-polyfills": "^2.0" + }, + "scripts": { + "coverage": "XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-clover build/logs/clover.xml --coverage-text", + "coverage-clover": "XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-clover build/logs/clover.xml", + "coverage-html": "XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html build/coverage-html", + "coverage-text": "XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-text", + "coveralls": "vendor/bin/php-coveralls -v", + "phpcbf": "XDEBUG_MODE=off vendor/bin/phpcbf", + "phpcs": "XDEBUG_MODE=off vendor/bin/phpcs -s -v", + "phplint": "vendor/bin/phplint", + "phpmd": "vendor/bin/phpmd src,tests text phpmd.ruleset.xml --suffixes php", + "phpstan": "vendor/bin/phpstan analyse --memory-limit=-1", + "phpunit": "vendor/bin/phpunit", + "post-autoload-dump": [ + "mkdir -p wordpress/wp-content/languages", + "mkdir -p wordpress/wp-content/languages/plugins", + "mkdir -p wordpress/wp-content/languages/themes", + "for file in languages/*.mo ; do cp $file wordpress/wp-content/languages/plugins/ ; done", + "for file in vendor/koodimonni-language/*/* ; do cp $file wordpress/wp-content/languages/ ; done", + "for file in vendor/koodimonni-language/*/* ; do cp $file wordpress/wp-content/languages/plugins/ ; done", + "for file in vendor/koodimonni-language/*/* ; do cp $file wordpress/wp-content/languages/themes/ ; done" + ], + "post-install-cmd": [ + "echo 'Optionally run: composer bin all install'", + "mkdir -p wordpress/wp-content && cp vendor/automattic/wordbless/src/dbless-wpdb.php wordpress/wp-content/db.php" + ], + "post-update-cmd": [ + "echo 'Optionally run: composer bin all update'", + "mkdir -p wordpress/wp-content && cp vendor/automattic/wordbless/src/dbless-wpdb.php wordpress/wp-content/db.php" + ], + "psalm": "vendor/bin/psalm" + } +} diff --git a/packages/pronamic/wp-datetime/languages/pronamic-datetime-nl_NL.mo b/packages/pronamic/wp-datetime/languages/pronamic-datetime-nl_NL.mo new file mode 100644 index 0000000..f3c3638 Binary files /dev/null and b/packages/pronamic/wp-datetime/languages/pronamic-datetime-nl_NL.mo differ diff --git a/packages/pronamic/wp-datetime/languages/pronamic-datetime-nl_NL.po b/packages/pronamic/wp-datetime/languages/pronamic-datetime-nl_NL.po new file mode 100644 index 0000000..a6212d1 --- /dev/null +++ b/packages/pronamic/wp-datetime/languages/pronamic-datetime-nl_NL.po @@ -0,0 +1,22 @@ +# Copyright (C) 2018 +# This file is distributed under the same license as the package. +msgid "" +msgstr "" +"Project-Id-Version: Pronamic DateTime\n" +"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/wp-datetime\n" +"POT-Creation-Date: 2019-12-17 22:34:03+00:00\n" +"PO-Revision-Date: 2018-08-16 11:53+0100\n" +"Last-Translator: Remco Tolsma \n" +"Language-Team: Pronamic \n" +"Language: nl_NL\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 1.7.4\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Poedit-SourceCharset: UTF-8\n" + +#: src/DateTime.php:292 +msgctxt "default datetime format" +msgid "D j M Y \\a\\t H:i" +msgstr "D j M Y \\o\\m H:i" diff --git a/packages/pronamic/wp-datetime/languages/pronamic-datetime.pot b/packages/pronamic/wp-datetime/languages/pronamic-datetime.pot new file mode 100644 index 0000000..ef55ef3 --- /dev/null +++ b/packages/pronamic/wp-datetime/languages/pronamic-datetime.pot @@ -0,0 +1,19 @@ +# Copyright (C) 2019 +# This file is distributed under the same license as the package. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/wp-datetime\n" +"POT-Creation-Date: 2019-12-17 22:34:03+00:00\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"PO-Revision-Date: 2019-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"X-Generator: grunt-wp-i18n 1.0.3\n" + +#: src/DateTime.php:292 +msgctxt "default datetime format" +msgid "D j M Y \\a\\t H:i" +msgstr "" \ No newline at end of file diff --git a/packages/pronamic/wp-datetime/package.json b/packages/pronamic/wp-datetime/package.json new file mode 100644 index 0000000..12aff5c --- /dev/null +++ b/packages/pronamic/wp-datetime/package.json @@ -0,0 +1,21 @@ +{ + "name": "wp-datetime", + "version": "2.1.7", + "description": "WordPress DateTime library.", + "repository": { + "type": "git", + "url": "git+https://github.com/pronamic/wp-datetime.git" + }, + "keywords": [ + "pronamic", + "wordpress", + "datetime", + "date", + "time" + ], + "license": "GPL-3.0-or-later", + "bugs": { + "url": "https://github.com/pronamic/wp-datetime/issues" + }, + "homepage": "https://github.com/pronamic/wp-datetime#readme" +} diff --git a/packages/pronamic/wp-datetime/pronamic-datetime.php b/packages/pronamic/wp-datetime/pronamic-datetime.php new file mode 100644 index 0000000..a22c50a --- /dev/null +++ b/packages/pronamic/wp-datetime/pronamic-datetime.php @@ -0,0 +1,38 @@ + + * @copyright 2005-2023 Pronamic + * @license GPL-3.0-or-later + * @package Pronamic\WordPress\Pay + */ + +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + +/** + * Pronamic DateTime load plugin text domain. + */ +function pronamic_datetime_load_plugin_textdomain() { + load_plugin_textdomain( 'pronamic-datetime', false, basename( __DIR__ ) . '/languages' ); +} + +add_action( 'init', 'pronamic_datetime_load_plugin_textdomain' ); diff --git a/packages/pronamic/wp-datetime/src/DateTime.php b/packages/pronamic/wp-datetime/src/DateTime.php new file mode 100644 index 0000000..edc203d --- /dev/null +++ b/packages/pronamic/wp-datetime/src/DateTime.php @@ -0,0 +1,24 @@ + + * @copyright 2005-2023 Pronamic + * @license GPL-3.0-or-later + * @package Pronamic\WordPress\DateTime + * @see https://github.com/woocommerce/woocommerce/blob/3.3.4/includes/class-wc-datetime.php + * @see https://github.com/Rarst/wpdatetime/ + */ + +namespace Pronamic\WordPress\DateTime; + +/** + * Date time + * + * @author Remco Tolsma + * @version 1.2.0 + * @since 1.0.0 + */ +class DateTime extends \DateTime implements \Pronamic\WordPress\DateTime\DateTimeInterface { + use DateTimeTrait; +} diff --git a/packages/pronamic/wp-datetime/src/DateTimeImmutable.php b/packages/pronamic/wp-datetime/src/DateTimeImmutable.php new file mode 100644 index 0000000..e26ba66 --- /dev/null +++ b/packages/pronamic/wp-datetime/src/DateTimeImmutable.php @@ -0,0 +1,24 @@ + + * @copyright 2005-2023 Pronamic + * @license GPL-3.0-or-later + * @package Pronamic\WordPress\DateTime + * @see https://github.com/woocommerce/woocommerce/blob/3.3.4/includes/class-wc-datetime.php + * @see https://github.com/Rarst/wpdatetime/ + */ + +namespace Pronamic\WordPress\DateTime; + +/** + * Date time immutable + * + * @author Remco Tolsma + * @version 1.2.0 + * @since 1.2.0 + */ +class DateTimeImmutable extends \DateTimeImmutable implements \Pronamic\WordPress\DateTime\DateTimeInterface { + use DateTimeTrait; +} diff --git a/packages/pronamic/wp-datetime/src/DateTimeInterface.php b/packages/pronamic/wp-datetime/src/DateTimeInterface.php new file mode 100644 index 0000000..58f92fe --- /dev/null +++ b/packages/pronamic/wp-datetime/src/DateTimeInterface.php @@ -0,0 +1,114 @@ + + * @copyright 2005-2023 Pronamic + * @license GPL-3.0-or-later + * @package Pronamic\WordPress\DateTime + * @see https://github.com/woocommerce/woocommerce/blob/3.3.4/includes/class-wc-datetime.php + * @see https://github.com/Rarst/wpdatetime/ + */ + +namespace Pronamic\WordPress\DateTime; + +/** + * Date time interface + * + * @author Remco Tolsma + * @version 1.2.0 + * @since 1.2.0 + */ +interface DateTimeInterface extends \DateTimeInterface { + /** + * MySQL datetime format. + * + * @link https://dev.mysql.com/doc/en/datetime.html + * @link https://github.com/Rarst/wpdatetime/blob/0.3/src/WpDateTime.php#L10 + * + * @var string + */ + const MYSQL = 'Y-m-d H:i:s'; + + /** + * Date format characters in PHP. + * + * @link https://www.php.net/manual/en/function.date.php + * @link https://github.com/php/php-src/blob/php-7.3.10/ext/date/php_date.c#L1128-L1288 + * @var string[] + */ + const DATE_FORMAT_CHARACTERS = [ + // Day. + 'd', + 'D', + 'j', + 'l', + 'S', + 'w', + 'N', + 'z', + // Week. + 'W', + 'o', + // Month. + 'F', + 'm', + 'M', + 'n', + 't', + // Year. + 'L', + 'y', + 'Y', + // Time. + 'a', + 'A', + 'B', + 'g', + 'G', + 'h', + 'H', + 'i', + 's', + 'u', + 'v', + // Timezone. + 'I', + 'P', + 'p', + 'O', + 'T', + 'e', + 'Z', + // Full date/time. + 'c', + 'r', + 'U', + ]; + + /** + * Format I18N. + * + * @link https://github.com/Rarst/wpdatetime/blob/0.3/src/WpDateTimeTrait.php#L79-L104 + * @link https://github.com/WordPress/WordPress/blob/4.9.4/wp-includes/functions.php#L72-L151 + * @link https://developer.wordpress.org/reference/functions/apply_filters/ + * + * @param string|null $format Format. + * + * @return string + */ + public function format_i18n( $format = null ); + + /** + * Create from format. + * + * @link https://www.php.net/manual/en/datetime.createfromformat.php + * @link https://www.php.net/manual/en/datetimeimmutable.createfromformat.php + * + * @param string $format Format accepted by date(). + * @param string $time String representing the time. + * @param \DateTimeZone $timezone A DateTimeZone object representing the desired time zone. + * @return self|false + */ + public static function create_from_format( $format, $time, \DateTimeZone $timezone = null ); +} diff --git a/packages/pronamic/wp-datetime/src/DateTimeTrait.php b/packages/pronamic/wp-datetime/src/DateTimeTrait.php new file mode 100644 index 0000000..a3bf1af --- /dev/null +++ b/packages/pronamic/wp-datetime/src/DateTimeTrait.php @@ -0,0 +1,316 @@ + + * @copyright 2005-2023 Pronamic + * @license GPL-3.0-or-later + * @package Pronamic\WordPress\DateTime + * @see https://github.com/woocommerce/woocommerce/blob/3.3.4/includes/class-wc-datetime.php + * @see https://github.com/Rarst/wpdatetime/ + */ + +namespace Pronamic\WordPress\DateTime; + +/** + * Date time trait + * + * @author Remco Tolsma + * @version 1.2.0 + * @since 1.2.0 + */ +trait DateTimeTrait { + /** + * Slash date format characters. + * + * @link https://github.com/WordPress/WordPress/blob/5.2/wp-includes/formatting.php#L2615-L2628 + * @link https://www.php.net/manual/en/function.addcslashes.php + * + * @param string $value Value. + * @return string + */ + private static function slash_date_format_characters( $value ) { + $charlist = implode( '', DateTimeInterface::DATE_FORMAT_CHARACTERS ); + + // Backslash the backslash. + $charlist .= '\\'; + + $value = \addcslashes( $value, $charlist ); + + return $value; + } + + /** + * Translate. + * + * @since 1.0.1 + * + * @link https://github.com/WordPress/WordPress/blob/4.9.6/wp-includes/functions.php#L103-L119 + * @link https://github.com/WordPress/WordPress/blob/4.9.6/wp-includes/class-wp-locale.php#L116-L235 + * + * @global \WP_Locale $wp_locale WordPress date and time locale object. + * + * @param string $format Format. + * + * @return string + */ + private function format_i18n_translate( $format ) { + global $wp_locale; + + if ( ! $wp_locale instanceof \WP_Locale ) { + return $format; + } + + if ( empty( $wp_locale->month ) || empty( $wp_locale->weekday ) ) { + return $format; + } + + $month = $wp_locale->get_month( $this->format( 'm' ) ); + $weekday = $wp_locale->get_weekday( \intval( $this->format( 'w' ) ) ); + + $format_length = \strlen( $format ); + + $format_new = ''; + + for ( $i = 0; $i < $format_length; $i++ ) { + switch ( $format[ $i ] ) { + case 'D': + $format_new .= self::slash_date_format_characters( $wp_locale->get_weekday_abbrev( $weekday ) ); + + break; + case 'F': + $format_new .= self::slash_date_format_characters( $month ); + + break; + case 'l': + $format_new .= self::slash_date_format_characters( $weekday ); + + break; + case 'M': + $format_new .= self::slash_date_format_characters( $wp_locale->get_month_abbrev( $month ) ); + + break; + case 'a': + $format_new .= self::slash_date_format_characters( $wp_locale->get_meridiem( $this->format( 'a' ) ) ); + + break; + case 'A': + $format_new .= self::slash_date_format_characters( $wp_locale->get_meridiem( $this->format( 'A' ) ) ); + + break; + case '\\': + $format_new .= $format[ $i ]; + + if ( $i < $format_length ) { + ++$i; + } + + // No break. + default: + $format_new .= $format[ $i ]; + + break; + } + } + + return $format_new; + } + + /** + * Format I18N timezone. + * + * @since 1.0.1 + * + * @link https://github.com/WordPress/WordPress/blob/4.9.6/wp-includes/functions.php#L120-L136 + * @link https://github.com/php/php-src/blob/php-7.2.7/ext/date/php_date.c#L1093-L1253 + * + * @param string $format Format. + * + * @return string + */ + private function format_i18n_timezone( $format ) { + $format_length = \strlen( $format ); + + $format_new = ''; + + for ( $i = 0; $i < $format_length; $i++ ) { + switch ( $format[ $i ] ) { + case 'P': + case 'I': + case 'O': + case 'T': + case 'Z': + case 'e': + $format_new .= self::slash_date_format_characters( $this->format( $format[ $i ] ) ); + + break; + case '\\': + $format_new .= $format[ $i ]; + + if ( $i < $format_length ) { + ++$i; + } + + // No break. + default: + $format_new .= $format[ $i ]; + + break; + } + } + + return $format_new; + } + + /** + * Get WordPress timestamp. + * + * @since 1.0.1 + * + * @return int + */ + private function get_wp_timestamp() { + return $this->getTimestamp() + DateTimeZone::get_offset( $this ); + } + + /** + * Get local date for this date. + * + * @since 1.0.1 + * + * @return self + */ + public function get_local_date() { + $wp_timezone = DateTimeZone::get_default(); + + /** + * PHP BUG: DateTime::setTimezone(): Can only do this for zones with ID for now. + * PHP version < 5.4.26 + * PHP version > 5.5 < 5.5.10 + * + * @link https://bugs.php.net/bug.php?id=45543 + * @link https://3v4l.org/mlZX7 + */ + if ( \version_compare( PHP_VERSION, '5.4.26', '<' ) || ( \version_compare( PHP_VERSION, '5.5', '>' ) && \version_compare( PHP_VERSION, '5.5.10', '<' ) ) ) { + return new self( \gmdate( DateTimeInterface::MYSQL, $this->get_wp_timestamp() ), $wp_timezone ); + } + + $date = clone $this; + + $date = $date->setTimezone( $wp_timezone ); + + return $date; + } + + /** + * Format translate. + * + * @link https://developer.wordpress.org/reference/functions/__/ + * + * @since 1.1.0 + * @param string $format Format. + * @return string + */ + public function format_translate( $format ) { + $format = $this->format_i18n_translate( $format ); + $format = $this->format_i18n_timezone( $format ); + + return $this->format( $format ); + } + + /** + * Format I18N. + * + * @link https://github.com/Rarst/wpdatetime/blob/0.3/src/WpDateTimeTrait.php#L79-L104 + * @link https://github.com/WordPress/WordPress/blob/4.9.4/wp-includes/functions.php#L72-L151 + * @link https://developer.wordpress.org/reference/functions/apply_filters/ + * + * @param string|null $format Format. + * + * @return string + */ + public function format_i18n( $format = null ) { + if ( \is_null( $format ) ) { + $format = \_x( 'D j M Y \a\t H:i', 'default datetime format', 'pronamic-datetime' ); + + $format = \apply_filters( 'pronamic_datetime_default_format', $format ); + } + + $date = $this->get_local_date(); + + $format = $date->format_i18n_translate( $format ); + $format = $date->format_i18n_timezone( $format ); + + $result = \date_i18n( $format, $date->get_wp_timestamp() ); + + return $result; + } + + /** + * Overrides upstream method to correct returned instance type to the inheriting one. + * + * {@inheritdoc} + * + * @param string $format Format. + * @param string $time String representing the time. + * @param DateTimeZone|null $timezone Timezone. + * @return self|false + */ + #[\ReturnTypeWillChange] + public static function createFromFormat( // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid + $format, + $time, + $timezone = null + ) { + return self::create_from_format( $format, $time, $timezone ); + } + + /** + * Parse a string into a new DateTime object according to the specified format. + * + * @link http://php.net/manual/en/datetime.createfromformat.php + * @link https://github.com/Rarst/wpdatetime/blob/0.3/src/WpDateTimeTrait.php#L56-L77 + * + * @since 1.0.1 + * + * @param string $format Format accepted by date(). + * @param string $time String representing the time. + * @param \DateTimeZone $timezone A DateTimeZone object representing the desired time zone. + * + * @return self|false + */ + #[\ReturnTypeWillChange] + public static function create_from_format( $format, $time, \DateTimeZone $timezone = null ) { + /* + * In PHP 5.6 or lower it's not possible to pass in an empty (null) timezone object. + * This will result in a `DateTime::createFromFormat() expects parameter 3 to be DateTimeZone, null given` error. + */ + $created = empty( $timezone ) ? + parent::createFromFormat( $format, $time ) : + parent::createFromFormat( $format, $time, $timezone ); + + if ( false === $created ) { + return false; + } + + $wp_date_time = new self( '@' . $created->getTimestamp() ); + + if ( null !== $timezone ) { + $wp_date_time = $wp_date_time->setTimezone( $timezone ); + } + + return $wp_date_time; + } + + /** + * Create from interface. + * + * @link https://www.php.net/manual/en/datetime.createfrominterface.php + * @link https://php.watch/versions/8.0/datetime-immutable-createfrominterface + * @param \DateTimeInterface $value The mutable DateTime object that you want to convert to an immutable version. + * @return self + */ + public static function create_from_interface( \DateTimeInterface $value ): self { + return new self( $value->format( 'Y-m-d H:i:s.u' ), $value->getTimezone() ); + } +} diff --git a/packages/pronamic/wp-datetime/src/DateTimeZone.php b/packages/pronamic/wp-datetime/src/DateTimeZone.php new file mode 100644 index 0000000..3a3e41c --- /dev/null +++ b/packages/pronamic/wp-datetime/src/DateTimeZone.php @@ -0,0 +1,75 @@ + + * @copyright 2005-2023 Pronamic + * @license GPL-3.0-or-later + * @package Pronamic\WordPress\DateTime + * @see https://github.com/woocommerce/woocommerce/blob/3.3.4/includes/class-wc-datetime.php + * @see https://github.com/Rarst/wpdatetime/ + */ + +namespace Pronamic\WordPress\DateTime; + +/** + * Date time zone + * + * @author Remco Tolsma + * @version 1.2.0 + * @since 1.0.0 + * @psalm-immutable + */ +class DateTimeZone extends \DateTimeZone { + /** + * Get default timezone. + * + * @link https://github.com/Rarst/wpdatetime/blob/0.3/src/WpDateTimeZone.php + * @link https://github.com/WordPress/WordPress/blob/4.9.4/wp-includes/functions.php#L72-L151 + * + * @return \DateTimeZone + */ + public static function get_default() { + $timezone_string = \get_option( 'timezone_string' ); + + if ( ! empty( $timezone_string ) ) { + return new DateTimeZone( $timezone_string ); + } + + $gmt_offset = \get_option( 'gmt_offset' ); + $hours = (int) $gmt_offset; + $minutes = \abs( ( $gmt_offset - (int) $gmt_offset ) * 60 ); + $offset = \sprintf( '%+03d:%02d', $hours, $minutes ); + + /** + * Offset values as timezone parameter are supported since PHP 5.5.10. + * + * @link http://php.net/manual/en/datetimezone.construct.php + */ + if ( \version_compare( PHP_VERSION, '5.5.10', '<' ) ) { + $date = new DateTime( $offset ); + + return $date->getTimezone(); + } + + return new \DateTimeZone( $offset ); + } + + /** + * Get offset. + * + * @param \DateTimeInterface $date DateTime object. + * @return int + */ + public static function get_offset( $date ) { + $timezone_string = \get_option( 'timezone_string' ); + + if ( empty( $timezone_string ) ) { + return \intval( \floatval( \get_option( 'gmt_offset', 0 ) ) * HOUR_IN_SECONDS ); + } + + $timezone = new DateTimeZone( $timezone_string ); + + return $timezone->getOffset( $date ); + } +} diff --git a/packages/pronamic/wp-html/CHANGELOG.md b/packages/pronamic/wp-html/CHANGELOG.md new file mode 100644 index 0000000..9803cdd --- /dev/null +++ b/packages/pronamic/wp-html/CHANGELOG.md @@ -0,0 +1,85 @@ +[2.0.2]: https://github.com/pronamic/wp-html/compare/2.0.1...2.0.2 +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [2.2.1] - 2023-10-30 + +### Commits + +- Improve escaping for `