Repository: spatie/opening-hours (php)

Issue:
createAndMergeOverlappingRanges ignores data in Time classes
The following:
```
$opening_hours = OpeningHours::createAndMergeOverlappingRanges([
  'monday' => [
    ['hours' => '08:00-12:00', 'data' => ['testdata' => true]],
    ['hours' => '12:00-24:00','data' => ['testdata' => true]],
  ],
]);
```

Will result in:
```
[
  'monday' => [
    ['hours' => '08:00-24:00', 'data' => null],
  ],
]
```

I would expect:
```
[
  'monday' => [
    ['hours' => '08:00-24:00', 'data' => ['testdata' => true]],
  ],
]
```

But if the data is different, then it should not merge.

Pull request description:
Merge time ranges takes in to account the data field.
Added functionality, merging timeRanges take in to account the data field. It merges only if data field is the same.

Fix #268

Patch:
diff --git a/src/OpeningHours.php b/src/OpeningHours.php
index 7078dd1..45aab1e 100644
--- a/src/OpeningHours.php
+++ b/src/OpeningHours.php
@@ -144,29 +144,32 @@ class OpeningHours
 
     /**
      * @param  array  $data  hours definition array or sub-array
+     * @param  bool  $ignoreData  should ignore data
      * @param  array  $excludedKeys  keys to ignore from parsing
      * @return array
      */
-    public static function mergeOverlappingRanges(array $data, array $excludedKeys = ['data', 'dateTimeClass', 'filters', 'overflow']): array
+    public static function mergeOverlappingRanges(array $data, bool $ignoreData = true, array $excludedKeys = ['data', 'dateTimeClass', 'filters', 'overflow']): array
     {
         $result = [];
         $ranges = [];
 
-        foreach (static::filterHours($data, $excludedKeys) as $key => $value) {
+        foreach (static::filterHours($data, $excludedKeys) as $key => [$value, $data]) {
+            $dataString = $ignoreData ? '' : json_encode($data);
+
             $value = is_array($value)
-                ? static::mergeOverlappingRanges($value, ['data'])
-                : (is_string($value) ? TimeRange::fromString($value) : $value);
+                ? static::mergeOverlappingRanges($value, $ignoreData)
+                : (is_string($value) ? TimeRange::fromString($value, $data) : $value);
 
             if ($value instanceof TimeRange) {
                 $newRanges = [];
 
-                foreach ($ranges as $range) {
+                foreach (($ranges[$dataString] ?? []) as $range) {
                     if ($value->format() === $range->format()) {
                         continue 2;
                     }
 
                     if ($value->overlaps($range) || $range->overlaps($value)) {
-                        $value = TimeRange::fromList([$value, $range]);
+                        $value = TimeRange::fromList([$value, $range], $data);
 
                         continue;
                     }
@@ -175,7 +178,7 @@ class OpeningHours
                 }
 
                 $newRanges[] = $value;
-                $ranges = $newRanges;
+                $ranges[$dataString] = $newRanges;
 
                 continue;
             }
@@ -184,7 +187,9 @@ class OpeningHours
         }
 
         foreach ($ranges as $range) {
-            $result[] = $range;
+            foreach ((array) $range as $rangeItem) {
+                $result[] = $rangeItem;
+            }
         }
 
         return $result;
@@ -205,11 +210,12 @@ class OpeningHours
      *         }                         $data
      * @param  string|DateTimeZone|null  $timezone
      * @param  string|DateTimeZone|null  $outputTimezone
+     * @param  bool  $ignoreData
      * @return static
      */
-    public static function createAndMergeOverlappingRanges(array $data, $timezone = null, $outputTimezone = null): self
+    public static function createAndMergeOverlappingRanges(array $data, $timezone = null, $outputTimezone = null, bool $ignoreData = true): self
     {
-        return static::create(static::mergeOverlappingRanges($data), $timezone, $outputTimezone);
+        return static::create(static::mergeOverlappingRanges($data, $ignoreData), $timezone, $outputTimezone);
     }
 
     /**
@@ -1034,13 +1040,13 @@ class OpeningHours
 
             if (is_int($key) && is_array($value) && isset($value['hours'])) {
                 foreach ((array) $value['hours'] as $subKey => $hour) {
-                    yield "$key.$subKey" => $hour;
+                    yield "$key.$subKey" => [$hour, $value['data'] ?? null];
                 }
 
                 continue;
             }
 
-            yield $key => $value;
+            yield $key => [$value, null];
         }
     }
 
diff --git a/src/OpeningHoursForDay.php b/src/OpeningHoursForDay.php
index 10e28f6..1ebb751 100644
--- a/src/OpeningHoursForDay.php
+++ b/src/OpeningHoursForDay.php
@@ -38,7 +38,7 @@ class OpeningHoursForDay implements ArrayAccess, Countable, IteratorAggregate
         uasort($strings, static fn ($a, $b) => strcmp(static::getHoursFromRange($a), static::getHoursFromRange($b)));
 
         return new static(
-            Arr::map($strings, static fn ($string) => TimeRange::fromDefinition($string)),
+            Arr::map($strings, static fn ($string) => $string instanceof TimeRange ? $string : TimeRange::fromDefinition($string)),
             $data,
         );
     }
diff --git a/src/TimeRange.php b/src/TimeRange.php
index 2730aeb..32e72c4 100644
--- a/src/TimeRange.php
+++ b/src/TimeRange.php
@@ -63,7 +63,7 @@ readonly class TimeRange implements TimeDataContainer
         return is_array($value) ? static::fromArray($value) : static::fromString($value);
     }
 
-    public static function fromList(array $ranges): self
+    public static function fromList(array $ranges, $data = null): self
     {
         if (count($ranges) === 0) {
             throw InvalidTimeRangeList::create();
@@ -88,7 +88,7 @@ readonly class TimeRange implements TimeDataContainer
             }
         }
 
-        return new self($start, $end);
+        return new self($start, $end, $data);
     }
 
     public static function fromMidnight(Time $end, $data = null): self


Test patch:
diff --git a/tests/OpeningHoursFillTest.php b/tests/OpeningHoursFillTest.php
index 2d994e1..9158c5e 100644
--- a/tests/OpeningHoursFillTest.php
+++ b/tests/OpeningHoursFillTest.php
@@ -359,6 +359,31 @@ class OpeningHoursFillTest extends TestCase
         ], $dump);
     }
 
+    #[Test]
+    public function it_should_merge_ranges_and_keep_data()
+    {
+        $hours = OpeningHours::createAndMergeOverlappingRanges([
+            'monday' => [
+                ['hours' => '08:00-12:00', 'data' => ['testdata' => true]],
+                ['hours' => '12:00-24:00', 'data' => ['testdata' => true]],
+                ['hours' => '05:00-08:00', 'data' => ['testdata' => false]],
+            ],
+        ], null, null, false);
+        $dump = [];
+        $data = null;
+        /** @var TimeRange $range */
+        foreach ($hours->forDay('monday') as $range) {
+            $data = $range->data;
+            $dump[] = $range->format();
+        }
+
+        $this->assertSame([
+            '05:00-08:00',
+            '08:00-24:00',
+        ], $dump);
+        $this->assertSame(['testdata' => true], $data);
+    }
+
     #[Test]
     public function it_should_reorder_ranges()
     {