Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FLIP 251: Cadence Date and Time #245

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
245 changes: 245 additions & 0 deletions cadence/20240115-cadence-date-time.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
---
status: draft
flip: 245
authors: darkdrag00n ([email protected])
sponsor: Bastian Müller ([email protected])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
sponsor: Bastian Müller (bastian@dapperlabs.com)
sponsor: Bastian Müller (bastian[email protected])

updated: 2024-01-29
---

# FLIP 245: Date & Time

## Objective

This FLIP proposes adding Date & Time types and manipulation utilities to Cadence. It proposes two new types, `LocalDateTime` and `Duration` along with functions to create and manipulate them.

## Motivation

Manipulating Date & Time is a common aspect of user programs. Presently, Cadence users have to implement these calculations using raw `UFix64` timestamps.

Doing them manually is both hard and error-prone. As a result, standard library support similar to other modern languages would enhance the usability of the language.

## User Benefit

Manipulating date and time using raw timestamps is difficult and error-prone. Providing rich suite of standard library types and functions would increase the usability and user-experience.

## Design Proposal

New types `LocalDateTime` & `Duration` will be added to Cadence. Both of these data types assume that there are 3600*24 seconds in every day and there are no leap years.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should add this functionality as a standard library contract, i.e. namespace it and not add them to the global namespace.


### LocalDateTime

The `LocalDateTime` type will be defined as follows:

```cadence
pub struct LocalDateTime {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The proposal needs to be updated to Cadence 1.0. For example:

  • pub -> access(all)
  • Add view function modifier to getter functions

let timestamp : UFix64
}
```

Please note the following points:
1. The stored timestamp represents the seconds elapsed since epoch (1st Jan 1970 00:00:00z)
2. The type will **not** be timezone aware i.e. it'll be the responsibility of the application developer to handle timezone details.
3. It is assumed that there are 3600*24 seconds in every day and there are no leap years.

Apart from the `LocalDateTime`, a few other types will be added to Cadence. They are:

1. `Month`
2. `Day`

```cadence
pub enum Month: UInt8 {
pub case january
pub case february
pub case march
pub case april
pub case may
pub case june
pub case july
pub case august
pub case september
pub case october
pub case november
pub case december
}

pub enum Day: UInt8 {
pub case monday
pub case tuesday
pub case wednesday
pub case thursday
pub case friday
pub case saturday
pub case sunday
}
```

#### Constructor Functions
Constructor functions will be defined to instantiate variables of type `LocalDateTime`. They are defined below.

- `LocalDateTime.now()`: Creates a `LocalDateTime` with value based on the current block timestamp.

- `LocalDateTime.fromTimestamp(timestamp : UFix64)`: Creates a `LocalDateTime` with value based on the passed timestamp argument.

- `LocalDateTime.of(year: UInt64, month: Month, day: UInt8, hour: UInt8, minute: UInt8, second: UInt8)`: Creates a `LocalDateTime` with value based on the passed arguments assuming that there are 3600*24 seconds in every day.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can maybe just be a constructor function. It might be good to improve the naming of day and make it explicit that it is the day of the month, e.g. dayOfMonth:

Suggested change
- `LocalDateTime.of(year: UInt64, month: Month, day: UInt8, hour: UInt8, minute: UInt8, second: UInt8)`: Creates a `LocalDateTime` with value based on the passed arguments assuming that there are 3600*24 seconds in every day.
- `LocalDateTime(year: UInt64, month: Month, dayOfMonth: UInt8, hour: UInt8, minute: UInt8, second: UInt8)`: Creates a `LocalDateTime` with value based on the passed arguments assuming that there are 3600*24 seconds in every day.

Examples below also need to be updated


#### Public Members
The `LocalDateTime` will provide the following public members:

1. `getYear(): UInt32`: Get the year of the date-time.
2. `getMonth(): Month`: Get the month of the date-time.
3. `getDate(): UInt8`: Get the date of the date-time.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe improve the naming and clarify it is the day of the month, i.e.

Suggested change
3. `getDate(): UInt8`: Get the date of the date-time.
3. `getDayOfMonth(): UInt8`: Get the day-of-month of the date-time.

Examples below also need to be updated

4. `getHour(): UInt8`: Get the hour of the date-time.
5. `getMinute(): UInt8`: Get the minute of the date-time.
6. `getSecond(): UInt8`: Get the second of the date-time.
7. `getDayOfWeek(): Day`: Get the day-of-week of the date-time.

#### Examples
Example usage of `LocalDateTime` are:

```cadence
/// Assume that the current block timestamp is 1707045210
let currentDateTime = LocalDateTime.now()
currentDateTime.getYear() // 2024
currentDateTime.getMonth() // february
currentDateTime.getDate() // 04
currentDateTime.getHour() // 11
currentDateTime.getMinute() // 13
currentDateTime.getSecond() // 30
currentDateTime.getDayOfWeek() // sunday

let dateTimeFromTs = LocalDateTime.fromTimestamp(1415829132)
dateTimeFromTs.getYear() // 2014
dateTimeFromTs.getMonth() // november
dateTimeFromTs.getDate() // 12
dateTimeFromTs.getHour() // 21
dateTimeFromTs.getMinute() // 52
dateTimeFromTs.getSecond() // 12
dateTimeFromTs.getDayOfWeek() // wednesday

let dateTimeFromComponents = LocalDateTime.of(2019, april, 29, 19, 49, 31)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to add argument labels, as required by the function declaration

dateTimeFromComponents.getYear() // 2019
dateTimeFromComponents.getMonth() // april
dateTimeFromComponents.getDate() // 29
dateTimeFromComponents.getHour() // 19
dateTimeFromComponents.getMinute() // 49
dateTimeFromComponents.getSecond() // 31
dateTimeFromComponents.getDayOfWeek() // monday
```

