Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/core/etl/src/Flow/ETL/Function/Cast.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Flow\ETL\Function;

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};
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};
use Flow\ETL\Exception\InvalidArgumentException;
use Flow\ETL\Function\ScalarFunction\ScalarResult;
use Flow\ETL\Row;
Expand Down Expand Up @@ -57,6 +57,7 @@ public function eval(Row $row) : ?ScalarResult
},
type_date()
),
'timezone' => new ScalarResult(type_time_zone()->cast($value), type_time_zone()),
'int', 'integer' => new ScalarResult(type_integer()->cast($value), type_integer()),
'float', 'double', 'real' => new ScalarResult(type_float()->cast($value), type_float()),
'string' => new ScalarResult(type_string()->cast($value), type_string()),
Expand Down
11 changes: 8 additions & 3 deletions src/core/etl/src/Flow/ETL/Row/EntryFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ enum_entry,
json_object_entry,
list_entry,
map_entry,
str_entry,
string_entry,
struct_entry,
time_entry,
uuid_entry,
Expand All @@ -38,6 +38,7 @@ enum_entry,
OptionalType,
StructureType,
TimeType,
TimeZoneType,
UuidType,
XMLElementType,
XMLType};
Expand Down Expand Up @@ -147,7 +148,7 @@ public function createAs(string $entryName, mixed $value, Definition|Type $defin

if (null === $value && $type instanceof OptionalType) {
return match ($type->base()::class) {
StringType::class => str_entry($entryName, null, $metadata),
StringType::class => string_entry($entryName, null, $metadata),
IntegerType::class => int_entry($entryName, null, $metadata),
FloatType::class => float_entry($entryName, null, $metadata),
BooleanType::class => bool_entry($entryName, null, $metadata),
Expand Down Expand Up @@ -177,7 +178,7 @@ public function createAs(string $entryName, mixed $value, Definition|Type $defin
}

if ($type instanceof StringType) {
return str_entry($entryName, type_optional($type)->cast($value), $metadata);
return string_entry($entryName, type_optional($type)->cast($value), $metadata);
}

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

if ($type instanceof TimeZoneType) {
return string_entry($entryName, type_optional(type_string())->cast($value), $metadata);
}

if ($type instanceof EnumType) {
$castValue = type_optional($type)->cast($value);

Expand Down
17 changes: 17 additions & 0 deletions src/core/etl/tests/Flow/ETL/Tests/Unit/Function/CastTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ public static function cast_provider() : array
'xml_to_string' => [$xml, 'string', '<root><foo baz="buz">bar</foo></root>'],
'datetime' => [new \DateTimeImmutable('2023-01-01 00:00:00 UTC'), 'string', '2023-01-01T00:00:00+00:00'],
'datetime_to_date' => [new \DateTimeImmutable('2023-01-01 00:01:00 UTC'), 'date', new \DateTimeImmutable('2023-01-01T00:00:00+00:00')],
'string_to_timezone' => ['UTC', 'timezone', new \DateTimeZone('UTC')],
'string_to_timezone_america' => ['America/New_York', 'timezone', new \DateTimeZone('America/New_York')],
'datetime_to_timezone' => [new \DateTimeImmutable('2023-01-01 00:00:00', new \DateTimeZone('Europe/London')), 'timezone', new \DateTimeZone('Europe/London')],
'uuid' => [Uuid::fromString('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'), 'string', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'],
'bool_to_string' => [true, 'string', 'true'],
];
Expand All @@ -67,13 +70,27 @@ public function test_cast(mixed $from, string $to, mixed $expected) : void
}
}

public function test_casting_integer_to_timezone() : void
{
self::assertNull(
ref('value')->cast('timezone')->eval(row((new EntryFactory())->create('value', 123)))
);
}

public function test_casting_integer_to_xml() : void
{
self::assertNull(
ref('value')->cast('xml')->eval(row((new EntryFactory())->create('value', 1)))
);
}

public function test_casting_invalid_string_to_timezone() : void
{
self::assertNull(
ref('value')->cast('timezone')->eval(row((new EntryFactory())->create('value', 'invalid-timezone')))
);
}

public function test_casting_non_xml_string_to_xml() : void
{
self::assertNull(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ enum_schema,
time_schema,
uuid_schema,
xml_schema};
use function Flow\Types\DSL\{type_datetime, type_float, type_integer, type_list, type_map, type_string, type_structure};
use function Flow\Types\DSL\{type_datetime, type_float, type_integer, type_list, type_map, type_string, type_structure, type_time_zone};
use Flow\ETL\Exception\{InvalidArgumentException, SchemaDefinitionNotFoundException};
use Flow\ETL\Row\Entry\TimeEntry;
use Flow\ETL\Row\EntryFactory;
Expand Down Expand Up @@ -413,6 +413,22 @@ public function test_time_from_string_with_definition() : void
);
}

public function test_timezone_creates_string_entry() : void
{
self::assertEquals(
str_entry('e', 'UTC'),
(new EntryFactory())->createAs('e', new \DateTimeZone('UTC'), type_time_zone())
);
}

public function test_timezone_from_string_creates_string_entry() : void
{
self::assertEquals(
str_entry('e', 'America/New_York'),
(new EntryFactory())->createAs('e', 'America/New_York', type_time_zone())
);
}

#[DataProvider('provide_unrecognized_data')]
public function test_unrecognized_data_set_same_as_provided(string $input) : void
{
Expand Down
10 changes: 10 additions & 0 deletions src/lib/types/src/Flow/Types/DSL/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
ScalarType,
StructureType,
TimeType,
TimeZoneType,
UuidType,
XMLElementType,
XMLType};
Expand Down Expand Up @@ -242,6 +243,15 @@ function type_time() : Type
return new TimeType();
}

