Implement addition and subtraction for Date_Period and Time_Period (#6956)

Date.+ should allow Date_Period, Time_Of_Day.+ should allow Time_Period and Date_TIme.+ should allow both.
Same for subtraction.
This commit is contained in:
GregoryTravis 2023-06-07 08:46:19 -04:00 committed by GitHub
parent 0a4df4faf3
commit fcc57e44e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 206 additions and 63 deletions

View File

@ -480,6 +480,8 @@
- [Split `Table.create_database_table` into `Connection.create_table` and
`Table.select_into_database_table`.][6925]
- [Speed improvements to `Column` `.truncate`, `.ceil`, and `.floor`.][6941]
- [Implemented addition and subtraction for `Date_Period` and
`Time_Period`.][6956]
[debug-shortcuts]:
https://github.com/enso-org/enso/blob/develop/app/gui/docs/product/shortcuts.md#debug
@ -698,6 +700,7 @@
[6922]: https://github.com/enso-org/enso/pull/6922
[6925]: https://github.com/enso-org/enso/pull/6925
[6941]: https://github.com/enso-org/enso/pull/6941
[6956]: https://github.com/enso-org/enso/pull/6956
#### Enso Compiler

View File

@ -430,7 +430,8 @@ type Date
Add the specified amount of time to this instant to get another date.
Arguments:
- amount: The time duration to add to this instant.
- amount: The amount of time to add to this instant. It can be a
`Period` or `Date_Period`.
> Example
Add 6 months to a local date.
@ -438,15 +439,20 @@ type Date
import Standard.Base.Data.Time.Duration
example_add = Date.new 2020 + (Period.new months=6)
+ : Period -> Date ! (Time_Error | Illegal_Argument)
+ self period =
case period of
_ : Period ->
> Example
Add a month to a local date.
import Standard.Base.Data.Time.Date_Period
example_add = Date.new 2020 + Date_Period.Month
+ : Period | Date_Period -> Date ! (Time_Error | Illegal_Argument)
+ self amount:(Period|Date_Period) =
case amount of
period : Period ->
Time_Utils.date_adjust self Time_Utils.AdjustOp.PLUS period.internal_period
_ : Duration ->
Error.throw (Time_Error.Error "Date does not support adding/subtracting Duration. Use Period instead.")
_ ->
Error.throw (Illegal_Argument.Error "Illegal period argument")
date_period : Date_Period ->
self + date_period.to_period
## ALIAS Date Range
Creates an increasing range of dates from `self` to `end`.
@ -623,7 +629,8 @@ type Date
date.
Arguments:
- amount: The time duration to subtract from this date.
- amount: The amount of time to add to this instant. It can be a
`Period` or `Date_Period`.
> Example
Subtract 7 days from a local date.
@ -632,16 +639,21 @@ type Date
import Standard.Base.Data.Time.Duration
example_subtract = Date.new 2020 - (Period.new days=7)
- : Period -> Date ! (Time_Error | Illegal_Argument)
- self period =
case period of
_ : Period ->
> Example
Subtract a month from a local date.
import Standard.Base.Data.Time.Date_Period
example_add = Date.new 2020 - Date_Period.Month
- : Period | Date_Period -> Date ! (Time_Error | Illegal_Argument)
- self amount:(Period|Date_Period) =
case amount of
period : Period ->
new_java_date = Time_Utils.date_adjust self Time_Utils.AdjustOp.MINUS period.internal_period
Date.new new_java_date.year new_java_date.month new_java_date.day
_ : Duration ->
Error.throw (Time_Error.Error "Date does not support adding/subtracting Duration. Use Period instead.")
_ ->
Error.throw (Illegal_Argument.Error "Illegal period argument")
date_period : Date_Period ->
self - date_period.to_period
## PRIVATE
Convert to a display representation of this Date.

View File

@ -506,9 +506,8 @@ type Date_Time
Add the specified amount of time to this instant to produce a new instant.
Arguments:
- amount: The amount of time to add to this instant, either `Duration` for
adding time (hours, minutes, etc.), or `Period` for adding date (days,
months, years).
- amount: The amount of time to add to this instant, It can be a
`Duration`, `Period`, `Time_Period`, or `Date_Period`.
> Example
Add 15 years and 3 hours to a zoned date time.
@ -517,13 +516,24 @@ type Date_Time
from Standard.Base.Data.Time import Duration
example_plus = Date_Time.new 2020 + (Period.new years=15) + (Duration.new hours=3)
+ : (Duration | Period) -> Date_Time ! Time_Error
+ self amount =
> Example
Add one quarter to a zoned date time.
from Standard.Base import Date_Time, Date_Period
example_plus = Date_Time.new 2020 + Date_Period.Quarter
+ : (Duration | Period | Time_Period | Date_Period) -> Date_Time ! Time_Error
+ self amount:(Duration|Period|Time_Period|Date_Period) =
case amount of
duration : Duration ->
Panic.catch ArithmeticException (self.plus_builtin duration) (err -> Error.throw (Time_Error.Error err.getMessage))
period : Period ->
Time_Utils.datetime_adjust self Time_Utils.AdjustOp.PLUS period.internal_period
time_period : Time_Period ->
self + time_period.to_duration
date_period : Date_Period ->
self + date_period.to_period
## Shift the date by the specified amount of business days.
@ -573,9 +583,8 @@ type Date_Time
Produces a warning if the resulting date time is before an Enso epoch.
Arguments:
- amount: The amount of time to subtract from this instant. Can be either
`Duration` for subtracting time (hours, minutes, etc.), or `Period`
for subtracting days, months and years.
- amount: The amount of time to add to this instant, It can be a
`Duration`, `Period`, `Time_Period`, or `Date_Period`.
> Example
Subtract 1 year, 9 months and 12 hours from a zoned date time.
@ -584,13 +593,24 @@ type Date_Time
import Standard.Base.Data.Time.Duration
example_minus = Date_Time.new 2020 - (Period.new years=1) - (Period.new months=9) - (Duration.new hours=5)
- : (Duration | Period) -> Date_Time ! Time_Error
- self amount =
> Example
Subtract one quarter from a zoned date time.
from Standard.Base import Date_Time, Date_Period
example_plus = Date_Time.new 2020 - Date_Period.Quarter
- : (Duration | Period | Time_Period | Date_Period) -> Date_Time ! Time_Error
- self amount:(Duration|Period|Time_Period|Date_Period) =
result = case amount of
duration : Duration ->
Panic.catch ArithmeticException (self.minus_builtin duration) (err -> Error.throw (Time_Error.Error err.getMessage))
period : Period ->
Time_Utils.datetime_adjust self Time_Utils.AdjustOp.MINUS period.internal_period
time_period : Time_Period ->
self - time_period.to_duration
date_period : Date_Period ->
self - date_period.to_period
ensure_in_epoch result result
## PRIVATE

View File

@ -258,26 +258,36 @@ type Time_Of_Day
Add the specified amount of time to this instant to get a new instant.
Arguments:
- amount: The amount of time to add to this instant. Can be only
`Duration`.
- amount: The amount of time to add to this instant. It can be a
`Duration` or `Time_Period`.
> Example
Add a `Duration` to a `Time_Of_Day`.
from Standard.Base import Time_Of_Day, Duration
example_plus = Time_Of_Day.new + (Duration.new seconds=3)
+ : Duration -> Time_Of_Day ! Time_Error
+ self amount =
> Example
Add a `Time_Period` to a `Time_Of_Day`.
from Standard.Base import Time_Of_Day, Time_Period
example_plus = Time_Of_Day.new + Time_Period.Hour
+ : Duration | Time_Period -> Time_Of_Day ! Time_Error
+ self amount:(Duration|Time_Period) =
case amount of
duration : Duration -> self.plus_builtin duration
_ : Period -> Error.throw (Time_Error.Error "Time_Of_Day does not support date intervals (periods)")
time_period : Time_Period ->
self + time_period.to_duration
## ALIAS Subtract Duration
Subtract the specified amount of time from this instant to get a new
instant.
Arguments:
- amount: The amount of time to subtract from this instant.
- amount: The amount of time to add to this instant. It can be a
`Duration` or `Time_Period`.
> Example
Subtract 12 hours from a local time.
@ -285,11 +295,19 @@ type Time_Of_Day
from Standard.Base import Time_Of_Day, Duration
example_minus = Time_Of_Day.now - (Duration.new hours=12)
- : Duration -> Time_Of_Day ! Time_Error
- self amount =
> Example
Subtract a `Time_Period` from a `Time_Of_Day`.
from Standard.Base import Time_Of_Day, Time_Period
example_plus = Time_Of_Day.new - Time_Period.Hour
- : Duration | Time_Period -> Time_Of_Day ! Time_Error
- self amount:(Duration|Time_Period) =
case amount of
duration : Duration -> self.minus_builtin duration
_ : Period -> Error.throw (Time_Error.Error "Time_Of_Day does not support date intervals (periods)")
time_period : Time_Period ->
self - time_period.to_duration
## PRIVATE
Convert to a JavaScript Object representing this Time_Of_Day.

View File

@ -1,5 +1,6 @@
import project.Data.Time.Time_Of_Day.Time_Of_Day
import project.Data.Time.Date_Time.Date_Time
import project.Data.Time.Duration.Duration
from project.Data.Boolean import Boolean, True, False
polyglot java import org.enso.base.Time_Utils
@ -33,3 +34,11 @@ type Time_Period
adjust_end : (Time_Of_Day | Date_Time) -> (Time_Of_Day | Date_Time)
adjust_end self date =
(Time_Utils.utils_for date).end_of_time_period date self.to_java_unit
## PRIVATE
to_duration : Duration
to_duration self = case self of
Time_Period.Day -> Duration.new 24
Time_Period.Hour -> Duration.new 1
Time_Period.Minute -> Duration.new 0 1
Time_Period.Second -> Duration.new 0 0 1

View File

@ -2,6 +2,7 @@ from Standard.Base import all
import Standard.Base.Errors.Common.Incomparable_Values
import Standard.Base.Errors.Common.Type_Error
import Standard.Base.Errors.Time_Error.Time_Error
import Standard.Base.Meta
from Standard.Test import Problems, Test, Test_Suite
import Standard.Test.Extensions
@ -139,18 +140,33 @@ spec_with name create_new_date parse_date =
date . day . should_equal 1
Test.specify "should throw error when adding time-based Duration" <|
case (create_new_date 1970 + (Duration.new hours=1)) . catch of
Time_Error.Error message ->
message . should_equal "Date does not support adding/subtracting Duration. Use Period instead."
result ->
Test.fail ("Unexpected result: " + result.to_text)
Test.expect_panic_with matcher=Type_Error <|
create_new_date 1970 + (Duration.new hours=1)
Test.specify "should throw error when subtracting time-based Duration" <|
case (create_new_date 1970 - (Duration.new minutes=1)) . catch of
Time_Error.Error message ->
message . should_equal "Date does not support adding/subtracting Duration. Use Period instead."
result ->
Test.fail ("Unexpected result: " + result.to_text)
Test.expect_panic_with matcher=Type_Error <|
create_new_date 1970 - (Duration.new minutes=1)
Test.specify "should support addition of Date_Period" <|
time = create_new_date 1970
time+Date_Period.Year . should_equal <| create_new_date 1971
time+Date_Period.Quarter . should_equal <| create_new_date 1970 4
time+Date_Period.Month . should_equal <| create_new_date 1970 2
time+Date_Period.Week . should_equal <| create_new_date 1970 1 8
time+Date_Period.Day . should_equal <| create_new_date 1970 1 2
Test.specify "should support subtraction of Date_Period" <|
time = create_new_date 1970
time-Date_Period.Year . should_equal <| create_new_date 1969
time-Date_Period.Quarter . should_equal <| create_new_date 1969 10
time-Date_Period.Month . should_equal <| create_new_date 1969 12
time-Date_Period.Week . should_equal <| create_new_date 1969 12 25
time-Date_Period.Day . should_equal <| create_new_date 1969 12 31
Test.specify "should support mixed addition and subtraction of Date_Period" <|
time = create_new_date 1970
time+Date_Period.Quarter-Date_Period.Month . should_equal <| create_new_date 1970 3 1
time-Date_Period.Month+Date_Period.Year . should_equal <| create_new_date 1970 12 1
Test.specify "should be comparable" <|
date_1 = parse_date "2021-01-02"

View File

@ -254,7 +254,7 @@ spec_with name create_new_datetime parse_datetime nanoseconds_loss_in_precision=
time . month . should_equal 1
time . day . should_equal 1
Test.specify "should add time interval" <|
Test.specify "should add Duration" <|
time = create_new_datetime 1970 (zone = Time_Zone.utc) + (Duration.new nanoseconds=1)
time . year . should_equal 1970
time . month . should_equal 1
@ -265,7 +265,7 @@ spec_with name create_new_datetime parse_datetime nanoseconds_loss_in_precision=
time . nanosecond . should_equal 1
time . zone . should_equal Time_Zone.utc
Test.specify "should add date interval" <|
Test.specify "should add Period" <|
time = (create_new_datetime 1970 (zone = Time_Zone.utc)) + (Period.new months=1)
time . year . should_equal 1970
time . month . should_equal 2
@ -276,7 +276,7 @@ spec_with name create_new_datetime parse_datetime nanoseconds_loss_in_precision=
time . nanosecond . should_equal 0
time . zone . zone_id . should_equal Time_Zone.utc.zone_id
Test.specify "should add mixed date time interval" <|
Test.specify "should add mixed Period and Duration" <|
time = create_new_datetime 1970 (zone = Time_Zone.utc) + (Period.new months=1) + (Duration.new hours=3)
time . year . should_equal 1970
time . month . should_equal 2
@ -287,7 +287,7 @@ spec_with name create_new_datetime parse_datetime nanoseconds_loss_in_precision=
time . nanosecond . should_equal 0
time . zone . zone_id . should_equal Time_Zone.utc.zone_id
Test.specify "should subtract time interval" <|
Test.specify "should subtract Duration" <|
time = create_new_datetime 1970 (zone = Time_Zone.utc) - (Duration.new hours=1)
time . year . should_equal 1969
time . month . should_equal 12
@ -298,7 +298,7 @@ spec_with name create_new_datetime parse_datetime nanoseconds_loss_in_precision=
time . nanosecond . should_equal 0
time . zone . zone_id . should_equal Time_Zone.utc.zone_id
Test.specify "should subtract date interval" <|
Test.specify "should subtract Period" <|
time = create_new_datetime 1970 (zone = Time_Zone.utc) - (Period.new months=1)
time . year . should_equal 1969
time . month . should_equal 12
@ -309,7 +309,7 @@ spec_with name create_new_datetime parse_datetime nanoseconds_loss_in_precision=
time . nanosecond . should_equal 0
time . zone . zone_id . should_equal Time_Zone.utc.zone_id
Test.specify "should subtract mixed date time interval" <|
Test.specify "should subtract mixed Period and Duration" <|
time = create_new_datetime 1970 (zone = Time_Zone.utc) - (Period.new months=1) - (Duration.new hours=3)
time . year . should_equal 1969
time . month . should_equal 11
@ -331,6 +331,58 @@ spec_with name create_new_datetime parse_datetime nanoseconds_loss_in_precision=
time . nanosecond . should_equal 0
time . zone . zone_id . should_equal Time_Zone.utc.zone_id
Test.specify "should support addition of Date_Period" <|
time = create_new_datetime 1970 (zone = Time_Zone.utc)
time+Date_Period.Year . should_equal <| create_new_datetime 1971 (zone = Time_Zone.utc)
time+Date_Period.Quarter . should_equal <| create_new_datetime 1970 4 (zone = Time_Zone.utc)
time+Date_Period.Month . should_equal <| create_new_datetime 1970 2 (zone = Time_Zone.utc)
time+Date_Period.Week . should_equal <| create_new_datetime 1970 1 8 (zone = Time_Zone.utc)
time+Date_Period.Day . should_equal <| create_new_datetime 1970 1 2 (zone = Time_Zone.utc)
Test.specify "should support subtraction of Date_Period" <|
time = create_new_datetime 1970 (zone = Time_Zone.utc)
time-Date_Period.Year . should_equal <| create_new_datetime 1969 (zone = Time_Zone.utc)
time-Date_Period.Quarter . should_equal <| create_new_datetime 1969 10 (zone = Time_Zone.utc)
time-Date_Period.Month . should_equal <| create_new_datetime 1969 12 (zone = Time_Zone.utc)
time-Date_Period.Week . should_equal <| create_new_datetime 1969 12 25 (zone = Time_Zone.utc)
time-Date_Period.Day . should_equal <| create_new_datetime 1969 12 31 (zone = Time_Zone.utc)
Test.specify "should support addition of Time_Period" <|
time = create_new_datetime 1970 (zone = Time_Zone.utc)
time+Time_Period.Day . should_equal <| create_new_datetime 1970 1 2 (zone = Time_Zone.utc)
time+Time_Period.Hour . should_equal <| create_new_datetime 1970 1 1 1 (zone = Time_Zone.utc)
time+Time_Period.Minute . should_equal <| create_new_datetime 1970 1 1 0 1 (zone = Time_Zone.utc)
time+Time_Period.Second . should_equal <| create_new_datetime 1970 1 1 0 0 1 (zone = Time_Zone.utc)
Test.specify "should support subtraction of Time_Period" <|
time = create_new_datetime 1970 (zone = Time_Zone.utc)
time-Time_Period.Day . should_equal <| create_new_datetime 1969 12 31 (zone = Time_Zone.utc)
time-Time_Period.Hour . should_equal <| create_new_datetime 1969 12 31 23 (zone = Time_Zone.utc)
time-Time_Period.Minute . should_equal <| create_new_datetime 1969 12 31 23 59 (zone = Time_Zone.utc)
time-Time_Period.Second . should_equal <| create_new_datetime 1969 12 31 23 59 59 (zone = Time_Zone.utc)
Test.specify "should support mixed addition and subtraction of Date_Period and Time_Period" <|
time = create_new_datetime 1970 (zone = Time_Zone.utc)
time+Date_Period.Quarter+Time_Period.Hour . should_equal <| create_new_datetime 1970 4 1 1 (zone = Time_Zone.utc)
time+Time_Period.Hour+Date_Period.Quarter . should_equal <| create_new_datetime 1970 4 1 1 (zone = Time_Zone.utc)
time+Date_Period.Year-Time_Period.Minute . should_equal <| create_new_datetime 1970 12 31 23 59 (zone = Time_Zone.utc)
time+Time_Period.Minute+Time_Period.Minute-Time_Period.Minute . should_equal <| create_new_datetime 1970 1 1 0 1 (zone = Time_Zone.utc)
time+Date_Period.Quarter-Date_Period.Month . should_equal <| create_new_datetime 1970 3 1 (zone = Time_Zone.utc)
time+Date_Period.Day-Time_Period.Day . should_equal <| create_new_datetime 1970 (zone = Time_Zone.utc)
Test.specify "will reflect that Time_Period.Day does not reflect daylight saving" <|
tz = Time_Zone.parse "Europe/Warsaw"
dt = Date_Time.new 2023 03 26 01 20 zone=tz
dt1 = dt + Time_Period.Day
dt2 = dt + Date_Period.Day
# Time Period adds 24 hours which will be 1 hour later due to the DST hole.
dt1 . should_equal (Date_Time.new 2023 03 27 02 20 zone=tz)
# Date Period shifts by 1 calendar day.
dt2 . should_equal (Date_Time.new 2023 03 27 01 20 zone=tz)
Test.specify "should get the default comparator for datetimes" <|
Comparable.from (create_new_datetime 2023 2 3 23 59) . should_equal Default_Comparator
Comparable.from (parse_datetime "2021-01-01T00:30:12.7102[UTC]") . should_equal Default_Comparator

View File

@ -110,18 +110,31 @@ specWith name create_new_time parse_time =
time . to_seconds . should_equal 3599
Test.specify "should throw error when adding date-based interval" <|
case (create_new_time 0 + (Period.new days=1)) . catch of
Time_Error.Error message ->
message . should_equal "Time_Of_Day does not support date intervals (periods)"
result ->
Test.fail ("Unexpected result: " + result.to_text)
Test.expect_panic_with matcher=Type_Error <|
create_new_time 0 + (Period.new days=1)
Test.specify "should throw error when subtracting date-based interval" <|
case (create_new_time 0 - (Period.new days=1)) . catch of
Time_Error.Error message ->
message . should_equal "Time_Of_Day does not support date intervals (periods)"
result ->
Test.fail ("Unexpected result: " + result.to_text)
Test.expect_panic_with matcher=Type_Error <|
create_new_time 0 - (Period.new days=1)
Test.specify "should support addition of Time_Period" <|
time = create_new_time 0
time+Time_Period.Day . should_equal <| create_new_time 0
time+Time_Period.Hour . should_equal <| create_new_time 1
time+Time_Period.Minute . should_equal <| create_new_time 0 1
time+Time_Period.Second . should_equal <| create_new_time 0 0 1
Test.specify "should support subtraction of Time_Period" <|
time = create_new_time 12
time-Time_Period.Day . should_equal <| create_new_time 12
time-Time_Period.Hour . should_equal <| create_new_time 11
time-Time_Period.Minute . should_equal <| create_new_time 11 59
time-Time_Period.Second . should_equal <| create_new_time 11 59 59
Test.specify "should support mixed addition and subtraction of Date_Period and Time_Period" <|
time = create_new_time 0
time+Time_Period.Hour-Time_Period.Minute . should_equal <| create_new_time 0 59
time+Time_Period.Minute+Time_Period.Minute-Time_Period.Minute . should_equal <| create_new_time 0 1
Test.specify "should be comparable" <|
time_1 = parse_time "12:30:12.7102"