Explorar o código

Preserve negative timezone offsets in date intervals (#9071)

* Preserve negative timezone offsets in date intervals

## Summary

- Preserve the sign of ISO 8601 timezone offsets while normalizing date intervals.
- Add regression coverage for a negative offset.

## Why

The previous normalization removed every hyphen, turning `-05:00` into `0500` and parsing the timestamp in the wrong timezone.

* Pad dates before timezone offsets

* Minor test comment and format

---------

Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
Gerard Alvear Porras hai 5 días
pai
achega
0dc3431168
Modificáronse 2 ficheiros con 27 adicións e 3 borrados
  1. 25 3
      lib/lib_date.php
  2. 2 0
      tests/lib/LibDateTest.php

+ 25 - 3
lib/lib_date.php

@@ -46,13 +46,25 @@ function example(string $dateInterval): void {
 
 function _dateFloor(string $isoDate): string {
 	$x = explode('T', $isoDate, 2);
-	$t = isset($x[1]) ? str_pad($x[1], 6, '0') : '000000';
+	$t = isset($x[1]) ? $x[1] : '';
+	$timezone = '';
+	if (preg_match('/([+-]\d{4}|Z)$/i', $t, $matches)) {
+		$timezone = $matches[1];
+		$t = substr($t, 0, -strlen($timezone));
+	}
+	$t = str_pad($t, 6, '0') . $timezone;
 	return str_pad($x[0], 8, '01') . 'T' . $t;
 }
 
 function _dateCeiling(string $isoDate): string {
 	$x = explode('T', $isoDate, 2);
-	$t = isset($x[1]) && strlen($x[1]) > 1 ? str_pad($x[1], 6, '59') : '235959';
+	$t = isset($x[1]) ? $x[1] : '';
+	$timezone = '';
+	if (preg_match('/([+-]\d{4}|Z)$/i', $t, $matches)) {
+		$timezone = $matches[1];
+		$t = substr($t, 0, -strlen($timezone));
+	}
+	$t = strlen($t) > 1 ? str_pad($t, 6, '59') . $timezone : '235959' . $timezone;
 	switch (strlen($x[0])) {
 		case 4:
 			return $x[0] . '1231T' . $t;
@@ -67,7 +79,17 @@ function _dateCeiling(string $isoDate): string {
 
 /** @phpstan-return ($isoDate is null ? null : ($isoDate is '' ? null : string)) */
 function _noDelimit(?string $isoDate): ?string {
-	return $isoDate === null || $isoDate === '' ? null : str_replace(['-', ':'], '', $isoDate);	//FIXME: Bug with negative time zone
+	if ($isoDate === null || $isoDate === '') {
+		return null;
+	}
+
+	$timezone = '';
+	if (preg_match('/([+-]\d{2}:?\d{2}|Z)$/i', $isoDate, $matches)) {
+		$timezone = $matches[1];
+		$isoDate = substr($isoDate, 0, -strlen($timezone));
+	}
+
+	return str_replace(['-', ':'], '', $isoDate) . str_replace(':', '', $timezone);
 }
 
 function _dateRelative(?string $d1, ?string $d2): ?string {

+ 2 - 0
tests/lib/LibDateTest.php

@@ -45,6 +45,8 @@ class LibDateTest extends \PHPUnit\Framework\TestCase {
 			['2014-03-30', strtotime('2014-03-30 00:00:00'), strtotime('2014-03-30 23:59:59')],
 			['2014-05-30T13', strtotime('2014-05-30 13:00:00'), strtotime('2014-05-30 13:59:59')],
 			['2014-05-30T13:30', strtotime('2014-05-30 13:30:00'), strtotime('2014-05-30 13:30:59')],
+			// With negative timezone offset
+			['2014-05-30T13:30-05:00', strtotime('2014-05-30T13:30:00-05:00'), strtotime('2014-05-30T13:30:59-05:00')],
 
 			// Date ranges with explicit end dates
 			['2014-02/2014-04', strtotime('2014-02-01 00:00:00'), strtotime('2014-04-30 23:59:59')],