/**
* @return Type<\DateTimeZone>
*/
#[DocumentationDSL(module: Module::TYPES, type: DSLType::TYPE)]
function type_time_zone() : Type
{
return new TimeZoneType();
}

/**
* @return Type<\DOMDocument>
*/
Expand Down
5 changes: 5 additions & 0 deletions src/lib/types/src/Flow/Types/Type/AutoCaster.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
type_float,
type_integer,
type_json,
type_time_zone,
type_uuid};
use Flow\Types\Type\Native\String\StringTypeChecker;

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

if ($typeChecker->isTimeZone()) {
return type_time_zone()->cast($value);
}

if ($typeChecker->isDate()) {
return type_date()->cast($value);
}
Expand Down
65 changes: 65 additions & 0 deletions src/lib/types/src/Flow/Types/Type/Logical/TimeZoneType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

declare(strict_types=1);

namespace Flow\Types\Type\Logical;

use Flow\Types\Exception\{CastingException, InvalidTypeException};
use Flow\Types\Type;

/**
* @implements Type<\DateTimeZone>
*/
final readonly class TimeZoneType implements Type
{
public function assert(mixed $value) : \DateTimeZone
{
if ($this->isValid($value)) {
return $value;
}

throw InvalidTypeException::value($value, $this);
}

public function cast(mixed $value) : \DateTimeZone
{
if ($this->isValid($value)) {
return $value;
}

if ($value instanceof \DOMElement) {
$value = $value->nodeValue;
}

try {
if (\is_string($value)) {
return new \DateTimeZone($value);
}

if ($value instanceof \DateTimeInterface) {
return $value->getTimezone();
}
} catch (\Throwable) {
throw new CastingException($value, $this);
}

throw new CastingException($value, $this);
}

public function isValid(mixed $value) : bool
{
return $value instanceof \DateTimeZone;
}

public function normalize() : array
{
return [
'type' => 'timezone',
];
}

public function toString() : string
{
return 'timezone';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,29 @@ public function isNull() : bool
return \in_array(\mb_strtolower($this->string), ['null', 'nil'], true);
}

public function isTimeZone() : bool
{
if ($this->string === '') {
return false;
}

if (\in_array($this->string, \DateTimeZone::listIdentifiers(), true)) {
return true;
}

if (\preg_match('/^[+-]\d{2}:\d{2}$/', $this->string) === 1) {
try {
new \DateTimeZone($this->string);

return true;
} catch (\Exception) {
return false;
}
}

return false;
}

public function isUuid() : bool
{
if ($this->string === '') {
Expand Down
6 changes: 5 additions & 1 deletion src/lib/types/src/Flow/Types/Type/TypeDetector.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Flow\Types\Type;

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};
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};
use Flow\Types\Exception\InvalidArgumentException;
use Flow\Types\Type;
use Flow\Types\Type\Logical\{ListType, StructureType};
Expand Down Expand Up @@ -86,6 +86,10 @@ public function detectType(mixed $value) : Type
return type_time();
}

if (type_time_zone()->isValid($value)) {
return type_time_zone();
}

if (type_date()->isValid($value)) {
return type_date();
}
Expand Down
2 changes: 2 additions & 0 deletions src/lib/types/src/Flow/Types/Type/TypeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
type_scalar,
type_string,
type_time,
type_time_zone,
type_uuid,
type_xml,
type_xml_element};
Expand Down Expand Up @@ -63,6 +64,7 @@ public static function fromArray(array $data) : Type
'class_string' => ClassStringType::fromArray($data),
'resource' => type_resource(),
'time' => type_time(),
'timezone' => type_time_zone(),
'date' => type_date(),
'datetime' => type_datetime(),
'json' => type_json(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,36 @@ public function test_auto_casting_array_of_ints_and_floats_into_array_of_floats(
(new AutoCaster())->cast([1, 2, 3.0])
);
}

public function test_auto_casting_non_timezone_string_returns_unchanged() : void
{
self::assertSame(
'not a timezone',
(new AutoCaster())->cast('not a timezone')
);
}

public function test_auto_casting_timezone_offset_to_timezone() : void
{
self::assertEquals(
new \DateTimeZone('+05:30'),
(new AutoCaster())->cast('+05:30')
);
}

public function test_auto_casting_timezone_string_america_to_timezone() : void
{
self::assertEquals(
new \DateTimeZone('America/New_York'),
(new AutoCaster())->cast('America/New_York')
);
}

public function test_auto_casting_timezone_string_to_timezone() : void
{
self::assertEquals(
new \DateTimeZone('UTC'),
(new AutoCaster())->cast('UTC')
);
}
}
Loading