Skip to content

Commit

Permalink
Add Period Start and End functions to Date and DateTime (#3695)
Browse files Browse the repository at this point in the history
  • Loading branch information
radeusgd authored Sep 13, 2022
1 parent fba5047 commit b304402
Show file tree
Hide file tree
Showing 18 changed files with 493 additions and 25 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
29 changes: 16 additions & 13 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,21 @@ 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

// ============================================================================
// === 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",
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -1007,7 +1010,6 @@ val truffleRunOptions = if (java.lang.Boolean.getBoolean("bench.compileOnly")) {
)
}


val truffleRunOptionsSettings = Seq(
fork := true,
javaOptions ++= truffleRunOptions
Expand Down Expand Up @@ -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
Expand All @@ -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",
Expand Down
13 changes: 11 additions & 2 deletions distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date.enso
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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.

Expand Down
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.

Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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.

Expand Down
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
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)
Expand Down Expand Up @@ -54,6 +55,11 @@ public static EnsoTimeZone system() {
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;
Expand Down
30 changes: 27 additions & 3 deletions std-bits/base/src/main/java/org/enso/base/Time_Utils.java
Original file line number Diff line number Diff line change
@@ -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. */
Expand Down Expand Up @@ -206,4 +210,24 @@ public static LocalTime parse_time(String text, String pattern, Locale locale) {
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();
}
}
Original file line number Diff line number Diff line change
@@ -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());
};
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
12 changes: 12 additions & 0 deletions std-bits/base/src/main/java/org/enso/base/time/Date_Utils.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading

0 comments on commit b304402

Please sign in to comment.