ballerina-platform / ballerina-library

The Ballerina Library
https://ballerina.io/learn/api-docs/ballerina/
Apache License 2.0
136 stars 58 forks source link

[Time] Add support add/subtract specified amount of year, month, date from the given time #6840

Open daneshk opened 1 month ago

daneshk commented 1 month ago

Description: In the current time module, we only support adding/subtracting seconds from the given time in UTC. This is because we don't support advanced cases like leap seconds, daylight saving, etc in the module. If we need to support adding/subtracting specific no of year, month, and date, we need to consider those scenarios as well.

This task is to check the possibility of supporting it in the time module.

Refer: https://github.com/wso2-enterprise/internal-support-ballerina/issues/743

daneshk commented 3 weeks ago

Extracted from the Ballerina time module specification,

Nominal duration

A specification of a duration in terms of a variety of units (e.g. months, years) which can be resolved to a definite duration only relative to a particular timestamp.

Apart from seconds, the units are

Note that all of these other than seconds are affected by leap seconds.

If you ignore leap seconds, all except months and years can be resolved into a definite number of seconds independent of a timestamp.

We can represent this as a record with optional fields (or with fields defaulting to 0).

Arithmetic using nominal durations and broken-down date-time has several cases that are poorly defined e.g.

There's an algorithm in XML Schema Part 2 we can use https://www.w3.org/TR/xmlschema-2/#adding-durations-to-dateTimes

daneshk commented 3 weeks ago

In practice, handling leap seconds is often done by relying on systems or services (like NTP servers) that are designed to manage leap seconds. Most applications simply ignore leap seconds because their occurrence is so infrequent and they typically have a negligible impact on most time calculations.

Leap Seconds in Java:

Leap Seconds in Golang:

Python's Time Libraries:

daneshk commented 3 weeks ago

When adding a duration to a specific timestamp while considering Daylight Saving Time (DST), you need to account for potential DST transitions that may cause the local time to shift forward or backward. Here we need timezone-aware timestamps

Using timezone-aware objects (like ZonedDateTime in Java or datetime with pytz/zoneinfo in Python) ensures that any DST-related shifts are accurately reflected in the final timestamp.

daneshk commented 3 weeks ago

The proposed design contains APIs to cater to the following scenarios,

Sample code:

time:Civil civil = check time:civilFromString("2021-04-12T23:20:50.520Z");
time:Duration duration = {years: 1, months: 2, days: 3, hours: 4, minutes: 5, seconds: 6};
time:Civil newCivil = time:civilAddDuration(civil, duration);
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;

    # Adds the given datetime duration to the given civil time.  This is a time zone-aware calculation
    # + civil - The civil time to which the duration should be added
    # + deltaTime - The datetime duration to be added
    public isolated function civilAddDuration(Civil civil, Duration dateTime) returns Civil;
};

Sample code

time:TimeZone timeZone = check new("Asia/Colombo");
time:Civil localTime = check timeZone.utcToCivil(time:utcNow());
time:Duration duration = {years: 1, months: 2, days: 3, hours: 4, minutes: 5, seconds: 6};
time:Civil newCivil = timeZone.civilAddDuration(localTime, duration);
daneshk commented 3 weeks ago

@jclark @sameerajayasoma, could you please review the proposed API to add duration for the civil record? The requirement is to add/subtract a DateTime (provided by the user) from the current date and get the modified date. We only provide the utcAddSeconds[1] API that works with seconds.

  1. https://central.ballerina.io/ballerina/time/latest#utcAddSeconds