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

New additions to dw::core::Periods #19

Open
machaval opened this issue Oct 6, 2020 · 7 comments
Open

New additions to dw::core::Periods #19

machaval opened this issue Oct 6, 2020 · 7 comments

Comments

@machaval
Copy link
Contributor

machaval commented Oct 6, 2020

After discussing that dates manipulation is hard we started working on a way to create Periods. I would like to start discussing the new additions

dw::core::Periods

/**
* This module contains functions for working and creating Periods
*/
%dw 2.0

/**
*
* Returns a Period consisting of the number of years, months,
* and days between two dates.
* The start date is included, but the end date is not.
* The period is calculated by removing complete months, then calculating
* the remaining number of days, adjusting to ensure that both have the same sign.
* The number of months is then split into years and months based on a 12 month year.
* A month is considered if the end day-of-month is greater than or equal to the start day-of-month.
* For example, from `2010-01-15` to `2011-03-18` is one year, two months and three days.
* The result of this method can be a negative period if the end is before the start.
*
*  _Introduced in DataWeave 2.3.1. Supported by Mule 4.3.1 and later._
*
* === Parameters
*
* [%header, cols="1,3"]
* |===
* | Name   | Description
* | startDateInclusive | the start date, inclusive.
* | endDateExclusive | the end date, exclusive.
* |===
*
* === Example
*
* This example shows how the `between` behaves under different inputs.
*
* ==== Source
*
* [source,DataWeave,linenums]
* ----
* import * from dw::core::Periods
* output application/json
* ---
* {
*   a: between(|2010-12-12|,|2010-12-10|),
*   b: between(|2010-12-10|,|2011-12-11|)
* }
*
* ----
*
* ==== Output
*
* [source,Json,linenums]
* ----
* {
*    "a": "P2D",
*    "b": "P-1Y-1D"
*  }
* ----
**/
@Since(version = "2.3.1")
fun between(startDateInclusive: Date, endDateExclusive: Date): Period = native("system::BetweenLocalDateOperator")


/**
* Creates a Duration that represents a time-based amount of time.
* The Duration is build with the given
*  - days : Number of days
*  - hours : Number of hours
*  - minutes : Number of minutes
*  - seconds : Number of seconds
*
*  Any of the given parts can be a decimal number and the corresponding transformation is going to be done.
*
*  _Introduced in DataWeave 2.3.1. Supported by Mule 4.3.1 and later._
*
* === Parameters
*
* [%header, cols="1,3"]
* |===
* | Name   | Description
* | period | The period value to be created
* |===
*
* === Example
*
* This example shows how the `duration` behaves under different inputs.
*
* ==== Source
*
* [source,DataWeave,linenums]
* ----
* %dw 2.0
* import * from dw::core::Periods
* output application/json
* ---
* {
*    yesterday: |2020-10-05T20:22:34.385Z| - duration({days: 1}),
*    dayBeforeHourBefore: |2020-10-05T20:22:34.385Z| - duration({days: 1, hours: 1}),
*    pointInTimeBefore: |2020-10-05T20:22:34.385Z| - duration({days: 1, hours: 1, minutes: 20, seconds: 10})
*  }
* ----
*
* ==== Output
*
* [source,Json,linenums]
* ----
* {
*   "yesterday": "2020-10-04T20:22:34.385Z",
*   "dayBeforeHourBefore": "2020-10-04T19:22:34.385Z",
*   "pointInTimeBefore": "2020-10-04T19:02:24.385Z"
* }
* ----
**/
@Since(version = "2.3.1")
fun duration(period:{ days?: Number, hours?: Number, minutes?: Number, seconds?: Number }): Period =  do {
    var nDays   = period.days default 0
    var days    = floor(nDays)
    var nHours  = period.hours default 0 + ((nDays - days) * 60)
    var hours   = floor(nHours)
    var nMinutes= period.minutes default 0 + ((nHours - hours) * 60)
    var minutes = floor(nMinutes )
    var seconds = period.seconds default 0 + ((nMinutes - minutes) * 60)
    ---
    "P$(days as String {format:'#'})DT$(hours as String {format:'#'})H$(minutes as String {format:'#'})M$(seconds as String)S" as Period
}