### Duration
A `Duration` will represent the difference between two dates or times. It will defined as follows:

```cadence
pub struct Duration {
let microseconds: UInt64
let seconds: UInt64
let days: Int64
}
```

The three internal fields will be stored in the normalized form with the following restrictions:
1. `0 <= microseconds < 10^6`
2. `0 <= seconds < 3600*24`
3. `-999999999 <= days <= 999999999`

This model is taken from the time-tested Python [datetime module](https://docs.python.org/3/library/datetime.html#timedelta-objects).

#### Constructor Functions
Constructor functions will be defined to instantiate variables of type `Duration`. They are defined below.

- `Duration.betweenLocalDateTime(t1 : LocalDateTime, t2 : LocalDateTime)`: Creates a `Duration` which represents the time duration between arguments `t1` and `t2`.

- `Duration.of(years: Int64, days: Int64, hours: Int64, minutes: Int64, seconds: Int64, miliseconds: Int64, microseconds: Int64)`: Creates a `Duration` with value based on the passed arguments assuming that there are 3600*24 seconds in every day.

During the normalization:
1. 1 year is converted to 365 days
2. 1 hour is converted to 3600 seconds
3. 1 minute is converted to 60 seconds
4. 1 milisecond is converted to 1000 microseconds

#### Public Members
The `Duration` will provide the following public members:

- `getDays(): Int32`: Get the days of the `Duration`.
- `getSeconds(): UInt32`: Get the second of the `Duration`.
- `getMicroseconds(): UInt32`: Get the microseconds of the `Duration`.
- `addYears(years : UInt32): Duration`: Return a new `Duration` after adding the provided number of years to it.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and for the other functions below: The function name already clarifies what the parameter is, so the argument label can be removed:

Suggested change
- `addYears(years : UInt32): Duration`: Return a new `Duration` after adding the provided number of years to it.
- `addYears(_ years: UInt32): Duration`: Return a new `Duration` after adding the provided number of years to it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The add name sounds like it is mutating, but it seems like that isn't the case right?

Maybe we can use the plus and minus naming like in Java?

- `addDays(days : UInt32): Duration`: Return a new `Duration` after adding the provided number of days to it.
- `addHours(hours : UInt32): Duration`: Return a new `Duration` after adding the provided number of hours to it.
- `addMinutes(minutes : UInt32): Duration`: Return a new `Duration` after adding the provided number of minutes to it.
- `addSeconds(seconds : UInt32): Duration`: Return a new `Duration` after adding the provided number of seconds to it.
- `addMiliseconds(ms : UInt32): Duration`: Return a new `Duration` after adding the provided number of miliseconds to it.
- `addMicroseconds(microsecs : UInt32): Duration`: Return a new `Duration` after adding the provided number of microseconds to it.
- `subtractYears(years : UInt32): Duration`: Return a new `Duration` after subtracting the provided number of years from it.
- `subtractDays(days : UInt32): Duration`: Return a new `Duration` after subtracting the provided number of days from it.
- `subtractHours(hours : UInt32): Duration`: Return a new `Duration` after subtracting the provided number of hours from it.
- `subtractMinutes(minutes : UInt32): Duration`: Return a new `Duration` after subtracting the provided number of minutes from it.
- `subtractSeconds(seconds : UInt32): Duration`: Return a new `Duration` after subtracting the provided number of seconds from it.
- `subtractMiliseconds(ms : UInt32): Duration`: Return a new `Duration` after subtracing the provided number of miliseconds from it.
- `subtractMicroseconds(microsecs : UInt32): Duration`: Return a new `Duration` after subtracing the provided number of microseconds from it.

#### Examples

Example usage of `Duration` are

```cadence
let t1 = LocalDateTime.fromTimestamp(1415829132)
/// Assume that the current block timestamp is 1707045210
let t2 = LocalDateTime.now()
let d = Duration.betweenLocalDateTime(t1, t2)
d.getDays() // 3370
d.getSeconds() // 48078
d.getMicroseconds() // 0

let d2 = Duration.of(0, 365, 0, 0, 0, 0, 0) // 365 days
let d3 = Duration.of(1, 0, 0, 0, 0, 0, 0) // 1 year
d2.getDays() // 365
d3.getDays() // 365

// Refer to the normalization rules. Only days can be negative.
let normalizedDuration = Duration.of(0, 0, 0, 0, 0, 0, -1) // -1 microseconds
normalizedDuration.getDays() // -1
normalizedDuration.getSeconds() // 86399
normalizedDuration.getMicroseconds() // 999999
```

### Drawbacks

None

### Alternatives Considered

None

### Performance Implications

None

### Dependencies

None

### Engineering Impact

It would require 3-4 weeks of engineering effort to implement, review & test the feature.

### Compatibility

This change has no impact on compatibility between systems (e.g. SDKs).

### User Impact

The proposed feature is a purely additive.
There is no impact on existing contracts and new transactions.

## Related Issues

None

## Questions and Discussion Topics

None

## Implementation
Will be done as part of https://github.com/onflow/cadence/issues/843.