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

Add basic types for manipulating dates and times #843

Open
Tracked by #1972
palmsey opened this issue Apr 26, 2021 · 10 comments
Open
Tracked by #1972

Add basic types for manipulating dates and times #843

palmsey opened this issue Apr 26, 2021 · 10 comments

Comments

@palmsey
Copy link

palmsey commented Apr 26, 2021

Issue To Be Solved

I would like to include calculations based on elapsed time in my contract. Cadence doesn't currently have a Date/Time type, so for now I'm attempting to roll my own using UFix64 timestamps.

Date manipulation is both difficult and common and seems like a good problem to solve at the language level.

Context

I'm trying to calculate the amount due for a payment based on the difference between a timestamp on the resource and the current time.

For current time, I'm allowing the transaction signer to pass a timestamp and I validate that it is later than the timestamp on the latest block. Ideally I would be able to get the current time from Cadence (for example, by initializing a new Date type).

@turbolent
Copy link
Member

Agreed, it would be nice to add date/time functionality. This is currently not a priority, but contributions are very welcome (happy to assist)

@darkdrag00nv2
Copy link
Contributor

A small MVP proposal to kickstart the discussion. We can discuss here and then I'll write up a FLIP later.

pub struct DateTime {
	/// The timestamp representing the DateTime
	let timestamp : UFix64

	/// Get the year of the date based on the timestamp.
	pub fun getYear(): UInt64

	/// Get the month of the date based on the timestamp.
	pub fun getMonth(): UInt64

	/// Get the day of the date based on the timestamp.
	pub fun getDay(): UInt64

	/// Get the hour of the date based on the timestamp.
	pub fun getHour(): UInt64

	/// Get the minute of the date based on the timestamp.
	pub fun getMinute(): UInt64

	/// Get the second of the date based on the timestamp.
	pub fun getSecond(): UInt64

	/// Mutate the DateTime by adding years to the timestamp.
	pub fun addYears(years: UInt64)

	/// Mutate the DateTime by adding months to the timestamp.
	pub fun addMonths(months: UInt64)

	/// Mutate the DateTime by adding days to the timestamp.
	pub fun addDays(days: UInt64)

	/// Mutate the DateTime by adding hours to the timestamp.
	pub fun addHours(hours: UInt64)

	/// Mutate the DateTime by adding minutes to the timestamp.
	pub fun addMinutes(minutes: UInt64)

	/// Mutate the DateTime by adding seconds to the timestamp.
	pub fun addSeconds(seconds: UInt64)
}

/// Returns the date time based on the current block timestamp.
DateTime.now()

/// Returns the date time based on the given timestamp.
DateTime.fromTimestamp(timestamp : UFix64)

@dsainati1
Copy link
Contributor

Couple minor suggestions:

  • Rather than having months be represented as a UInt64, let's use a Month enum (for the getter) whose underlying values are the months from 1-12. This should be easier to read and has less room for error.

  • I don't think the return types of these getters need to be UInt64s either, that would allocate a lot of unnecessary size for values like hours or days that fit within a UInt8

  • Are seconds the lowest level of granularity we'll support with this interface? Are block timestamps measured in seconds?

  • Might be nice to be able to get the day of the week from a DateTime, could also represent this as an enum.

  • Is there support for time zones? If so it would be useful to be able to convert an existing DateTime to the same timestamp in a different time zone.

@turbolent
Copy link
Member

Good start @darkdrag00nv2!

In general, we do not have to reinvent the wheel here and can probably get a lot of inspiration from existing APIs in other languages. For example:

I'm not saying this issue should result in all the functionality of those libraries being made available, but rather just that we can start with some simple data structures, that are inspired by proven solutions.

@darkdrag00nv2
Copy link
Contributor

Thanks for the suggestions @dsainati1 and @turbolent.

I'm not saying this issue should result in all the functionality of those libraries being made available, but rather just that we can start with some simple data structures, that are inspired by proven solutions.

Agreed.

I believe we can start with for an MVP:

  1. DateTime
  2. Duration (datetime.timedelta of Python, Interval of Joda time)

In future we could add the following depending on requests/need.

  1. Timezone
  2. Date
  3. Time

@dsainati1

Ack on your suggestion 1, 2 (enums and UInt8) and 4 (day of week).

Are seconds the lowest level of granularity we'll support with this interface? Are block timestamps measured in seconds?

We could support microseconds as well. Python datetime supports it.

Yes, seems like block timestamps are measured in seconds based on this snippet.

return uint64(time.Unix(0, block.Timestamp).Unix())

Is there support for time zones? If so it would be useful to be able to convert an existing DateTime to the same timestamp in a different time zone.

I originally didn't plan on supporting time zones in the first phase but can do if needed.

@dsainati1
Copy link
Contributor

There's no point IMO in supporting microseconds if the minimum granularity of block timestamps is seconds; we should support the granularity with which blocks are measured and no smaller.

@turbolent
Copy link
Member

AFAIK the block time granularity is undefined. Supporting sub-second granularity is maybe unnecessary today, but future-proof

@dsainati1
Copy link
Contributor

Possibly I am missing something, but it seems like in order to support any kind of DateTime, we have to implicitly choose a timezone in which all those dates/times appear. I.e. in order to convert a unix timestamp into a concrete date and time, that time implicitly has a zone associated with it. Given this I am not sure how it is possible to support DateTime without implementing timezone support along with it, unless we would be choosing one timezone only to support and representing all times in that zone. IMO given the global nature of Flow's community this would be an unfortunate decision to make.

@turbolent
Copy link
Member

[...] it seems like in order to support any kind of DateTime, we have to implicitly choose a timezone in which all those dates/times appear.

This choice can be made by the application/developer, it does not necessarily have to be part of the type itself. Providing types without timezone information and related operations is a good starting point. It will put the burden of handling timezone details (if needed) on developers at first, but we can later add timezone-aware types/functionality.

For example, in Java, this is what the Local* types are (LocalDate, LocalTime, LocalDateTime; see e.g. https://belief-driven-design.com/essentials-of-java-time-59cff478fdf/#local-types), and in Python these are so-called "naive" types (see https://docs.python.org/3/library/datetime.html#aware-and-naive-objects).

@j1010001 j1010001 changed the title Add a Date type Add basic types for manipulating dates and times Jan 11, 2024
@turbolent
Copy link
Member

This is a great overview of pitfalls to avoid when designing the library: https://dev.arie.bovenberg.net/blog/python-datetime-pitfalls/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants