Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 18 commits into from
Sep 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
radeusgd marked this conversation as resolved.
Show resolved Hide resolved
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 {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jdunkerley do you think I should add some doc comments? Seems a bit 'naked' without them but OTOH I'd say the name of the function seems to be pretty self explanatory so I'm not sure if the comments would add any clarity. What do you think?

radeusgd marked this conversation as resolved.
Show resolved Hide resolved

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