diff --git a/lib/classes/date.php b/lib/classes/date.php
index 01b8ac0b664..bcc575ca25a 100644
--- a/lib/classes/date.php
+++ b/lib/classes/date.php
@@ -717,10 +717,10 @@ class core_date {
// Windows format.
$locale = $locale ?: get_string('locale', 'langconfig');
- // The following code is taken from https://github.com/alphp/strftime without modifications.
+ // The following code is taken from https://github.com/alphp/strftime.
// phpcs:disable
if (!($timestamp instanceof DateTimeInterface)) {
- $timestamp = is_int($timestamp) ? '@' . $timestamp : (string) $timestamp;
+ $timestamp = is_numeric($timestamp) ? '@' . $timestamp : (string) $timestamp;
try {
$timestamp = new DateTime($timestamp);
diff --git a/lib/phpunit/classes/base_testcase.php b/lib/phpunit/classes/base_testcase.php
index 1ce5e56db0e..3ef4aa5cc04 100644
--- a/lib/phpunit/classes/base_testcase.php
+++ b/lib/phpunit/classes/base_testcase.php
@@ -121,6 +121,31 @@ abstract class base_testcase extends PHPUnit\Framework\TestCase {
return $hash;
}
+ /**
+ * Assert that two Date/Time strings are equal.
+ *
+ * The strings generated by \DateTime, \strtotime, \date, \time, etc. are generated outside of our control.
+ * From time-to-time string changes are made.
+ * One such example is from ICU 72.1 which changed the time format to include a narrow-non-breaking-space (U+202F)
+ * between the time and AM/PM.
+ *
+ * We should not update our tests to match these changes, as it is not our code that is
+ * generating the strings and they may change again.
+ * In addition, the changes are not equal amongst all systems as they depend on the version of ICU installed.
+ *
+ * @param string $expected
+ * @param string $actual
+ * @param string $message
+ */
+ public function assertEqualsIgnoringWhitespace($expected, $actual, string $message = ''): void {
+ // ICU 72.1 introduced the use of a narrow-non-breaking-space (U+202F) between the time and the AM/PM.
+ // Normalise all whitespace when performing the comparison.
+ $expected = preg_replace('/\s+/u', ' ', $expected);
+ $actual = preg_replace('/\s+/u', ' ', $actual);
+
+ $this->assertEquals($expected, $actual, $message);
+ }
+
/**
* Parse out the options from the tag using DOM object tree.
*
diff --git a/lib/phpunit/tests/basic_test.php b/lib/phpunit/tests/basic_test.php
index 190807b17c6..88ab7bd6ecb 100644
--- a/lib/phpunit/tests/basic_test.php
+++ b/lib/phpunit/tests/basic_test.php
@@ -145,6 +145,84 @@ STRING;
self::assertTag(['id' => 'testid'], "
");
}
+ /**
+ * Tests for assertEqualsIgnoringWhitespace.
+ *
+ * @param string $expected
+ * @param string $actual
+ * @param bool $expectationvalid
+ * @dataProvider equals_ignoring_whitespace_provider
+ */
+ public function test_assertEqualsIgnoringWhitespace( // phpcs:ignore
+ string $expected,
+ string $actual,
+ bool $expectationvalid,
+ ): void {
+ if (!$expectationvalid) {
+ $this->expectException(\PHPUnit\Framework\ExpectationFailedException::class);
+ }
+ self::assertEqualsIgnoringWhitespace($expected, $actual);
+ }
+
+ /**
+ * Data provider for assertEqualsIgnoringWhitespace tests
+ *
+ * @return array
+ */
+ public static function equals_ignoring_whitespace_provider(): array {
+ return [
+ 'equal' => ['a b c', 'a b c', true],
+ 'equal with whitespace' => ["a b c", "a\nb c", true],
+ 'equal with extra whitespace' => ["a b c", "a\nb c", true],
+ 'whitespace missing' => ["ab c", "a\nb c", false],
+ 'not equal' => ['a b c', 'a b d', false],
+ 'various space types' => [
+ implode(' ', [
+ '20', // Regular space.
+ "a0", // No-Break Space (NBSP).
+ "80", // Ogham Space Mark.
+ "0", // En Quad.
+ "1", // Em Quad.
+ "2", // En Space.
+ "3", // Em Space.
+ "4", // Three-Per-Em Space.
+ "5", // Four-Per-Em Space.
+ "6", // Six-Per-Em Space.
+ "7", // Figure Space.
+ "8", // Punctuation Space.
+ "9", // Thin Space.
+ "0a", // Hair Space.
+ "2f", // Narrow No-Break Space (NNBSP).
+ "5f", // Medium Mathematical Space.
+ "3000", // Ideographic Space.
+ ".",
+ ]),
+ implode('', [
+ // All space chars taken from https://www.compart.com/en/unicode/category/Zs.
+ "20\u{0020}", // Regular space.
+ "a0\u{00a0}", // No-Break Space (NBSP).
+ "80\u{1680}", // Ogham Space Mark.
+ "0\u{2000}", // En Quad.
+ "1\u{2001}", // Em Quad.
+ "2\u{2002}", // En Space.
+ "3\u{2003}", // Em Space.
+ "4\u{2004}", // Three-Per-Em Space.
+ "5\u{2005}", // Four-Per-Em Space.
+ "6\u{2006}", // Six-Per-Em Space.
+ "7\u{2007}", // Figure Space.
+ "8\u{2008}", // Punctuation Space.
+ "9\u{2009}", // Thin Space.
+ "0a\u{200a}", // Hair Space.
+ "2f\u{202f}", // Narrow No-Break Space (NNBSP).
+ "5f\u{205f}", // Medium Mathematical Space.
+ "3000\u{3000}", // Ideographic Space.
+ ".",
+ ]),
+ true,
+ ],
+ ];
+ }
+
// Uncomment following tests to see logging of unexpected changes in global state and database.
/*
public function test_db_modification() {
diff --git a/lib/tests/date_test.php b/lib/tests/date_test.php
index 74060fbe07e..ed825e84d4e 100644
--- a/lib/tests/date_test.php
+++ b/lib/tests/date_test.php
@@ -614,4 +614,56 @@ class date_test extends advanced_testcase {
$this->assertSame($zone, $tz->getName());
}
}
+
+ /**
+ * Data provider for the values for test_core_strftime().
+ *
+ * @return array
+ */
+ public static function get_strftime_provider(): array {
+ return [
+ 'string_c' => [
+ "1708405742",
+ "%c",
+ "20 February 2024 at 1:09 pm",
+ ],
+ 'numeric_c' => [
+ 1708405742,
+ "%c",
+ "20 February 2024 at 1:09 pm",
+ ],
+ 'string_strftimedatetime' => [
+ "1708405742",
+ get_string("strftimedatetime", 'langconfig'),
+ "20 February 2024, 01:09 PM",
+ ],
+ 'numeric_strftimedatetime' => [
+ 1708405742,
+ get_string("strftimedatetime", 'langconfig'),
+ "20 February 2024, 01:09 PM",
+ ],
+ 'string_strftimedatetimeshortaccurate' => [
+ "1708405742",
+ get_string("strftimedatetimeshortaccurate", 'langconfig'),
+ "20/02/24, 13:09:02",
+ ],
+ 'numeric_strftimedatetimeshortaccurate' => [
+ 1708405742,
+ get_string("strftimedatetimeshortaccurate", 'langconfig'),
+ "20/02/24, 13:09:02",
+ ],
+ ];
+ }
+
+ /**
+ * Test \core_date::strftime function.
+ *
+ * @dataProvider get_strftime_provider
+ * @param mixed $input Input passed to strftime
+ * @param string $format The date format to pass to strftime, falls back to '%c' if null
+ * @param string $expected The output generated by strftime
+ */
+ public function test_strftime(mixed $input, string $format, string $expected): void {
+ $this->assertEqualsIgnoringWhitespace($expected, core_date::strftime($format, $input));
+ }
}