diff --git a/.gitignore b/.gitignore index 5490780..96caade 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,5 @@ velocity.log* # VSCode .vscode +.project +.settings/ diff --git a/ballerina/tests/time_test.bal b/ballerina/tests/time_test.bal index 0daa75d..1e1488a 100644 --- a/ballerina/tests/time_test.bal +++ b/ballerina/tests/time_test.bal @@ -19,7 +19,7 @@ import ballerina/test; isolated function testUtcNow() { Utc|Error oldUtc = utcFromString("2007-12-03T10:15:30.00Z"); Utc currentUtc = utcNow(); - if (oldUtc is Utc) { + if oldUtc is Utc { test:assertTrue(currentUtc[0] > oldUtc[0]); } else { test:assertFail(msg = oldUtc.message()); @@ -51,7 +51,7 @@ isolated function testMonotonicNow() { @test:Config {} isolated function testUtcFromString() { Utc|Error utc = utcFromString("2007-12-03T10:15:30.00Z"); - if (utc is Utc) { + if utc is Utc { test:assertEquals(utc[0], 1196676930); test:assertEquals(utc[1], 0.0); } else { @@ -62,10 +62,10 @@ isolated function testUtcFromString() { @test:Config {} isolated function testUtcFromStringWithInvalidFormat() { Utc|Error utc = utcFromString("2007-12-0310:15:30.00Z"); - if (utc is Utc) { + if utc is Utc { test:assertFail("Expected time:Error not found"); } else { - test:assertEquals(utc.message(), + test:assertEquals(utc.message(), "Provided '2007-12-0310:15:30.00Z' is not adhere to the expected format '2007-12-03T10:15:30.00Z'"); } } @@ -75,7 +75,7 @@ isolated function testUtcToString() { Utc|Error utc = utcFromString("1985-04-12T23:20:50.520Z"); int expectedSecondsFromEpoch = 482196050; decimal expectedSecondFraction = 0.52; - if (utc is Utc) { + if utc is Utc { test:assertEquals(utc[0], expectedSecondsFromEpoch); test:assertEquals(utc[1], expectedSecondFraction); string utcString = utcToString(utc); @@ -94,7 +94,7 @@ isolated function testUtcToStringWithoutFraction() { @test:Config {} isolated function testUtcAddSeconds() { Utc|Error utc1 = utcFromString("2021-04-12T23:20:50.520Z"); - if (utc1 is Utc) { + if utc1 is Utc { Utc utc2 = utcAddSeconds(utc1, 20.900); string utcString = utcToString(utc2); test:assertEquals(utcString, "2021-04-12T23:21:11.420Z"); @@ -108,11 +108,11 @@ isolated function testUtcDiffSeconds() { Utc|Error utc1 = utcFromString("2021-04-12T23:20:50.520Z"); Utc|Error utc2 = utcFromString("2021-04-11T23:20:50.520Z"); decimal expectedSeconds1 = 86400; - if (utc1 is Utc && utc2 is Utc) { + if utc1 is Utc && utc2 is Utc { test:assertEquals(utcDiffSeconds(utc1, utc2), expectedSeconds1); - } else if (utc1 is Error) { + } else if utc1 is Error { test:assertFail(msg = utc1.message()); - } else if (utc2 is Error) { + } else if utc2 is Error { test:assertFail(msg = utc2.message()); } else { test:assertFail("Unknown error"); @@ -121,11 +121,11 @@ isolated function testUtcDiffSeconds() { Utc|Error utc3 = utcFromString("2021-04-12T23:20:50.520Z"); Utc|Error utc4 = utcFromString("2021-04-11T23:20:55.640Z"); decimal expectedSeconds2 = 86394.88; - if (utc3 is Utc && utc4 is Utc) { + if utc3 is Utc && utc4 is Utc { test:assertEquals(utcDiffSeconds(utc3, utc4), expectedSeconds2); - } else if (utc3 is Error) { + } else if utc3 is Error { test:assertFail(msg = utc3.message()); - } else if (utc4 is Error) { + } else if utc4 is Error { test:assertFail(msg = utc4.message()); } else { test:assertFail("Unknown error"); @@ -134,11 +134,11 @@ isolated function testUtcDiffSeconds() { Utc|Error utc5 = utcFromString("2021-04-12T23:20:50.520Z"); Utc|Error utc6 = utcFromString("2021-04-11T23:20:55.640Z"); decimal expectedSecond3 = -86394.88; - if (utc5 is Utc && utc6 is Utc) { + if utc5 is Utc && utc6 is Utc { test:assertEquals(utcDiffSeconds(utc6, utc5), expectedSecond3); - } else if (utc5 is Error) { + } else if utc5 is Error { test:assertFail(msg = utc5.message()); - } else if (utc6 is Error) { + } else if utc6 is Error { test:assertFail(msg = utc6.message()); } else { test:assertFail("Unknown error"); @@ -149,7 +149,7 @@ isolated function testUtcDiffSeconds() { isolated function testDateValidateUsingValidDate() { Date date = {year: 1994, month: 11, day: 7}; Error? err = dateValidate(date); - if (err is Error) { + if err is Error { test:assertFail(msg = err.message()); } } @@ -159,7 +159,7 @@ isolated function testDateValidateUsingInvalidDate() { // Invalid number of days for a leap year Date date1 = {year: 1994, month: 2, day: 29}; Error? err1 = dateValidate(date1); - if (err1 is Error) { + if err1 is Error { test:assertEquals(err1.message(), "Invalid date 'February 29' as '1994' is not a leap year"); } else { test:assertFail("Expected error not found"); @@ -168,7 +168,7 @@ isolated function testDateValidateUsingInvalidDate() { // Out of range month Date date2 = {year: 1994, month: 50, day: 10}; Error? err2 = dateValidate(date2); - if (err2 is Error) { + if err2 is Error { test:assertEquals(err2.message(), "Invalid value for MonthOfYear (valid values 1 - 12): 50"); } else { test:assertFail("Expected error not found"); @@ -177,7 +177,7 @@ isolated function testDateValidateUsingInvalidDate() { // Out of range day Date date3 = {year: 1994, month: 4, day: 60}; Error? err3 = dateValidate(date3); - if (err3 is Error) { + if err3 is Error { test:assertEquals(err3.message(), "Invalid value for DayOfMonth (valid values 1 - 28/31): 60"); } else { test:assertFail("Expected error not found"); @@ -194,7 +194,7 @@ isolated function testDayOfWeekUsingValidDate() { isolated function testDayOfWeekUsingInvalidDate() { Date date = {year: 1994, month: 2, day: 29}; DayOfWeek|error err = trap dayOfWeek(date); - if (err is Error) { + if err is Error { test:assertEquals(err.message(), "Invalid date 'February 29' as '1994' is not a leap year"); } else { test:assertFail("Expected panic did not occur"); @@ -204,7 +204,7 @@ isolated function testDayOfWeekUsingInvalidDate() { @test:Config {} isolated function testUtcToCivil() { Utc|Error utc = utcFromString("2021-04-12T23:20:50.520Z"); - if (utc is Utc) { + if utc is Utc { Civil civil = utcToCivil(utc); Civil expectedCivil = { year: 2021, @@ -242,11 +242,11 @@ isolated function testUtcFromCivil() { utcOffset: zoneOffset }; Utc|Error utc = utcFromCivil(civil); - if (expectedUtc is Utc && utc is Utc) { + if expectedUtc is Utc && utc is Utc { test:assertEquals(utc, expectedUtc); - } else if (expectedUtc is Error) { + } else if expectedUtc is Error { test:assertFail(msg = expectedUtc.message()); - } else if (utc is Error) { + } else if utc is Error { test:assertFail(msg = utc.message()); } else { test:assertFail("Unknown error"); @@ -267,11 +267,11 @@ isolated function testUtcFromCivilWithoutSecond() { utcOffset: zoneOffset }; Utc|Error utc = utcFromCivil(civil); - if (expectedUtc is Utc && utc is Utc) { + if expectedUtc is Utc && utc is Utc { test:assertEquals(utc, expectedUtc); - } else if (expectedUtc is Error) { + } else if expectedUtc is Error { test:assertFail(msg = expectedUtc.message()); - } else if (utc is Error) { + } else if utc is Error { test:assertFail(msg = utc.message()); } else { test:assertFail("Unknown error"); @@ -291,7 +291,7 @@ isolated function testUtcFromCivilWithInvalidValue() { utcOffset: zoneOffset }; Utc|Error utc = utcFromCivil(civil); - if (utc is Error) { + if utc is Error { test:assertEquals(utc.message(), "Invalid value for MinuteOfHour (valid values 0 - 59): 70"); } else { test:assertFail("Expected `time:Error` not found"); @@ -309,7 +309,7 @@ isolated function testUtcFromCivilWithoutOffset() { timeAbbrev: "Asia/Colombo" }; Utc|Error utc = utcFromCivil(civil); - if (utc is Error) { + if utc is Error { test:assertEquals(utc.message(), "civilTime.utcOffset must not be null"); } else { test:assertFail("Expected `time:Error` not found"); @@ -320,11 +320,11 @@ isolated function testUtcFromCivilWithoutOffset() { isolated function testCivilFromString() { string dateString = "2021-04-12T23:20:50.520Z"; Civil|Error civil = civilFromString(dateString); - if (civil is Civil) { + if civil is Civil { Utc|Error utc = utcFromString(dateString); - if (utc is Utc) { + if utc is Utc { Civil|Error expectedCivil = utcToCivil(utc); - if (expectedCivil is Civil) { + if expectedCivil is Civil { test:assertEquals(civil, expectedCivil); } else { test:assertFail(expectedCivil.message()); @@ -353,7 +353,7 @@ isolated function testCivilFromStringWithZone() { dayOfWeek: MONDAY }; Civil|Error civil = civilFromString(dateString); - if (civil is Civil) { + if civil is Civil { test:assertEquals(civil, expectedCivil); } else { test:assertFail(msg = civil.message()); @@ -374,7 +374,7 @@ isolated function testCivilFromStringWithoutZone() { dayOfWeek: MONDAY }; Civil|Error civil = civilFromString(dateString); - if (civil is Civil) { + if civil is Civil { test:assertEquals(civil, expectedCivil); } else { test:assertFail(msg = civil.message()); @@ -396,7 +396,7 @@ isolated function testCivilFromStringWithoutSecond() { dayOfWeek: MONDAY }; Civil|Error civil = civilFromString(dateString); - if (civil is Civil) { + if civil is Civil { test:assertEquals(civil, expectedCivil); } else { test:assertFail(msg = civil.message()); @@ -419,7 +419,7 @@ isolated function testCivilFromStringWithoutZoneMinutes() { dayOfWeek: MONDAY }; Civil|Error civil = civilFromString(dateString); - if (civil is Civil) { + if civil is Civil { test:assertEquals(civil, expectedCivil); } else { test:assertFail(msg = civil.message()); @@ -442,7 +442,7 @@ isolated function testCivilFromStringWithZoneSeconds() { dayOfWeek: MONDAY }; Civil|Error civil = civilFromString(dateString); - if (civil is Civil) { + if civil is Civil { test:assertEquals(civil, expectedCivil); } else { test:assertFail(msg = civil.message()); @@ -453,7 +453,7 @@ isolated function testCivilFromStringWithZoneSeconds() { isolated function testCivilFromStringWithInvalidInput() { string dateString = "2021-04-12T23:20:50.520.05:30:45"; Civil|Error err = civilFromString(dateString); - if (err is Error) { + if err is Error { test:assertEquals(err.message(), "Text '2021-04-12T23:20:50.520.05:30:45' could not be parsed at index 23"); } else { test:assertFail(msg = "Expected time:Error not found"); @@ -475,7 +475,7 @@ isolated function testCivilToString() { }; string|Error civilStr = civilToString(civil); string expectedStr = "2021-03-04T19:03:28.839564Z"; - if (civilStr is string) { + if civilStr is string { test:assertEquals(civilStr, expectedStr); } else { test:assertFail(msg = civilStr.message()); @@ -506,7 +506,7 @@ isolated function testCivilToStringWithTimeOfDay() { }; string|Error civilStr = civilToString(civil); string expectedStr = "2021-03-04T19:03:28.839564Z"; - if (civilStr is string) { + if civilStr is string { test:assertEquals(civilStr, expectedStr); } else { test:assertFail(msg = civilStr.message()); @@ -524,7 +524,7 @@ isolated function testCivilToStringWithoutOffset() { timeAbbrev: "Asia/Colombo" }; string|Error civilStr = civilToString(civil); - if (civilStr is Error) { + if civilStr is Error { test:assertEquals(civilStr.message(), "civil.utcOffset must not be null"); } else { test:assertFail("Expected `time:Error` not found"); @@ -545,7 +545,7 @@ isolated function testCivilToStringWithInvalidInput() { utcOffset: zoneOffset }; string|Error err = civilToString(civil); - if (err is Error) { + if err is Error { test:assertEquals(err.message(), "Invalid value for HourOfDay (valid values 0 - 23): 45"); } else { test:assertFail(msg = "Expected `time:Error` not found"); @@ -555,7 +555,7 @@ isolated function testCivilToStringWithInvalidInput() { @test:Config {} isolated function testUtcToEmailString() { Utc|Error utc = utcFromString("2007-12-03T10:15:30.00Z"); - if (utc is Utc) { + if utc is Utc { test:assertEquals(utcToEmailString(utc, "GMT"), "Mon, 3 Dec 2007 10:15:30 GMT"); } else { test:assertFail(msg = utc.message()); @@ -565,7 +565,7 @@ isolated function testUtcToEmailString() { @test:Config {} isolated function testUtcToEmailStringWithZ() { Utc|Error utc = utcFromString("2007-12-03T10:15:30.00Z"); - if (utc is Utc) { + if utc is Utc { test:assertEquals(utcToEmailString(utc, "Z"), "Mon, 3 Dec 2007 10:15:30 Z"); } else { test:assertFail(msg = utc.message()); @@ -588,7 +588,7 @@ isolated function testCivilFromEmailString() { dayOfWeek: WEDNESDAY }; Civil|Error civil = civilFromEmailString(dateString); - if (civil is Civil) { + if civil is Civil { test:assertEquals(civil, expectedCivil); } else { test:assertFail(msg = civil.message()); @@ -599,7 +599,7 @@ isolated function testCivilFromEmailString() { isolated function testCivilFromEmailStringWithInvalidInput() { string dateString = "Wed, 10 2021 19:51:55 -0800 (PST)"; Civil|Error err = civilFromEmailString(dateString); - if (err is Error) { + if err is Error { test:assertEquals(err.message(), "Text 'Wed, 10 2021 19:51:55 -0800 (PST)' could not be parsed at index 8"); } else { test:assertFail(msg = "Expected time:Error not found"); @@ -621,7 +621,7 @@ isolated function testCivilToEmailString() { utcOffset: zoneOffset }; string|Error emailString = civilToEmailString(civil, ZONE_OFFSET_WITH_TIME_ABBREV_COMMENT); - if (emailString is string) { + if emailString is string { test:assertEquals(emailString, expectedString); } else { test:assertFail(msg = emailString.message()); @@ -643,7 +643,7 @@ isolated function testCivilToEmailStringWithZonePreference() { utcOffset: zoneOffset }; string|Error emailString = civilToEmailString(civil, PREFER_ZONE_OFFSET); - if (emailString is string) { + if emailString is string { test:assertEquals(emailString, expectedString); } else { test:assertFail(msg = emailString.message()); @@ -661,7 +661,7 @@ isolated function testCivilToEmailStringWithoutOffset() { timeAbbrev: "Asia/Colombo" }; string|Error emailString = civilToEmailString(civil, PREFER_ZONE_OFFSET); - if (emailString is Error) { + if emailString is Error { test:assertEquals(emailString.message(), "civil.utcOffset must not be null"); } else { test:assertFail("Expected `time:Error` not found"); @@ -682,9 +682,134 @@ isolated function testCivilToEmailStringWithInvalidInput() { utcOffset: zoneOffset }; string|Error err = civilToEmailString(civil, ZONE_OFFSET_WITH_TIME_ABBREV_COMMENT); - if (err is Error) { + if err is Error { test:assertEquals(err.message(), "Invalid value for MonthOfYear (valid values 1 - 12): 30"); } else { test:assertFail(msg = "Expected time:Error not found"); } } + +@test:Config {} +isolated function testLoadSystemZone() returns Error? { + _ = check loadSystemZone(); + //final Zone systemZone = check loadSystemZone(); + //test:assertTrue(systemZone.fixedOffset() is ()); // Cannot test this extensively since this may change in different environments (or docker images). +} + +@test:Config {} +isolated function testGetZone() returns Error? { + Zone? systemZone1 = getZone("Asia/Colombo"); + if systemZone1 is Zone { + test:assertTrue(systemZone1.fixedOffset() is ()); + } else { + test:assertFail(msg = "Expected time:Zone not found"); + } + + Zone? systemZone2 = getZone("Greenwich"); + if systemZone2 is Zone { + ZoneOffset? zoneOffset = systemZone2.fixedOffset(); + if zoneOffset is ZoneOffset { + test:assertEquals(zoneOffset, {hours: 0, minutes: 0}); + } else { + test:assertFail(msg = "Expected time:ZoneOffset not found"); + } + } else { + test:assertFail(msg = "Expected time:Zone not found"); + } + + Zone? systemZone3 = getZone("Etc/GMT-9"); + if systemZone3 is Zone { + ZoneOffset? zoneOffset = systemZone3.fixedOffset(); + if zoneOffset is ZoneOffset { + test:assertEquals(zoneOffset, {hours: 9, minutes: 0}); + } else { + test:assertFail(msg = "Expected time:ZoneOffset not found"); + } + } else { + test:assertFail(msg = "Expected time:Zone not found"); + } +} + +@test:Config {} +isolated function testZoneUtcFromCivil() returns Error? { + Civil civil = { + year: 2021, + month: 3, + day: 10, + hour: 19, + minute: 51, + second: 55, + timeAbbrev: "America/Los_Angeles" + }; + Zone? zone = getZone("Etc/GMT-9"); + if zone is Zone { + test:assertEquals(check zone.utcFromCivil(civil), [1615434715, 0]); + } else { + test:assertFail(msg = "Expected time:Zone not found"); + } +} + +@test:Config {} +isolated function testZoneUtcFromCivilWithoutTimeAbbrev() returns Error? { + Civil civil = { + year: 2021, + month: 3, + day: 10, + hour: 19, + minute: 51, + second: 55 + }; + Zone? zone = getZone("Etc/GMT-9"); + if zone is Zone { + Utc|Error utc = zone.utcFromCivil(civil); + if utc is Error { + test:assertEquals(utc.message(), "Abbreviation for the local time is required for the conversion"); + } else { + test:assertFail("Expected time:Error not found"); + } + } else { + test:assertFail(msg = "Expected time:Zone not found"); + } +} + +@test:Config {} +isolated function testZoneUtcToCivil1() returns Error? { + Utc utc = check utcFromString("2007-12-03T10:15:30.00Z"); + Civil civil = { + year: 2007, + month: 12, + day: 3, + hour: 19, + minute: 15, + second: 30, + timeAbbrev: "Etc/GMT-9", + dayOfWeek: 1 + }; + Zone? zone = getZone("Etc/GMT-9"); + if zone is Zone { + test:assertEquals(zone.utcToCivil(utc), civil); + } else { + test:assertFail(msg = "Expected time:Zone not found"); + } +} + +@test:Config {} +isolated function testZoneUtcToCivil2() returns Error? { + Utc utc = check utcFromString("2007-12-03T10:15:30.00Z"); + Civil civil = { + year: 2007, + month: 12, + day: 3, + hour: 15, + minute: 45, + second: 30, + timeAbbrev: "Asia/Colombo", + dayOfWeek: 1 + }; + Zone? zone = getZone("Asia/Colombo"); + if zone is Zone { + test:assertEquals(zone.utcToCivil(utc), civil); + } else { + test:assertFail(msg = "Expected time:Zone not found"); + } +} diff --git a/ballerina/time_apis.bal b/ballerina/time_apis.bal index e03f153..a83fce6 100644 --- a/ballerina/time_apis.bal +++ b/ballerina/time_apis.bal @@ -69,7 +69,7 @@ public isolated function utcAddSeconds(Utc utc, Seconds seconds) returns Utc { [int, decimal] [secondsFromEpoch, lastSecondFraction] = utc; secondsFromEpoch = secondsFromEpoch + seconds.floor(); lastSecondFraction = lastSecondFraction + (seconds - seconds.floor()); - if (lastSecondFraction >= 1.0d) { + if lastSecondFraction >= 1.0d { secondsFromEpoch = secondsFromEpoch + lastSecondFraction.floor(); lastSecondFraction = lastSecondFraction - lastSecondFraction.floor(); } @@ -132,7 +132,7 @@ public isolated function utcToCivil(Utc utc) returns Civil { # + civilTime - `Civil` time # + return - The corresponding `Utc` value or an error if `civilTime.utcOffset` is missing public isolated function utcFromCivil(Civil civilTime) returns Utc|Error { - if (civilTime?.utcOffset is ()) { + if civilTime?.utcOffset is () { return error FormatError("civilTime.utcOffset must not be null"); } ZoneOffset utcOffset = civilTime?.utcOffset; @@ -164,7 +164,7 @@ 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 { - if (civil?.utcOffset is ()) { + if civil?.utcOffset is () { return error FormatError("civil.utcOffset must not be null"); } ZoneOffset utcOffset = civil?.utcOffset; diff --git a/ballerina/time_types.bal b/ballerina/time_types.bal index f224523..c40fcf7 100644 --- a/ballerina/time_types.bal +++ b/ballerina/time_types.bal @@ -14,6 +14,8 @@ // specific language governing permissions and limitations // under the License. +import ballerina/jballerina.java; + # Holds the seconds as a decimal value. public type Seconds decimal; @@ -172,3 +174,111 @@ public enum HeaderZoneHandling { PREFER_ZONE_OFFSET, ZONE_OFFSET_WITH_TIME_ABBREV_COMMENT } + +# Abstract object representation to handle time zones. +public type Zone readonly & object { + + # If always at a fixed offset from Utc, then this function returns it; otherwise nil. + # + # + return - The fixed zone offset or nil + public isolated function fixedOffset() returns ZoneOffset?; + + # Converts a given `Civil` value to an `Utc` timestamp based on the time zone value. + # + # + civil - `Civil` time + # + return - The corresponding `Utc` value or an error if `civil.timeAbbrev` is missing + public isolated function utcFromCivil(Civil civil) returns Utc|Error; + + # Converts a given `Utc` timestamp to a `Civil` value based on the time zone value. + # + # + utc - `Utc` timestamp + # + return - The corresponding `Civil` value + public isolated function utcToCivil(Utc utc) returns Civil; +}; + +# Localized time zone implementation to handle time zones. +public readonly class TimeZone { + *Zone; + + # Initialize a TimeZone class using a zone ID. + # + # + zoneId - Zone ID as a string or nil to initialize a TimeZone object with the system default time zone + # + return - An error or nil + public isolated function init(string? zoneId = ()) returns Error? { + if zoneId is string { + externTimeZoneInitWithId(self, zoneId); + } else { + check externTimeZoneInitWithSystemZone(self); + } + } + + # If always at a fixed offset from Utc, then this function returns it; otherwise nil. + # + # + return - The fixed zone offset or nil + public isolated function fixedOffset() returns ZoneOffset? { + return externTimeZoneFixedOffset(self); + } + + # Converts a given `Civil` value to an `Utc` timestamp based on the time zone value. + # + # + civil - `Civil` time + # + return - The corresponding `Utc` value or an error if `civil.timeAbbrev` is missing + public isolated function utcFromCivil(Civil civil) returns Utc|Error { + string? timeAbbrev = civil?.timeAbbrev; + if timeAbbrev is () { + return error FormatError("Abbreviation for the local time is required for the conversion"); + } + decimal? civilTimeSecField = civil?.second; + decimal civilTimeSeconds = (civilTimeSecField is Seconds) ? civilTimeSecField : 0.0; + + return externTimeZoneUtcFromCivil(self, civil.year, civil.month, civil.day, civil.hour, civil.minute, civilTimeSeconds, timeAbbrev, PREFER_TIME_ABBREV); + } + + # Converts a given `Utc` timestamp to a `Civil` value based on the time zone value. + # + # + utc - `Utc` timestamp + # + return - The corresponding `Civil` value + public isolated function utcToCivil(Utc utc) returns Civil { + return externTimeZoneUtcToCivil(self, utc); + } +} + +# Load the default time zone of the system. +# + return - Zone value or error when the zone ID of the system is in invalid format. +public isolated function loadSystemZone() returns Zone|Error { + return check new TimeZone(); +} + +# Return the time zone object of a given zone ID. +# +# + id - Time zone ID (e.g. "Continent/City") +# + return - Corresponding ime zone object or null +public isolated function getZone(string id) returns Zone? { + TimeZone|Error timeZone = new TimeZone(id); + if timeZone is TimeZone { + return timeZone; + } + return; +} + +isolated function externTimeZoneInitWithSystemZone(TimeZone timeZone) returns Error? = @java:Method { + 'class: "io.ballerina.stdlib.time.nativeimpl.TimeZoneExternUtils" +} external; + +isolated function externTimeZoneInitWithId(TimeZone timeZone, string zoneId) = @java:Method { + 'class: "io.ballerina.stdlib.time.nativeimpl.TimeZoneExternUtils" +} external; + +isolated function externTimeZoneFixedOffset(TimeZone timeZone) returns ZoneOffset? = @java:Method { + 'class: "io.ballerina.stdlib.time.nativeimpl.TimeZoneExternUtils" +} external; + +isolated function externTimeZoneUtcToCivil(TimeZone timeZone, Utc utc) returns Civil = @java:Method { + 'class: "io.ballerina.stdlib.time.nativeimpl.TimeZoneExternUtils" +} external; + +isolated function externTimeZoneUtcFromCivil(TimeZone timeZone, int year, int month, int day, +int hour, int minute, decimal second, string timeAbber, HeaderZoneHandling zoneHandling) +returns Utc|Error = @java:Method { + 'class: "io.ballerina.stdlib.time.nativeimpl.TimeZoneExternUtils" +} external; diff --git a/native/src/main/java/io/ballerina/stdlib/time/nativeimpl/Civil.java b/native/src/main/java/io/ballerina/stdlib/time/nativeimpl/Civil.java index 3775100..8e1ae9d 100644 --- a/native/src/main/java/io/ballerina/stdlib/time/nativeimpl/Civil.java +++ b/native/src/main/java/io/ballerina/stdlib/time/nativeimpl/Civil.java @@ -15,7 +15,7 @@ * specific language governing permissions and limitations * under the License. */ - package io.ballerina.stdlib.time.nativeimpl; +package io.ballerina.stdlib.time.nativeimpl; import io.ballerina.runtime.api.creators.ValueCreator; import io.ballerina.runtime.api.utils.StringUtils; @@ -23,12 +23,12 @@ import io.ballerina.runtime.api.values.BString; import io.ballerina.stdlib.time.util.Constants; import io.ballerina.stdlib.time.util.ModuleUtils; +import io.ballerina.stdlib.time.util.Utils; import java.math.BigDecimal; import java.math.MathContext; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.util.HashMap; import java.util.Map; import java.util.regex.Pattern; @@ -41,13 +41,38 @@ */ public class Civil { - private ZonedDateTime zonedDateTime = ZonedDateTime.now(); - private BMap civilMap = ValueCreator.createRecordValue(ModuleUtils.getModule(), + private final ZonedDateTime zonedDateTime; + private boolean isSecondExists = false; + private boolean isLocalTimeZoneExists = false; + private final BMap civilMap = ValueCreator.createRecordValue(ModuleUtils.getModule(), Constants.CIVIL_RECORD); - public BMap buildFromZonedDateTime(ZonedDateTime zonedDateTime) { + public Civil(ZonedDateTime zonedDateTime) { this.zonedDateTime = zonedDateTime; + } + + public Civil(String zonedDateTimeString, Constants.CivilInputStringTypes inputStringTypes) { + + if (Constants.CivilInputStringTypes.EMAIL_STRING.toString().equals(inputStringTypes.toString())) { + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(Constants.EMAIL_DATE_TIME_FORMAT); + this.zonedDateTime = ZonedDateTime.parse(zonedDateTimeString, dateTimeFormatter); + this.isSecondExists = true; + this.isLocalTimeZoneExists = true; + } else { + this.zonedDateTime = ZonedDateTime.parse(zonedDateTimeString); + this.isSecondExists = isSecondExists(zonedDateTimeString); + this.isLocalTimeZoneExists = isLocalTimeZoneExists(zonedDateTimeString); + } + } + + public ZonedDateTime getZonedDateTime() { + + return zonedDateTime; + } + + public BMap build() { + setCommonCivilFields(); BigDecimal second = new BigDecimal(zonedDateTime.getSecond()); second = second.add(new BigDecimal(zonedDateTime.getNano()).divide(ANALOG_GIGA, MathContext.DECIMAL128)); @@ -57,18 +82,16 @@ public BMap buildFromZonedDateTime(ZonedDateTime zonedDateTime) } - public BMap buildFromZonedDateTimeString(String zonedDateTimeString) { + public BMap buildWithZone() { - ZonedDateTime zonedDateTime = ZonedDateTime.parse(zonedDateTimeString); - this.zonedDateTime = zonedDateTime; setCommonCivilFields(); BigDecimal second = new BigDecimal(zonedDateTime.getSecond()); second = second.add(new BigDecimal(zonedDateTime.getNano()).divide(ANALOG_GIGA, MathContext.DECIMAL128)); - if (isSecondExists(zonedDateTimeString)) { + if (this.isSecondExists) { civilMap.put(Constants.TIME_OF_DAY_RECORD_SECOND_BSTRING, ValueCreator.createDecimalValue(second)); } - if (isLocalTimeZoneExists(zonedDateTimeString)) { + if (this.isLocalTimeZoneExists) { civilMap.put(Constants.CIVIL_RECORD_UTC_OFFSET_BSTRING, createZoneOffsetFromZonedDateTime(zonedDateTime)); } @@ -77,22 +100,6 @@ public BMap buildFromZonedDateTimeString(String zonedDateTimeSt } - public BMap buildFromEmailString(String zonedDateTimeString) { - - DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(Constants.EMAIL_DATE_TIME_FORMAT); - ZonedDateTime zonedDateTime = ZonedDateTime.parse(zonedDateTimeString, dateTimeFormatter); - this.zonedDateTime = zonedDateTime; - setCommonCivilFields(); - BigDecimal second = new BigDecimal(zonedDateTime.getSecond()); - second = second.add(new BigDecimal(zonedDateTime.getNano()).divide(ANALOG_GIGA, MathContext.DECIMAL128)); - civilMap.put(Constants.TIME_OF_DAY_RECORD_SECOND_BSTRING, ValueCreator.createDecimalValue(second)); - civilMap.put(Constants.CIVIL_RECORD_UTC_OFFSET_BSTRING, - createZoneOffsetFromZonedDateTime(zonedDateTime)); - - return civilMap; - - } - private void setCommonCivilFields() { civilMap.put(Constants.DATE_RECORD_YEAR_BSTRING, zonedDateTime.getYear()); @@ -119,60 +126,8 @@ private boolean isSecondExists(String time) { public BMap createZoneOffsetFromZonedDateTime(ZonedDateTime zonedDateTime) { - BMap civilMap = ValueCreator.createRecordValue(ModuleUtils.getModule(), - Constants.READABLE_ZONE_OFFSET_RECORD); - Map zoneInfo = zoneOffsetMapFromString(zonedDateTime.getOffset().toString()); - if (zoneInfo.get(Constants.ZONE_OFFSET_RECORD_HOUR) != null) { - civilMap.put(Constants.ZONE_OFFSET_RECORD_HOUR_BSTRING, - zoneInfo.get(Constants.ZONE_OFFSET_RECORD_HOUR).longValue()); - } else { - civilMap.put(Constants.ZONE_OFFSET_RECORD_HOUR_BSTRING, 0); - } - - if (zoneInfo.get(Constants.ZONE_OFFSET_RECORD_MINUTE) != null) { - civilMap.put(Constants.ZONE_OFFSET_RECORD_MINUTE_BSTRING, - zoneInfo.get(Constants.ZONE_OFFSET_RECORD_MINUTE).longValue()); - } else { - civilMap.put(Constants.ZONE_OFFSET_RECORD_MINUTE_BSTRING, 0); - } - - if (zoneInfo.get(Constants.ZONE_OFFSET_RECORD_SECOND) != null) { - civilMap.put(Constants.ZONE_OFFSET_RECORD_SECOND_BSTRING, - zoneInfo.get(Constants.ZONE_OFFSET_RECORD_SECOND).longValue()); - } - civilMap.freezeDirect(); - return civilMap; - } - - public Map zoneOffsetMapFromString(String dateTime) { - - Map zone = new HashMap<>(); - if (dateTime.strip().startsWith("+")) { - dateTime = dateTime.replaceFirst("\\+", ""); - String[] zoneInfo = dateTime.split(":"); - if (zoneInfo.length > 0 && zoneInfo[0] != null) { - zone.put(Constants.ZONE_OFFSET_RECORD_HOUR, Integer.parseInt(zoneInfo[0])); - } - if (zoneInfo.length > 1 && zoneInfo[1] != null) { - zone.put(Constants.ZONE_OFFSET_RECORD_MINUTE, Integer.parseInt(zoneInfo[1])); - } - if (zoneInfo.length > 2 && zoneInfo[2] != null) { - zone.put(Constants.ZONE_OFFSET_RECORD_SECOND, Integer.parseInt(zoneInfo[2])); - } - } else if (dateTime.strip().startsWith("-")) { - dateTime = dateTime.replaceFirst("\\-", ""); - String[] zoneInfo = dateTime.split(":"); - if (zoneInfo.length > 0 && zoneInfo[0] != null) { - zone.put(Constants.ZONE_OFFSET_RECORD_HOUR, Integer.parseInt(zoneInfo[0]) * -1); - } - if (zoneInfo.length > 1 && zoneInfo[1] != null) { - zone.put(Constants.ZONE_OFFSET_RECORD_MINUTE, Integer.parseInt(zoneInfo[1]) * -1); - } - if (zoneInfo.length > 2 && zoneInfo[2] != null) { - zone.put(Constants.ZONE_OFFSET_RECORD_SECOND, Integer.parseInt(zoneInfo[2]) * -1); - } - } - return zone; + Map zoneInfo = Utils.zoneOffsetMapFromString(zonedDateTime.getOffset().toString()); + return Utils.createZoneOffsetFromZoneInfoMap(zoneInfo); } } 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 d3380f3..25aefc4 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 @@ -27,6 +27,7 @@ import io.ballerina.stdlib.time.util.Constants; import io.ballerina.stdlib.time.util.Errors; import io.ballerina.stdlib.time.util.TimeValueHandler; +import io.ballerina.stdlib.time.util.Utils; import java.math.BigDecimal; import java.time.DateTimeException; @@ -69,7 +70,7 @@ public static Object externUtcFromString(BString str) { Instant utcTimeInstant = ZonedDateTime.parse(str.getValue()).toInstant(); return new Utc(utcTimeInstant).build(); } catch (DateTimeException e) { - return TimeValueHandler.createError(Errors.FormatError, + return Utils.createError(Errors.FormatError, "Provided '" + str.getValue() + "' is not adhere to the expected format '2007-12-03T10:15:30.00Z'"); } } @@ -100,7 +101,7 @@ public static Object externDateValidate(BMap date) { LocalDate.of(year, month, day); return null; } catch (DateTimeException e) { - return TimeValueHandler.createError(Errors.FormatError, e.getMessage()); + return Utils.createError(Errors.FormatError, e.getMessage()); } } @@ -112,7 +113,7 @@ public static Object externDayOfWeek(BMap date) { try { return ((LocalDate.of(year, month, day).getDayOfWeek().getValue()) % 7); } catch (DateTimeException e) { - return TimeValueHandler.createError(Errors.FormatError, e.getMessage()); + return Utils.createError(Errors.FormatError, e.getMessage()); } } @@ -132,7 +133,7 @@ public static Object externUtcFromCivil(long year, long month, long day, long ho Constants.HeaderZoneHandling.PREFER_ZONE_OFFSET.toString()); return new Utc(dateTime.toInstant()).build(); } catch (DateTimeException e) { - return TimeValueHandler.createError(Errors.FormatError, e.getMessage()); + return Utils.createError(Errors.FormatError, e.getMessage()); } } @@ -141,7 +142,7 @@ public static Object externCivilFromString(BString dateTimeString) { try { return TimeValueHandler.createCivilFromZoneDateTimeString(dateTimeString.getValue()); } catch (DateTimeException e) { - return TimeValueHandler.createError(Errors.FormatError, e.getMessage()); + return Utils.createError(Errors.FormatError, e.getMessage()); } } @@ -150,7 +151,7 @@ public static Object externCivilFromEmailString(BString dateTimeString) { try { return TimeValueHandler.createCivilFromEmailString(dateTimeString.getValue()); } catch (DateTimeException | IllegalArgumentException e) { - return TimeValueHandler.createError(Errors.FormatError, e.getMessage()); + return Utils.createError(Errors.FormatError, e.getMessage()); } } @@ -163,7 +164,7 @@ public static Object externCivilToString(long year, long month, long day, long h Constants.HeaderZoneHandling.PREFER_ZONE_OFFSET.toString()); return StringUtils.fromString(dateTime.toInstant().toString()); } catch (DateTimeException e) { - return TimeValueHandler.createError(Errors.FormatError, e.getMessage()); + return Utils.createError(Errors.FormatError, e.getMessage()); } } @@ -189,7 +190,7 @@ public static Object externCivilToEmailString(long year, long month, long day, l return StringUtils.fromString(dateTime.format(DateTimeFormatter.ofPattern( Constants.EMAIL_DATE_TIME_FORMAT))); } catch (DateTimeException e) { - return TimeValueHandler.createError(Errors.FormatError, e.getMessage()); + return Utils.createError(Errors.FormatError, e.getMessage()); } } diff --git a/native/src/main/java/io/ballerina/stdlib/time/nativeimpl/TimeZoneExternUtils.java b/native/src/main/java/io/ballerina/stdlib/time/nativeimpl/TimeZoneExternUtils.java new file mode 100644 index 0000000..d8b0bef --- /dev/null +++ b/native/src/main/java/io/ballerina/stdlib/time/nativeimpl/TimeZoneExternUtils.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2021, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package io.ballerina.stdlib.time.nativeimpl; + +import io.ballerina.runtime.api.values.BArray; +import io.ballerina.runtime.api.values.BDecimal; +import io.ballerina.runtime.api.values.BMap; +import io.ballerina.runtime.api.values.BObject; +import io.ballerina.runtime.api.values.BString; +import io.ballerina.stdlib.time.util.Errors; +import io.ballerina.stdlib.time.util.Utils; + +import java.time.DateTimeException; +import java.time.ZonedDateTime; + +/** + * Contains the extern APIs related to Ballerina TimeZone type generations and operations. + * + * @since 2.0.2 + */ +public class TimeZoneExternUtils { + + private static final String ZONE_ID_ENTRY = "zoneId"; + + private TimeZoneExternUtils() { + + } + + public static Object externTimeZoneInitWithSystemZone(BObject timeZoneObj) { + + try { + Zone zone = new Zone(); + timeZoneObj.addNativeData(ZONE_ID_ENTRY, zone); + return null; + } catch (DateTimeException e) { + return Utils.createError(Errors.FormatError, e.getMessage()); + } + } + + public static void externTimeZoneInitWithId(BObject timeZoneObj, BString zoneId) { + + Zone zone = new Zone(zoneId.getValue()); + timeZoneObj.addNativeData(ZONE_ID_ENTRY, zone); + } + + public static Object externTimeZoneFixedOffset(BObject timeZoneObj) { + + Zone zone = (Zone) timeZoneObj.getNativeData(ZONE_ID_ENTRY); + return zone.isFixedOffset(); + } + + public static Object externTimeZoneUtcFromCivil(BObject timeZoneObj, long year, long month, long day, long hour, + long minute, BDecimal second, BString zoneAbbr, + BString zoneHandling) { + + Zone zone = (Zone) timeZoneObj.getNativeData(ZONE_ID_ENTRY); + ZonedDateTime zonedDateTime = Utils.createZoneDateTimeFromCivilValues(year, month, day, hour, minute, + second, 0, 0, BDecimal.valueOf(0), zoneAbbr, zoneHandling.getValue()); + return zone.utcFromCivil(new Civil(zonedDateTime)).build(); + } + + public static BMap externTimeZoneUtcToCivil(BObject timeZoneObj, BArray utc) { + + Zone zone = (Zone) timeZoneObj.getNativeData(ZONE_ID_ENTRY); + return zone.utcToCivil(new Utc(utc)).build(); + } + +} diff --git a/native/src/main/java/io/ballerina/stdlib/time/nativeimpl/Zone.java b/native/src/main/java/io/ballerina/stdlib/time/nativeimpl/Zone.java new file mode 100644 index 0000000..a472d40 --- /dev/null +++ b/native/src/main/java/io/ballerina/stdlib/time/nativeimpl/Zone.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2021, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package io.ballerina.stdlib.time.nativeimpl; + +import io.ballerina.stdlib.time.util.Utils; + +import java.time.DateTimeException; +import java.time.Instant; +import java.time.ZoneId; +import java.util.Map; + +/** + * Contains the APIs related to Ballerina TimeZone type generations and operations. + * + * @since 2.0.2 + */ +public class Zone { + + private ZoneId zoneId; + + public Zone() throws DateTimeException { + + zoneId = ZoneId.systemDefault(); + } + + public Zone(String zoneId) { + + this.zoneId = ZoneId.of(zoneId); + } + + public Object isFixedOffset() { + + if (zoneId.getRules().isFixedOffset()) { + Map zoneInfo = Utils.zoneOffsetMapFromString( + zoneId.getRules().getOffset(Instant.now()).toString()); + return Utils.createZoneOffsetFromZoneInfoMap(zoneInfo); + } + return null; + } + + public Utc utcFromCivil(Civil civil) { + + return new Utc(civil.getZonedDateTime().toInstant()); + } + + public Civil utcToCivil(Utc utc) { + + return new Civil(utc.generateInstant().atZone(zoneId)); + } +} diff --git a/native/src/main/java/io/ballerina/stdlib/time/util/Constants.java b/native/src/main/java/io/ballerina/stdlib/time/util/Constants.java index 1ff584f..fe5fa81 100644 --- a/native/src/main/java/io/ballerina/stdlib/time/util/Constants.java +++ b/native/src/main/java/io/ballerina/stdlib/time/util/Constants.java @@ -89,5 +89,14 @@ public enum HeaderZoneHandling { PREFER_ZONE_OFFSET, ZONE_OFFSET_WITH_TIME_ABBREV_COMMENT } + + /** + * String types that can generates a Civil record. + * + */ + public enum CivilInputStringTypes { + RFC3339_STRING, // RFC 3339 + EMAIL_STRING + } } diff --git a/native/src/main/java/io/ballerina/stdlib/time/util/TimeValueHandler.java b/native/src/main/java/io/ballerina/stdlib/time/util/TimeValueHandler.java index f1ee484..5672762 100644 --- a/native/src/main/java/io/ballerina/stdlib/time/util/TimeValueHandler.java +++ b/native/src/main/java/io/ballerina/stdlib/time/util/TimeValueHandler.java @@ -18,21 +18,13 @@ package io.ballerina.stdlib.time.util; -import io.ballerina.runtime.api.creators.ErrorCreator; -import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.values.BArray; import io.ballerina.runtime.api.values.BDecimal; -import io.ballerina.runtime.api.values.BError; import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BString; import io.ballerina.stdlib.time.nativeimpl.Civil; import io.ballerina.stdlib.time.nativeimpl.Utc; -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.time.DateTimeException; -import java.time.ZoneId; -import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.Date; import java.util.Map; @@ -44,52 +36,37 @@ */ public class TimeValueHandler { + private TimeValueHandler() { + + } + public static BMap createCivilFromZoneDateTime(ZonedDateTime zonedDateTime) { - return new Civil().buildFromZonedDateTime(zonedDateTime); + return new Civil(zonedDateTime).build(); } public static BMap createCivilFromZoneDateTimeString(String zonedDateTimeString) { - return new Civil().buildFromZonedDateTimeString(zonedDateTimeString); + return new Civil(zonedDateTimeString, Constants.CivilInputStringTypes.RFC3339_STRING).buildWithZone(); } public static BMap createCivilFromEmailString(String zonedDateTimeString) { - return new Civil().buildFromEmailString(zonedDateTimeString); + return new Civil(zonedDateTimeString, Constants.CivilInputStringTypes.EMAIL_STRING).buildWithZone(); } public static ZonedDateTime createZoneDateTimeFromCivilValues(long year, long month, long day, long hour, long minute, BDecimal second, long zoneHour, long zoneMinute, BDecimal zoneSecond, - BString zoneAbbr, String zoneHandling) - throws DateTimeException { - - ZoneId zoneId; - int intSecond = second.decimalValue().setScale(0, RoundingMode.FLOOR).intValue(); - int intNanoSecond = second.decimalValue().subtract(new BigDecimal(intSecond)).multiply(Constants.ANALOG_GIGA) - .setScale(0, RoundingMode.HALF_UP).intValue(); - int intZoneSecond = zoneSecond.decimalValue().setScale(0, RoundingMode.HALF_UP).intValue(); - if (Constants.HeaderZoneHandling.PREFER_ZONE_OFFSET.toString().equals(zoneHandling)) { - zoneId = ZoneId.of(ZoneOffset.ofHoursMinutesSeconds( - Long.valueOf(zoneHour).intValue(), Long.valueOf(zoneMinute).intValue(), intZoneSecond).toString()); - } else { - zoneId = ZoneId.of(zoneAbbr.getValue()); - } - return ZonedDateTime.of( - Long.valueOf(year).intValue(), Long.valueOf(month).intValue(), Long.valueOf(day).intValue(), - Long.valueOf(hour).intValue(), Long.valueOf(minute).intValue(), intSecond, intNanoSecond, zoneId); - } - - public static BError createError(Errors errorType, String errorMsg) { + BString zoneAbbr, String zoneHandling) { - return ErrorCreator.createDistinctError(errorType.name(), ModuleUtils.getModule(), - StringUtils.fromString(errorMsg)); + return Utils.createZoneDateTimeFromCivilValues(year, month, day, hour, minute, second, zoneHour, zoneMinute, + zoneSecond, zoneAbbr, zoneHandling); } public static Map zoneOffsetMapFromString(String dateTime) { - return new Civil().zoneOffsetMapFromString(dateTime); + return Utils.zoneOffsetMapFromString(dateTime); } public static BArray createUtcFromDate(Date date) { diff --git a/native/src/main/java/io/ballerina/stdlib/time/util/Utils.java b/native/src/main/java/io/ballerina/stdlib/time/util/Utils.java new file mode 100644 index 0000000..39ccc30 --- /dev/null +++ b/native/src/main/java/io/ballerina/stdlib/time/util/Utils.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2021, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package io.ballerina.stdlib.time.util; + +import io.ballerina.runtime.api.creators.ErrorCreator; +import io.ballerina.runtime.api.creators.ValueCreator; +import io.ballerina.runtime.api.utils.StringUtils; +import io.ballerina.runtime.api.values.BDecimal; +import io.ballerina.runtime.api.values.BError; +import io.ballerina.runtime.api.values.BMap; +import io.ballerina.runtime.api.values.BString; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.HashMap; +import java.util.Map; + +/** + * Contains the generic utility APIs for the time package. + * + * @since 2.0.2 + */ +public class Utils { + + private Utils() { + + } + + public static Map zoneOffsetMapFromString(String dateTime) { + + Map zone = new HashMap<>(); + if (dateTime.strip().startsWith("+")) { + dateTime = dateTime.replaceFirst("\\+", ""); + String[] zoneInfo = dateTime.split(":"); + if (zoneInfo.length > 0 && zoneInfo[0] != null) { + zone.put(Constants.ZONE_OFFSET_RECORD_HOUR, Integer.parseInt(zoneInfo[0])); + } + if (zoneInfo.length > 1 && zoneInfo[1] != null) { + zone.put(Constants.ZONE_OFFSET_RECORD_MINUTE, Integer.parseInt(zoneInfo[1])); + } + if (zoneInfo.length > 2 && zoneInfo[2] != null) { + zone.put(Constants.ZONE_OFFSET_RECORD_SECOND, Integer.parseInt(zoneInfo[2])); + } + } else if (dateTime.strip().startsWith("-")) { + dateTime = dateTime.replaceFirst("\\-", ""); + String[] zoneInfo = dateTime.split(":"); + if (zoneInfo.length > 0 && zoneInfo[0] != null) { + zone.put(Constants.ZONE_OFFSET_RECORD_HOUR, Integer.parseInt(zoneInfo[0]) * -1); + } + if (zoneInfo.length > 1 && zoneInfo[1] != null) { + zone.put(Constants.ZONE_OFFSET_RECORD_MINUTE, Integer.parseInt(zoneInfo[1]) * -1); + } + if (zoneInfo.length > 2 && zoneInfo[2] != null) { + zone.put(Constants.ZONE_OFFSET_RECORD_SECOND, Integer.parseInt(zoneInfo[2]) * -1); + } + } + return zone; + } + + public static BMap createZoneOffsetFromZoneInfoMap(Map zoneInfo) { + + BMap zoneOffsetMap = ValueCreator.createRecordValue(ModuleUtils.getModule(), + Constants.READABLE_ZONE_OFFSET_RECORD); + if (zoneInfo.get(Constants.ZONE_OFFSET_RECORD_HOUR) != null) { + zoneOffsetMap.put(Constants.ZONE_OFFSET_RECORD_HOUR_BSTRING, + zoneInfo.get(Constants.ZONE_OFFSET_RECORD_HOUR).longValue()); + } else { + zoneOffsetMap.put(Constants.ZONE_OFFSET_RECORD_HOUR_BSTRING, 0); + } + + if (zoneInfo.get(Constants.ZONE_OFFSET_RECORD_MINUTE) != null) { + zoneOffsetMap.put(Constants.ZONE_OFFSET_RECORD_MINUTE_BSTRING, + zoneInfo.get(Constants.ZONE_OFFSET_RECORD_MINUTE).longValue()); + } else { + zoneOffsetMap.put(Constants.ZONE_OFFSET_RECORD_MINUTE_BSTRING, 0); + } + + if (zoneInfo.get(Constants.ZONE_OFFSET_RECORD_SECOND) != null) { + zoneOffsetMap.put(Constants.ZONE_OFFSET_RECORD_SECOND_BSTRING, + zoneInfo.get(Constants.ZONE_OFFSET_RECORD_SECOND).longValue()); + } + zoneOffsetMap.freezeDirect(); + return zoneOffsetMap; + } + + public static ZonedDateTime createZoneDateTimeFromCivilValues(long year, long month, long day, long hour, + long minute, BDecimal second, long zoneHour, + long zoneMinute, BDecimal zoneSecond, + BString zoneAbbr, String zoneHandling) { + + ZoneId zoneId; + int intSecond = second.decimalValue().setScale(0, RoundingMode.FLOOR).intValue(); + int intNanoSecond = second.decimalValue().subtract(new BigDecimal(intSecond)).multiply(Constants.ANALOG_GIGA) + .setScale(0, RoundingMode.HALF_UP).intValue(); + int intZoneSecond = zoneSecond.decimalValue().setScale(0, RoundingMode.HALF_UP).intValue(); + if (Constants.HeaderZoneHandling.PREFER_ZONE_OFFSET.toString().equals(zoneHandling)) { + zoneId = ZoneId.of(ZoneOffset.ofHoursMinutesSeconds( + Long.valueOf(zoneHour).intValue(), Long.valueOf(zoneMinute).intValue(), intZoneSecond).toString()); + } else { + zoneId = ZoneId.of(zoneAbbr.getValue()); + } + return ZonedDateTime.of( + Long.valueOf(year).intValue(), Long.valueOf(month).intValue(), Long.valueOf(day).intValue(), + Long.valueOf(hour).intValue(), Long.valueOf(minute).intValue(), intSecond, intNanoSecond, zoneId); + } + + public static BError createError(Errors errorType, String errorMsg) { + + return ErrorCreator.createError(ModuleUtils.getModule(), errorType.name(), + StringUtils.fromString(errorMsg), null, null); + } +}