Skip to content

Commit 3f448d3

Browse files
authored
Merge pull request #697 from durable-workflow/fix/898-datetime-visibility
Preserve UTC instants and microseconds in visibility queries
2 parents 44fd778 + 94f86d0 commit 3f448d3

2 files changed

Lines changed: 57 additions & 3 deletions

File tree

‎app/Support/WorkflowVisibilityQuery.php‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Illuminate\Database\Eloquent\Builder;
77
use Illuminate\Support\Carbon;
88
use Illuminate\Validation\ValidationException;
9+
use Workflow\V2\Support\UtcScheduleTimestamp;
910

1011
class WorkflowVisibilityQuery
1112
{
@@ -744,15 +745,15 @@ private function coerceBool(mixed $literal, string $field): bool
744745
throw $this->invalidValue($field, 'a boolean literal');
745746
}
746747

747-
private function coerceDatetime(mixed $literal, string $field): Carbon
748+
private function coerceDatetime(mixed $literal, string $field): string
748749
{
749750
if ($literal instanceof \DateTimeInterface) {
750-
return Carbon::instance($literal);
751+
return UtcScheduleTimestamp::databaseValue($literal);
751752
}
752753

753754
if (is_string($literal) || is_int($literal) || is_float($literal)) {
754755
try {
755-
return Carbon::parse($literal);
756+
return UtcScheduleTimestamp::databaseValue(Carbon::parse($literal));
756757
} catch (\Throwable) {
757758
throw $this->invalidValue($field, 'a datetime literal');
758759
}

‎tests/Feature/WorkflowSearchAttributeVisibilityQueryTest.php‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Illuminate\Foundation\Testing\RefreshDatabase;
77
use Illuminate\Support\Facades\DB;
88
use Illuminate\Support\Facades\Queue;
9+
use PHPUnit\Framework\Attributes\DataProvider;
910
use Tests\Feature\Concerns\ServerTestHelpers;
1011
use Tests\Fixtures\AwaitApprovalWorkflow;
1112
use Tests\TestCase;
@@ -209,6 +210,58 @@ public function test_workflow_list_query_rejects_undefined_search_attribute(): v
209210
);
210211
}
211212

213+
#[DataProvider('datetimeInstants')]
214+
public function test_datetime_visibility_preserves_offset_instants_and_microseconds(string $literal): void
215+
{
216+
foreach (['default', 'other'] as $namespace) {
217+
SearchAttributeDefinition::query()->create([
218+
'namespace' => $namespace,
219+
'name' => 'occurred_at',
220+
'type' => 'datetime',
221+
]);
222+
}
223+
224+
$this->startWorkflow('default', 'wf-date-match', [
225+
'occurred_at' => '2026-10-08T10:34:56.123456Z',
226+
]);
227+
$this->startWorkflow('default', 'wf-date-neighbor', [
228+
'occurred_at' => '2026-10-08T10:34:56.123457Z',
229+
]);
230+
$this->startWorkflow('other', 'wf-date-other', [
231+
'occurred_at' => '2026-10-08T10:34:56.123456Z',
232+
]);
233+
234+
$this->assertSame(['wf-date-match'], $this->workflowIdsForQuery('occurred_at = "'.$literal.'"'));
235+
$this->assertSame(['wf-date-neighbor'], $this->workflowIdsForQuery('occurred_at != "'.$literal.'"'));
236+
$this->assertSame(['wf-date-match'], $this->workflowIdsForQuery('occurred_at IN ("'.$literal.'")'));
237+
$this->assertSame(['wf-date-neighbor'], $this->workflowIdsForQuery('NOT occurred_at IN ("'.$literal.'")'));
238+
$this->assertSame(['wf-date-match'], $this->workflowIdsForQuery(
239+
'occurred_at >= "'.$literal.'" AND occurred_at < "2026-10-08T10:34:56.123457Z"',
240+
));
241+
$this->assertSame(['wf-date-other'], $this->workflowIdsForQuery('occurred_at = "'.$literal.'"', 'other'));
242+
243+
DB::table('workflow_run_summaries')->where('workflow_instance_id', 'wf-date-match')->update([
244+
'started_at' => '2026-10-08 10:34:56.123456',
245+
]);
246+
DB::table('workflow_run_summaries')->where('workflow_instance_id', 'wf-date-neighbor')->update([
247+
'started_at' => '2026-10-08 10:34:56.123457',
248+
]);
249+
$this->assertSame(['wf-date-match'], $this->workflowIdsForQuery('StartTime = "'.$literal.'"'));
250+
$this->assertSame(['wf-date-match'], $this->workflowIdsForQuery('StartTime IN ("'.$literal.'")'));
251+
}
252+
253+
/**
254+
* @return iterable<string, array{string}>
255+
*/
256+
public static function datetimeInstants(): iterable
257+
{
258+
yield 'positive offset' => ['2026-10-08T12:34:56.123456+02:00'];
259+
yield 'negative offset' => ['2026-10-08T06:34:56.123456-04:00'];
260+
yield 'next local day' => ['2026-10-09T00:34:56.123456+14:00'];
261+
yield 'previous local day' => ['2026-10-07T22:34:56.123456-12:00'];
262+
yield 'utc' => ['2026-10-08T10:34:56.123456Z'];
263+
}
264+
212265
/**
213266
* @param array<string, scalar|list<string>|null> $searchAttributes
214267
*/

0 commit comments

Comments
 (0)