Skip to content

Commit e8260d6

Browse files
committed
Improved Timezone Handling
Goes alongside the changes in statamic/cms#11409.
1 parent fdf0c81 commit e8260d6

File tree

4 files changed

+159
-18
lines changed

4 files changed

+159
-18
lines changed

src/Orders/Order.php

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -84,27 +84,41 @@ public function date($date = null)
8484
}
8585

8686
if ($date instanceof \Carbon\CarbonInterface) {
87-
return $date->setTimezone('UTC');
87+
return $date->utc();
8888
}
8989

90-
if (strlen($date) === 10) {
91-
return Carbon::createFromFormat('Y-m-d', $date)
92-
->setTimezone('UTC')
93-
->startOfDay();
94-
}
95-
96-
if (strlen($date) === 15) {
97-
return Carbon::createFromFormat('Y-m-d-Hi', $date)
98-
->setTimezone('UTC')
99-
->startOfMinute();
100-
}
101-
102-
return Carbon::createFromFormat('Y-m-d-His', $date)
103-
->setTimezone('UTC');
90+
return $this->parseDateFromString($date);
10491
})
10592
->args(func_get_args());
10693
}
10794

95+
private function parseDateFromString($date)
96+
{
97+
if (strlen($date) === 10) {
98+
return Carbon::createFromFormat('Y-m-d', $date)->startOfDay();
99+
}
100+
101+
if (strlen($date) === 15) {
102+
return Carbon::createFromFormat('Y-m-d-Hi', $date)->startOfMinute();
103+
}
104+
105+
return Carbon::createFromFormat('Y-m-d-His', $date);
106+
}
107+
108+
public function hasTime()
109+
{
110+
return $this->date && ! $this->date->isStartOfDay();
111+
}
112+
113+
public function hasSeconds()
114+
{
115+
if (! $this->hasTime()) {
116+
return false;
117+
}
118+
119+
return $this->date && $this->date->second !== 0;
120+
}
121+
108122
public function cart($cart = null)
109123
{
110124
return $this
@@ -333,10 +347,26 @@ public function path(): string
333347

334348
public function buildPath(): string
335349
{
336-
return vsprintf('%s/%s%s.%s.yaml', [
350+
$prefix = '';
351+
352+
if ($this->date) {
353+
$format = 'Y-m-d';
354+
355+
if ($this->hasTime()) {
356+
$format .= '-Hi';
357+
}
358+
359+
if ($this->hasSeconds()) {
360+
$format .= 's';
361+
}
362+
363+
$prefix = $this->date->format($format).'.';
364+
}
365+
366+
return vsprintf('%s/%s%s%s.yaml', [
337367
rtrim(Stache::store('orders')->directory(), '/'),
338368
Site::multiEnabled() ? $this->site()->handle().'/' : '',
339-
$this->date()->format('Y-m-d-His'),
369+
$prefix,
340370
$this->orderNumber(),
341371
]);
342372
}

src/Stache/Stores/OrdersStore.php

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace DuncanMcClean\Cargo\Stache\Stores;
44

5+
use Carbon\Carbon;
56
use DuncanMcClean\Cargo\Contracts\Orders\Order as OrderContract;
67
use DuncanMcClean\Cargo\Facades\Order;
78
use Illuminate\Support\Str;
@@ -37,7 +38,7 @@ public function makeItemFromFile($path, $contents): OrderContract
3738
->site($site)
3839
->id(Arr::pull($data, 'id'))
3940
->orderNumber((new GetSlugFromPath)($path))
40-
->date((new GetDateFromPath)($path))
41+
->date($this->getDateFromPath($path))
4142
->cart(Arr::pull($data, 'cart'))
4243
->status(Arr::pull($data, 'status'))
4344
->customer(Arr::pull($data, 'customer'))
@@ -51,6 +52,27 @@ public function makeItemFromFile($path, $contents): OrderContract
5152
->data($data);
5253
}
5354

55+
private function getDateFromPath($path)
56+
{
57+
if (! $date = (new GetDateFromPath)($path)) {
58+
return null;
59+
}
60+
61+
$format = match (strlen($date)) {
62+
10 => 'Y-m-d',
63+
15 => 'Y-m-d-Hi',
64+
17 => 'Y-m-d-His',
65+
};
66+
67+
$carbon = Carbon::createFromFormat($format, $date, config('app.timezone'));
68+
69+
if (strlen($date) === 10) {
70+
$carbon->startOfDay();
71+
}
72+
73+
return $carbon->utc();
74+
}
75+
5476
protected function extractSiteFromPath($path)
5577
{
5678
$site = Site::default()->handle();

tests/Orders/OrderTest.php

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use DuncanMcClean\Cargo\Shipping\ShippingOption;
1919
use Illuminate\Support\Carbon;
2020
use Illuminate\Support\Facades\Event;
21+
use PHPUnit\Framework\Attributes\DataProvider;
2122
use PHPUnit\Framework\Attributes\Test;
2223
use Statamic\Facades\Collection;
2324
use Statamic\Facades\Entry;
@@ -30,6 +31,71 @@ class OrderTest extends TestCase
3031
{
3132
use PreventsSavingStacheItemsToDisk;
3233

34+
#[Test]
35+
#[DataProvider('dateProvider')]
36+
public function can_get_date(
37+
string|Carbon $date,
38+
string $expectedDate,
39+
string $expectedPath
40+
) {
41+
Carbon::setTestNow(Carbon::parse('2015-09-24 13:45:23'));
42+
43+
$order = Order::make()
44+
->orderNumber('1234')
45+
->date($date);
46+
47+
$this->assertEquals($expectedDate, $order->date()->format('Y-m-d H:i:s'));
48+
$this->assertEquals($expectedPath, pathinfo($order->path(), PATHINFO_FILENAME));
49+
}
50+
51+
public static function dateProvider(): array
52+
{
53+
return [
54+
'date from string' => ['2025-04-05', '2025-04-05 00:00:00', '2025-04-05.1234'],
55+
'date from string, with time' => ['2025-04-05-1241', '2025-04-05 12:41:00', '2025-04-05-1241.1234'],
56+
'date from string, with seconds' => ['2025-04-05-124124', '2025-04-05 12:41:24', '2025-04-05-124124.1234'],
57+
58+
'date from carbon instance' => [Carbon::parse('2025-04-05'), '2025-04-05 00:00:00', '2025-04-05.1234'],
59+
'date from carbon instance, with time' => [Carbon::parse('2025-04-05 12:41:00'), '2025-04-05 12:41:00', '2025-04-05-1241.1234'],
60+
'date from carbon instance, with seconds' => [Carbon::parse('2025-04-05 12:41:24'), '2025-04-05 12:41:24', '2025-04-05-124124.1234'],
61+
62+
'date from carbon instance in another timezone' => [Carbon::parse('2025-04-05 22:00', 'America/New_York'), '2025-04-06 02:00:00', '2025-04-06-0200.1234'],
63+
];
64+
}
65+
66+
#[Test]
67+
#[DataProvider('datesAsStringProvider')]
68+
public function can_convert_date_strings_to_utc(
69+
string $appTimezone,
70+
string|Carbon $date,
71+
string $expectedDate
72+
) {
73+
config(['app.timezone' => $appTimezone]);
74+
75+
Carbon::setTestNow(Carbon::parse('2015-09-24 13:45:23'));
76+
77+
$order = Order::make()->date($date);
78+
79+
$this->assertEquals($expectedDate, $order->date()->toIso8601String());
80+
}
81+
82+
public static function datesAsStringProvider()
83+
{
84+
// The date is treated as UTC regardless of the timezone so no conversion should be done.
85+
return [
86+
'utc' => [
87+
'UTC',
88+
'2023-02-20-033513',
89+
'2023-02-20T03:35:13+00:00',
90+
],
91+
'not utc' => [
92+
'America/New_York',
93+
'2023-02-20-033513',
94+
'2023-02-20T03:35:13+00:00',
95+
],
96+
];
97+
}
98+
3399
#[Test]
34100
public function can_get_and_set_guest_customer()
35101
{

tests/Stache/Stores/OrdersStoreTest.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,29 @@ public function it_makes_order_instances_from_files()
4040
$this->assertEquals('baz', $item->get('bar'));
4141
}
4242

43+
#[Test]
44+
#[DataProvider('timezoneProvider')]
45+
public function it_makes_order_instances_from_files_with_different_timezone($filename, $expectedDate)
46+
{
47+
config(['app.timezone' => 'America/New_York']);
48+
49+
$item = $this->store->makeItemFromFile(
50+
$this->directory.'/'.$filename,
51+
"id: foo\norder_number: 12345\nbar: baz",
52+
);
53+
54+
$this->assertEquals($expectedDate, $item->date()->format('Y-m-d H:i:s'));
55+
}
56+
57+
public static function timezoneProvider()
58+
{
59+
return [
60+
'midnight' => ['2017-01-02.12345.yaml', '2017-01-02 05:00:00'],
61+
'10pm' => ['2017-01-02-2200.12345.yaml', '2017-01-03 03:00:00'],
62+
'10pm with seconds' => ['2017-01-02-220013.12345.yaml', '2017-01-03 03:00:13'],
63+
];
64+
}
65+
4366
#[Test]
4467
public function it_saves_to_disk()
4568
{

0 commit comments

Comments
 (0)