Skip to content

Commit 435fc57

Browse files
authored
Added TimeZoneType (#1924)
* Added TimeZoneType * Updated DSL definitions
1 parent 2b79301 commit 435fc57

File tree

16 files changed

+476
-7
lines changed

16 files changed

+476
-7
lines changed

src/core/etl/src/Flow/ETL/Function/Cast.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
namespace Flow\ETL\Function;
66

7-
use function Flow\Types\DSL\{type_array, type_boolean, type_date, type_datetime, type_float, type_instance_of, type_integer, type_json, type_string, type_xml};
7+
use function Flow\Types\DSL\{type_array, type_boolean, type_date, type_datetime, type_float, type_instance_of, type_integer, type_json, type_string, type_time_zone, type_xml};
88
use Flow\ETL\Exception\InvalidArgumentException;
99
use Flow\ETL\Function\ScalarFunction\ScalarResult;
1010
use Flow\ETL\Row;
@@ -57,6 +57,7 @@ public function eval(Row $row) : ?ScalarResult
5757
},
5858
type_date()
5959
),
60+
'timezone' => new ScalarResult(type_time_zone()->cast($value), type_time_zone()),
6061
'int', 'integer' => new ScalarResult(type_integer()->cast($value), type_integer()),
6162
'float', 'double', 'real' => new ScalarResult(type_float()->cast($value), type_float()),
6263
'string' => new ScalarResult(type_string()->cast($value), type_string()),

