Add Period Start and End functions to Date and DateTime (#3695)

Implements https://www.pivotaltracker.com/story/show/183081152
This commit is contained in:
Radosław Waśko 2022-09-13 11:51:08 +02:00 committed by GitHub
parent fba5047acc
commit b304402d8e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 493 additions and 25 deletions

View File

@ -193,6 +193,8 @@
- [Implemented specialized storage for the in-memory Table.][3673]
- [Implemented `Table.distinct` for the in-memory backend.][3684]
- [Added `databases`, `schemas`, `tables` support to database Connection.][3632]
- [Implemented `start_of` and `end_of` methods for date/time types allowing to
find start and end of a period of time containing the provided time.][3695]
[debug-shortcuts]:
https://github.com/enso-org/enso/blob/develop/app/gui/docs/product/shortcuts.md#debug
@ -309,6 +311,7 @@
[3647]: https://github.com/enso-org/enso/pull/3647
[3673]: https://github.com/enso-org/enso/pull/3673
[3684]: https://github.com/enso-org/enso/pull/3684
[3695]: https://github.com/enso-org/enso/pull/3695
#### Enso Compiler

View File

@ -5,8 +5,11 @@ import sbt.Keys.{libraryDependencies, scalacOptions}
import sbt.addCompilerPlugin
import sbt.complete.DefaultParsers._
import sbt.complete.Parser
import sbtcrossproject.CrossPlugin.autoImport.{CrossType, crossProject}
import src.main.scala.licenses.{DistributionDescription, SBTDistributionComponent}
import sbtcrossproject.CrossPlugin.autoImport.{crossProject, CrossType}
import src.main.scala.licenses.{
DistributionDescription,
SBTDistributionComponent
}
import java.io.File
@ -14,9 +17,9 @@ import java.io.File
// === Global Configuration ===================================================
// ============================================================================
val scalacVersion = "2.13.8"
val graalVersion = "21.3.0"
val javaVersion = "11"
val scalacVersion = "2.13.8"
val graalVersion = "21.3.0"
val javaVersion = "11"
val defaultDevEnsoVersion = "0.0.0-dev"
val ensoVersion = sys.env.getOrElse(
"ENSO_VERSION",
@ -713,11 +716,11 @@ lazy val `profiling-utils` = project
"org.netbeans.api" % "org-netbeans-modules-sampler" % netbeansApiVersion
exclude ("org.netbeans.api", "org-openide-loaders")
exclude ("org.netbeans.api", "org-openide-nodes")
exclude("org.netbeans.api", "org-netbeans-api-progress-nb")
exclude("org.netbeans.api", "org-netbeans-api-progress")
exclude("org.netbeans.api", "org-openide-util-lookup")
exclude("org.netbeans.api", "org-openide-util")
exclude("org.netbeans.api", "org-openide-dialogs")
exclude ("org.netbeans.api", "org-netbeans-api-progress-nb")
exclude ("org.netbeans.api", "org-netbeans-api-progress")
exclude ("org.netbeans.api", "org-openide-util-lookup")
exclude ("org.netbeans.api", "org-openide-util")
exclude ("org.netbeans.api", "org-openide-dialogs")
exclude ("org.netbeans.api", "org-openide-filesystems")
exclude ("org.netbeans.api", "org-openide-util-ui")
exclude ("org.netbeans.api", "org-openide-awt")
@ -1007,7 +1010,6 @@ val truffleRunOptions = if (java.lang.Boolean.getBoolean("bench.compileOnly")) {
)
}
val truffleRunOptionsSettings = Seq(
fork := true,
javaOptions ++= truffleRunOptions
@ -1744,7 +1746,8 @@ lazy val `std-base` = project
Compile / packageBin / artifactPath :=
`base-polyglot-root` / "std-base.jar",
libraryDependencies ++= Seq(
"com.ibm.icu" % "icu4j" % icuVersion
"com.ibm.icu" % "icu4j" % icuVersion,
"org.graalvm.truffle" % "truffle-api" % graalVersion % "provided"
),
Compile / packageBin := Def.task {
val result = (Compile / packageBin).value
@ -1767,7 +1770,7 @@ lazy val `std-table` = project
Compile / packageBin / artifactPath :=
`table-polyglot-root` / "std-table.jar",
libraryDependencies ++= Seq(
"com.ibm.icu" % "icu4j" % icuVersion % "provided",
"com.ibm.icu" % "icu4j" % icuVersion % "provided",
"com.univocity" % "univocity-parsers" % "2.9.1",
"org.apache.poi" % "poi-ooxml" % "5.2.2",
"org.apache.xmlbeans" % "xmlbeans" % "5.1.0",

View File

@ -1,6 +1,7 @@
from Standard.Base import all
import Standard.Base.Data.Time.Duration
import Standard.Base.Data.Time.Date_Period
import Standard.Base.Polyglot
from Standard.Base.Error.Common import Time_Error_Data
@ -230,6 +231,14 @@ type Date
day_of_week self =
Day_Of_Week.from (Time_Utils.get_field_as_localdate self ChronoField.DAY_OF_WEEK) Day_Of_Week.Monday
## Returns the first date within the `Date_Period` containing self.
start_of : Date_Period -> Date
start_of self period=Date_Period.Month = period.adjust_start self
## Returns the last date within the `Date_Period` containing self.
end_of : Date_Period -> Date
end_of self period=Date_Period.Month = period.adjust_end self
## ALIAS Date to Time
Combine this date with time of day to create a point in time.
@ -244,8 +253,8 @@ type Date
from Standard.Base import Date, Time_Of_Day, Time_Zone
example_to_time = Date.new 2020 2 3 . to_time Time_Of_Day.new Time_Zone.utc
to_time : Time_Of_Day -> Time_Zone -> Date_Time
to_time self time_of_day (zone=Time_Zone.system) = self.to_time_builtin time_of_day zone
to_date_time : Time_Of_Day -> Time_Zone -> Date_Time
to_date_time self (time_of_day=Time_Of_Day.new) (zone=Time_Zone.system) = self.to_time_builtin time_of_day zone
## Add the specified amount of time to this instant to get another date.

View File

@ -0,0 +1,36 @@
from Standard.Base import all
polyglot java import org.enso.base.Time_Utils
polyglot java import org.enso.base.time.Date_Period_Utils
polyglot java import java.time.temporal.TemporalAdjuster
polyglot java import java.time.temporal.TemporalAdjusters
## Represents a period of time longer on the scale of days (longer than a day).
type Date_Period
Year
Quarter
Month
## 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 =
adjuster = case self of
Year -> TemporalAdjusters.firstDayOfYear
Quarter -> Date_Period_Utils.quarter_start
Month -> TemporalAdjusters.firstDayOfMonth
(Time_Utils.utils_for date).apply_adjuster date adjuster
## PRIVATE
adjust_end : (Date | Date_Time) -> (Date | Date_Time)
adjust_end self date =
adjuster = case self of
Year -> TemporalAdjusters.lastDayOfYear
Quarter -> Date_Period_Utils.quarter_end
Month -> TemporalAdjusters.lastDayOfMonth
(Time_Utils.utils_for date).apply_adjuster date adjuster

View File

@ -1,6 +1,8 @@
from Standard.Base import all
import Standard.Base.Data.Time.Duration
import Standard.Base.Data.Time.Date_Period
import Standard.Base.Data.Time.Time_Period
from Standard.Base.Error.Common import Time_Error
polyglot java import java.time.format.DateTimeFormatter
@ -326,6 +328,24 @@ type Date_Time
day_of_week self =
Day_Of_Week.from (Time_Utils.get_field_as_zoneddatetime self ChronoField.DAY_OF_WEEK) Day_Of_Week.Monday
## Returns the first date within the `Time_Period` or `Date_Period`
containing self.
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
## 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
## ALIAS Time to Date
Convert this point in time to date, discarding the time of day
@ -409,7 +429,7 @@ type Date_Time
example_to_json = Date_Time.now.to_json
to_json : Json.Object
to_json self = Json.from_pairs [["type", "Time"], ["year", self.year], ["month", self.month], ["day", self.day], ["hour", self.hour], ["minute", self.minute], ["second", self.second], ["nanosecond", self.nanosecond], ["zone", self.zone]]
to_json self = Json.from_pairs [["type", "Date_Time"], ["year", self.year], ["month", self.month], ["day", self.day], ["hour", self.hour], ["minute", self.minute], ["second", self.second], ["nanosecond", self.nanosecond], ["zone", self.zone]]
## Format this time as text using the specified format specifier.

View File

@ -1,6 +1,7 @@
from Standard.Base import all
import Standard.Base.Data.Time.Duration
import Standard.Base.Data.Time.Time_Period
from Standard.Base.Error.Common import Time_Error
polyglot java import java.time.format.DateTimeFormatter
@ -170,6 +171,14 @@ type Time_Of_Day
nanosecond : Integer
nanosecond self = @Builtin_Method "Time_Of_Day.nanosecond"
## Returns the first time within the `Time_Period` containing self.
start_of : Time_Period -> Time_Of_Day
start_of self period=Time_Period.Day = period.adjust_start self
## Returns the last time within the `Time_Period` containing self.
end_of : Time_Period -> Time_Of_Day
end_of self period=Time_Period.Day = period.adjust_end self
## Extracts the time as the number of seconds, from 0 to 24 * 60 * 60 - 1.
> Example
@ -193,8 +202,8 @@ type Time_Of_Day
from Standard.Base import Time_Of_Day
example_to_time = Time_Of_Day.new 12 30 . to_time (Date.new 2020)
to_time : Date -> Time_Zone -> Time
to_time self date (zone=Time_Zone.system) = self.to_time_builtin date zone
to_date_time : Date -> Time_Zone -> Date_Time
to_date_time self date (zone=Time_Zone.system) = self.to_time_builtin date zone
## Add the specified amount of time to this instant to get a new instant.

View File

@ -0,0 +1,35 @@
from Standard.Base import all
polyglot java import org.enso.base.Time_Utils
polyglot java import java.time.temporal.ChronoUnit
## Represents a period of time of a day or shorter.
type Time_Period
Day
Hour
Minute
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
Day -> ChronoUnit.DAYS
Hour -> ChronoUnit.HOURS
Minute -> ChronoUnit.MINUTES
Second -> ChronoUnit.SECONDS
## PRIVATE
adjust_start : (Time_Of_Day | Date_Time) -> (Time_Of_Day | Date_Time)
adjust_start self date =
(Time_Utils.utils_for date).start_of_time_period date self.to_java_unit
## PRIVATE
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

View File

@ -14,6 +14,7 @@ import org.enso.interpreter.runtime.library.dispatch.TypesLibrary;
import java.time.DateTimeException;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.zone.ZoneRulesException;
@ExportLibrary(InteropLibrary.class)
@ -54,6 +55,11 @@ public final class EnsoTimeZone implements TruffleObject {
return new EnsoTimeZone(ZoneId.systemDefault());
}
@Builtin.Method(description = "Return the text representation of this timezone.")
public Text toText() {
return Text.create(zone.toString());
}
@ExportMessage
boolean isTimeZone() {
return true;

View File

@ -1,11 +1,15 @@
package org.enso.base;
import org.enso.base.time.Date_Time_Utils;
import org.enso.base.time.Date_Utils;
import org.enso.base.time.TimeUtilsBase;
import org.enso.base.time.Time_Of_Day_Utils;
import org.graalvm.polyglot.Value;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalField;
import java.time.temporal.WeekFields;
import java.time.temporal.*;
import java.util.Locale;
/** Utils for standard library operations on Time. */
@ -206,4 +210,24 @@ public class Time_Utils {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
return (LocalTime.parse(text, formatter.withLocale(locale)));
}
/**
* Normally this method could be done in Enso by pattern matching, but currently matching on Time
* types is not supported, so this is a workaround.
*
* <p>TODO once the related issue is fixed, this workaround may be replaced with pattern matching
* in Enso; the related Pivotal issue: https://www.pivotaltracker.com/story/show/183219169
*/
public static TimeUtilsBase utils_for(Value value) {
boolean isDate = value.isDate();
boolean isTime = value.isTime();
if (isDate && isTime) return Date_Time_Utils.INSTANCE;
if (isDate) return Date_Utils.INSTANCE;
if (isTime) return Time_Of_Day_Utils.INSTANCE;
throw new IllegalArgumentException("Unexpected argument type: " + value);
}
public static ZoneOffset get_datetime_offset(ZonedDateTime datetime) {
return datetime.getOffset();
}
}

View File

@ -0,0 +1,25 @@
package org.enso.base.time;
import java.time.YearMonth;
import java.time.temporal.*;
public class Date_Period_Utils implements TimeUtilsBase {
public static TemporalAdjuster quarter_start =
(Temporal temporal) -> {
int currentQuarter = temporal.get(IsoFields.QUARTER_OF_YEAR);
int month = (currentQuarter - 1) * 3 + 1;
return temporal
.with(ChronoField.MONTH_OF_YEAR, month)
.with(TemporalAdjusters.firstDayOfMonth());
};
public static TemporalAdjuster quarter_end =
(Temporal temporal) -> {
int currentQuarter = YearMonth.from(temporal).get(IsoFields.QUARTER_OF_YEAR);
int month = (currentQuarter - 1) * 3 + 3;
return temporal
.with(ChronoField.MONTH_OF_YEAR, month)
.with(TemporalAdjusters.lastDayOfMonth());
};
}

View File

@ -0,0 +1,21 @@
package org.enso.base.time;
import java.time.ZonedDateTime;
import java.time.temporal.TemporalAdjuster;
import java.time.temporal.TemporalUnit;
public class Date_Time_Utils implements TimeUtilsBase {
public static final Date_Time_Utils INSTANCE = new Date_Time_Utils();
public ZonedDateTime start_of_time_period(ZonedDateTime date, TemporalUnit unit) {
return date.truncatedTo(unit);
}
public ZonedDateTime end_of_time_period(ZonedDateTime date, TemporalUnit unit) {
return date.truncatedTo(unit).plus(1, unit).minusNanos(1);
}
public ZonedDateTime apply_adjuster(ZonedDateTime date, TemporalAdjuster adjuster) {
return date.with(adjuster);
}
}

View File

@ -0,0 +1,12 @@
package org.enso.base.time;
import java.time.LocalDate;
import java.time.temporal.TemporalAdjuster;
public class Date_Utils implements TimeUtilsBase {
public static final Date_Utils INSTANCE = new Date_Utils();
public LocalDate apply_adjuster(LocalDate date, TemporalAdjuster adjuster) {
return date.with(adjuster);
}
}

View File

@ -0,0 +1,6 @@
package org.enso.base.time;
/**
* A base type for date/time util classes. Used just to mark the return type of {@code utils_for}.
*/
public interface TimeUtilsBase {}

View File

@ -0,0 +1,19 @@
package org.enso.base.time;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalUnit;
public class Time_Of_Day_Utils implements TimeUtilsBase {
public static final Time_Of_Day_Utils INSTANCE = new Time_Of_Day_Utils();
public LocalTime start_of_time_period(LocalTime date, TemporalUnit unit) {
return date.truncatedTo(unit);
}
public LocalTime end_of_time_period(LocalTime date, TemporalUnit unit) {
LocalTime truncated = date.truncatedTo(unit);
LocalTime adjusted = unit.equals(ChronoUnit.DAYS) ? truncated : truncated.plus(1, unit);
return adjusted.minusNanos(1);
}
}

View File

@ -1,6 +1,7 @@
from Standard.Base import all
import Standard.Base.Data.Text.Text_Sub_Range
import Standard.Base.Data.Time.Date_Period
import Standard.Base.Data.Time.Duration
from Standard.Base.Error.Common import Time_Error
@ -76,7 +77,7 @@ spec_with name create_new_date parse_date =
Test.fail ("Unexpected result: " + result.to_text)
Test.specify "should convert to time" <|
time = create_new_date 2000 12 21 . to_time (Time_Of_Day.new 12 30 45) Time_Zone.utc
time = create_new_date 2000 12 21 . to_date_time (Time_Of_Day.new 12 30 45) Time_Zone.utc
time . year . should_equal 2000
time . month . should_equal 12
time . day . should_equal 21
@ -132,8 +133,52 @@ spec_with name create_new_date parse_date =
date_1>date_2 . should_be_true
date_1<date_2 . should_be_false
Test.specify "should allow to find start and end of a Date_Period containing the current date" <|
d1 = create_new_date 2022 9 12
d1.start_of Date_Period.Year . should_equal (Date.new 2022 1 1)
d1.end_of Date_Period.Year . should_equal (Date.new 2022 12 31)
d1.start_of Date_Period.Quarter . should_equal (Date.new 2022 7 1)
d1.end_of Date_Period.Quarter . should_equal (Date.new 2022 9 30)
d1.start_of Date_Period.Month . should_equal (Date.new 2022 9 1)
d1.end_of Date_Period.Month . should_equal (Date.new 2022 9 30)
d2 = create_new_date 2022 2 7
d2.start_of Date_Period.Quarter . should_equal (Date.new 2022 1 1)
d2.end_of Date_Period.Quarter . should_equal (Date.new 2022 3 31)
d2.start_of Date_Period.Month . should_equal (Date.new 2022 2 1)
d2.end_of Date_Period.Month . should_equal (Date.new 2022 2 28)
d3 = create_new_date 2020 2 17
d3.start_of Date_Period.Year . should_equal (Date.new 2020 1 1)
d3.end_of Date_Period.Year . should_equal (Date.new 2020 12 31)
d3.start_of Date_Period.Month . should_equal (Date.new 2020 2 1)
d3.end_of Date_Period.Month . should_equal (Date.new 2020 2 29)
d4 = create_new_date 1970 12 31
d4.start_of Date_Period.Year . should_equal (Date.new 1970 1 1)
d4.end_of Date_Period.Year . should_equal (Date.new 1970 12 31)
d4.start_of Date_Period.Quarter . should_equal (Date.new 1970 10 1)
d4.end_of Date_Period.Quarter . should_equal (Date.new 1970 12 31)
d4.start_of Date_Period.Month . should_equal (Date.new 1970 12 1)
d4.end_of Date_Period.Month . should_equal (Date.new 1970 12 31)
d5 = create_new_date 2040 1 1
d5.start_of Date_Period.Year . should_equal (Date.new 2040 1 1)
d5.end_of Date_Period.Year . should_equal (Date.new 2040 12 31)
d5.start_of Date_Period.Quarter . should_equal (Date.new 2040 1 1)
d5.end_of Date_Period.Quarter . should_equal (Date.new 2040 3 31)
d5.start_of Date_Period.Month . should_equal (Date.new 2040 1 1)
d5.end_of Date_Period.Month . should_equal (Date.new 2040 1 31)
(create_new_date 2000 7 1).start_of Date_Period.Quarter . should_equal (Date.new 2000 7 1)
(create_new_date 2000 6 30).start_of Date_Period.Quarter . should_equal (Date.new 2000 4 1)
(create_new_date 2000 7 1).end_of Date_Period.Quarter . should_equal (Date.new 2000 9 30)
(create_new_date 2000 6 30).end_of Date_Period.Quarter . should_equal (Date.new 2000 6 30)
Date_Part_Spec.spec name create_new_date
main = Test.Suite.run_main spec
parseNormally x y = (Date.parse x y) . to_text

View File

@ -1,12 +1,16 @@
from Standard.Base import all
import Standard.Base.Data.Time.Date_Period
import Standard.Base.Data.Time.Time_Period
import Standard.Base.Data.Time.Duration
import Standard.Test
import project.Data.Time.Date_Part_Spec
polyglot java import org.enso.base.Time_Utils
polyglot java import java.time.ZonedDateTime
polyglot java import java.time.LocalDateTime
polyglot java import java.time.ZoneOffset
polyglot java import java.time.format.DateTimeFormatter
spec =
@ -49,7 +53,7 @@ spec_with name create_new_datetime parse_datetime nanoseconds_loss_in_precision=
time.to_json.should_equal <|
zone_pairs = [["zone", Time_Zone.utc]]
time_pairs = [["year", time.year], ["month", time.month], ["day", time.day], ["hour", time.hour], ["minute", time.minute], ["second", time.second], ["nanosecond", time.nanosecond]]
Json.from_pairs ([["type", "Time"]] + time_pairs + zone_pairs)
Json.from_pairs ([["type", "Date_Time"]] + time_pairs + zone_pairs)
Test.specify "should parse default time format" <|
text = create_new_datetime 1970 (zone = Time_Zone.utc) . to_text
@ -298,6 +302,163 @@ spec_with name create_new_datetime parse_datetime nanoseconds_loss_in_precision=
time_1>time_2 . should_be_true
time_1<time_2 . should_be_false
max_nanos = 999999999
Test.specify "should allow to find start/end of a Date_Period containing the current datetime" <|
d1 = create_new_datetime 2022 9 12 15 37 58
d1.start_of Date_Period.Year . should_equal (Date_Time.new 2022 1 1)
d1.end_of Date_Period.Year . should_equal (Date_Time.new 2022 12 31 23 59 59 max_nanos)
d1.start_of Date_Period.Quarter . should_equal (Date_Time.new 2022 7 1)
d1.end_of Date_Period.Quarter . should_equal (Date_Time.new 2022 9 30 23 59 59 max_nanos)
d1.start_of Date_Period.Month . should_equal (Date_Time.new 2022 9 1)
d1.end_of Date_Period.Month . should_equal (Date_Time.new 2022 9 30 23 59 59 max_nanos)
d2 = create_new_datetime 2022 2 7 12 34 56 123456789
d2.start_of Date_Period.Quarter . should_equal (Date_Time.new 2022 1 1)
d2.end_of Date_Period.Quarter . should_equal (Date_Time.new 2022 3 31 23 59 59 max_nanos)
d2.start_of Date_Period.Month . should_equal (Date_Time.new 2022 2 1)
d2.end_of Date_Period.Month . should_equal (Date_Time.new 2022 2 28 23 59 59 max_nanos)
d3 = create_new_datetime 2020 2 17
d3.start_of Date_Period.Year . should_equal (Date_Time.new 2020 1 1)
d3.end_of Date_Period.Year . should_equal (Date_Time.new 2020 12 31 23 59 59 max_nanos)
d3.start_of Date_Period.Month . should_equal (Date_Time.new 2020 2 1)
d3.end_of Date_Period.Month . should_equal (Date_Time.new 2020 2 29 23 59 59 max_nanos)
d4 = create_new_datetime 1970 12 31 23 59 59 max_nanos
d4.start_of Date_Period.Year . should_equal (Date_Time.new 1970 1 1)
d4.end_of Date_Period.Year . should_equal (Date_Time.new 1970 12 31 23 59 59 max_nanos)
d4.start_of Date_Period.Quarter . should_equal (Date_Time.new 1970 10 1)
d4.end_of Date_Period.Quarter . should_equal (Date_Time.new 1970 12 31 23 59 59 max_nanos)
d4.start_of Date_Period.Month . should_equal (Date_Time.new 1970 12 1)
d4.end_of Date_Period.Month . should_equal (Date_Time.new 1970 12 31 23 59 59 max_nanos)
d5 = create_new_datetime 2040 1 1
d5.start_of Date_Period.Year . should_equal (Date_Time.new 2040 1 1)
d5.end_of Date_Period.Year . should_equal (Date_Time.new 2040 12 31 23 59 59 max_nanos)
d5.start_of Date_Period.Quarter . should_equal (Date_Time.new 2040 1 1)
d5.end_of Date_Period.Quarter . should_equal (Date_Time.new 2040 3 31 23 59 59 max_nanos)
d5.start_of Date_Period.Month . should_equal (Date_Time.new 2040 1 1)
d5.end_of Date_Period.Month . should_equal (Date_Time.new 2040 1 31 23 59 59 max_nanos)
(create_new_datetime 2000 7 1 14 54).start_of Date_Period.Quarter . should_equal (Date_Time.new 2000 7 1)
(create_new_datetime 2000 6 30 15 34).start_of Date_Period.Quarter . should_equal (Date_Time.new 2000 4 1)
(create_new_datetime 2000 7 1 16 50).end_of Date_Period.Quarter . should_equal (Date_Time.new 2000 9 30 23 59 59 max_nanos)
(create_new_datetime 2000 6 30 17 40).end_of Date_Period.Quarter . should_equal (Date_Time.new 2000 6 30 23 59 59 max_nanos)
Test.specify "should allow to find start/end of a Time_Period containing the current datetime" <|
d1 = create_new_datetime 2022 9 12 15 37 58 123456789
d1.start_of Time_Period.Day . should_equal (Date_Time.new 2022 9 12)
d1.end_of Time_Period.Day . should_equal (Date_Time.new 2022 9 12 23 59 59 max_nanos)
d1.start_of Time_Period.Hour . should_equal (Date_Time.new 2022 9 12 15 0 0 0)
d1.end_of Time_Period.Hour . should_equal (Date_Time.new 2022 9 12 15 59 59 max_nanos)
d1.start_of Time_Period.Minute . should_equal (Date_Time.new 2022 9 12 15 37 0 0)
d1.end_of Time_Period.Minute . should_equal (Date_Time.new 2022 9 12 15 37 59 max_nanos)
d1.start_of Time_Period.Second . should_equal (Date_Time.new 2022 9 12 15 37 58 0)
d1.end_of Time_Period.Second . should_equal (Date_Time.new 2022 9 12 15 37 58 max_nanos)
d2 = create_new_datetime 1970 1 1 0 0 0
d2.start_of Time_Period.Day . should_equal (Date_Time.new 1970)
d2.end_of Time_Period.Day . should_equal (Date_Time.new 1970 1 1 23 59 59 max_nanos)
d2.start_of Time_Period.Hour . should_equal (Date_Time.new 1970 1 1 0 0 0 0)
d2.end_of Time_Period.Hour . should_equal (Date_Time.new 1970 1 1 0 59 59 max_nanos)
d2.start_of Time_Period.Minute . should_equal (Date_Time.new 1970 1 1 0 0 0 0)
d2.end_of Time_Period.Minute . should_equal (Date_Time.new 1970 1 1 0 0 59 max_nanos)
d2.start_of Time_Period.Second . should_equal (Date_Time.new 1970 1 1 0 0 0 0)
d2.end_of Time_Period.Second . should_equal (Date_Time.new 1970 1 1 0 0 0 max_nanos)
d3 = create_new_datetime 2100 12 31 23 59 59 max_nanos
d3.start_of Time_Period.Day . should_equal (Date_Time.new 2100 12 31)
d3.end_of Time_Period.Day . should_equal (Date_Time.new 2100 12 31 23 59 59 max_nanos)
d3.start_of Time_Period.Hour . should_equal (Date_Time.new 2100 12 31 23 0 0 0)
d3.end_of Time_Period.Hour . should_equal (Date_Time.new 2100 12 31 23 59 59 max_nanos)
d3.start_of Time_Period.Minute . should_equal (Date_Time.new 2100 12 31 23 59 0 0)
d3.end_of Time_Period.Minute . should_equal (Date_Time.new 2100 12 31 23 59 59 max_nanos)
d3.start_of Time_Period.Second . should_equal (Date_Time.new 2100 12 31 23 59 59 0)
d3.end_of Time_Period.Second . should_equal (Date_Time.new 2100 12 31 23 59 59 max_nanos)
offset_1_h = ZoneOffset.ofTotalSeconds 3600
offset_2_h = ZoneOffset.ofTotalSeconds 2*3600
tz = Time_Zone.parse "Europe/Warsaw"
js_dst_pending = if name.contains "Javascript" then
"Javascript implementation does not support time zones correctly, so the tests for conversion around DST switches would fail and thus are disabled. We may revisit once JS gets better time support, see project Temporal: https://tc39.es/proposal-temporal/docs/ and our Pivotal issue tracking our integration: https://www.pivotaltracker.com/story/show/183261296"
Test.specify "should find start/end of a Date_Period or Time_Period containing the current datetime correctly near the spring DST switch" pending=js_dst_pending <|
d1 = create_new_datetime 2022 3 27 1 34 15 0 tz
d2 = create_new_datetime 2022 3 27 3 34 15 0 tz
d1_plus = d1 + 1.hour
d1_plus . should_equal d2
check_dates_spring date =
date.start_of Time_Period.Day . should_equal (Date_Time.new 2022 3 27 0 0 0 0 tz)
date.end_of Time_Period.Day . should_equal (Date_Time.new 2022 3 27 23 59 59 max_nanos tz)
date.start_of Date_Period.Month . should_equal (Date_Time.new 2022 3 1 0 0 0 0 tz)
date.end_of Date_Period.Month . should_equal (Date_Time.new 2022 3 31 23 59 59 max_nanos tz)
check_dates_spring d1
check_dates_spring d2
d1_start = d1.start_of Time_Period.Hour
d1_end = d1.end_of Time_Period.Hour
(d1.to_epoch_seconds - d1_start.to_epoch_seconds) . should_equal (34*60 + 15)
(d1_end.to_epoch_seconds - d1.to_epoch_seconds) . should_equal (60*60 - (34*60 + 15) - 1)
d1_start . should_equal (Date_Time.new 2022 3 27 1 0 0 0 tz)
d1_end . should_equal (Date_Time.new 2022 3 27 1 59 59 max_nanos tz)
d1.start_of Time_Period.Minute . should_equal (Date_Time.new 2022 3 27 1 34 0 0 tz)
d1.end_of Time_Period.Minute . should_equal (Date_Time.new 2022 3 27 1 34 59 max_nanos tz)
d2_start = d2.start_of Time_Period.Hour
d2_end = d2.end_of Time_Period.Hour
(d2.to_epoch_seconds - d2_start.to_epoch_seconds) . should_equal (34*60 + 15)
(d2_end.to_epoch_seconds - d2.to_epoch_seconds) . should_equal (60*60 - (34*60 + 15) - 1)
d2_start . should_equal (Date_Time.new 2022 3 27 3 0 0 0 tz)
d2_end . should_equal (Date_Time.new 2022 3 27 3 59 59 max_nanos tz)
dst_overlap_message = "We cannot correctly migrate the datetime inside of the timeline overlap through the polyglot boundar - as due to polyglot conversion limitation, always the earlier one is chosen. See the bug report: https://github.com/oracle/graal/issues/4918"
Test.specify "should find start/end of a Date_Period or Time_Period containing the current datetime correctly near the autumn DST switch" pending=dst_overlap_message <|
d3 = create_new_datetime 2022 10 30 2 30 15 0 tz
d4 = d3 + (1.hour)
d3.hour . should_equal 2
d4.hour . should_equal 2
d3.minute . should_equal 30
d4.minute . should_equal 30
(d4.to_epoch_milliseconds - d3.to_epoch_milliseconds) . should_equal 60*60*1000
Time_Utils.get_datetime_offset d3 . should_equal offset_2_h
Time_Utils.get_datetime_offset d4 . should_equal offset_1_h
check_dates_autumn date =
date.start_of Time_Period.Day . should_equal (Date_Time.new 2022 10 30 0 0 0 0 tz)
date.end_of Time_Period.Day . should_equal (Date_Time.new 2022 10 30 23 59 59 max_nanos tz)
date.start_of Date_Period.Month . should_equal (Date_Time.new 2022 10 1 0 0 0 0 tz)
date.end_of Date_Period.Month . should_equal (Date_Time.new 2022 10 31 23 59 59 max_nanos tz)
check_dates_autumn d3
check_dates_autumn d4
d3_start = d3.start_of Time_Period.Hour
d3_end = d3.end_of Time_Period.Hour
(d3.to_epoch_seconds - d3_start.to_epoch_seconds) . should_equal (30*60 + 15)
(d3_end.to_epoch_seconds - d3.to_epoch_seconds) . should_equal (60*60 - (30*60 + 15) - 1)
d3_start . should_equal (Date_Time.new 2022 10 30 2 0 0 0 tz)
Time_Utils.get_datetime_offset d3_start . should_equal offset_2_h
d3_end . should_equal (Date_Time.new 2022 10 30 2 59 59 max_nanos tz)
Time_Utils.get_datetime_offset d3_end . should_equal offset_2_h
d4_start = d4.start_of Time_Period.Hour
d4_end = d4.end_of Time_Period.Hour
(d4.to_epoch_seconds - d4_start.to_epoch_seconds) . should_equal (30*60 + 15)
(d4_end.to_epoch_seconds - d4.to_epoch_seconds) . should_equal (60*60 - (30*60 + 15) - 1)
d4_start.hour . should_equal 2
d4_start.minute . should_equal 0
Time_Utils.get_datetime_offset d4_start . should_equal offset_1_h
d4_end.hour . should_equal 2
d4_end.minute . should_equal 59
d4_end.second . should_equal 59
d4_end.nanosecond . should_equal max_nanos
Time_Utils.get_datetime_offset d4_end . should_equal offset_1_h
Date_Part_Spec.spec name create_new_datetime
js_datetime year month=1 day=1 hour=0 minute=0 second=0 nanosecond=0 zone=Time_Zone.system =

View File

@ -1,5 +1,6 @@
from Standard.Base import all
import Standard.Base.Data.Time.Duration
import Standard.Base.Data.Time.Time_Period
from Standard.Base.Error.Common import Time_Error_Data
import Standard.Test
@ -71,7 +72,7 @@ specWith name create_new_time parse_time =
Test.fail ("Unexpected result: " + result.to_text)
Test.specify "should convert to time" <|
datetime = create_new_time 1 0 0 . to_time (Date.new 2000 12 21) Time_Zone.utc
datetime = create_new_time 1 0 0 . to_date_time (Date.new 2000 12 21) Time_Zone.utc
datetime . year . should_equal 2000
datetime . month . should_equal 12
datetime . day . should_equal 21
@ -116,6 +117,39 @@ specWith name create_new_time parse_time =
time_1>time_2 . should_be_true
time_1<time_2 . should_be_false
max_nanos = 999999999
Test.specify "should allow to find start/end of a Time_Period containing the current time of day" <|
d1 = create_new_time 15 37 58 123456789
d1.start_of Time_Period.Day . should_equal (Time_Of_Day.new)
d1.end_of Time_Period.Day . should_equal (Time_Of_Day.new 23 59 59 max_nanos)
d1.start_of Time_Period.Hour . should_equal (Time_Of_Day.new 15 0 0 0)
d1.end_of Time_Period.Hour . should_equal (Time_Of_Day.new 15 59 59 max_nanos)
d1.start_of Time_Period.Minute . should_equal (Time_Of_Day.new 15 37 0 0)
d1.end_of Time_Period.Minute . should_equal (Time_Of_Day.new 15 37 59 max_nanos)
d1.start_of Time_Period.Second . should_equal (Time_Of_Day.new 15 37 58 0)
d1.end_of Time_Period.Second . should_equal (Time_Of_Day.new 15 37 58 max_nanos)
d2 = create_new_time 0 0 0
d2.start_of Time_Period.Day . should_equal (Time_Of_Day.new)
d2.end_of Time_Period.Day . should_equal (Time_Of_Day.new 23 59 59 max_nanos)
d2.start_of Time_Period.Hour . should_equal (Time_Of_Day.new 0 0 0 0)
d2.end_of Time_Period.Hour . should_equal (Time_Of_Day.new 0 59 59 max_nanos)
d2.start_of Time_Period.Minute . should_equal (Time_Of_Day.new 0 0 0 0)
d2.end_of Time_Period.Minute . should_equal (Time_Of_Day.new 0 0 59 max_nanos)
d2.start_of Time_Period.Second . should_equal (Time_Of_Day.new 0 0 0 0)
d2.end_of Time_Period.Second . should_equal (Time_Of_Day.new 0 0 0 max_nanos)
d3 = create_new_time 23 59 59 max_nanos
d3.start_of Time_Period.Day . should_equal (Time_Of_Day.new)
d3.end_of Time_Period.Day . should_equal (Time_Of_Day.new 23 59 59 max_nanos)
d3.start_of Time_Period.Hour . should_equal (Time_Of_Day.new 23 0 0 0)
d3.end_of Time_Period.Hour . should_equal (Time_Of_Day.new 23 59 59 max_nanos)
d3.start_of Time_Period.Minute . should_equal (Time_Of_Day.new 23 59 0 0)
d3.end_of Time_Period.Minute . should_equal (Time_Of_Day.new 23 59 59 max_nanos)
d3.start_of Time_Period.Second . should_equal (Time_Of_Day.new 23 59 59 0)
d3.end_of Time_Period.Second . should_equal (Time_Of_Day.new 23 59 59 max_nanos)
enso_time hour minute=0 second=0 nanoOfSecond=0 =
Time_Of_Day.new hour minute second nanoOfSecond

View File

@ -47,7 +47,7 @@ spec =
Test.specify "send Enso date into Java" <|
ensodate = Date.new 2022 04 01
javatime = LocalTime.of 10 26
javatimedate = javatime . to_time ensodate
javatimedate = javatime . to_date_time ensodate
april1st = javatimedate . date
april1st.year.should_equal 2022
april1st.month.should_equal 4