/**
* Creates a Period that represents date-based amount of time in the ISO-8601 calendar system, such as '2 years, 3 months and 4 days'.
*
*
*  _Introduced in DataWeave 2.3.1. Supported by Mule 4.3.1 and later._
*
* === Parameters
*
* [%header, cols="1,3"]
* |===
* | Name   | Description
* | period | The period object
* |===
*
* === Example
*
* This example shows how the `period` behaves under different inputs.
*
* ==== Source
*
* [source,DataWeave,linenums]
* ----
* %dw 2.0
* output application/json
* import * from dw::core::Periods
* ---
* {
*   lastYear: |2020-10-05T20:22:34.385Z| - period({years: 1}),
*   "1year1month1dayBefore": |2020-10-05T20:22:34.385Z| - duration({years: 1, months: 1, days: 1})
* }
*
* ----
*
* ==== Output
*
* [source,Json,linenums]
* ----
* {
*    "lastYear": "2019-10-05T20:22:34.385Z",
*    "1year1month1dayBefore": "2019-09-04T20:22:34.385Z"
*  }
* ----
**/
@Since(version = "2.3.1")
fun period(period:{ years?: Number, months?: Number, days?: Number}): Period =  do {
    var years = (period.years default 0)
    var months = (period.years default 0)
    var days = (period.years default 0)
    var nYears = if(isDecimal(years)) dw::Runtime::fail("Field years: `$(years)`, can not be decimal.") else years as String {format: "#"}
    var nMonth = if(isDecimal(months)) dw::Runtime::fail("Field months: `$(months)`, can not be decimal.") else months as String {format: "#"}
    var nDays = if(isDecimal(days)) dw::Runtime::fail("Field days: `$(days)`, can not be decimal.") else days as String {format: "#"}
    ---
    "P$(nYears)Y$(nMonth)M$(nDays)D" as Period
}

/**
* Create a Period that represents a given amount of years, such as `3years`
*
*
*  _Introduced in DataWeave 2.3.1. Supported by Mule 4.3.1 and later._
*
* === Parameters
*
* [%header, cols="1,3"]
* |===
* | Name   | Description
* | nYears | The number of years
* |===
*
* === Example
*
* This example shows how the `years` behaves under different inputs.
*
* ==== Source
*
* [source,DataWeave,linenums]
* ----
* %dw 2.0
* import * from dw::core::Periods
* output application/json
* ---
* {
*   nextYear: |2020-10-05T20:22:34.385Z| + years(1)
* }
*
* ----
*
* ==== Output
*
* [source,Json,linenums]
* ----
* {
*    "nextYear": "2021-10-05T20:22:34.385Z"
* }
* ----
**/
@Since(version = "2.3.1")
fun years(nYears: Number):Period =
  period({years: nYears})


/**
* Create a Period that represents a given amount of months, such as `2months`
*
*
*  _Introduced in DataWeave 2.3.1. Supported by Mule 4.3.1 and later._
*
* === Parameters
*
* [%header, cols="1,3"]
* |===
* | Name   | Description
* | nYears | The number of months
* |===
*
* === Example
*
* This example shows how the `months` behaves under different inputs.
*
* ==== Source
*
* [source,DataWeave,linenums]
* ----
* %dw 2.0
* import * from dw::core::Periods
* output application/json
* ---
* {
*   nextMonth: |2020-10-05T20:22:34.385Z| + months(1)
* }
*
* ----
*
* ==== Output
*
* [source,Json,linenums]
* ----
* {
*   "nextMonth": "2020-11-05T20:22:34.385Z"
* }
* ----
**/
@Since(version = "2.3.1")
fun months(nMonths: Number):Period =
  period({months: nMonths})

/**
* Create a Period that represents a given amount of days, such as `2days`
*
*
*  _Introduced in DataWeave 2.3.1. Supported by Mule 4.3.1 and later._
*
* === Parameters
*
* [%header, cols="1,3"]
* |===
* | Name   | Description
* | nYears | The number of days. It can be a decimal number
* |===
*
* === Example
*
* This example shows how the `days` behaves under different inputs.
*
* ==== Source
*
* [source,DataWeave,linenums]
* ----
* %dw 2.0
* import * from dw::core::Periods
* output application/json
* ---
* {
*   tomorrow: |2020-10-05T20:22:34.385Z| + days(1),
*   yesterday: |2020-10-05T20:22:34.385Z| - days(1)
* }
* ----
*
* ==== Output
*
* [source,Json,linenums]
* ----
* {
*   "tomorrow": "2020-10-06T20:22:34.385Z",
*   "yesterday": "2020-10-04T20:22:34.385Z"
* }
* ----
**/
@Since(version = "2.3.1")
fun days(nDays: Number):Period =
  duration({days: nDays})

/**
* Create a Duration that represents a given amount of hours, such as `4hours`
*
*
*  _Introduced in DataWeave 2.3.1. Supported by Mule 4.3.1 and later._
*
* === Parameters
*
* [%header, cols="1,3"]
* |===
* | Name   | Description
* | nYears | The number of hours.  It can be a decimal number
* |===
*
* === Example
*
* This example shows how the `hours` behaves under different inputs.
*
* ==== Source
*
* [source,DataWeave,linenums]
* ----
* %dw 2.0
* import * from dw::core::Periods
* output application/json
* ---
* {
*   nextHour: |2020-10-05T20:22:34.385Z| + hours(1),
*   previousHour: |2020-10-05T20:22:34.385Z| - hours(1)
* }
* ----
*
* ==== Output
*
* [source,Json,linenums]
* ----
*{
*  "nextHour": "2020-10-05T21:22:34.385Z",
*  "previousHour": "2020-10-05T19:22:34.385Z"
*}
* ----
**/
@Since(version = "2.3.1")
fun hours(nHours: Number):Period =
  duration({hours: nHours})