src/core/etl/src/Flow/ETL/Row/EntryFactory.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ enum_entry,
1414
json_object_entry,
1515
list_entry,
1616
map_entry,
17-
str_entry,
17+
string_entry,
1818
struct_entry,
1919
time_entry,
2020
uuid_entry,
@@ -38,6 +38,7 @@ enum_entry,
3838
OptionalType,
3939
StructureType,
4040
TimeType,
41+
TimeZoneType,
4142
UuidType,
4243
XMLElementType,
4344
XMLType};
@@ -147,7 +148,7 @@ public function createAs(string $entryName, mixed $value, Definition|Type $defin
147148

148149
if (null === $value && $type instanceof OptionalType) {
149150
return match ($type->base()::class) {
150-
StringType::class => str_entry($entryName, null, $metadata),
151+
StringType::class => string_entry($entryName, null, $metadata),
151152
IntegerType::class => int_entry($entryName, null, $metadata),
152153
FloatType::class => float_entry($entryName, null, $metadata),
153154
BooleanType::class => bool_entry($entryName, null, $metadata),
@@ -177,7 +178,7 @@ public function createAs(string $entryName, mixed $value, Definition|Type $defin
177178
}
178179

179180
if ($type instanceof StringType) {
180-
return str_entry($entryName, type_optional($type)->cast($value), $metadata);
181+
return string_entry($entryName, type_optional($type)->cast($value), $metadata);
181182
}
182183

183184
if ($type instanceof IntegerType) {
@@ -208,6 +209,10 @@ public function createAs(string $entryName, mixed $value, Definition|Type $defin
208209
return datetime_entry($entryName, type_optional($type)->cast($value), $metadata);
209210
}
210211

212+
if ($type instanceof TimeZoneType) {
213+
return string_entry($entryName, type_optional(type_string())->cast($value), $metadata);
214+
}
215+
211216
if ($type instanceof EnumType) {
212217
$castValue = type_optional($type)->cast($value);
213218

src/core/etl/tests/Flow/ETL/Tests/Unit/Function/CastTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ public static function cast_provider() : array
4747
'xml_to_string' => [$xml, 'string', '<root><foo baz="buz">bar</foo></root>'],
4848
'datetime' => [new \DateTimeImmutable('2023-01-01 00:00:00 UTC'), 'string', '2023-01-01T00:00:00+00:00'],
4949
'datetime_to_date' => [new \DateTimeImmutable('2023-01-01 00:01:00 UTC'), 'date', new \DateTimeImmutable('2023-01-01T00:00:00+00:00')],
50+
'string_to_timezone' => ['UTC', 'timezone', new \DateTimeZone('UTC')],
51+
'string_to_timezone_america' => ['America/New_York', 'timezone', new \DateTimeZone('America/New_York')],
52+
'datetime_to_timezone' => [new \DateTimeImmutable('2023-01-01 00:00:00', new \DateTimeZone('Europe/London')), 'timezone', new \DateTimeZone('Europe/London')],
5053
'uuid' => [Uuid::fromString('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'), 'string', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'],
5154
'bool_to_string' => [true, 'string', 'true'],
5255
];
@@ -67,13 +70,27 @@ public function test_cast(mixed $from, string $to, mixed $expected) : void
6770
}
6871
}
6972

73+
public function test_casting_integer_to_timezone() : void
74+
{
75+
self::assertNull(
76+
ref('value')->cast('timezone')->eval(row((new EntryFactory())->create('value', 123)))
77+
);
78+
}
79+
7080
public function test_casting_integer_to_xml() : void
7181
{
7282
self::assertNull(
7383
ref('value')->cast('xml')->eval(row((new EntryFactory())->create('value', 1)))
7484
);
7585
}
7686

87+
public function test_casting_invalid_string_to_timezone() : void
88+
{
89+
self::assertNull(
90+
ref('value')->cast('timezone')->eval(row((new EntryFactory())->create('value', 'invalid-timezone')))
91+
);
92+
}
93+
7794
public function test_casting_non_xml_string_to_xml() : void
7895
{
7996
self::assertNull(

src/core/etl/tests/Flow/ETL/Tests/Unit/Row/EntryFactoryTest.php

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ enum_schema,
3131
time_schema,
3232
uuid_schema,
3333
xml_schema};
34-
use function Flow\Types\DSL\{type_datetime, type_float, type_integer, type_list, type_map, type_string, type_structure};
34+
use function Flow\Types\DSL\{type_datetime, type_float, type_integer, type_list, type_map, type_string, type_structure, type_time_zone};
3535
use Flow\ETL\Exception\{InvalidArgumentException, SchemaDefinitionNotFoundException};
3636
use Flow\ETL\Row\Entry\TimeEntry;
3737
use Flow\ETL\Row\EntryFactory;
@@ -413,6 +413,22 @@ public function test_time_from_string_with_definition() : void
413413
);
414414
}
415415

416+
public function test_timezone_creates_string_entry() : void
417+
{
418+
self::assertEquals(
419+
str_entry('e', 'UTC'),
420+
(new EntryFactory())->createAs('e', new \DateTimeZone('UTC'), type_time_zone())
421+
);
422+
}
423+
424+
public function test_timezone_from_string_creates_string_entry() : void
425+
{
426+
self::assertEquals(
427+
str_entry('e', 'America/New_York'),
428+
(new EntryFactory())->createAs('e', 'America/New_York', type_time_zone())
429+
);
430+
}
431+
416432
#[DataProvider('provide_unrecognized_data')]
417433
public function test_unrecognized_data_set_same_as_provided(string $input) : void
418434
{

src/lib/types/src/Flow/Types/DSL/functions.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
ScalarType,
2323
StructureType,
2424
TimeType,
25+
TimeZoneType,
2526
UuidType,
2627
XMLElementType,
2728
XMLType};
@@ -242,6 +243,15 @@ function type_time() : Type
242243
return new TimeType();
243244
}
244245

246+
/**
247+
* @return Type<\DateTimeZone>
248+
*/
249+
#[DocumentationDSL(module: Module::TYPES, type: DSLType::TYPE)]
250+
function type_time_zone() : Type
251+
{
252+
return new TimeZoneType();
253+
}
254+
245255
/**
246256
* @return Type<\DOMDocument>
247257
*/

src/lib/types/src/Flow/Types/Type/AutoCaster.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
type_float,
1212
type_integer,
1313
type_json,
14+
type_time_zone,
1415
type_uuid};
1516
use Flow\Types\Type\Native\String\StringTypeChecker;
1617

@@ -87,6 +88,10 @@ private function castToString(string $value) : mixed
8788
return type_uuid()->cast($value);
8889
}
8990

