mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 02:21:54 +03:00
parent
1302b693d8
commit
cd7fb73232
@ -433,6 +433,7 @@
|
||||
- [Implemented `Column.format` for in-memory `Column`s.][6538]
|
||||
- [Added `at_least_one` flag to `Table.tokenize_to_rows`.][6539]
|
||||
- [Moved `Redshift` connector into a separate `AWS` library.][6550]
|
||||
- [Added `Date_Range`.][6621]
|
||||
|
||||
[debug-shortcuts]:
|
||||
https://github.com/enso-org/enso/blob/develop/app/gui/docs/product/shortcuts.md#debug
|
||||
@ -642,6 +643,7 @@
|
||||
[6538]: https://github.com/enso-org/enso/pull/6538
|
||||
[6539]: https://github.com/enso-org/enso/pull/6539
|
||||
[6550]: https://github.com/enso-org/enso/pull/6550
|
||||
[6621]: https://github.com/enso-org/enso/pull/6621
|
||||
|
||||
#### Enso Compiler
|
||||
|
||||
|
@ -745,7 +745,9 @@ type Array
|
||||
reverse : Vector Any
|
||||
reverse self = Vector.reverse self
|
||||
|
||||
## Applies a function to each element of the array.
|
||||
## PRIVATE
|
||||
ADVANCED
|
||||
Applies a function to each element of the array.
|
||||
|
||||
Unlike `map`, this method does not return the individual results,
|
||||
therefore it is only useful for side-effecting computations.
|
||||
@ -760,7 +762,9 @@ type Array
|
||||
each : (Any -> Any) -> Nothing
|
||||
each self f = Vector.each self f
|
||||
|
||||
## Applies a function to each element of the array.
|
||||
## PRIVATE
|
||||
ADVANCED
|
||||
Applies a function to each element of the array.
|
||||
|
||||
Arguments:
|
||||
- function: A function to apply that takes an index and an item.
|
||||
|
@ -286,7 +286,9 @@ type List
|
||||
go t res.fill
|
||||
res.value
|
||||
|
||||
## Applies a function to each element of the list.
|
||||
## PRIVATE
|
||||
ADVANCED
|
||||
Applies a function to each element of the list.
|
||||
|
||||
Arguments:
|
||||
- f: The function to apply to each element of the list.
|
||||
|
@ -308,7 +308,9 @@ type Map key value
|
||||
self.to_vector.fold init acc-> pair->
|
||||
function acc pair.first pair.last
|
||||
|
||||
## Applies a function to each value in the map.
|
||||
## PRIVATE
|
||||
ADVANCED
|
||||
Applies a function to each value in the map.
|
||||
|
||||
Arguments:
|
||||
- function: The function to apply to each value in the map, taking a
|
||||
@ -329,7 +331,9 @@ type Map key value
|
||||
kv_func = _ -> function
|
||||
self.each_with_key kv_func
|
||||
|
||||
## Applies a function to each key-value pair in the map.
|
||||
## PRIVATE
|
||||
ADVANCED
|
||||
Applies a function to each key-value pair in the map.
|
||||
|
||||
Arguments:
|
||||
- function: The function to apply to each key-value pair in the map,
|
||||
|
@ -232,7 +232,9 @@ type Pair
|
||||
reverse : Pair
|
||||
reverse self = Pair.new self.second self.first
|
||||
|
||||
## Applies a function to each element of the pair.
|
||||
## PRIVATE
|
||||
ADVANCED
|
||||
Applies a function to each element of the pair.
|
||||
|
||||
Unlike `map`, this method does not return the individual results,
|
||||
therefore it is only useful for side-effecting computations.
|
||||
|
@ -1,7 +1,6 @@
|
||||
import project.Any.Any
|
||||
import project.Data.Filter_Condition.Filter_Condition
|
||||
import project.Data.Numbers.Integer
|
||||
import project.Data.Numbers.Number
|
||||
import project.Data.Text.Text
|
||||
import project.Data.Vector.Vector
|
||||
import project.Errors.Common.Index_Out_Of_Bounds
|
||||
@ -55,18 +54,21 @@ type Range
|
||||
_ ->
|
||||
Error.throw (Illegal_Argument.Error "Range step should be an integer.")
|
||||
|
||||
## Returns the first element that is included within the range or `Nothing`
|
||||
if the range is empty.
|
||||
## Returns the first element that is included within the range.
|
||||
|
||||
It will raise `Index_Out_Of_Bounds` if the range is empty.
|
||||
first : Integer ! Index_Out_Of_Bounds
|
||||
first self = if self.is_empty then Error.throw (Index_Out_Of_Bounds.Error 0 0) else self.start
|
||||
|
||||
## Returns the second element that is included within the range or `Nothing`
|
||||
if the range has less than 2 element
|
||||
## Returns the second element that is included within the range.
|
||||
|
||||
It will raise `Index_Out_Of_Bounds` if the range has less than two elements.
|
||||
second : Integer ! Index_Out_Of_Bounds
|
||||
second self = if self.length < 2 then Error.throw (Index_Out_Of_Bounds.Error 1 self.length) else self.start + self.step
|
||||
|
||||
## Returns the last element that is included within the range or `Nothing`
|
||||
if the range is empty.
|
||||
## Returns the last element that is included within the range.
|
||||
|
||||
It will raise `Index_Out_Of_Bounds` if the range is empty.
|
||||
last : Integer ! Index_Out_Of_Bounds
|
||||
last self = if self.is_empty then Error.throw (Index_Out_Of_Bounds.Error 0 0) else
|
||||
self.start + self.step*(self.length - 1)
|
||||
@ -77,18 +79,22 @@ type Range
|
||||
The following range has 100 elements.
|
||||
|
||||
0.up_to 100 . length
|
||||
length : Number
|
||||
length : Integer
|
||||
length self = if self.is_empty then 0 else
|
||||
diff = self.end - self.start
|
||||
steps = diff . div self.step
|
||||
if self.start + steps*self.step == self.end then steps else steps+1
|
||||
exact_fit = (self.start + steps*self.step) == self.end
|
||||
## The `end` is excluded if it is reached exactly by the last step.
|
||||
If it is not reached, that means that the last step is also included,
|
||||
so we increase by one.
|
||||
if exact_fit then steps else steps+1
|
||||
|
||||
## Gets an element from the Range at a specified index (0-based).
|
||||
|
||||
Arguments:
|
||||
- index: The location in the Range to get the element from. The index is
|
||||
also allowed be negative, then the elements are indexed from the back
|
||||
of the final item, i.e. -1 will correspond to the last element.
|
||||
also allowed be negative, then the elements are indexed from the back,
|
||||
i.e. -1 will correspond to the last element.
|
||||
|
||||
> Example
|
||||
Get the second element of a range.
|
||||
@ -108,8 +114,8 @@ type Range
|
||||
|
||||
Arguments:
|
||||
- index: The location in the Range to get the element from. The index is
|
||||
also allowed be negative, then the elements are indexed from the back
|
||||
of the Range, i.e. -1 will correspond to the last element.
|
||||
also allowed be negative, then the elements are indexed from the back,
|
||||
i.e. -1 will correspond to the last element.
|
||||
- if_missing: The value to return if the index is out of bounds.
|
||||
get : Integer -> Any -> Any
|
||||
get self index ~if_missing=Nothing =
|
||||
@ -149,7 +155,7 @@ type Range
|
||||
the range.
|
||||
|
||||
1.up_to 10 . map (*2)
|
||||
map : (Number -> Any) -> Vector Any
|
||||
map : (Integer -> Any) -> Vector Any
|
||||
map self function =
|
||||
Vector.new self.length (i -> function (self.start + i*self.step))
|
||||
|
||||
@ -166,7 +172,7 @@ type Range
|
||||
|
||||
(0.up_to 7).filter (> 3)
|
||||
(0.up_to 7).filter (Filter_Condition.Greater than=3)
|
||||
filter : (Filter_Condition | (Any -> Boolean)) -> Vector Any
|
||||
filter : (Filter_Condition | (Integer -> Boolean)) -> Vector Integer
|
||||
filter self filter = case filter of
|
||||
_ : Filter_Condition -> self.filter filter.to_predicate
|
||||
predicate : Function ->
|
||||
@ -174,7 +180,9 @@ type Range
|
||||
if predicate elem then builder.append elem else builder
|
||||
builder.to_vector
|
||||
|
||||
## Applies a function for each element in the range.
|
||||
## PRIVATE
|
||||
ADVANCED
|
||||
Applies a function for each element in the range.
|
||||
|
||||
Arguments:
|
||||
- function: The function to apply to each integer in the range.
|
||||
@ -182,7 +190,7 @@ type Range
|
||||
> Example
|
||||
To print all the numbers from 1 to 10 use:
|
||||
1.up_to 11 . each IO.println
|
||||
each : (Number -> Any) -> Nothing
|
||||
each : (Integer -> Any) -> Nothing
|
||||
each self function =
|
||||
if self.step == 0 then throw_zero_step_error else
|
||||
end_condition = if self.step > 0 then (>=) else (<=)
|
||||
@ -193,6 +201,7 @@ type Range
|
||||
go self.start
|
||||
|
||||
## PRIVATE
|
||||
ADVANCED
|
||||
Applies a function to each element of the range.
|
||||
|
||||
Essentially acts like `range.to_vector.each_with_index`, but it is more
|
||||
@ -206,7 +215,7 @@ type Range
|
||||
Print range elements with their indices within the range.
|
||||
|
||||
(10.up_to 13).each_with_index ix-> elem-> IO.println (Pair ix elem) # Will print Pair 0 10, Pair 1 11, Pair 2 12
|
||||
each_with_index : (Integer -> Any -> Any) -> Nothing
|
||||
each_with_index : (Integer -> Integer -> Nothing) -> Nothing
|
||||
each_with_index self function =
|
||||
if self.step == 0 then throw_zero_step_error else
|
||||
end_condition = if self.step > 0 then (>=) else (<=)
|
||||
@ -220,7 +229,7 @@ type Range
|
||||
passed function with next elements of the range.
|
||||
|
||||
Arguments:
|
||||
- init: The initial integral value for the fold.
|
||||
- init: The initial value for the fold.
|
||||
- function: A binary function taking an item and a number, and returning
|
||||
an item.
|
||||
|
||||
@ -234,7 +243,7 @@ type Range
|
||||
less than 100.
|
||||
|
||||
0.up_to 100 . with_step 2 . fold 0 (+)
|
||||
fold : Any -> (Any -> Number -> Any) -> Any
|
||||
fold : Any -> (Any -> Integer -> Any) -> Any
|
||||
fold self init function =
|
||||
if self.step == 0 then throw_zero_step_error else
|
||||
end_condition = if self.step > 0 then (>=) else (<=)
|
||||
@ -253,9 +262,9 @@ type Range
|
||||
- function: A function taking two elements and combining them.
|
||||
|
||||
> Example
|
||||
Compute the running sum of all of the elements in a vector
|
||||
Compute the running sum of all of the elements in a range.
|
||||
|
||||
[1, 2, 3].running_fold 0 (+)
|
||||
(0.up_to 4).running_fold 0 (+)
|
||||
running_fold : Any -> (Any -> Any -> Any) -> Vector Any
|
||||
running_fold self init function =
|
||||
wrapped builder value =
|
||||
@ -275,7 +284,7 @@ type Range
|
||||
Checking that all numbers in the range are greater than 5.
|
||||
|
||||
10.up_to 100 . all (> 5)
|
||||
all : (Number -> Boolean) -> Boolean
|
||||
all : (Integer -> Boolean) -> Boolean
|
||||
all self predicate = self . any (predicate >> .not) . not
|
||||
|
||||
## Checks whether `predicate` is satisfied for any number in this range.
|
||||
@ -289,7 +298,7 @@ type Range
|
||||
Checking that at least one number in the range is greater than 10.
|
||||
|
||||
1.up_to 100 . any (> 10)
|
||||
any : (Number -> Boolean) -> Boolean
|
||||
any : (Integer -> Boolean) -> Boolean
|
||||
any self predicate = self.find predicate . is_nothing . not
|
||||
|
||||
## Gets the first index when `predicate` is satisfied this range.
|
||||
@ -347,7 +356,7 @@ type Range
|
||||
0.up_to 100 . index_of 20 == 20
|
||||
0.up_to 100 . with_step 5 . index_of 20 == 4
|
||||
0.up_to 100 . with_step 5 . index_of (>10) == 3
|
||||
index_of : (Integer | (Any -> Boolean)) -> Integer -> Integer | Nothing
|
||||
index_of : (Integer | (Integer -> Boolean)) -> Integer -> Integer | Nothing
|
||||
index_of self element start=0 =
|
||||
check_start_valid start self used_start->
|
||||
case element of
|
||||
@ -370,7 +379,7 @@ type Range
|
||||
Find the last index of an element in a pair.
|
||||
|
||||
Pair.new 2 2 . last_index_of 2 == 1
|
||||
last_index_of : (Any | (Any -> Boolean)) -> Integer -> Integer | Nothing
|
||||
last_index_of : (Integer | (Integer -> Boolean)) -> Integer -> Integer | Nothing
|
||||
last_index_of self element start=-1 =
|
||||
if self.is_empty && (start==-1 || start==0) then Nothing else
|
||||
check_start_valid start self include_end=False used_start->
|
||||
@ -403,21 +412,21 @@ type Range
|
||||
Getting a vector of the numbers 1 to 5.
|
||||
|
||||
1.up_to 6 . to_vector
|
||||
to_vector : Vector
|
||||
to_vector : Vector Integer
|
||||
to_vector self = self.map x->x
|
||||
|
||||
## Combines all the elements of a non-empty range using a binary operation.
|
||||
If the range is empty, returns `if_empty`.
|
||||
|
||||
Arguments:
|
||||
- function: A binary operation that takes two items and combines them.
|
||||
- function: A binary operation that takes two integers and combines them.
|
||||
- if_empty: Value returned if the range is empty.
|
||||
|
||||
> Example
|
||||
Compute the sum of all the elements in a range.
|
||||
|
||||
0.up_to 10 . reduce (+)
|
||||
reduce : (Any -> Any -> Any) -> Any -> Any
|
||||
reduce : (Integer -> Integer -> Integer) -> Any -> Any
|
||||
reduce self function ~if_empty=(Error.throw Empty_Error) =
|
||||
len = self.length
|
||||
case len of
|
||||
|
@ -3,20 +3,31 @@ import project.Data.Range.Range
|
||||
import project.Error.Error
|
||||
import project.Errors.Illegal_Argument.Illegal_Argument
|
||||
|
||||
from project.Data.Boolean import Boolean, True, False
|
||||
|
||||
## ALIAS Range
|
||||
|
||||
Creates an increasing right-exclusive range of integers from `self` to `n`.
|
||||
Creates an increasing range of integers from `self` to `n`.
|
||||
|
||||
Arguments:
|
||||
- n: The end of the range.
|
||||
- include_end: Specifies if the right end of the range should be included. By
|
||||
default, the range is right-exclusive.
|
||||
|
||||
> Example
|
||||
Create a range containing the numbers 0, 1, 2, 3, 4.
|
||||
|
||||
0.up_to 5
|
||||
Integer.up_to : Integer -> Range
|
||||
Integer.up_to self n = case n of
|
||||
_ : Integer -> Range.Between self n
|
||||
|
||||
> Example
|
||||
Create a range containing elements 1, 2, 3.
|
||||
|
||||
1.up_to 3 include_end=True
|
||||
Integer.up_to : Integer -> Boolean -> Range
|
||||
Integer.up_to self n include_end=False = case n of
|
||||
_ : Integer ->
|
||||
effective_end = if include_end then n+1 else n
|
||||
Range.Between self effective_end 1
|
||||
_ -> Error.throw (Illegal_Argument.Error "Expected range end to be an Integer.")
|
||||
|
||||
## ALIAS Range
|
||||
@ -25,12 +36,21 @@ Integer.up_to self n = case n of
|
||||
|
||||
Arguments:
|
||||
- n: The end of the range.
|
||||
- include_end: Specifies if the right end of the range should be included. By
|
||||
default, the range is right-exclusive.
|
||||
|
||||
> Example
|
||||
Create a range containing the numbers 5, 4, 3, 2, 1.
|
||||
|
||||
5.down_to 0
|
||||
Integer.down_to : Integer -> Range
|
||||
Integer.down_to self n = case n of
|
||||
_ : Integer -> Range.Between self n -1
|
||||
|
||||
> Example
|
||||
Create a range containing elements 3, 2, 1.
|
||||
|
||||
3.down_to 1 include_end=True
|
||||
Integer.down_to : Integer -> Boolean -> Range
|
||||
Integer.down_to self n include_end=False = case n of
|
||||
_ : Integer ->
|
||||
effective_end = if include_end then n-1 else n
|
||||
Range.Between self effective_end -1
|
||||
_ -> Error.throw (Illegal_Argument.Error "Expected range end to be an Integer.")
|
||||
|
@ -71,7 +71,9 @@ Text.reverse self =
|
||||
@Tail_Call iterate next iterator.previous
|
||||
iterate iterator.last iterator.previous
|
||||
|
||||
## Applies the provided `function` to each character in `self`.
|
||||
## PRIVATE
|
||||
ADVANCED
|
||||
Applies the provided `function` to each character in `self`.
|
||||
|
||||
Arguments:
|
||||
- function: The operation to apply to each character in the text.
|
||||
|
@ -6,6 +6,7 @@ import project.Data.Ordering.Ordering
|
||||
import project.Data.Ordering.Comparable
|
||||
import project.Data.Text.Text
|
||||
import project.Data.Time.Date_Period.Date_Period
|
||||
import project.Data.Time.Date_Range.Date_Range
|
||||
import project.Data.Time.Date_Time.Date_Time
|
||||
import project.Data.Time.Day_Of_Week.Day_Of_Week
|
||||
import project.Data.Time.Day_Of_Week_From
|
||||
@ -445,6 +446,56 @@ type Date
|
||||
_ ->
|
||||
Error.throw (Illegal_Argument.Error "Illegal period argument")
|
||||
|
||||
## ALIAS Date Range
|
||||
|
||||
Creates an increasing range of dates from `self` to `end`.
|
||||
|
||||
Arguments:
|
||||
- end: The end of the range.
|
||||
- include_end: Specifies if the right end of the range should be included. By
|
||||
default, the range is right-exclusive.
|
||||
|
||||
> Example
|
||||
Create a range of dates.
|
||||
|
||||
(Date.new 2021 12 05).up_to (Date.new 2021 12 10)
|
||||
|
||||
> Example
|
||||
Create a range containing dates [2021-12-05, 2021-12-06].
|
||||
|
||||
(Date.new 2021 12 05).up_to (Date.new 2021 12 06) include_end=True
|
||||
up_to : Date -> Boolean -> Date_Range
|
||||
up_to self end include_end=False = case end of
|
||||
_ : Date ->
|
||||
effective_end = if include_end then end.next else end
|
||||
Date_Range.new_internal self effective_end increasing=True step=(Period.new days=1)
|
||||
_ -> Error.throw (Type_Error.Error Date end "end")
|
||||
|
||||
## ALIAS Date Range
|
||||
|
||||
Creates a decreasing range of dates from `self` to `end`.
|
||||
|
||||
Arguments:
|
||||
- end: The end of the range.
|
||||
- include_end: Specifies if the right end of the range should be included. By
|
||||
default, the range is right-exclusive.
|
||||
|
||||
> Example
|
||||
Create a reverse range of dates.
|
||||
|
||||
(Date.new 2021 12 10).down_to (Date.new 2021 12 05)
|
||||
|
||||
> Example
|
||||
Create a range containing dates [2021-12-06, 2021-12-05].
|
||||
|
||||
(Date.new 2021 12 06).down_to (Date.new 2021 12 05) include_end=True
|
||||
down_to : Date -> Boolean -> Date_Range
|
||||
down_to self end include_end=False = case end of
|
||||
_ : Date ->
|
||||
effective_end = if include_end then end.previous else end
|
||||
Date_Range.new_internal self effective_end increasing=False step=(Period.new days=1)
|
||||
_ -> Error.throw (Type_Error.Error Date end "end")
|
||||
|
||||
## Shift the date by the specified amount of business days.
|
||||
|
||||
For the purpose of this method, the business days are defined to be
|
||||
|
@ -25,12 +25,6 @@ type Date_Period
|
||||
|
||||
Day
|
||||
|
||||
## PRIVATE
|
||||
This method could be replaced with matching on `Date_Period` supertype
|
||||
if/when that is supported.
|
||||
is_date_period : Boolean
|
||||
is_date_period self = True
|
||||
|
||||
## PRIVATE
|
||||
adjust_start : (Date | Date_Time) -> (Date | Date_Time)
|
||||
adjust_start self date =
|
||||
|
@ -0,0 +1,504 @@
|
||||
import project.Any.Any
|
||||
import project.Data.Filter_Condition.Filter_Condition
|
||||
import project.Data.Json.JS_Object
|
||||
import project.Data.Numbers.Integer
|
||||
import project.Data.Range.Empty_Error
|
||||
import project.Data.Range.Extensions
|
||||
import project.Data.Text.Text
|
||||
import project.Data.Time.Date.Date
|
||||
import project.Data.Time.Date_Period.Date_Period
|
||||
import project.Data.Time.Period.Period
|
||||
import project.Data.Vector.Vector
|
||||
import project.Error.Error
|
||||
import project.Errors.Common.Index_Out_Of_Bounds
|
||||
import project.Errors.Illegal_Argument.Illegal_Argument
|
||||
import project.Function.Function
|
||||
import project.Nothing.Nothing
|
||||
|
||||
from project.Data.Boolean import Boolean, True, False
|
||||
|
||||
polyglot java import org.enso.base.Time_Utils
|
||||
|
||||
## Represents a range of dates.
|
||||
type Date_Range
|
||||
## PRIVATE
|
||||
Never use the constructor directly to construct a range, as it does not
|
||||
allow to verify invariants and may lead to unexpected behavior.
|
||||
Internal_Constructor (start : Date) (end : Date) (step : Period) (increasing : Boolean) (cached_length : Integer)
|
||||
|
||||
## Create a representation of a right-exclusive range of dates.
|
||||
|
||||
The range is increasing or decreasing, depending on if the start date is
|
||||
before or after the end date.
|
||||
|
||||
Arguments:
|
||||
- start: The left boundary of the range. Its value is included.
|
||||
- end: The right boundary of the range. Its value is excluded.
|
||||
- step: The step between dates. It must be positive - to construct a
|
||||
decreasing range, flip the start and the end or use `down_to`, but
|
||||
keeping the positive step.
|
||||
new : Date -> Date -> Date_Period|Period -> Date_Range
|
||||
new start=Date.now end=Date.now step=Date_Period.Day =
|
||||
increasing = start <= end
|
||||
Date_Range.new_internal start end increasing step
|
||||
|
||||
## PRIVATE
|
||||
new_internal : Date -> Date -> Boolean -> Date_Period|Period -> Date_Range
|
||||
new_internal start end increasing step =
|
||||
one_day = Period.new days=1
|
||||
base_length = start.days_until end . abs
|
||||
Date_Range.Internal_Constructor start end one_day increasing base_length . with_step step
|
||||
|
||||
## Creates a copy of this range with a changed step.
|
||||
|
||||
Arguments:
|
||||
- new_step: The new step to use. It can either be a `Date_Period` or
|
||||
`Period`. The provided `Period` must be positive, i.e. all of `years`,
|
||||
`months` and `days` must be non-negative and at least one of them has
|
||||
to be positive.
|
||||
|
||||
> Example
|
||||
Create a range representing the first day of every month in a year.
|
||||
|
||||
(Date.new 2020 1 1).up_to (Date.new 2020 12 31) . with_step Date_Period.Month
|
||||
|
||||
> Example
|
||||
Create a a decreasing range of every other day between two dates.
|
||||
|
||||
(Date.new 2022 10 23).down_to (Date.new 2022 10 1) . with_step (Period.new days=2)
|
||||
with_step : Date_Period|Period -> Date_Range ! Illegal_Argument
|
||||
with_step self new_step = case new_step of
|
||||
_ : Period ->
|
||||
effective_length = compute_length_and_verify self.start self.end new_step self.increasing
|
||||
Date_Range.Internal_Constructor self.start self.end new_step self.increasing effective_length
|
||||
_ : Date_Period ->
|
||||
self.with_step new_step.to_period
|
||||
|
||||
## Convert to a textual representation.
|
||||
to_text : Text
|
||||
to_text self =
|
||||
middle = if self.increasing then " up to " else " down to "
|
||||
step = if self.step == (Period.new days=1) then "" else
|
||||
" by " + self.step.to_display_text
|
||||
"(Date_Range from " + self.start.to_text + middle + self.end.to_text + step + ")"
|
||||
|
||||
## Convert to a display representation.
|
||||
to_display_text : Text
|
||||
to_display_text self =
|
||||
start = "[" + self.start.to_display_text + " .. " + self.end.to_display_text
|
||||
step = if self.step == (Period.new days=1) then "" else
|
||||
effective_step = if self.increasing then self.step else self.step.negated
|
||||
" by " + effective_step.to_display_text
|
||||
start + step + "]"
|
||||
|
||||
## Convert to a human-readable representation.
|
||||
pretty : Text
|
||||
pretty self = self.to_text
|
||||
|
||||
## PRIVATE
|
||||
Converts this value to a JSON serializable object.
|
||||
to_js_object : JS_Object
|
||||
to_js_object self =
|
||||
JS_Object.from_pairs [["type", "Date_Range"], ["start", self.start.to_js_object], ["end", self.end.to_js_object], ["step", self.step.to_js_object], ["increasing", self.increasing]]
|
||||
|
||||
## Returns the first element that is included within the range or `Nothing`
|
||||
if the range is empty.
|
||||
first : Integer ! Index_Out_Of_Bounds
|
||||
first self = if self.is_empty then Error.throw (Index_Out_Of_Bounds.Error 0 0) else self.start
|
||||
|
||||
## Returns the second element that is included within the range or `Nothing`
|
||||
if the range has less than 2 element
|
||||
second : Integer ! Index_Out_Of_Bounds
|
||||
second self = if self.length < 2 then Error.throw (Index_Out_Of_Bounds.Error 1 self.length) else self.start + self.step
|
||||
|
||||
## Returns the last element that is included within the range or `Nothing`
|
||||
if the range is empty.
|
||||
last : Integer ! Index_Out_Of_Bounds
|
||||
last self = if self.is_empty then Error.throw (Index_Out_Of_Bounds.Error 0 0) else
|
||||
self.start + self.step*(self.length - 1)
|
||||
|
||||
## Get the number of elements in the range.
|
||||
|
||||
> Example
|
||||
The following range has 2 elements.
|
||||
|
||||
(Date.new 2023 04 05) . up_to (Date.new 2023 04 07) . length
|
||||
length : Integer
|
||||
length self = self.cached_length
|
||||
|
||||
## Gets an element from the range at a specified index (0-based).
|
||||
|
||||
Arguments:
|
||||
- index: The location in the range to get the element from. The index is
|
||||
also allowed be negative, then the elements are indexed from the back,
|
||||
i.e. -1 will correspond to the last element.
|
||||
|
||||
> Example
|
||||
Get the second element of a range.
|
||||
|
||||
(Date.new 2023 04 05) . up_to (Date.new 2023 04 07) . get 1 == (Date.new 2023 04 06)
|
||||
|
||||
> Example
|
||||
Get the last element of a range with step.
|
||||
|
||||
(Date.new 2023 04 05) . up_to (Date.new 2023 10 07) . with_step Date_Period.Month . get -1 == (Date.new 2023 10 05)
|
||||
at : Integer -> Any ! Index_Out_Of_Bounds
|
||||
at self index =
|
||||
self.get index (Error.throw (Index_Out_Of_Bounds.Error index self.length))
|
||||
|
||||
## Gets an element from the range at a specified index (0-based).
|
||||
If the index is invalid then `if_missing` is returned.
|
||||
|
||||
Arguments:
|
||||
- index: The location in the range to get the element from. The index is
|
||||
also allowed be negative, then the elements are indexed from the back,
|
||||
i.e. -1 will correspond to the last element.
|
||||
- if_missing: The value to return if the index is out of bounds.
|
||||
get : Integer -> Any -> Any
|
||||
get self index ~if_missing=Nothing =
|
||||
len = self.length
|
||||
effective_index = if index < 0 then len + index else index
|
||||
if effective_index >= 0 && effective_index < len then self.internal_at effective_index else
|
||||
if_missing
|
||||
|
||||
## PRIVATE
|
||||
Generates the i-th element of the range.
|
||||
|
||||
This method does no bounds checking, it should be used only internally.
|
||||
internal_at self i =
|
||||
nth_element_of_range self.start self.step self.increasing i
|
||||
|
||||
## Applies a function to each element in the range, producing a vector of
|
||||
results.
|
||||
|
||||
Arguments:
|
||||
- function: The function to apply to each date in the range.
|
||||
|
||||
> Example
|
||||
Create a vector that contains the numbers twice that of the numbers in
|
||||
the range.
|
||||
|
||||
1.up_to 10 . map (*2)
|
||||
map : (Date -> Any) -> Vector Any
|
||||
map self function =
|
||||
Vector.new self.length (i -> function (self.internal_at i))
|
||||
|
||||
## Converts the range to a vector containing the dates in the range.
|
||||
|
||||
> Example
|
||||
Getting a vector of dates from 2021-05-07 to 2021-05-10 (exclusive).
|
||||
|
||||
(Date.new 2021 05 07).up_to (Date.new 2021 05 10) . to_vector
|
||||
to_vector : Vector Date
|
||||
to_vector self = self.map x->x
|
||||
|
||||
## Checks if this range is empty.
|
||||
|
||||
> Example
|
||||
Checking if the range of days from start of 2020 to start of 2023 is empty.
|
||||
|
||||
(Date.new 2020).up_to (Date.new 2023) . is_empty
|
||||
is_empty : Boolean
|
||||
is_empty self = self.length == 0
|
||||
|
||||
## Checks if this range is not empty.
|
||||
|
||||
> Example
|
||||
Checking if the range of days from start of 2020 to start of 2023 is not empty.
|
||||
|
||||
(Date.new 2020).up_to (Date.new 2023) . is_empty
|
||||
not_empty : Boolean
|
||||
not_empty self = self.is_empty.not
|
||||
|
||||
## Returns a vector of all elements of this range which satisfy a condition.
|
||||
|
||||
Arguments:
|
||||
- filter: The filter to apply to the range. It can either be an instance
|
||||
of `Filter_Condition` or a predicate taking a value and returning a
|
||||
boolean value indicating whether the corresponding element should be
|
||||
kept or not.
|
||||
|
||||
> Example
|
||||
Selecting all elements that are greater than 2020-10-12.
|
||||
|
||||
(Date.new 2020 10 01).up_to (Date.new 2020 10 15) . filter (> (Date.new 2020 10 12))
|
||||
(Date.new 2020 10 01).up_to (Date.new 2020 10 15) . filter (Filter_Condition.Greater than=(Date.new 2020 10 12))
|
||||
filter : (Filter_Condition | (Date -> Boolean)) -> Vector Date
|
||||
filter self filter = case filter of
|
||||
_ : Filter_Condition -> self.filter filter.to_predicate
|
||||
predicate : Function ->
|
||||
builder = self.fold Vector.new_builder builder-> elem->
|
||||
if predicate elem then builder.append elem else builder
|
||||
builder.to_vector
|
||||
|
||||
## PRIVATE
|
||||
ADVANCED
|
||||
Applies a function for each element in the range.
|
||||
|
||||
Arguments:
|
||||
- function: The function to apply to each integer in the range.
|
||||
|
||||
> Example
|
||||
To print all dates from 2020-10-01 to 2020-10-05.
|
||||
(Date.new 2020 10 01).up_to (Date.new 2020 10 05) include_end=True . each IO.println
|
||||
each : (Date -> Any) -> Nothing
|
||||
each self function =
|
||||
(0.up_to self.length).each ix->
|
||||
function (self.internal_at ix)
|
||||
|
||||
## PRIVATE
|
||||
ADVANCED
|
||||
Applies a function to each element of the range.
|
||||
|
||||
Essentially acts like `range.to_vector.each_with_index`, but it is more
|
||||
efficient.
|
||||
|
||||
Arguments:
|
||||
- function: A function to apply that takes two parameters: first the
|
||||
index of a given range element and then the actual range element.
|
||||
|
||||
> Example
|
||||
Print range elements with their indices within the range.
|
||||
|
||||
(Date.new 2020 10 01).up_to (Date.new 2020 10 05).each_with_index ix-> elem-> IO.println (Pair ix elem)
|
||||
each_with_index : (Integer -> Date -> Nothing) -> Nothing
|
||||
each_with_index self function =
|
||||
(0.up_to self.length).each_with_index ix->
|
||||
function ix (self.internal_at ix)
|
||||
|
||||
## Combines all the elements of the range, by iteratively applying the
|
||||
passed function with next elements of the range.
|
||||
|
||||
Arguments:
|
||||
- init: The initial value for the fold.
|
||||
- function: A binary function taking an item and a date, and returning
|
||||
an item.
|
||||
|
||||
> Example
|
||||
In the following example, we'll compute how many days in the range are
|
||||
a Monday.
|
||||
|
||||
(Date.new 2020 10 01).up_to (Date.new 2020 10 31) . fold 0 acc-> date->
|
||||
if date.day_of_week == Day_Of_Week.Monday then acc+1 else acc
|
||||
fold : Any -> (Any -> Date -> Any) -> Any
|
||||
fold self init function =
|
||||
(0.up_to self.length).fold init acc-> ix->
|
||||
function acc (self.internal_at ix)
|
||||
|
||||
## Combines all the elements of the range, by iteratively applying the
|
||||
passed function with the next element of the range. After each step the
|
||||
value is stored resulting in a new Vector of the same size as self.
|
||||
|
||||
Arguments:
|
||||
- init: The initial value for the fold.
|
||||
- function: A function taking two elements and combining them.
|
||||
running_fold : Any -> (Any -> Date -> Any) -> Vector Any
|
||||
running_fold self init function =
|
||||
(0.up_to self.length).running_fold init acc-> ix->
|
||||
function acc (self.internal_at ix)
|
||||
|
||||
## Checks whether `predicate` is satisfied for all dates in this range.
|
||||
|
||||
Arguments:
|
||||
- predicate: A function that takes a list element and returns a boolean
|
||||
value that says whether that value satisfies the conditions of the
|
||||
function.
|
||||
|
||||
> Example
|
||||
Checking that all dates in the range are after 2020-10-01.
|
||||
|
||||
(Date.new 2020 10 01).up_to (Date.new 2020 10 31) . all (> (Date.new 2020 10 01))
|
||||
all : (Date -> Boolean) -> Boolean
|
||||
all self predicate = self . any (predicate >> .not) . not
|
||||
|
||||
## Checks whether `predicate` is satisfied for any date in this range.
|
||||
|
||||
Arguments:
|
||||
- predicate: A function that takes a list element and returns a boolean
|
||||
value that says whether that value satisfies the conditions of the
|
||||
function.
|
||||
|
||||
> Example
|
||||
Checking that at least one date in the range is after 2020-10-01.
|
||||
|
||||
(Date.new 2020 10 01).up_to (Date.new 2020 10 31) . any (> (Date.new 2020 10 01))
|
||||
any : (Date -> Boolean) -> Boolean
|
||||
any self predicate = self.find predicate . is_nothing . not
|
||||
|
||||
## Gets the first index when `predicate` is satisfied this range.
|
||||
If no index satisfies the predicate, returns `if_missing`.
|
||||
|
||||
Arguments:
|
||||
- predicate: A function that takes a list element and returns a boolean
|
||||
value that says whether that value satisfies the conditions of the
|
||||
function.
|
||||
- start: The index to start searching from. If the index is negative, it
|
||||
is counted from the end of the range.
|
||||
- if_missing: Value returned if no element satisfies the predicate.
|
||||
|
||||
> Example
|
||||
Get the first date in the range that is a Monday.
|
||||
|
||||
(Date.new 2020 10 01).up_to (Date.new 2020 10 31) . find (d-> d.day_of_week == Day_Of_Week.Monday)
|
||||
find : (Date -> Boolean) -> Integer -> Any -> Any
|
||||
find self predicate start=0 ~if_missing=Nothing =
|
||||
index = self.index_of predicate start
|
||||
case index of
|
||||
Nothing -> if_missing
|
||||
_ : Integer -> self.internal_at index
|
||||
|
||||
## Checks if the range contains the specified value.
|
||||
|
||||
> Example
|
||||
Check if a particular date is in the range.
|
||||
|
||||
(Date.new 2020 10 01).up_to (Date.new 2020 10 31) . with_step (Period.new days=2) . contains (Date.new 2020 10 15)
|
||||
contains : Date -> Boolean
|
||||
contains self value = self.find (== value) . is_nothing . not
|
||||
|
||||
## Returns the index of an element in the range.
|
||||
Returns Nothing if the element is not found.
|
||||
|
||||
Arguments:
|
||||
- element: The date to search for or a predicate function to test for
|
||||
each element.
|
||||
- start: The index to start searching from. If the index is negative, it
|
||||
is counted from the end of the range.
|
||||
|
||||
> Example
|
||||
Find the index of a first day that is a Monday.
|
||||
|
||||
(Date.new 2020 10 01).up_to (Date.new 2020 10 31) . index_of (d-> d.day_of_week == Day_Of_Week.Monday)
|
||||
index_of : (Date | (Date -> Boolean)) -> Integer -> Integer | Nothing
|
||||
index_of self element start=0 =
|
||||
predicate = case element of
|
||||
d : Date ->
|
||||
ix-> self.internal_at ix == d
|
||||
f : Function ->
|
||||
ix-> f (self.internal_at ix)
|
||||
(0.up_to self.length).index_of predicate start
|
||||
|
||||
## Returns the last index of an element in the range.
|
||||
Returns Nothing if the element is not found.
|
||||
|
||||
Arguments:
|
||||
- element: The date to search for or a predicate function to test for
|
||||
each element.
|
||||
- start: The index to start searching backwards from. If the index is
|
||||
negative, it is counted from the end of the range.
|
||||
|
||||
> Example
|
||||
Find the index of a first day that is a Monday.
|
||||
|
||||
(Date.new 2020 10 01).up_to (Date.new 2020 10 31) . last_index_of (d-> d.day_of_week == Day_Of_Week.Monday)
|
||||
last_index_of : (Date | (Date -> Boolean)) -> Integer -> Integer | Nothing
|
||||
last_index_of self element start=-1 =
|
||||
predicate = case element of
|
||||
d : Date ->
|
||||
ix-> self.internal_at ix == d
|
||||
f : Function ->
|
||||
ix-> f (self.internal_at ix)
|
||||
(0.up_to self.length).last_index_of predicate start
|
||||
|
||||
## Reverses the range, returning a vector with the same elements as the
|
||||
original range, but in the opposite order.
|
||||
|
||||
> Example
|
||||
Reverse a range of dates.
|
||||
|
||||
(Date.new 2020 10 01).up_to (Date.new 2020 10 31) . reverse
|
||||
|
||||
? Returning a `Vector`
|
||||
|
||||
This method cannot return back a `Date_Range`, as some ranges are not
|
||||
reversible. For example, the range `(Date.new 2020 02 29).up_to (Date.new 2023) . with_step Date_Period.Year`
|
||||
will have `2022-02-28` as its last entry. But if we create a
|
||||
range starting at `2022-02-28` and going backwards by a year, its last
|
||||
element will be `2020-02-28` and not `2020-02-29` as in the original.
|
||||
Thus, to preserve the contents we need to return a vector.
|
||||
reverse : Vector Date
|
||||
reverse self = self.to_vector.reverse
|
||||
|
||||
## Combines all the elements of a non-empty range using a binary operation.
|
||||
If the range is empty, returns `if_empty`.
|
||||
|
||||
Arguments:
|
||||
- function: A binary operation that takes two dates and combines them
|
||||
into a new date.
|
||||
- if_empty: Value returned if the range is empty.
|
||||
reduce : (Date -> Date -> Date) -> Any -> Any
|
||||
reduce self function ~if_empty=(Error.throw Empty_Error) =
|
||||
case self.length of
|
||||
0 -> if_empty
|
||||
1 -> self.start
|
||||
length ->
|
||||
(1.up_to length).fold self.start acc-> ix->
|
||||
function acc (self.internal_at ix)
|
||||
|
||||
|
||||
## PRIVATE
|
||||
Computes the length of the range and verifies its invariants.
|
||||
|
||||
If any of the invariants are violated, a dataflow error is raised.
|
||||
compute_length_and_verify : Date -> Date -> Period -> Boolean -> Integer ! Illegal_Argument
|
||||
compute_length_and_verify start end step increasing =
|
||||
if is_period_positive step . not then Error.throw (Illegal_Argument.Error "The step `Period` for `Date_Range` must be positive, i.e. all of `years`, `months` and `days` must be non-negative and at least one of them must be strictly positive.") else
|
||||
is_range_empty = case increasing of
|
||||
True -> start >= end
|
||||
False -> start <= end
|
||||
if is_range_empty then 0 else
|
||||
# First a few heuristics for a fast path.
|
||||
# If there are no years or months, we can perform a simple computation on day difference.
|
||||
if step.total_months == 0 then compute_length_step_days start end step.days increasing else
|
||||
# Similarly, if we are only shifting by months, we can rely on a simpler computation.
|
||||
if step.days == 0 then compute_length_step_months start end step.total_months increasing else
|
||||
# Then we go brute force for the general case.
|
||||
compute_length_step_brute_force start end step increasing
|
||||
|
||||
## PRIVATE
|
||||
is_period_positive period =
|
||||
if (period.years < 0) || (period.months < 0) || (period.days < 0) then False else
|
||||
(period.total_months > 0) || (period.days > 0)
|
||||
|
||||
## PRIVATE
|
||||
Assumes that the range is not empty.
|
||||
compute_length_step_days : Date -> Date -> Integer -> Boolean -> Integer
|
||||
compute_length_step_days start end step increasing =
|
||||
# Logic analogous to `Range.length`.
|
||||
diff = case increasing of
|
||||
True -> Time_Utils.days_between start end
|
||||
False -> Time_Utils.days_between end start
|
||||
# assert (diff >= 0)
|
||||
steps = diff . div step
|
||||
exact_fit = diff % step == 0
|
||||
if exact_fit then steps else steps+1
|
||||
|
||||
## PRIVATE
|
||||
Assumes that the range is not empty.
|
||||
compute_length_step_months start end step increasing =
|
||||
diff = case increasing of
|
||||
True -> Time_Utils.months_between start end
|
||||
False -> Time_Utils.months_between end start
|
||||
# assert (diff >= 0)
|
||||
steps = diff . div step
|
||||
exact_fit = case increasing of
|
||||
True -> start + Period.new months=steps*step == end
|
||||
False -> start - Period.new months=steps*step == end
|
||||
if exact_fit then steps else steps+1
|
||||
|
||||
|
||||
## PRIVATE
|
||||
nth_element_of_range start step increasing n = case increasing of
|
||||
True -> start + step*n
|
||||
False -> start - step*n
|
||||
|
||||
## PRIVATE
|
||||
compute_length_step_brute_force start end step increasing =
|
||||
is_exceeded = case increasing of
|
||||
True -> (x -> x >= end)
|
||||
False -> (x -> x <= end)
|
||||
go current_date acc_length =
|
||||
if is_exceeded current_date then acc_length else
|
||||
next_date = nth_element_of_range start step increasing (acc_length + 1)
|
||||
@Tail_Call go next_date (acc_length + 1)
|
||||
go start 0
|
@ -457,18 +457,18 @@ type Date_Time
|
||||
start_of : (Date_Period|Time_Period) -> Date_Time
|
||||
start_of self period=Date_Period.Month =
|
||||
adjusted = period.adjust_start self
|
||||
case period.is_date_period of
|
||||
True -> Time_Period.Day.adjust_start adjusted
|
||||
False -> adjusted
|
||||
case period of
|
||||
_ : Date_Period -> Time_Period.Day.adjust_start adjusted
|
||||
_ : Time_Period -> adjusted
|
||||
|
||||
## Returns the last date within the `Time_Period` or `Date_Period`
|
||||
containing self.
|
||||
end_of : (Date_Period|Time_Period) -> Date_Time
|
||||
end_of self period=Date_Period.Month =
|
||||
adjusted = period.adjust_end self
|
||||
case period.is_date_period of
|
||||
True -> Time_Period.Day.adjust_end adjusted
|
||||
False -> adjusted
|
||||
case period of
|
||||
_ : Date_Period -> Time_Period.Day.adjust_end adjusted
|
||||
_ : Time_Period -> adjusted
|
||||
|
||||
## ALIAS Time to Date
|
||||
|
||||
|
@ -78,7 +78,7 @@ type Period
|
||||
Arguments:
|
||||
- internal_period: An internal representation of period of type
|
||||
java.time.Period.
|
||||
Value internal_period
|
||||
Value (internal_period : Java_Period)
|
||||
|
||||
## Get the portion of the period expressed in years.
|
||||
years : Integer
|
||||
@ -88,6 +88,11 @@ type Period
|
||||
months : Integer
|
||||
months self = self.internal_period.getMonths
|
||||
|
||||
## Get the portion of the period coming from months and years as months
|
||||
(every year is translated to 12 months).
|
||||
total_months : Integer
|
||||
total_months self = self.internal_period.toTotalMonths
|
||||
|
||||
## Get the portion of the period expressed in days.
|
||||
days : Integer
|
||||
days self = self.internal_period.getDays
|
||||
@ -107,10 +112,8 @@ type Period
|
||||
+ : Period -> Period ! (Time_Error | Illegal_Argument)
|
||||
+ self other_period =
|
||||
ensure_period other_period <|
|
||||
Panic.catch Any (Period.Value (self.internal_period.plus other_period.internal_period)) err->
|
||||
case err of
|
||||
_ : DateTimeException -> Error.throw Time_Error.Error "Period addition failed:"+err.getMessage
|
||||
_ : ArithmeticException -> Error.throw Illegal_Argument.Error "Arithmetic error:"+err.getMessage cause=err
|
||||
catch_java_exceptions "Period.+" <|
|
||||
Period.Value (self.internal_period.plus other_period.internal_period)
|
||||
|
||||
## Subtract a specified amount of time from this period.
|
||||
|
||||
@ -128,10 +131,31 @@ type Period
|
||||
- : Period -> Period ! (Time_Error | Illegal_Argument)
|
||||
- self other_period =
|
||||
ensure_period other_period <|
|
||||
Panic.catch Any (Period.Value (self.internal_period.minus other_period.internal_period)) err->
|
||||
case err of
|
||||
DateTimeException -> Error.throw Time_Error.Error "Period subtraction failed"
|
||||
ArithmeticException -> Error.throw Illegal_Argument.Error "Arithmetic error"
|
||||
catch_java_exceptions "Period.-" <|
|
||||
Period.Value (self.internal_period.minus other_period.internal_period)
|
||||
|
||||
## Multiply the amount of time in this period by the specified scalar.
|
||||
|
||||
Arguments:
|
||||
- factor: The scalar to multiply by.
|
||||
|
||||
> Example
|
||||
Multiply a period of 1 year and 2 months by 2
|
||||
|
||||
import Standard.Base.Data.Time.Period
|
||||
|
||||
example_multiply = (Period.new years=1 months=2) * 2
|
||||
* : Integer -> Period ! Time_Error
|
||||
* self factor =
|
||||
catch_java_exceptions "Period.*" <|
|
||||
Period.Value (self.internal_period.multipliedBy factor)
|
||||
|
||||
## Negate all amounts in the period.
|
||||
|
||||
This is useful when a period used for going forward in time needs to be
|
||||
used for going backwards instead.
|
||||
negated : Period
|
||||
negated self = Period.Value (self.internal_period.negated)
|
||||
|
||||
## PRIVATE
|
||||
Convert Period to a friendly string.
|
||||
@ -163,3 +187,13 @@ type Period
|
||||
if self.months==0 . not then b.append ["months", self.months]
|
||||
if self.days==0 . not then b.append ["days", self.days]
|
||||
JS_Object.from_pairs b.to_vector
|
||||
|
||||
## PRIVATE
|
||||
catch_java_exceptions operation ~action =
|
||||
handle_arithmetic_exception caught_panic =
|
||||
Error.throw (Time_Error.Error "An overflow has occurred during the "+operation+" operation:"+caught_panic.payload.getMessage)
|
||||
handle_date_time_exception caught_panic =
|
||||
Error.throw (Time_Error.Error "The operation "+operation+" has failed:"+caught_panic.payload.getMessage)
|
||||
Panic.catch ArithmeticException handler=handle_arithmetic_exception <|
|
||||
Panic.catch DateTimeException handler=handle_date_time_exception <|
|
||||
action
|
||||
|
@ -16,12 +16,6 @@ type Time_Period
|
||||
|
||||
Second
|
||||
|
||||
## PRIVATE
|
||||
This method could be replaced with matching on `Date_Period` supertype
|
||||
if/when that is supported.
|
||||
is_date_period : Boolean
|
||||
is_date_period self = False
|
||||
|
||||
## PRIVATE
|
||||
to_java_unit : TemporalUnit
|
||||
to_java_unit self = case self of
|
||||
|
@ -566,7 +566,9 @@ type Vector a
|
||||
map_with_index : (Integer -> Any -> Any) -> Vector Any
|
||||
map_with_index self function = Vector.new self.length i-> function i (self.at i)
|
||||
|
||||
## Applies a function to each element of the vector.
|
||||
## PRIVATE
|
||||
ADVANCED
|
||||
Applies a function to each element of the vector.
|
||||
|
||||
Unlike `map`, this method does not return the individual results,
|
||||
therefore it is only useful for side-effecting computations.
|
||||
@ -583,7 +585,9 @@ type Vector a
|
||||
0.up_to self.length . each ix->
|
||||
f (self.at ix)
|
||||
|
||||
## Applies a function to each element of the vector.
|
||||
## PRIVATE
|
||||
ADVANCED
|
||||
Applies a function to each element of the vector.
|
||||
|
||||
Arguments:
|
||||
- function: A function to apply that takes an index and an item.
|
||||
|
@ -100,6 +100,7 @@ import project.Data.Text.Text_Ordering.Text_Ordering
|
||||
import project.Data.Text.Text_Sub_Range.Text_Sub_Range
|
||||
import project.Data.Time.Date.Date
|
||||
import project.Data.Time.Date_Period.Date_Period
|
||||
import project.Data.Time.Date_Range.Date_Range
|
||||
import project.Data.Time.Date_Time.Date_Time
|
||||
import project.Data.Time.Day_Of_Week.Day_Of_Week
|
||||
import project.Data.Time.Day_Of_Week_From
|
||||
@ -149,6 +150,7 @@ export project.Data.Text.Text_Ordering.Text_Ordering
|
||||
export project.Data.Text.Text_Sub_Range.Text_Sub_Range
|
||||
export project.Data.Time.Date.Date
|
||||
export project.Data.Time.Date_Period.Date_Period
|
||||
export project.Data.Time.Date_Range.Date_Range
|
||||
export project.Data.Time.Date_Time.Date_Time
|
||||
export project.Data.Time.Day_Of_Week.Day_Of_Week
|
||||
export project.Data.Time.Day_Of_Week_From
|
||||
|
@ -204,10 +204,15 @@ public class Time_Utils {
|
||||
|
||||
/**
|
||||
* Counts days within the range from start (inclusive) to end (exclusive).
|
||||
*
|
||||
* <p>If start is before end, it will return 0.
|
||||
*/
|
||||
public static long days_between(LocalDate start, LocalDate end) {
|
||||
return ChronoUnit.DAYS.between(start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts months within the range from start (inclusive) to end (exclusive).
|
||||
*/
|
||||
public static long months_between(LocalDate start, LocalDate end) {
|
||||
return ChronoUnit.MONTHS.between(start, end);
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,13 @@ spec = Test.group "Range" <|
|
||||
range_3.end . should_equal 0
|
||||
range_3.step . should_equal -1
|
||||
|
||||
Test.specify "should allow to include the end" <|
|
||||
1.up_to 3 include_end=True . to_vector . should_equal [1, 2, 3]
|
||||
3.down_to 1 include_end=True . to_vector . should_equal [3, 2, 1]
|
||||
|
||||
1.up_to 1 include_end=True . to_vector . should_equal [1]
|
||||
1.down_to 1 include_end=True . to_vector . should_equal [1]
|
||||
|
||||
Test.specify "should allow creation with Range.new" <|
|
||||
Range.new . should_equal (Range.Between 0 100 1)
|
||||
Range.new 5 20 . should_equal (Range.Between 5 20 1)
|
||||
|
176
test/Tests/src/Data/Time/Date_Range_Spec.enso
Normal file
176
test/Tests/src/Data/Time/Date_Range_Spec.enso
Normal file
@ -0,0 +1,176 @@
|
||||
from Standard.Base import all
|
||||
import Standard.Base.Errors.Illegal_Argument.Illegal_Argument
|
||||
|
||||
from Standard.Test import Problems, Test, Test_Suite
|
||||
import Standard.Test.Extensions
|
||||
|
||||
main = Test_Suite.run_main spec
|
||||
|
||||
spec =
|
||||
Test.group "Date_Range" <|
|
||||
Test.specify "should be created with up_to and down_to extension methods" <|
|
||||
(Date.new 2020 02 28).up_to (Date.new 2020 03 02) . to_vector . should_equal [Date.new 2020 02 28, Date.new 2020 02 29, Date.new 2020 03 01]
|
||||
(Date.new 2020 02 28).up_to (Date.new 2020 03 02) include_end=True . to_vector . should_equal [Date.new 2020 02 28, Date.new 2020 02 29, Date.new 2020 03 01, Date.new 2020 03 02]
|
||||
|
||||
(Date.new 2021 03 01).down_to (Date.new 2021 02 28) . to_vector . should_equal [Date.new 2021 03 01]
|
||||
(Date.new 2021 03 01).down_to (Date.new 2021 02 28) include_end=True . to_vector . should_equal [Date.new 2021 03 01, Date.new 2021 02 28]
|
||||
|
||||
(Date.new 2023 12 31).up_to (Date.new 2023 12 31) . to_vector . should_equal []
|
||||
(Date.new 2023 12 31).up_to (Date.new 2023 12 31) include_end=True . to_vector . should_equal [Date.new 2023 12 31]
|
||||
|
||||
(Date.new 2023 12 31).down_to (Date.new 2023 12 31) . to_vector . should_equal []
|
||||
(Date.new 2023 12 31).down_to (Date.new 2023 12 31) include_end=True . to_vector . should_equal [Date.new 2023 12 31]
|
||||
|
||||
(Date.new 2023 12 31).down_to (Date.new 2023 12 31) . with_step Date_Period.Month . to_vector . should_equal []
|
||||
|
||||
Test.specify ".new should infer if the range should be increasing or not" <|
|
||||
Date_Range.new (Date.new 2023 10 01) (Date.new 2023 10 04) . to_vector . should_equal [Date.new 2023 10 01, Date.new 2023 10 02, Date.new 2023 10 03]
|
||||
Date_Range.new (Date.new 2023 10 04) (Date.new 2023 10 01) . to_vector . should_equal [Date.new 2023 10 04, Date.new 2023 10 03, Date.new 2023 10 02]
|
||||
|
||||
Test.specify "will be empty if the start and end are swapped with up_to or down_to" <|
|
||||
(Date.new 2023 10 01).down_to (Date.new 2023 10 04) . to_vector . should_equal []
|
||||
(Date.new 2023 10 04).up_to (Date.new 2023 10 01) . to_vector . should_equal []
|
||||
|
||||
(Date.new 2023 10 01).down_to (Date.new 2023 10 04) . with_step Date_Period.Month . to_vector . should_equal []
|
||||
(Date.new 2023 10 04).up_to (Date.new 2023 10 01) . with_step Date_Period.Month . to_vector . should_equal []
|
||||
|
||||
Test.specify "should allow setting a custom step" <|
|
||||
(Date.new 2020 01 10).up_to (Date.new 2020 01 31) . with_step (Period.new days=5) . to_vector . should_equal [Date.new 2020 01 10, Date.new 2020 01 15, Date.new 2020 01 20, Date.new 2020 01 25, Date.new 2020 01 30]
|
||||
(Date.new 2020 01 10).up_to (Date.new 2020 01 30) . with_step (Period.new days=5) . to_vector . should_equal [Date.new 2020 01 10, Date.new 2020 01 15, Date.new 2020 01 20, Date.new 2020 01 25]
|
||||
(Date.new 2020 01 10).up_to (Date.new 2020 01 30) include_end=True . with_step (Period.new days=5) . to_vector . should_equal [Date.new 2020 01 10, Date.new 2020 01 15, Date.new 2020 01 20, Date.new 2020 01 25, Date.new 2020 01 30]
|
||||
|
||||
(Date.new 2020 01 10).down_to (Date.new 2020 01 01) . with_step Date_Period.Week . to_vector . should_equal [Date.new 2020 01 10, Date.new 2020 01 03]
|
||||
|
||||
(Date.new 2020 01 01).up_to (Date.new 2020 12 31) . with_step Date_Period.Month . to_vector . should_equal [Date.new 2020 01 01, Date.new 2020 02 01, Date.new 2020 03 01, Date.new 2020 04 01, Date.new 2020 05 01, Date.new 2020 06 01, Date.new 2020 07 01, Date.new 2020 08 01, Date.new 2020 09 01, Date.new 2020 10 01, Date.new 2020 11 01, Date.new 2020 12 01]
|
||||
(Date.new 2020 01 01).up_to (Date.new 2026) . with_step (Period.new years=2) . to_vector . should_equal [Date.new 2020 01 01, Date.new 2022 01 01, Date.new 2024 01 01]
|
||||
(Date.new 2020 01 01).up_to (Date.new 2026) include_end=True . with_step (Period.new years=2) . to_vector . should_equal [Date.new 2020 01 01, Date.new 2022 01 01, Date.new 2024 01 01, Date.new 2026 01 01]
|
||||
(Date.new 2060 11 25).down_to (Date.new 2020 11 24) . with_step (Period.new years=20) . to_vector . should_equal [Date.new 2060 11 25, Date.new 2040 11 25, Date.new 2020 11 25]
|
||||
|
||||
(Date.new 2020).up_to (Date.new 2023) . with_step (Period.new years=1 months=2 days=3) . to_vector . should_equal [Date.new 2020 01 01, Date.new 2021 03 04, Date.new 2022 05 07]
|
||||
|
||||
Test.specify "should handle end of month edge cases" <|
|
||||
(Date.new 2020 01 31).up_to (Date.new 2020 12 31) include_end=True . with_step Date_Period.Month . to_vector . should_equal [Date.new 2020 01 31, Date.new 2020 02 29, Date.new 2020 03 31, Date.new 2020 04 30, Date.new 2020 05 31, Date.new 2020 06 30, Date.new 2020 07 31, Date.new 2020 08 31, Date.new 2020 09 30, Date.new 2020 10 31, Date.new 2020 11 30, Date.new 2020 12 31]
|
||||
(Date.new 2021 01 28).up_to (Date.new 2021 05 10) . with_step Date_Period.Month . to_vector . should_equal [Date.new 2021 01 28, Date.new 2021 02 28, Date.new 2021 03 28, Date.new 2021 04 28]
|
||||
(Date.new 2023 01 30).up_to (Date.new 2023 06 10) . with_step Date_Period.Month . to_vector . should_equal [Date.new 2023 01 30, Date.new 2023 02 28, Date.new 2023 03 30, Date.new 2023 04 30, Date.new 2023 05 30]
|
||||
(Date.new 2023 01 30).up_to (Date.new 2023 06 10) . with_step (Period.new months=2) . to_vector . should_equal [Date.new 2023 01 30, Date.new 2023 03 30, Date.new 2023 05 30]
|
||||
(Date.new 2020 02 29).up_to (Date.new 2023) . with_step Date_Period.Year . to_vector . should_equal [Date.new 2020 02 29, Date.new 2021 02 28, Date.new 2022 02 28]
|
||||
|
||||
Test.specify "should handle edge cases" <|
|
||||
(Date.new 2020 02 27).up_to (Date.new 2020 03 02) include_end=True . with_step (Period.new days=2) . to_vector . should_equal [Date.new 2020 02 27, Date.new 2020 02 29, Date.new 2020 03 02]
|
||||
|
||||
(Date.new 2020 02 27).up_to (Date.new 2020 02 28) . with_step Date_Period.Month . to_vector . should_equal [Date.new 2020 02 27]
|
||||
(Date.new 2020 02 27).up_to (Date.new 2020 04 27) . with_step Date_Period.Month . to_vector . should_equal [Date.new 2020 02 27, Date.new 2020 03 27]
|
||||
(Date.new 2020 02 27).up_to (Date.new 2020 04 27) include_end=True . with_step Date_Period.Month . to_vector . should_equal [Date.new 2020 02 27, Date.new 2020 03 27, Date.new 2020 04 27]
|
||||
(Date.new 2020 02 27).up_to (Date.new 2020 04 01) . with_step Date_Period.Month . to_vector . should_equal [Date.new 2020 02 27, Date.new 2020 03 27]
|
||||
|
||||
(Date.new 2021 02 01).up_to (Date.new 2021 03 01) include_end=True . with_step Date_Period.Month . to_vector . should_equal [Date.new 2021 02 01, Date.new 2021 03 01]
|
||||
|
||||
(Date.new 2020 01 31).up_to (Date.new 2020 04 30) . with_step Date_Period.Month . to_vector . should_equal [Date.new 2020 01 31, Date.new 2020 02 29, Date.new 2020 03 31]
|
||||
(Date.new 2020 01 31).up_to (Date.new 2020 04 30) include_end=True . with_step Date_Period.Month . to_vector . should_equal [Date.new 2020 01 31, Date.new 2020 02 29, Date.new 2020 03 31, Date.new 2020 04 30]
|
||||
(Date.new 2020 01 31).up_to (Date.new 2020 04 01) include_end=True . with_step Date_Period.Month . to_vector . should_equal [Date.new 2020 01 31, Date.new 2020 02 29, Date.new 2020 03 31]
|
||||
|
||||
v = (Date.new 2020 01 01).up_to (Date.new 2020 12 31) include_end=True . with_step Date_Period.Month . to_vector
|
||||
v.length . should_equal 12
|
||||
v.first . should_equal (Date.new 2020 01 01)
|
||||
v.last . should_equal (Date.new 2020 12 01)
|
||||
|
||||
(Date.new 2020 01 01).up_to (Date.new 2020 12 31) include_end=True . with_step (Period.new months=3) . to_vector . should_equal [Date.new 2020 01 01, Date.new 2020 04 01, Date.new 2020 07 01, Date.new 2020 10 01]
|
||||
(Date.new 2020 01 01).up_to (Date.new 2021 01 01) include_end=True . with_step (Period.new months=3) . to_vector . should_equal [Date.new 2020 01 01, Date.new 2020 04 01, Date.new 2020 07 01, Date.new 2020 10 01, Date.new 2021 01 01]
|
||||
(Date.new 2020 01 01).up_to (Date.new 2021 01 01) include_end=False . with_step (Period.new months=3) . to_vector . should_equal [Date.new 2020 01 01, Date.new 2020 04 01, Date.new 2020 07 01, Date.new 2020 10 01]
|
||||
|
||||
(Date.new 2020 01 31).up_to (Date.new 2020 05 01) . with_step (Period.new months=2) . to_vector . should_equal [Date.new 2020 01 31, Date.new 2020 03 31]
|
||||
(Date.new 2020 01 31).up_to (Date.new 2020 03 31) include_end=True . with_step (Period.new months=2) . to_vector . should_equal [Date.new 2020 01 31, Date.new 2020 03 31]
|
||||
(Date.new 2020 01 31).up_to (Date.new 2020 03 31) . with_step (Period.new months=2) . to_vector . should_equal [Date.new 2020 01 31]
|
||||
(Date.new 2020 01 31).up_to (Date.new 2020 04 02) . with_step (Period.new months=2) . to_vector . should_equal [Date.new 2020 01 31, Date.new 2020 03 31]
|
||||
|
||||
(Date.new 2020 12 31).up_to (Date.new 2021 01 01) . with_step (Period.new years=1) . to_vector . should_equal [Date.new 2020 12 31]
|
||||
(Date.new 2020 12 31).up_to (Date.new 2021 01 01) . with_step (Period.new years=10) . to_vector . should_equal [Date.new 2020 12 31]
|
||||
(Date.new 2020 12 31).up_to (Date.new 2023 01 01) . with_step (Period.new years=1) . to_vector . should_equal [Date.new 2020 12 31, Date.new 2021 12 31, Date.new 2022 12 31]
|
||||
(Date.new 2020 12 31).up_to (Date.new 2023 01 01) . with_step (Period.new years=2) . to_vector . should_equal [Date.new 2020 12 31, Date.new 2022 12 31]
|
||||
(Date.new 2020 12 31).up_to (Date.new 2023 01 01) . with_step (Period.new years=10) . to_vector . should_equal [Date.new 2020 12 31]
|
||||
(Date.new 2021 01 01).up_to (Date.new 2023 12 31) . with_step (Period.new years=1) . to_vector . should_equal [Date.new 2021 01 01, Date.new 2022 01 01, Date.new 2023 01 01]
|
||||
(Date.new 2021 01 01).up_to (Date.new 2023 12 31) . with_step (Period.new years=2) . to_vector . should_equal [Date.new 2021 01 01, Date.new 2023 01 01]
|
||||
(Date.new 2021 01 01).up_to (Date.new 2023 12 31) include_end=True . with_step (Period.new years=2) . to_vector . should_equal [Date.new 2021 01 01, Date.new 2023 01 01]
|
||||
|
||||
Test.specify "should not allow a non-positive step" <|
|
||||
(Date.new 2010).up_to (Date.new 2050) . with_step (Period.new years=0 months=0 days=0) . should_fail_with Illegal_Argument
|
||||
(Date.new 2010).up_to (Date.new 2050) . with_step (Period.new years=0 months=-1 days=0) . should_fail_with Illegal_Argument
|
||||
(Date.new 2010).up_to (Date.new 2050) . with_step (Period.new years=0 months=0 days=-1) . should_fail_with Illegal_Argument
|
||||
(Date.new 2010).up_to (Date.new 2050) . with_step (Period.new years=-1 months=0 days=0) . should_fail_with Illegal_Argument
|
||||
(Date.new 2010).up_to (Date.new 2050) . with_step (Period.new years=2 months=-1 days=0) . should_fail_with Illegal_Argument
|
||||
(Date.new 2010).up_to (Date.new 2050) . with_step (Period.new years=-1 months=40 days=0) . should_fail_with Illegal_Argument
|
||||
(Date.new 2010).up_to (Date.new 2050) . with_step (Period.new years=1 months=40 days=-20) . should_fail_with Illegal_Argument
|
||||
(Date.new 2010).up_to (Date.new 2050) . with_step (Period.new years=-1 months=-2 days=2) . should_fail_with Illegal_Argument
|
||||
(Date.new 2010).up_to (Date.new 2050) . with_step (Period.new years=-1 months=-2 days=-1) . should_fail_with Illegal_Argument
|
||||
|
||||
# e.g. 2021-06-05 + 1 month - 30 days == 2021-06-05 --> no progression
|
||||
(Date.new 2021 06 05).up_to (Date.new 2021 06 08) . with_step (Period.new months=1 days=(-30)) . should_fail_with Illegal_Argument
|
||||
(Date.new 2021 05 05).up_to (Date.new 2021 06 08) . with_step (Period.new months=1 days=(-30)) . should_fail_with Illegal_Argument
|
||||
(Date.new 2021 02 28).up_to (Date.new 2021 03 31) . with_step ((Period.new years=1 months=(-11) days=(-28))) . should_fail_with Illegal_Argument
|
||||
|
||||
Test.specify "should allow to reverse a range, returning a vector" <|
|
||||
(Date.new 2020 01 02).up_to (Date.new 2020 01 02) . reverse . should_equal []
|
||||
(Date.new 2020 01 02).up_to (Date.new 2020 01 02) include_end=True . reverse . should_equal [Date.new 2020 01 02]
|
||||
|
||||
(Date.new 2020 01 03).down_to (Date.new 2020 01 01) . reverse . should_equal [Date.new 2020 01 02, Date.new 2020 01 03]
|
||||
|
||||
(Date.new 2020 02 29).up_to (Date.new 2023) . with_step Date_Period.Year . reverse . should_equal [Date.new 2022 02 28, Date.new 2021 02 28, Date.new 2020 02 29]
|
||||
|
||||
Test.specify "should be consistent with its to_vector representation" <|
|
||||
r1 = (Date.new 2020 02 28).up_to (Date.new 2020 03 02)
|
||||
r2 = (Date.new 2020 02 28).up_to (Date.new 2020 03 02) include_end=True
|
||||
r3 = (Date.new 2021 03 01).down_to (Date.new 2021 02 28)
|
||||
r4 = (Date.new 2021 03 01).down_to (Date.new 2021 02 28) include_end=True
|
||||
r5 = (Date.new 2023 12 31).up_to (Date.new 2023 12 31)
|
||||
r6 = (Date.new 2023 12 31).up_to (Date.new 2023 12 31) include_end=True
|
||||
r7 = (Date.new 2023 12 31).down_to (Date.new 2023 12 31)
|
||||
r8 = (Date.new 2023 12 31).down_to (Date.new 2023 12 31) include_end=True
|
||||
|
||||
r9 = (Date.new 2020 01 10).down_to (Date.new 2020 01 01) . with_step Date_Period.Week
|
||||
r10 = (Date.new 2020 01 01).up_to (Date.new 2020 12 31) . with_step Date_Period.Month
|
||||
r11 = (Date.new 2020 01 01).up_to (Date.new 2026) . with_step (Period.new years=2)
|
||||
r12 = (Date.new 2020 01 01).up_to (Date.new 2026) include_end=True . with_step (Period.new years=2)
|
||||
r13 = (Date.new 2060 11 25).down_to (Date.new 2020 11 24) . with_step (Period.new years=20)
|
||||
|
||||
r14 = (Date.new 2020 01 31).up_to (Date.new 2020 12 31) include_end=True . with_step Date_Period.Month
|
||||
r15 = (Date.new 2020 02 29).up_to (Date.new 2023) . with_step Date_Period.Year
|
||||
|
||||
ranges = [r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15]
|
||||
ranges.each r-> Test.with_clue r.to_text+": " <|
|
||||
r.length . should_equal r.to_vector.length
|
||||
r.is_empty . should_equal r.to_vector.is_empty
|
||||
r.not_empty . should_equal r.to_vector.not_empty
|
||||
|
||||
r.map .day_of_week . should_equal (r.to_vector.map .day_of_week)
|
||||
p = d-> d.day_of_week == Day_Of_Week.Monday
|
||||
r.filter p . should_equal (r.to_vector.filter p)
|
||||
r.all p . should_equal (r.to_vector.all p)
|
||||
r.any p . should_equal (r.to_vector.any p)
|
||||
r.find p . should_equal (r.to_vector.find p)
|
||||
r.index_of p . should_equal (r.to_vector.index_of p)
|
||||
r.last_index_of p . should_equal (r.to_vector.last_index_of p)
|
||||
count_mondays acc date =
|
||||
if date.day_of_week == Day_Of_Week.Monday then acc+1 else acc
|
||||
r.fold 0 count_mondays . should_equal (r.to_vector.fold 0 count_mondays)
|
||||
r.running_fold 0 count_mondays . should_equal (r.to_vector.running_fold 0 count_mondays)
|
||||
|
||||
reducer x y = if x > y then x else y
|
||||
# Catch+to_text to fix Empty_Error equality.
|
||||
r.reduce reducer . catch . to_text . should_equal (r.to_vector.reduce reducer . catch . to_text)
|
||||
|
||||
Test.specify "should define friendly text representations" <|
|
||||
r1 = (Date.new 2020 02 28).up_to (Date.new 2020 03 02)
|
||||
r2 = (Date.new 2020 03 20).down_to (Date.new 2020 03 01) include_end=True . with_step Date_Period.Week
|
||||
|
||||
r1.to_text . should_equal '(Date_Range from 2020-02-28 up to 2020-03-02)'
|
||||
r2.to_text . should_equal '(Date_Range from 2020-03-20 down to 2020-02-29 by 7D)'
|
||||
|
||||
r1.pretty . should_equal r1.to_text
|
||||
r2.pretty . should_equal r2.to_text
|
||||
|
||||
r1.to_display_text . should_equal '[2020-02-28 .. 2020-03-02]'
|
||||
r2.to_display_text . should_equal '[2020-03-20 .. 2020-02-29 by -7D]'
|
||||
|
||||
Test.specify "should be serializable to JSON" <|
|
||||
r = (Date.new 2020 01 01).up_to (Date.new 2020 01 03)
|
||||
r.to_json . should_equal '{"type":"Date_Range","start":{"type":"Date","constructor":"new","day":1,"month":1,"year":2020},"end":{"type":"Date","constructor":"new","day":3,"month":1,"year":2020},"step":{"type":"Period","constructor":"new","days":1},"increasing":true}'
|
@ -7,12 +7,14 @@ import project.Data.Time.Duration_Spec
|
||||
import project.Data.Time.Period_Spec
|
||||
import project.Data.Time.Time_Of_Day_Spec
|
||||
import project.Data.Time.Date_Spec
|
||||
import project.Data.Time.Date_Range_Spec
|
||||
import project.Data.Time.Date_Time_Spec
|
||||
import project.Data.Time.Time_Zone_Spec
|
||||
import project.Data.Time.Day_Of_Week_Spec
|
||||
|
||||
spec =
|
||||
Date_Spec.spec
|
||||
Date_Range_Spec.spec
|
||||
Duration_Spec.spec
|
||||
Period_Spec.spec
|
||||
Time_Of_Day_Spec.spec
|
||||
|
Loading…
Reference in New Issue
Block a user