From 4ab3708f7574a7e65cdfac457bee5bd83e86c92e Mon Sep 17 00:00:00 2001 From: ayeshLK Date: Wed, 11 Sep 2024 14:44:57 +0530 Subject: [PATCH 01/14] [Automated] Update native jar versions in toml files --- ballerina/Ballerina.toml | 6 +++--- ballerina/Dependencies.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index f6fe874..4c63785 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -1,7 +1,7 @@ [package] org = "ballerina" name = "time" -version = "2.4.0" +version = "2.4.1" authors = ["Ballerina"] keywords = ["time", "utc", "epoch", "civil"] repository = "https://github.com/ballerina-platform/module-ballerina-time" @@ -15,5 +15,5 @@ graalvmCompatible = true [[platform.java17.dependency]] groupId = "io.ballerina.stdlib" artifactId = "time-native" -version = "2.4.0" -path = "../native/build/libs/time-native-2.4.0.jar" +version = "2.4.1" +path = "../native/build/libs/time-native-2.4.1-SNAPSHOT.jar" diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 7900e51..488af93 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -40,7 +40,7 @@ modules = [ [[package]] org = "ballerina" name = "time" -version = "2.4.0" +version = "2.4.1" dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "test"} From e9b416b1a03a8091837bfdbbecd1fb33595ff67b Mon Sep 17 00:00:00 2001 From: ayeshLK Date: Thu, 12 Sep 2024 10:14:53 +0530 Subject: [PATCH 02/14] Add support for introduce civil-to-string by using time-abbreviation --- ballerina/time_apis.bal | 28 +++++++++++-------- .../stdlib/time/nativeimpl/ExternMethods.java | 6 ++-- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/ballerina/time_apis.bal b/ballerina/time_apis.bal index 35cd33b..e0ca535 100644 --- a/ballerina/time_apis.bal +++ b/ballerina/time_apis.bal @@ -170,23 +170,28 @@ public isolated function civilFromString(string dateTimeString) returns Civil|Er # + civil - `time:Civil` that needs to be converted # + return - The corresponding string value or an error if the specified `time:Civil` contains invalid parameters (e.g., `month` > 12) public isolated function civilToString(Civil civil) returns string|Error { - ZoneOffset utcOffset; - if civil?.utcOffset is () { - if civil?.timeAbbrev !is () && string:toLowerAscii(civil?.timeAbbrev) == "z" { - utcOffset = {hours: 0, minutes: 0, seconds: 0}; - } else { - return error FormatError("civil.utcOffset must not be null"); - } - } else { + ZoneOffset utcOffset = { + hours: 0, + minutes: 0, + seconds: 0 + }; + HeaderZoneHandling zoneHandling = PREFER_ZONE_OFFSET; + if civil?.utcOffset is () && civil?.timeAbbrev is () { + return error FormatError("civil.utcOffset and civil.timeAbbrev both must not be null"); + } else if civil?.utcOffset is ZoneOffset { utcOffset = civil?.utcOffset; + } else if civil?.timeAbbrev is string { + zoneHandling = PREFER_TIME_ABBREV; } + + string timeAbbrev = civil?.timeAbbrev ?: ""; decimal? civilTimeSecField = civil?.second; decimal? utcOffsetSecField = utcOffset?.seconds; decimal civilTimeSeconds = (civilTimeSecField is Seconds) ? civilTimeSecField : 0.0; decimal utcOffsetSeconds = (utcOffsetSecField is decimal) ? utcOffsetSecField : 0.0; return externCivilToString(civil.year, civil.month, civil.day, civil.hour, civil.minute, civilTimeSeconds, utcOffset. - hours, utcOffset.minutes, utcOffsetSeconds); + hours, utcOffset.minutes, utcOffsetSeconds, timeAbbrev, zoneHandling); } # Converts a given UTC to an email formatted string (e.g `Mon, 3 Dec 2007 10:15:30 GMT`). @@ -298,8 +303,9 @@ isolated function externCivilFromString(string dateTimeString) returns Civil|Err 'class: "io.ballerina.stdlib.time.nativeimpl.ExternMethods" } external; -isolated function externCivilToString(int year, int month, int day, int hour, int minute, decimal second, int zoneHour, - int zoneMinute, decimal zoneSecond) returns string|Error = @java:Method { +isolated function externCivilToString(int year, int month, int day, int hour, int minute, decimal second, + int zoneHour, int zoneMinute, decimal zoneSecond, + string timeAbber, HeaderZoneHandling zoneHandling) returns string|Error = @java:Method { name: "externCivilToString", 'class: "io.ballerina.stdlib.time.nativeimpl.ExternMethods" } external; diff --git a/native/src/main/java/io/ballerina/stdlib/time/nativeimpl/ExternMethods.java b/native/src/main/java/io/ballerina/stdlib/time/nativeimpl/ExternMethods.java index 1c0089b..e277910 100644 --- a/native/src/main/java/io/ballerina/stdlib/time/nativeimpl/ExternMethods.java +++ b/native/src/main/java/io/ballerina/stdlib/time/nativeimpl/ExternMethods.java @@ -157,12 +157,12 @@ public static Object externCivilFromEmailString(BString dateTimeString) { } public static Object externCivilToString(long year, long month, long day, long hour, long minute, BDecimal second, - long zoneHour, long zoneMinute, BDecimal zoneSecond) { + long zoneHour, long zoneMinute, BDecimal zoneSecond, BString zoneAbbr, + BString zoneHandling) { try { ZonedDateTime dateTime = TimeValueHandler.createZoneDateTimeFromCivilValues(year, month, day, hour, - minute, second, zoneHour, zoneMinute, zoneSecond, null, - Constants.HeaderZoneHandling.PREFER_ZONE_OFFSET.toString()); + minute, second, zoneHour, zoneMinute, zoneSecond, zoneAbbr, zoneHandling.getValue()); return StringUtils.fromString(dateTime.toInstant().toString()); } catch (DateTimeException e) { return Utils.createError(Errors.FormatError, e.getMessage()); From 2b380cf5b01a68ca1ec19a46971e02b94658a918 Mon Sep 17 00:00:00 2001 From: ayeshLK Date: Thu, 12 Sep 2024 10:20:26 +0530 Subject: [PATCH 03/14] Disable invalid negative test cases --- ballerina/tests/time_test.bal | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ballerina/tests/time_test.bal b/ballerina/tests/time_test.bal index e27e6e8..40ef52f 100644 --- a/ballerina/tests/time_test.bal +++ b/ballerina/tests/time_test.bal @@ -387,14 +387,16 @@ isolated function testCivilToStringWithTimeOfDay() returns Error? { test:assertEquals(civilStr, expectedStr); } -@test:Config {} +@test:Config { + enable: false +} isolated function testCivilToStringWithoutOffset() { Civil civil = { year: 2021, month: 4, day: 13, hour: 4, - minute: 70, + minute: 33, timeAbbrev: "Asia/Colombo" }; string|Error err = civilToString(civil); @@ -727,7 +729,7 @@ isolated function testUtcFromCivilWithEmptyTimeOffsetNegative() returns Error? { } } -@test:Config {enable: true} +@test:Config {enable: false} isolated function testCivilToStringWithEmptyTimeOffsetNegative() returns Error? { Civil civil = { year: 2021, @@ -776,7 +778,7 @@ isolated function testCivilToStringWithEmptyTimeOffsetAndAbbreviation() returns }; string|error civilString = civilToString(civil); if civilString is error { - test:assertEquals(civilString.message(), "civil.utcOffset must not be null"); + test:assertEquals(civilString.message(), "civil.utcOffset and civil.timeAbbrev both must not be null"); } else { test:assertFail("civilString should be error"); } From d6701e562019db57b6883c439a8d40c0566f25c3 Mon Sep 17 00:00:00 2001 From: ayeshLK Date: Thu, 12 Sep 2024 10:46:18 +0530 Subject: [PATCH 04/14] Restrucutre date-time formatting logic --- .../java/io/ballerina/stdlib/time/nativeimpl/ExternMethods.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native/src/main/java/io/ballerina/stdlib/time/nativeimpl/ExternMethods.java b/native/src/main/java/io/ballerina/stdlib/time/nativeimpl/ExternMethods.java index e277910..8809ffd 100644 --- a/native/src/main/java/io/ballerina/stdlib/time/nativeimpl/ExternMethods.java +++ b/native/src/main/java/io/ballerina/stdlib/time/nativeimpl/ExternMethods.java @@ -163,7 +163,7 @@ public static Object externCivilToString(long year, long month, long day, long h try { ZonedDateTime dateTime = TimeValueHandler.createZoneDateTimeFromCivilValues(year, month, day, hour, minute, second, zoneHour, zoneMinute, zoneSecond, zoneAbbr, zoneHandling.getValue()); - return StringUtils.fromString(dateTime.toInstant().toString()); + return StringUtils.fromString(dateTime.toString()); } catch (DateTimeException e) { return Utils.createError(Errors.FormatError, e.getMessage()); } From 971acfc51515de01de613d78d4fd6b80a9e4bc1d Mon Sep 17 00:00:00 2001 From: ayeshLK Date: Thu, 12 Sep 2024 13:54:04 +0530 Subject: [PATCH 05/14] Refactor the code --- ballerina/time_apis.bal | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/ballerina/time_apis.bal b/ballerina/time_apis.bal index e0ca535..600b691 100644 --- a/ballerina/time_apis.bal +++ b/ballerina/time_apis.bal @@ -170,28 +170,25 @@ public isolated function civilFromString(string dateTimeString) returns Civil|Er # + civil - `time:Civil` that needs to be converted # + return - The corresponding string value or an error if the specified `time:Civil` contains invalid parameters (e.g., `month` > 12) public isolated function civilToString(Civil civil) returns string|Error { - ZoneOffset utcOffset = { - hours: 0, - minutes: 0, - seconds: 0 - }; + ZoneOffset? utcOffset = civil?.utcOffset; + string? timeAbbrev = civil?.timeAbbrev; + HeaderZoneHandling zoneHandling = PREFER_ZONE_OFFSET; - if civil?.utcOffset is () && civil?.timeAbbrev is () { + if utcOffset is () && timeAbbrev is () { return error FormatError("civil.utcOffset and civil.timeAbbrev both must not be null"); } else if civil?.utcOffset is ZoneOffset { utcOffset = civil?.utcOffset; - } else if civil?.timeAbbrev is string { + } else if timeAbbrev is string { zoneHandling = PREFER_TIME_ABBREV; } - string timeAbbrev = civil?.timeAbbrev ?: ""; - decimal? civilTimeSecField = civil?.second; - decimal? utcOffsetSecField = utcOffset?.seconds; - decimal civilTimeSeconds = (civilTimeSecField is Seconds) ? civilTimeSecField : 0.0; - decimal utcOffsetSeconds = (utcOffsetSecField is decimal) ? utcOffsetSecField : 0.0; + int utcOffsetHours = utcOffset?.hours ?: 0; + int utcOffsetMinutes = utcOffset?.minutes ?: 0; + decimal utcOffsetSeconds = utcOffset?.seconds ?: 0.0; + decimal civilTimeSeconds = civil?.second ?: 0.0; - return externCivilToString(civil.year, civil.month, civil.day, civil.hour, civil.minute, civilTimeSeconds, utcOffset. - hours, utcOffset.minutes, utcOffsetSeconds, timeAbbrev, zoneHandling); + return externCivilToString(civil.year, civil.month, civil.day, civil.hour, civil.minute, civilTimeSeconds, + utcOffsetHours, utcOffsetMinutes, utcOffsetSeconds, timeAbbrev ?: "", zoneHandling); } # Converts a given UTC to an email formatted string (e.g `Mon, 3 Dec 2007 10:15:30 GMT`). From b71a2dd5c1fd4b6ad48e63e15399adbf56c05512 Mon Sep 17 00:00:00 2001 From: ayeshLK Date: Thu, 12 Sep 2024 16:58:22 +0530 Subject: [PATCH 06/14] Update package documentation --- ballerina/time_apis.bal | 2 +- docs/spec/spec.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ballerina/time_apis.bal b/ballerina/time_apis.bal index 600b691..fe441db 100644 --- a/ballerina/time_apis.bal +++ b/ballerina/time_apis.bal @@ -162,7 +162,7 @@ public isolated function civilFromString(string dateTimeString) returns Civil|Er return check externCivilFromString(dateTimeString); } -# Obtain a RFC 3339 timestamp (e.g., `2007-12-03T10:15:30.00Z`) from a given `time:Civil`. +# Obtain a RFC 3339 timestamp (e.g., `2021-03-05T00:33:28.839564+05:30`) from a given `time:Civil`. # ```ballerina # time:Civil civil = check time:civilFromString("2007-12-03T10:15:30.00Z"); # string|time:Error civilString = time:civilToString(civil); diff --git a/docs/spec/spec.md b/docs/spec/spec.md index 868f1d0..29e89e9 100644 --- a/docs/spec/spec.md +++ b/docs/spec/spec.md @@ -115,7 +115,7 @@ The time library contains several conversion APIs to convert UTC to civil. The t ```ballerina public isolated function civilFromString(string dateTimeString) returns Civil|Error; ``` -4. Civil to an RFC timestamp string +4. Civil to an RFC 3339 timestamp string (e.g., `2021-03-05T00:33:28.839564+05:30`). ```ballerina public isolated function civilToString(Civil civil) returns string|Error; ``` From bdfe6968eea54ee2657756a52cf3b62c96daeb4a Mon Sep 17 00:00:00 2001 From: ayeshLK Date: Thu, 12 Sep 2024 16:58:34 +0530 Subject: [PATCH 07/14] Restructure test cases --- ballerina/tests/time_test.bal | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/ballerina/tests/time_test.bal b/ballerina/tests/time_test.bal index 40ef52f..cba00f9 100644 --- a/ballerina/tests/time_test.bal +++ b/ballerina/tests/time_test.bal @@ -356,7 +356,7 @@ isolated function testCivilToString() returns Error? { utcOffset: zoneOffset }; string civilStr = check civilToString(civil); - string expectedStr = "2021-03-04T19:03:28.839564Z"; + string expectedStr = "2021-03-05T00:33:28.839564+05:30"; test:assertEquals(civilStr, expectedStr); } @@ -383,14 +383,12 @@ isolated function testCivilToStringWithTimeOfDay() returns Error? { utcOffset: zoneOffset }; string civilStr = check civilToString(civil); - string expectedStr = "2021-03-04T19:03:28.839564Z"; + string expectedStr = "2021-03-05T00:33:28.839564+05:30"; test:assertEquals(civilStr, expectedStr); } -@test:Config { - enable: false -} -isolated function testCivilToStringWithoutOffset() { +@test:Config {} +isolated function testCivilToStringWithTimeAbbreviation() returns Error? { Civil civil = { year: 2021, month: 4, @@ -399,9 +397,9 @@ isolated function testCivilToStringWithoutOffset() { minute: 33, timeAbbrev: "Asia/Colombo" }; - string|Error err = civilToString(civil); - test:assertTrue(err is Error); - test:assertEquals((err).message(), "civil.utcOffset must not be null"); + string civilStr = check civilToString(civil); + string expectedStr = "2021-04-13T04:33+05:30[Asia/Colombo]"; + test:assertEquals(civilStr, expectedStr); } @test:Config {} From 113156896b6c85febb243ccf24ef54834e30c41d Mon Sep 17 00:00:00 2001 From: ayeshLK Date: Thu, 12 Sep 2024 17:00:58 +0530 Subject: [PATCH 08/14] Update package version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 5227da2..e6e7ed9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ org.gradle.caching=true group=io.ballerina.stdlib -version=2.4.1-SNAPSHOT +version=2.5.0-SNAPSHOT ballerinaLangVersion=2201.8.0 puppycrawlCheckstyleVersion=10.12.0 ballerinaGradlePluginVersion=2.0.1 From acbbad8e94714f4cbd02b742594a8a1086ac42bd Mon Sep 17 00:00:00 2001 From: ayeshLK Date: Thu, 12 Sep 2024 17:01:17 +0530 Subject: [PATCH 09/14] [Automated] Update native jar versions in toml files --- ballerina/Ballerina.toml | 6 +++--- ballerina/Dependencies.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index 4c63785..5e2a77d 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -1,7 +1,7 @@ [package] org = "ballerina" name = "time" -version = "2.4.1" +version = "2.5.0" authors = ["Ballerina"] keywords = ["time", "utc", "epoch", "civil"] repository = "https://github.com/ballerina-platform/module-ballerina-time" @@ -15,5 +15,5 @@ graalvmCompatible = true [[platform.java17.dependency]] groupId = "io.ballerina.stdlib" artifactId = "time-native" -version = "2.4.1" -path = "../native/build/libs/time-native-2.4.1-SNAPSHOT.jar" +version = "2.5.0" +path = "../native/build/libs/time-native-2.5.0-SNAPSHOT.jar" diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 488af93..bed40f3 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -40,7 +40,7 @@ modules = [ [[package]] org = "ballerina" name = "time" -version = "2.4.1" +version = "2.5.0" dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "test"} From 82b8854570a69a4aa5b305008b82fa2502eda254 Mon Sep 17 00:00:00 2001 From: ayeshLK Date: Thu, 12 Sep 2024 17:06:56 +0530 Subject: [PATCH 10/14] Update changelog --- changelog.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/changelog.md b/changelog.md index a3d423f..562981c 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [unreleased] + +### Fixed +- [When converting a `time:Civil` with time-zone information to a string using `time:civilToString` API error is thrown](https://github.com/ballerina-platform/ballerina-library/issues/6986) + +## [2.3.0] - 2023-06-30 ### changed - [Mark Standard Libraries as GraalVM Compatible](https://github.com/ballerina-platform/ballerina-standard-library/issues/4568) - [Add support for inferring utc offset for zulu time(Z) in utcFromCivil and civilToString APIs](https://github.com/ballerina-platform/module-ballerina-time/pull/459) From d938915f0490f823f0a4f7c2e532ee4b03d5f320 Mon Sep 17 00:00:00 2001 From: ayeshLK Date: Thu, 12 Sep 2024 17:09:02 +0530 Subject: [PATCH 11/14] Refactor the code --- ballerina/time_apis.bal | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ballerina/time_apis.bal b/ballerina/time_apis.bal index fe441db..ab8552f 100644 --- a/ballerina/time_apis.bal +++ b/ballerina/time_apis.bal @@ -176,8 +176,8 @@ public isolated function civilToString(Civil civil) returns string|Error { HeaderZoneHandling zoneHandling = PREFER_ZONE_OFFSET; if utcOffset is () && timeAbbrev is () { return error FormatError("civil.utcOffset and civil.timeAbbrev both must not be null"); - } else if civil?.utcOffset is ZoneOffset { - utcOffset = civil?.utcOffset; + } else if utcOffset is ZoneOffset { + utcOffset = utcOffset; } else if timeAbbrev is string { zoneHandling = PREFER_TIME_ABBREV; } From 8d36a27dac4e75746fb5754c5f02f7ef4efa5a40 Mon Sep 17 00:00:00 2001 From: ayeshLK Date: Thu, 12 Sep 2024 17:26:28 +0530 Subject: [PATCH 12/14] Remove the unused test case --- ballerina/tests/time_test.bal | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/ballerina/tests/time_test.bal b/ballerina/tests/time_test.bal index cba00f9..2ef01ef 100644 --- a/ballerina/tests/time_test.bal +++ b/ballerina/tests/time_test.bal @@ -727,25 +727,6 @@ isolated function testUtcFromCivilWithEmptyTimeOffsetNegative() returns Error? { } } -@test:Config {enable: false} -isolated function testCivilToStringWithEmptyTimeOffsetNegative() returns Error? { - Civil civil = { - year: 2021, - month: 4, - day: 12, - hour: 23, - minute: 20, - second: 50.52, - timeAbbrev: "Asia/Colombo" - }; - string|error civilString = civilToString(civil); - if civilString is error { - test:assertEquals(civilString.message(), "civil.utcOffset must not be null"); - } else { - test:assertFail("civilString should be error"); - } -} - isolated function testUtcFromCivilWithEmptyTimeOffsetAndAbbreviation() returns Error? { Utc expectedUtc = check utcFromString("2021-04-12T23:20:50.520Z"); Civil civil = { From a02ed7764168231b5db538972d59cf35fb4f040d Mon Sep 17 00:00:00 2001 From: ayeshLK Date: Thu, 12 Sep 2024 17:33:34 +0530 Subject: [PATCH 13/14] Restructure the code --- ballerina/time_apis.bal | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ballerina/time_apis.bal b/ballerina/time_apis.bal index ab8552f..b6632f7 100644 --- a/ballerina/time_apis.bal +++ b/ballerina/time_apis.bal @@ -176,9 +176,7 @@ public isolated function civilToString(Civil civil) returns string|Error { HeaderZoneHandling zoneHandling = PREFER_ZONE_OFFSET; if utcOffset is () && timeAbbrev is () { return error FormatError("civil.utcOffset and civil.timeAbbrev both must not be null"); - } else if utcOffset is ZoneOffset { - utcOffset = utcOffset; - } else if timeAbbrev is string { + } else if utcOffset is () && timeAbbrev is string { zoneHandling = PREFER_TIME_ABBREV; } From d10416452dddbd09e6fc72cd9b1eea9d9562e051 Mon Sep 17 00:00:00 2001 From: ayeshLK Date: Thu, 12 Sep 2024 17:34:56 +0530 Subject: [PATCH 14/14] Update error message --- ballerina/tests/time_test.bal | 2 +- ballerina/time_apis.bal | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ballerina/tests/time_test.bal b/ballerina/tests/time_test.bal index 2ef01ef..33ad1ca 100644 --- a/ballerina/tests/time_test.bal +++ b/ballerina/tests/time_test.bal @@ -757,7 +757,7 @@ isolated function testCivilToStringWithEmptyTimeOffsetAndAbbreviation() returns }; string|error civilString = civilToString(civil); if civilString is error { - test:assertEquals(civilString.message(), "civil.utcOffset and civil.timeAbbrev both must not be null"); + test:assertEquals(civilString.message(), "the civil value should have either `utcOffset` or `timeAbbrev`"); } else { test:assertFail("civilString should be error"); } diff --git a/ballerina/time_apis.bal b/ballerina/time_apis.bal index b6632f7..689bc57 100644 --- a/ballerina/time_apis.bal +++ b/ballerina/time_apis.bal @@ -175,7 +175,7 @@ public isolated function civilToString(Civil civil) returns string|Error { HeaderZoneHandling zoneHandling = PREFER_ZONE_OFFSET; if utcOffset is () && timeAbbrev is () { - return error FormatError("civil.utcOffset and civil.timeAbbrev both must not be null"); + return error FormatError("the civil value should have either `utcOffset` or `timeAbbrev`"); } else if utcOffset is () && timeAbbrev is string { zoneHandling = PREFER_TIME_ABBREV; }