/**
* Create a Duration that represents a given amount of minutes, such as `1minutes`
*
*
*  _Introduced in DataWeave 2.3.1. Supported by Mule 4.3.1 and later._
*
* === Parameters
*
* [%header, cols="1,3"]
* |===
* | Name   | Description
* | nYears | The number of minutes.  It can be a decimal number
* |===
*
* === Example
*
* This example shows how the `minutes` behaves under different inputs.
*
* ==== Source
*
* [source,DataWeave,linenums]
* ----
* %dw 2.0
* import * from dw::core::Periods
* output application/json
* ---
* {
*   nextMinute: |2020-10-05T20:22:34.385Z| + minutes(1),
*   previousMinute: |2020-10-05T20:22:34.385Z| - minutes(1)
* }
* ----
*
* ==== Output
*
* [source,Json,linenums]
* ----
{
  "nextMinute": "2020-10-05T20:23:34.385Z",
  "previousMinute": "2020-10-05T20:21:34.385Z"
}
* ----
**/
@Since(version = "2.3.1")
fun minutes(nMinutes: Number):Period =
  duration({minutes: nMinutes})



/**
* Create a Duration that represents a given amount of seconds, such as `1second`
*
*
*  _Introduced in DataWeave 2.3.1. Supported by Mule 4.3.1 and later._
*
* === Parameters
*
* [%header, cols="1,3"]
* |===
* | Name   | Description
* | nYears | The number of seconds.  It can be a decimal number
* |===
*
* === Example
*
* This example shows how the `seconds` behaves under different inputs.
*
* ==== Source
*
* [source,DataWeave,linenums]
* ----
* %dw 2.0
* import * from dw::core::Periods
* output application/json
* ---
* {
*   nextSecond: |2020-10-05T20:22:34.385Z| + seconds(1),
*   previousSecond: |2020-10-05T20:22:34.385Z| - seconds(1)
* }
* ----
*
* ==== Output
*
* [source,Json,linenums]
* ----
* {
*   "nextSecond": "2020-10-05T20:22:35.385Z",
*   "previousSecond": "2020-10-05T20:22:33.385Z"
* }
* ----
**/
@Since(version = "2.3.1")
fun seconds(nSecs: Number):Period =
  duration({seconds: nSecs})

Examples

import * from dw::core::Periods
---
{
  yesterday: now() - days(1),
  lastYear: now() - years(1),
  lastYearBis: now() - period({years: 1}),
  anHourAndHalf: now() - hours(1.5),
  anHourAndHalf: now() - duration({hours:1.5}),
}

A duration is a Time Based Period. And a Period is a Date base Period.

Time based does support decimal numbers and the correct calculation should be done.
Date based DOESN'T support decimal numbers and it will FAIL.

@machaval
Copy link
Contributor Author

machaval commented Oct 6, 2020

This is releated from #17

@jorgegarciamule
Copy link

I think
var nHours = period.hours default 0 + ((nDays - days) * 60)
should be
var nHours = period.hours default 0 + ((nDays - days) * 24)

@gozdy
Copy link

gozdy commented Oct 6, 2020

Would it be possible to set a return format for the between function? This would allow the function to return different time formats. Example:

import * from dw::core::Periods
output application/json
---
{
  a: between(|2010-12-12|,|2010-12-10|) // default, returns a period
   b: between(|2010-12-12|,|2010-12-10|, "seconds") // returns difference in seconds
  c:  between(|2010-12-12|,|2010-12-10|, "hours") // returns difference in hours

 }

==== Output

 {
    "a": "P2D",
    "b": 172800,
    "c": 48
  }

@jorgegarciamule
Copy link

The idea is to not truncate seconds anywhere to handle milis or even nanos? What does Period support? I'm thinking about problems that can arise about not defining hard limits.

@jorgegarciamule
Copy link

jorgegarciamule commented Oct 6, 2020

Would it be possible to set a return format for the between function? This would allow the function to return different time formats...

Now you can do:

between(|2010-12-12|,|2010-12-10|) as Number {unit: "months"}

Not very intuitive to guess you need to write "months" neither.

The other way can be also to create casting functions like toDays() but you will end with a full list of methods like this one. I'm not sure which is the cleanest way.

@machaval
Copy link
Contributor Author

machaval commented Oct 6, 2020

Sorry for not explaining between function was introduce because of a backwards comp issue. For date - date operator it used to return a Period and not a Duration. This was very problematic as Duration is the only thing that can be translated to millis o any other unit of time. So we changed date - date to return Period so it is consistent with datetime - datetime etc... but in order to have a migration path we introduce a System property that behaves just as it used to be and we also introduce this function that returns the Period between the two dates.
Regarding type conversions we have a separate issue #18 where we should start talking about this kind of things. We know is it not intuitive to do this so we think we should start working on a new way of doing this things.

@jorgegarciamule
Copy link

I see we are tackling durations against Time, but what about working with durations to sum or subtract them? Would simplify this scenario https://stackoverflow.com/questions/68958484/how-to-aggregate-data-with-particular-column-using-dataweave-2-0

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

No branches or pull requests

3 participants