91+
if ($typeChecker->isTimeZone()) {
92+
return type_time_zone()->cast($value);
93+
}
94+
9095
if ($typeChecker->isDate()) {
9196
return type_date()->cast($value);
9297
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Flow\Types\Type\Logical;
6+
7+
use Flow\Types\Exception\{CastingException, InvalidTypeException};
8+
use Flow\Types\Type;
9+
10+
/**
11+
* @implements Type<\DateTimeZone>
12+
*/
13+
final readonly class TimeZoneType implements Type
14+
{
15+
public function assert(mixed $value) : \DateTimeZone
16+
{
17+
if ($this->isValid($value)) {
18+
return $value;
19+
}
20+
21+
throw InvalidTypeException::value($value, $this);
22+
}
23+
24+
public function cast(mixed $value) : \DateTimeZone
25+
{
26+
if ($this->isValid($value)) {
27+
return $value;
28+
}
29+
30+
if ($value instanceof \DOMElement) {
31+
$value = $value->nodeValue;
32+
}
33+
34+
try {
35+
if (\is_string($value)) {
36+
return new \DateTimeZone($value);
37+
}
38+
39+
if ($value instanceof \DateTimeInterface) {
40+
return $value->getTimezone();
41+
}
42+
} catch (\Throwable) {
43+
throw new CastingException($value, $this);
44+
}
45+
46+
throw new CastingException($value, $this);
47+
}
48+
49+
public function isValid(mixed $value) : bool
50+
{
51+
return $value instanceof \DateTimeZone;
52+
}
53+
54+
public function normalize() : array
55+
{
56+
return [
57+
'type' => 'timezone',
58+
];
59+
}
60+
61+
public function toString() : string
62+
{
63+
return 'timezone';
64+
}
65+
}

src/lib/types/src/Flow/Types/Type/Native/String/StringTypeChecker.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,29 @@ public function isNull() : bool
154154
return \in_array(\mb_strtolower($this->string), ['null', 'nil'], true);
155155
}
156156

157+
public function isTimeZone() : bool
158+
{
159+
if ($this->string === '') {
160+
return false;
161+
}
162+
163+
if (\in_array($this->string, \DateTimeZone::listIdentifiers(), true)) {
164+
return true;
165+
}
166+
167+
if (\preg_match('/^[+-]\d{2}:\d{2}$/', $this->string) === 1) {
168+
try {
169+
new \DateTimeZone($this->string);
170+
171+
return true;
172+
} catch (\Exception) {
173+
return false;
174+
}
175+
}
176+
177+
return false;
178+
}
179+
157180
public function isUuid() : bool
158181
{
159182
if ($this->string === '') {

src/lib/types/src/Flow/Types/Type/TypeDetector.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
namespace Flow\Types\Type;
66

7-
use function Flow\Types\DSL\{type_array, type_boolean, type_date, type_datetime, type_enum, type_float, type_instance_of, type_integer, type_json, type_map, type_null, type_string, type_time, type_uuid, type_xml, type_xml_element, types};
7+
use function Flow\Types\DSL\{type_array, type_boolean, type_date, type_datetime, type_enum, type_float, type_instance_of, type_integer, type_json, type_map, type_null, type_string, type_time, type_time_zone, type_uuid, type_xml, type_xml_element, types};
88
use Flow\Types\Exception\InvalidArgumentException;
99
use Flow\Types\Type;
1010
use Flow\Types\Type\Logical\{ListType, StructureType};
@@ -86,6 +86,10 @@ public function detectType(mixed $value) : Type
8686
return type_time();
8787
}
8888

89+
if (type_time_zone()->isValid($value)) {
90+
return type_time_zone();
91+
}
92+
8993
if (type_date()->isValid($value)) {
9094
return type_date();
9195
}

src/lib/types/src/Flow/Types/Type/TypeFactory.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
type_scalar,
2323
type_string,
2424
type_time,
25+
type_time_zone,
2526
type_uuid,
2627
type_xml,
2728
type_xml_element};
@@ -63,6 +64,7 @@ public static function fromArray(array $data) : Type
6364
'class_string' => ClassStringType::fromArray($data),
6465
'resource' => type_resource(),
6566
'time' => type_time(),
67+
'timezone' => type_time_zone(),
6668
'date' => type_date(),
6769
'datetime' => type_datetime(),
6870
'json' => type_json(),

0 commit comments

Comments
 (0)