Improved polyglot Date support (#3559)

Significantly improves the polyglot Date support (as introduced by #3374). It enhances the `Date_Spec` to run it in four flavors:
- with Enso Date (as of now)
- with JavaScript Date
- with JavaScript Date wrapped in (JavaScript) array
- with Java LocalDate allocated directly

The code is then improved by necessary modifications to make the `Date_Spec` pass.

# Important Notes
James has requested in [#181755990](https://www.pivotaltracker.com/n/projects/2539304/stories/181755990) - e.g. _Review and improve InMemory Table support for Dates, Times, DateTimes, BigIntegers_ the following program to work:
```
foreign js dateArr = """
return [1, new Date(), 7]

main =
IO.println <| (dateArr.at 1).week_of_year
```
the program works with here in provided changes and prints `27` as of today.

@jdunkerley has provided tests for proper behavior of date in `Table` and `Column`. Those tests are working as of [f16d07e](f16d07e640). One just needs to accept `List<Value>` and then query `Value` for `isDate()` when needed.

Last round of changes is related to **exception handling**. 8b686b12bd makes sure `makePolyglotError` accepts only polyglot values. Then it wraps plain Java exceptions into `WrapPlainException` with `has_type` method - 60da5e70ed - the remaining changes in the PR are only trying to get all tests working in the new setup.

The support for `Time` isn't part of this PR yet.
This commit is contained in:
Jaroslav Tulach 2022-07-21 08:32:40 +02:00 committed by GitHub
parent 3b99e18f94
commit 4465d63dd8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 608 additions and 168 deletions

View File

@ -269,7 +269,7 @@
- [Fixed issues related to constructors' default arguments][3330]
- [Fixed compiler issue related to module cache.][3367]
- [Fixed execution of defaulted arguments of Atom Constructors][3358]
- [Converting Enso Date to java.time.LocalDate and back][3374]
- [Converting Enso Date to java.time.LocalDate and back][3559]
- [Incremental Reparsing of a Simple Edits][3508]
- [Functions with all-defaulted arguments now execute automatically][3414]
- [Provide `tagValues` for function arguments in the language server][3422]
@ -297,7 +297,7 @@
[3358]: https://github.com/enso-org/enso/pull/3358
[3360]: https://github.com/enso-org/enso/pull/3360
[3367]: https://github.com/enso-org/enso/pull/3367
[3374]: https://github.com/enso-org/enso/pull/3374
[3559]: https://github.com/enso-org/enso/pull/3559
[3508]: https://github.com/enso-org/enso/pull/3508
[3412]: https://github.com/enso-org/enso/pull/3412
[3414]: https://github.com/enso-org/enso/pull/3414

View File

@ -122,7 +122,7 @@ type Engine
internal_pattern = maybe_java_pattern.map_error case _ of
Polyglot_Error err ->
if Java.is_instance err PatternSyntaxException . not then err else
if err.is_a PatternSyntaxException . not then err else
Regex.Syntax_Error err.getMessage
other -> other
@ -823,8 +823,8 @@ type Match
handle_error : Any -> (Text | Integer) -> Any
handle_error error id = case error of
Polyglot_Error err ->
is_ioob = Java.is_instance err IndexOutOfBoundsException
is_iae = Java.is_instance err IllegalArgumentException
is_ioob = err.is_a IndexOutOfBoundsException
is_iae = err.is_a IllegalArgumentException
maps_to_no_such_group = is_ioob || is_iae
if maps_to_no_such_group.not then err else

View File

@ -300,7 +300,7 @@ type Time
example_date = Time.now.date
date : Date
date = Date.Date self.internal_zoned_date_time.toLocalDate
date = self.internal_zoned_date_time.toLocalDate
## ALIAS Change Time Zone
@ -466,13 +466,11 @@ type Time
sign = self.internal_zoned_date_time.compareTo that.internal_zoned_date_time
Ordering.from_sign sign
## Checks if `self` equals `that`.
Arguments:
- that: The other `Time` to compare against.
## Compares two Time for equality.
== : Time -> Boolean
== that =
self.internal_zoned_date_time.equals that.internal_zoned_date_time
== that = case that of
Time _ -> self.internal_zoned_date_time.equals that.internal_zoned_date_time
_ -> False
type Time_Error

View File

@ -4,11 +4,8 @@ import Standard.Base.Data.Time
import Standard.Base.Data.Time.Duration
import Standard.Base.Data.Time.Time_Of_Day
import Standard.Base.Data.Time.Zone
import Standard.Base.Polyglot
polyglot java import java.time.format.DateTimeFormatter
polyglot java import java.time.Instant
polyglot java import java.time.LocalDate
polyglot java import java.time.temporal.WeekFields
polyglot java import org.enso.base.Time_Utils
## Obtains the current date from the system clock in the system timezone.
@ -20,7 +17,7 @@ polyglot java import org.enso.base.Time_Utils
example_now = Date.now
now : Date
now = LocalDate.now
now = @Builtin_Method "Date.now"
## ALIAS Current Date
@ -67,9 +64,9 @@ new year (month = 1) (day = 1) =
instead of Enso format. Hopefully this will be fixed with
https://github.com/enso-org/enso/pull/3559
Then this should be switched to use `Panic.catch_java`.
Panic.recover Any (LocalDate.of year month day) . catch Any e-> case e of
Panic.recover Any (Date.internal_new year month day) . catch Any e-> case e of
Polyglot_Error err -> Error.throw (Time.Time_Error err.getMessage)
x -> x
ex -> ex
## ALIAS Date from Text
@ -136,27 +133,25 @@ new year (month = 1) (day = 1) =
parse : Text -> (Text | Nothing) -> Date ! Time.Time_Error
parse text pattern=Nothing =
result = Panic.recover Any <| case pattern of
Nothing -> LocalDate.parse text
Text -> LocalDate.parse text (DateTimeFormatter.ofPattern pattern)
Nothing -> Date.internal_parse text 0
Text -> Date.internal_parse text pattern
_ -> Panic.throw (Time.Time_Error "An invalid pattern was provided.")
Date result . map_error <| case _ of
result . map_error <| case _ of
Polyglot_Error err -> Time.Time_Error err.getMessage
x -> x
ex -> ex
type Date
## This type represents a date, often viewed as year-month-day.
Arguments:
- internal_local_date: The internal date representation.
For example, the value "2nd October 2007" can be stored in a `Date`.
This class does not store or represent a time or timezone. Instead, it
is a description of the date, as used for birthdays. It cannot represent
an instant on the time-line without additional information such as an
offset or timezone.
type Date internal_local_date
@Builtin_Type
type Date
## Get the year field.
@ -167,7 +162,7 @@ type Date
example_year = Date.now.year
year : Integer
year = self . internal_local_date . getYear
year = @Builtin_Method "Date.year"
## Get the month of year field, as a number from 1 to 12.
@ -178,7 +173,7 @@ type Date
example_month = Date.now.month
month : Integer
month = self . internal_local_date . getMonthValue
month = @Builtin_Method "Date.month"
## Get the day of month field.
@ -189,7 +184,7 @@ type Date
example_day = Date.now.day
day : Integer
day = self . internal_local_date . getDayOfMonth
day = @Builtin_Method "Date.day"
## Returns the number of week of year this date falls into.
@ -205,9 +200,7 @@ type Date
containing the first Thursday of the year. Therefore it is important to
properly specify the `locale` argument.
week_of_year : Locale.Locale -> Integer
week_of_year locale=Locale.default =
field = WeekFields.of locale.java_locale . weekOfYear
self.internal_local_date.get field
week_of_year locale=Locale.default = Time_Utils.week_of_year self locale.java_locale
## ALIAS Date to Time
@ -226,7 +219,7 @@ type Date
example_to_time = Date.new 2020 2 3 . to_time Time_Of_Day.new Zone.utc
to_time : Time_Of_Day -> Zone -> Time
to_time time_of_day (zone = Zone.system) = Time.Time (self . internal_local_date . atTime time_of_day.internal_local_time . atZone zone.internal_zone_id)
to_time time_of_day (zone = Zone.system) = Time.Time (Time_Utils.date_with_time self time_of_day.internal_local_time zone.internal_zone_id)
## Add the specified amount of time to this instant to get another date.
@ -242,7 +235,7 @@ type Date
example_add = Date.new 2020 + 6.months
+ : Duration -> Date
+ amount = if amount.is_time then Error.throw (Time.Time_Error "Date does not support time intervals") else
Date (self . internal_local_date . plus amount.internal_period)
(Time_Utils.date_adjust self 1 amount.internal_period) . internal_local_date
## Subtract the specified amount of time from this instant to get another
date.
@ -259,18 +252,8 @@ type Date
example_subtract = Date.new 2020 - 7.days
- : Duration -> Date
- amount = if amount.is_time then Error.throw (Time.Time_Error "Date does not support time intervals") else
(self . internal_local_date . minus amount.internal_period)
(Time_Utils.date_adjust self -1 amount.internal_period) . internal_local_date
## Format this date using the default formatter.
> Example
Convert the current date to text.
import Standard.Base.Data.Time.Date
example_to_text = Date.now.to_text
to_text : Text
to_text = Time_Utils.default_date_formatter . format self.internal_local_date
## A Date to Json conversion.
@ -327,7 +310,7 @@ type Date
example_format = Date.new 2020 6 2 . format "yyyyGG"
format : Text -> Text
format pattern = DateTimeFormatter.ofPattern pattern . format self.internal_local_date
format pattern = Time_Utils.local_date_format self pattern
## Compares `self` to `that` to produce an ordering.
@ -340,5 +323,11 @@ type Date
(Date.new 2000).compare_to (Date.new 2001)
compare_to : Date -> Ordering
compare_to that =
sign = self.internal_local_date.compareTo that.internal_local_date
sign = Time_Utils.compare_to self that
Ordering.from_sign sign
## Compares two Dates for equality.
== : Date -> Boolean
== that =
sign = Time_Utils.compare_to self that
0 == sign

View File

@ -208,7 +208,7 @@ type Time_Of_Day
example_to_time = Time_Of_Day.new 12 30 . to_time (Date.new 2020)
to_time : Date -> Zone -> Time
to_time date (zone = Zone.system) =
Time.Time (self . internal_local_time . atDate date.internal_local_date . atZone zone.internal_zone_id)
Time.Time (self . internal_local_time . atDate date . atZone zone.internal_zone_id)
## Add the specified amount of time to this instant to get a new instant.
@ -332,3 +332,9 @@ type Time_Of_Day
compare_to that =
sign = self.internal_local_time.compareTo that.internal_local_time
Ordering.from_sign sign
## Compares two Time_Of_Day for equality.
== : Date -> Boolean
== that = case that of
Time_Of_Day _ -> self.internal_local_time.equals that.internal_local_time
_ -> False

View File

@ -362,7 +362,7 @@ type Panic
False -> Panic.throw caught_panic
True -> case caught_panic.payload of
Polyglot_Error java_exception ->
case Java.is_instance java_exception panic_type of
case java_exception.is_a panic_type of
True -> handler caught_panic
False -> Panic.throw caught_panic
_ -> Panic.throw caught_panic
@ -392,7 +392,7 @@ type Panic
catch_java panic_type ~action handler =
Panic.catch_primitive action caught_panic-> case caught_panic.payload of
Polyglot_Error java_exception ->
case (panic_type == Any) || (Java.is_instance java_exception panic_type) of
case (panic_type == Any) || (java_exception.is_a panic_type) of
True -> handler java_exception
False -> Panic.throw caught_panic
_ -> Panic.throw caught_panic

View File

@ -597,7 +597,7 @@ type Http
request : Request -> Response ! Request_Error
request req =
handle_request_error =
Panic.catch_java Any handler=(err-> Error.throw (Request_Error err.getClass.getSimpleName err.getMessage))
Panic.catch_java Any handler=(err-> Error.throw (Request_Error 'IllegalArgumentException' err.getMessage))
Panic.recover Any <| handle_request_error <|
body_publishers = HttpRequest.BodyPublishers
builder = HttpRequest.newBuilder

View File

@ -955,10 +955,10 @@ handle_java_exceptions file ~action =
Converts a Java `IOException` into its Enso counterpart.
wrap_io_exception file io_exception =
if Java.is_instance io_exception NoSuchFileException then Error.throw (File_Not_Found file) else
if Java.is_instance io_exception FileAlreadyExistsException then Error.throw (File_Already_Exists_Error file) else
if Java.is_instance io_exception AccessDeniedException then Error.throw (IO_Error file "You do not have permission to access the file") else
Error.throw (IO_Error file "An IO error has occurred: "+io_exception.getMessage)
if io_exception.is_a NoSuchFileException then Error.throw (File_Not_Found file) else
if io_exception.is_a FileAlreadyExistsException then Error.throw (File_Already_Exists_Error file) else
if io_exception.is_a AccessDeniedException then Error.throw (IO_Error file "You do not have permission to access the file") else
Error.throw (IO_Error file "An IO error has occurred: "+io_exception.to_text)
## PRIVATE

View File

@ -15,6 +15,7 @@ import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import com.oracle.truffle.api.profiles.BranchProfile;
import java.time.LocalDate;
import org.enso.interpreter.epb.node.ContextRewrapExceptionNode;
import org.enso.interpreter.epb.node.ContextRewrapNode;
@ -909,4 +910,29 @@ public class PolyglotProxy implements TruffleObject {
leaveOrigin(node, p);
}
}
@ExportMessage
boolean isDate(
@CachedLibrary("this.delegate") InteropLibrary datum,
@CachedLibrary("this") InteropLibrary node) {
Object p = enterOrigin(node);
try {
return datum.isDate(delegate);
} finally {
leaveOrigin(node, p);
}
}
@ExportMessage
LocalDate asDate(
@CachedLibrary("this.delegate") InteropLibrary datume,
@CachedLibrary("this") InteropLibrary node)
throws UnsupportedMessageException {
Object p = enterOrigin(node);
try {
return datume.asDate(delegate);
} finally {
leaveOrigin(node, p);
}
}
}

View File

@ -23,6 +23,7 @@ import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.data.ArrayRope;
import org.enso.interpreter.runtime.data.EnsoDate;
import org.enso.interpreter.runtime.data.text.Text;
import org.enso.interpreter.runtime.error.*;
import org.enso.interpreter.runtime.library.dispatch.MethodDispatchLibrary;
@ -246,15 +247,15 @@ public abstract class InvokeMethodNode extends BaseNode {
@CachedLibrary(limit = "10") MethodDispatchLibrary methods,
@CachedLibrary(limit = "1") MethodDispatchLibrary dateDispatch,
@CachedLibrary(limit = "10") InteropLibrary interop) {
var ctx = Context.get(this);
try {
var dateConstructor = Context.get(this).getDateConstructor();
Object date = dateConstructor.isPresent() ? dateConstructor.get().newInstance(self) : self;
var hostLocalDate = interop.asDate(self);
var date = new EnsoDate(hostLocalDate);
Function function = dateDispatch.getFunctionalDispatch(date, symbol);
arguments[0] = date;
return invokeFunctionNode.execute(function, frame, state, arguments);
} catch (MethodDispatchLibrary.NoSuchMethodException e) {
throw new PanicException(
Context.get(this).getBuiltins().error().makeNoSuchMethodError(self, symbol), this);
} catch (MethodDispatchLibrary.NoSuchMethodException | UnsupportedMessageException e) {
throw new PanicException(ctx.getBuiltins().error().makeNoSuchMethodError(self, symbol), this);
}
}

View File

@ -80,7 +80,11 @@ public abstract class HostMethodCallNode extends Node {
*/
public static PolyglotCallType getPolyglotCallType(
Object self, String methodName, InteropLibrary library) {
if (library.isMemberInvocable(self, methodName)) {
if (library.isDate(self) && !library.isTime(self)) {
return PolyglotCallType.CONVERT_TO_DATE;
} else if (library.isString(self)) {
return PolyglotCallType.CONVERT_TO_TEXT;
} else if (library.isMemberInvocable(self, methodName)) {
return PolyglotCallType.CALL_METHOD;
} else if (library.isMemberReadable(self, methodName)) {
return PolyglotCallType.GET_MEMBER;
@ -90,12 +94,6 @@ public abstract class HostMethodCallNode extends Node {
return PolyglotCallType.GET_ARRAY_LENGTH;
} else if (library.hasArrayElements(self) && methodName.equals(ARRAY_READ_NAME)) {
return PolyglotCallType.READ_ARRAY_ELEMENT;
} else if (library.isString(self)) {
return PolyglotCallType.CONVERT_TO_TEXT;
} else if (library.isDate(self)) {
if (!library.isTime(self)) {
return PolyglotCallType.CONVERT_TO_DATE;
}
}
return PolyglotCallType.NOT_SUPPORTED;
}

View File

@ -0,0 +1,32 @@
package org.enso.interpreter.node.expression.builtin.date;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.runtime.data.EnsoDate;
@BuiltinMethod(
type = "Date",
name = "internal_local_date",
description = "Converts any format of a Date to Enso Date")
public abstract class ToEnsoDateNode extends Node {
public static ToEnsoDateNode build() {
return ToEnsoDateNodeGen.create();
}
abstract EnsoDate execute(Object self);
@Specialization
EnsoDate executeConversion(Object self, @CachedLibrary(limit = "3") InteropLibrary iop) {
try {
return new EnsoDate(iop.asDate(self));
} catch (UnsupportedMessageException ex) {
CompilerDirectives.transferToInterpreter();
throw new IllegalStateException(ex);
}
}
}

View File

@ -3,10 +3,9 @@ package org.enso.interpreter.node.expression.builtin.interop.generic;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.profiles.BranchProfile;
import org.enso.interpreter.Constants;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.runtime.error.PanicException;
import org.enso.interpreter.runtime.data.Array;
@BuiltinMethod(
type = "Polyglot",
@ -15,14 +14,12 @@ import org.enso.interpreter.runtime.error.PanicException;
public class GetMembersNode extends Node {
private @Child InteropLibrary library =
InteropLibrary.getFactory().createDispatched(Constants.CacheSizes.BUILTIN_INTEROP_DISPATCH);
private final BranchProfile err = BranchProfile.create();
Object execute(Object self, Object object) {
try {
return library.getMembers(object);
} catch (UnsupportedMessageException e) {
err.enter();
throw new PanicException(e.getMessage(), this);
return Array.empty();
}
}
}

View File

@ -12,7 +12,7 @@ import org.enso.interpreter.runtime.error.PanicException;
description = "Gets the current execution stacktrace.")
public class GetStackTraceNode extends Node {
Array execute(Object self) {
var exception = new PanicException(null, this);
var exception = new PanicException("Stacktrace", this);
TruffleStackTrace.fillIn(exception);
return stackTraceToArray(exception);
}

View File

@ -1,14 +1,15 @@
package org.enso.interpreter.node.expression.constant;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.TruffleObject;
import org.enso.interpreter.node.ExpressionNode;
import org.enso.interpreter.runtime.error.PanicException;
/** Throws a runtime panic containing a statically-known payload. */
public class ErrorNode extends ExpressionNode {
private final Object payload;
private final TruffleObject payload;
private ErrorNode(Object payload) {
private ErrorNode(TruffleObject payload) {
this.payload = payload;
}
@ -29,7 +30,7 @@ public class ErrorNode extends ExpressionNode {
* @param payload the payload carried by exceptions thrown in the course of this node's execution.
* @return a new instance of this node.
*/
public static ErrorNode build(Object payload) {
public static ErrorNode build(TruffleObject payload) {
return new ErrorNode(payload);
}
}

View File

@ -468,27 +468,4 @@ public class Context {
public long clockTick() {
return clock.getAndIncrement();
}
/**
* Return the {@code Standard.Base.Data.Time.Date} constructor.
*
* @return optional with {@link AtomConstructor} for the date, if it can be found
*/
public Optional<AtomConstructor> getDateConstructor() {
if (date == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
final String stdDateModuleName = "Standard.Base.Data.Time.Date";
final String stdDateConstructorName = "Date";
ensureModuleIsLoaded(stdDateModuleName);
Optional<Module> dateModule = findModule(stdDateModuleName);
if (dateModule.isPresent()) {
date =
Optional.ofNullable(
dateModule.get().getScope().getConstructors().get(stdDateConstructorName));
} else {
date = Optional.empty();
}
}
return date;
}
}

View File

@ -75,6 +75,7 @@ public class Builtins {
private final BuiltinAtomConstructor debug;
private final BuiltinAtomConstructor projectDescription;
private final BuiltinAtomConstructor file;
private final BuiltinAtomConstructor date;
private final BuiltinAtomConstructor warning;
/**
@ -92,7 +93,7 @@ public class Builtins {
builtinMethodNodes = readBuiltinMethodsMetadata(scope);
registerBuiltinMethods(builtinTypes, scope, language);
error = new Error(this);
error = new Error(this, context);
ordering = new Ordering(this);
system = new System(this);
number = new Number(this);
@ -113,6 +114,9 @@ public class Builtins {
debug = new BuiltinAtomConstructor(this, Debug.class);
projectDescription = new BuiltinAtomConstructor(this, ProjectDescription.class);
file = new BuiltinAtomConstructor(this, File.class);
date =
new BuiltinAtomConstructor(
this, org.enso.interpreter.node.expression.builtin.date.Date.class);
special = new Special(language);
warning = new BuiltinAtomConstructor(this, Warning.class);
}
@ -413,6 +417,15 @@ public class Builtins {
return file.constructor();
}
/**
* Returns the {@code Date} atom constructor.
*
* @return the {@code Date} atom constructor
*/
public AtomConstructor date() {
return date.constructor();
}
/**
* Returns the {@code Debug} atom constructor. TODO: this is redundant, figure out a way to avoid
* createing spurious Debug builtin type

View File

@ -11,10 +11,21 @@ import org.enso.interpreter.runtime.data.Array;
import org.enso.interpreter.runtime.data.text.Text;
import static com.oracle.truffle.api.CompilerDirectives.transferToInterpreterAndInvalidate;
import com.oracle.truffle.api.exception.AbstractTruffleException;
import com.oracle.truffle.api.interop.ArityException;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.interop.UnknownIdentifierException;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.interop.UnsupportedTypeException;
import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import org.enso.interpreter.runtime.Context;
/** Container for builtin Error types */
public class Error {
private final Context context;
private final BuiltinAtomConstructor syntaxError;
private final BuiltinAtomConstructor typeError;
private final BuiltinAtomConstructor compileError;
@ -42,7 +53,8 @@ public class Error {
private static final Text divideByZeroMessage = Text.create("Cannot divide by zero.");
/** Creates builders for error Atom Constructors. */
public Error(Builtins builtins) {
public Error(Builtins builtins, Context context) {
this.context = context;
syntaxError = new BuiltinAtomConstructor(builtins, SyntaxError.class);
typeError = new BuiltinAtomConstructor(builtins, TypeError.class);
compileError = new BuiltinAtomConstructor(builtins, CompileError.class);
@ -132,8 +144,8 @@ public class Error {
* @param cause the cause of the error.
* @return a runtime representation of the polyglot error.
*/
public Atom makePolyglotError(Object cause) {
return polyglotError.newInstance(cause);
public Atom makePolyglotError(Throwable cause) {
return polyglotError.newInstance(WrapPlainException.wrap(cause, context));
}
/**
@ -207,4 +219,127 @@ public class Error {
public Atom makeNotInvokableError(Object target) {
return notInvokableError.newInstance(target);
}
/** Represents plain Java exception as a {@link TruffleObject}.
*/
@ExportLibrary(InteropLibrary.class)
static final class WrapPlainException extends AbstractTruffleException {
private final AbstractTruffleException prototype;
private final Throwable original;
private WrapPlainException(Throwable cause) {
super(cause.getMessage(), cause, AbstractTruffleException.UNLIMITED_STACK_TRACE, null);
this.prototype = null;
this.original = cause;
}
private WrapPlainException(AbstractTruffleException prototype, Throwable original) {
super(prototype);
this.prototype = prototype;
this.original = original;
}
static AbstractTruffleException wrap(Throwable cause, Context ctx) {
var env = ctx.getEnvironment();
if (env.isHostException(cause)) {
var orig = env.asHostException(cause);
return new WrapPlainException((AbstractTruffleException) cause, orig);
} else if (cause instanceof AbstractTruffleException truffleEx) {
return truffleEx;
} else {
return new WrapPlainException(cause);
}
}
@ExportMessage
boolean hasExceptionMessage() {
return getMessage() != null;
}
@ExportMessage
public Object getExceptionMessage() {
return Text.create(getMessage());
}
@ExportMessage
String toDisplayString(boolean sideEffects) {
return original.toString();
}
@ExportMessage
Object getMembers(boolean includeInternal) {
return Array.empty();
}
@ExportMessage
boolean hasMembers() {
return true;
}
@ExportMessage
boolean isMemberInvocable(String member, @CachedLibrary(limit="1") InteropLibrary delegate) {
boolean knownMembers = "is_a".equals(member) || "getMessage".equals(member);
return knownMembers || (prototype != null && delegate.isMemberInvocable(prototype, member));
}
@ExportMessage
Object invokeMember(String name, Object[] args, @CachedLibrary(limit="2") InteropLibrary iop) throws ArityException, UnknownIdentifierException, UnsupportedTypeException, UnsupportedMessageException {
if ("is_a".equals(name)) {
if (args.length != 1) {
throw ArityException.create(1,1, args.length);
}
Object meta;
if (iop.isString(args[0])) {
meta = args[0];
} else {
try {
meta = iop.getMetaQualifiedName(args[0]);
} catch (UnsupportedMessageException e) {
meta = args[0];
}
}
if (!iop.isString(meta)) {
throw UnsupportedTypeException.create(args, "Provide class or fully qualified name of class to check");
}
return hasType(iop.asString(meta), original.getClass());
}
if ("getMessage".equals(name)) {
return getExceptionMessage();
}
return iop.invokeMember(this.prototype, name, args);
}
@ExportMessage
boolean isMemberReadable(String member, @CachedLibrary(limit="1") InteropLibrary delegate) {
if (prototype == null) {
return false;
}
return delegate.isMemberReadable(prototype, member);
}
@ExportMessage
Object readMember(String name, @CachedLibrary(limit="2") InteropLibrary iop) throws UnsupportedMessageException, UnknownIdentifierException {
return iop.readMember(this.prototype, name);
}
@CompilerDirectives.TruffleBoundary
private static boolean hasType(String fqn, Class<?> type) {
if (type == null) {
return false;
}
if (type.getName().equals(fqn)) {
return true;
}
if (hasType(fqn, type.getSuperclass())) {
return true;
}
for (Class<?> interfaceType : type.getInterfaces()) {
if (hasType(fqn, interfaceType)) {
return true;
}
}
return false;
}
}
}

View File

@ -1,6 +1,5 @@
package org.enso.interpreter.runtime.callable.atom;
import java.time.LocalDate;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.Cached;
@ -203,22 +202,6 @@ public final class Atom implements TruffleObject {
return true;
}
@ExportMessage
boolean isDate(@CachedLibrary("this") InteropLibrary iop) {
var dateConstructor = Context.get(iop).getDateConstructor();
if (dateConstructor.isPresent()) {
return dateConstructor.get() == this.constructor;
} else {
return false;
}
}
@ExportMessage
LocalDate asDate(@CachedLibrary(limit = "3") InteropLibrary iop)
throws UnsupportedMessageException {
return iop.asDate(fields[0]);
}
@ExportMessage
static class GetFunctionalDispatch {
static final int CACHE_SIZE = 10;

View File

@ -144,6 +144,11 @@ public class Array implements TruffleObject {
return false;
}
@ExportMessage
String toDisplayString(boolean b) {
return toString();
}
@Override
public String toString() {
return Arrays.toString(items);

View File

@ -0,0 +1,125 @@
package org.enso.interpreter.runtime.data;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import java.time.DateTimeException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import org.enso.interpreter.dsl.Builtin;
import org.enso.interpreter.node.expression.builtin.error.PolyglotError;
import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.data.text.Text;
import org.enso.interpreter.runtime.library.dispatch.MethodDispatchLibrary;
@ExportLibrary(InteropLibrary.class)
@ExportLibrary(MethodDispatchLibrary.class)
@Builtin(pkg = "date", name = "Date")
public final class EnsoDate implements TruffleObject {
private final LocalDate date;
public EnsoDate(LocalDate date) {
this.date = date;
}
@Builtin.Method(description = "Return current Date")
public static EnsoDate now() {
return new EnsoDate(LocalDate.now());
}
@Builtin.Method(name = "internal_parse", description = "Constructs a new Date from text with optional pattern")
@Builtin.Specialize
@Builtin.WrapException(from = DateTimeParseException.class, to = PolyglotError.class, propagate = true)
public static EnsoDate parse(Text text, Object noneOrPattern) {
var str = text.getContents().toString();
if (noneOrPattern instanceof Text pattern) {
var formatter = DateTimeFormatter.ofPattern(pattern.getContents().toString());
return new EnsoDate(LocalDate.parse(str, formatter));
} else {
return new EnsoDate(LocalDate.parse(str));
}
}
@Builtin.Method(name = "internal_new", description = "Constructs a new Date from a year, month, and day")
@Builtin.WrapException(from = DateTimeException.class, to = PolyglotError.class, propagate = true)
public static EnsoDate create(long year, long month, long day) {
return new EnsoDate(LocalDate.of(Math.toIntExact(year), Math.toIntExact(month), Math.toIntExact(day)));
}
@Builtin.Method(name = "year", description = "Gets a value of year")
public long year() {
return date.getYear();
}
@Builtin.Method(name = "month", description = "Gets a value month")
public long month() {
return date.getMonthValue();
}
@Builtin.Method(name = "day", description = "Gets a value day")
public long day() {
return date.getDayOfMonth();
}
@ExportMessage
boolean isDate() {
return true;
}
@ExportMessage
LocalDate asDate() {
return date;
}
@ExportMessage
boolean hasFunctionalDispatch() {
return true;
}
@ExportMessage
static class GetFunctionalDispatch {
@CompilerDirectives.TruffleBoundary
static Function doResolve(InteropLibrary my, UnresolvedSymbol symbol) {
Context context = Context.get(my);
return symbol.resolveFor(context.getBuiltins().date(), context.getBuiltins().any());
}
@Specialization(
guards = {"cachedSymbol == symbol", "function != null"},
limit = "3")
static Function resolveCached(
EnsoDate self,
UnresolvedSymbol symbol,
@Cached("symbol") UnresolvedSymbol cachedSymbol,
@CachedLibrary("self") InteropLibrary mySelf,
@Cached("doResolve(mySelf, cachedSymbol)") Function function) {
return function;
}
@Specialization(replaces = "resolveCached")
static Function resolve(
EnsoDate self, UnresolvedSymbol symbol, @CachedLibrary("self") InteropLibrary mySelf)
throws MethodDispatchLibrary.NoSuchMethodException {
Function function = doResolve(mySelf, symbol);
if (function == null) {
throw new MethodDispatchLibrary.NoSuchMethodException();
}
return function;
}
}
@ExportMessage
public final Object toDisplayString(boolean allowSideEffects) {
return DateTimeFormatter.ISO_LOCAL_DATE.format(date);
}
}

View File

@ -1,5 +1,6 @@
package org.enso.interpreter.runtime.error;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.exception.AbstractTruffleException;
import com.oracle.truffle.api.interop.*;
@ -25,6 +26,10 @@ public class PanicException extends AbstractTruffleException {
*/
public PanicException(Object payload, Node location) {
super(location);
if (!InteropLibrary.isValidValue(payload)) {
CompilerDirectives.transferToInterpreter();
throw new IllegalArgumentException("Only interop values are supported: " + payload);
}
this.payload = payload;
}

View File

@ -9,10 +9,7 @@ import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
import org.enso.interpreter.runtime.callable.atom.Atom;
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.data.Array;
import org.enso.interpreter.runtime.data.EnsoFile;
import org.enso.interpreter.runtime.data.ManagedResource;
import org.enso.interpreter.runtime.data.Ref;
import org.enso.interpreter.runtime.data.*;
import org.enso.interpreter.runtime.data.text.Text;
import org.enso.interpreter.runtime.error.DataflowError;
import org.enso.interpreter.runtime.error.PanicException;
@ -51,7 +48,8 @@ import org.enso.polyglot.data.TypeGraph;
PanicException.class,
PanicSentinel.class,
Warning.class,
EnsoFile.class
EnsoFile.class,
EnsoDate.class
})
public class Types {

View File

@ -56,6 +56,7 @@ import org.enso.interpreter.runtime.callable.argument.{
ArgumentDefinition,
CallArgument
}
import org.enso.interpreter.runtime.callable.atom.Atom
import org.enso.interpreter.runtime.callable.atom.AtomConstructor
import org.enso.interpreter.runtime.callable.function.{
FunctionSchema,
@ -1170,7 +1171,7 @@ class IrToTruffle(
* @return a runtime node representing the error.
*/
def processError(error: IR.Error): RuntimeExpression = {
val payload: AnyRef = error match {
val payload: Atom = error match {
case Error.InvalidIR(_, _, _) =>
throw new CompilerError("Unexpected Invalid IR during codegen.")
case err: Error.Syntax =>

View File

@ -13,7 +13,6 @@ import java.util.TreeSet;
import org.enso.polyglot.RuntimeOptions;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Engine;
import org.graalvm.polyglot.HostAccess;
import org.graalvm.polyglot.Language;
import org.graalvm.polyglot.Source;
import org.junit.Assert;

View File

@ -86,9 +86,11 @@ class DateTest extends InterpreterTest {
| IO.println ensomonth
| IO.println ensoday
| IO.println ensotext
| ensodate
|""".stripMargin
eval(code)
val ensoDate = eval(code)
consumeOut shouldEqual List("2022", "4", "1", "2022-04-01")
ensoDate.getMemberKeys().size() shouldEqual 0
}
}
}

View File

@ -57,6 +57,20 @@ class PolyglotTest extends InterpreterTest {
)
}
"empty members when message not supported" in {
val code =
"""from Standard.Base import all
|
|main =
| instance = "Hi There"
| members = Polyglot.get_members instance
| IO.println members.length
| IO.println members
|""".stripMargin
eval(code)
consumeOut shouldEqual List("0", "[]")
}
"match on Polyglot type when imported everything from stdlib" in {
val code =
"""from Standard.Base import all

View File

@ -35,7 +35,7 @@ public record SafeWrapException(Attribute.Class from, Attribute.Class to, Boolea
return List.of(
" } catch (" + from + " e) {",
" Builtins builtins = Context.get(this).getBuiltins();",
" throw new PanicException(e, this);"
" throw new PanicException(e.getMessage(), this);"
);
} else {
return List.of(

View File

@ -41,6 +41,7 @@ public record TypeWithKind(String baseType, TypeKind kind) {
"org.enso.interpreter.runtime.callable.atom.Atom",
"org.enso.interpreter.runtime.data.Array",
"org.enso.interpreter.runtime.data.EnsoFile",
"org.enso.interpreter.runtime.data.EnsoDate",
"org.enso.interpreter.runtime.data.ManagedResource",
"org.enso.interpreter.runtime.data.Ref",
"org.enso.interpreter.runtime.data.text.Text",

View File

@ -1,14 +1,17 @@
package org.enso.base;
import java.time.DateTimeException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Period;
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.WeekFields;
import java.util.Locale;
import java.util.Random;
/** Utils for standard library operations on Time. */
public class Time_Utils {
@ -53,6 +56,26 @@ public class Time_Utils {
return DateTimeFormatter.ISO_LOCAL_TIME;
}
public static String local_date_format(LocalDate date, Object format) {
return DateTimeFormatter.ofPattern(format.toString()).format(date);
}
public static ZonedDateTime date_with_time(LocalDate date, LocalTime time, ZoneId zone) {
return date.atTime(time).atZone(zone);
}
public static LocalDate date_adjust(LocalDate date, long add, Period duration) {
return add == 1 ? date.plus(duration) : date.minus(duration);
}
public static long week_of_year(LocalDate date, Locale locale) {
return WeekFields.of(locale).weekOfYear().getFrom(date);
}
public static int compare_to(LocalDate self, LocalDate that) {
return self.compareTo(that);
}
/**
* Obtains an instance of ZonedDateTime from a text string.
*

View File

@ -13,6 +13,7 @@ import org.enso.table.data.index.HashIndex;
import org.enso.table.data.index.Index;
import org.enso.table.data.mask.OrderMask;
import org.enso.table.error.UnexpectedColumnTypeException;
import org.graalvm.polyglot.Value;
/** A representation of a column. Consists of a column name and the underlying storage. */
public class Column {
@ -116,10 +117,10 @@ public class Column {
* @param items the items contained in the column
* @return a column with given name and items
*/
public static Column fromItems(String name, List<Object> items) {
public static Column fromItems(String name, List<Value> items) {
InferredBuilder builder = new InferredBuilder(items.size());
for (Object item : items) {
builder.appendNoGrow(item);
for (var item : items) {
builder.appendNoGrow(item.isDate() ? item.asDate() : item.as(Object.class));
}
return new Column(name, new DefaultIndex(items.size()), builder.seal());
}

View File

@ -3,6 +3,7 @@ package org.enso.table.formatting;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import org.graalvm.polyglot.Value;
public class DateFormatter implements DataFormatter {
private final DateTimeFormatter formatter;
@ -17,6 +18,10 @@ public class DateFormatter implements DataFormatter {
return NULL_REPRESENTATION;
}
if (value instanceof Value v && v.isDate()) {
value = v.asDate();
}
if (value instanceof LocalDate date) {
return date.format(formatter);
}
@ -26,6 +31,6 @@ public class DateFormatter implements DataFormatter {
@Override
public boolean canFormat(Object value) {
return value instanceof LocalDate;
return value instanceof LocalDate || (value instanceof Value v && v.isDate());
}
}

View File

@ -0,0 +1,8 @@
Number,Party,Title,From,To
71,Conservative,Margaret Thatcher,1979-05-04,1990-11-28
72,Conservative,John Major,1990-11-28,1997-05-02
73,Labour,Tony Blair,1997-05-02,2007-06-27
74,Labour,Gordon Brown,2007-06-27,2010-05-11
75,Conservative,David Cameron,2010-05-11,2016-07-13
76,Conservative,Theresa May,2016-07-13,2019-07-24
77,Conservative,Boris Johnson,2019-07-24,2022-07-07
1 Number Party Title From To
2 71 Conservative Margaret Thatcher 1979-05-04 1990-11-28
3 72 Conservative John Major 1990-11-28 1997-05-02
4 73 Labour Tony Blair 1997-05-02 2007-06-27
5 74 Labour Gordon Brown 2007-06-27 2010-05-11
6 75 Conservative David Cameron 2010-05-11 2016-07-13
7 76 Conservative Theresa May 2016-07-13 2019-07-24
8 77 Conservative Boris Johnson 2019-07-24 2022-07-07

View File

@ -143,13 +143,13 @@ spec =
Test.specify "should format dates" <|
formatter = Data_Formatter
formatter.format (Date.new 2022 . internal_local_date) . should_equal "2022-01-01"
formatter.format (Date.new 2022) . should_equal "2022-01-01"
formatter.format (Time.new 1999 . internal_zoned_date_time) . should_equal "1999-01-01 00:00:00"
formatter.format (Time_Of_Day.new . internal_local_time) . should_equal "00:00:00"
Test.specify "should allow custom date formats" <|
formatter = Data_Formatter date_formats=["E, d MMM y", "d MMM y[ G]"] datetime_formats=["dd/MM/yyyy HH:mm"] time_formats=["h:mma"] datetime_locale=Locale.uk
formatter.format (Date.new 2022 06 21 . internal_local_date) . should_equal "Tue, 21 Jun 2022"
formatter.format (Date.new 2022 06 21) . should_equal "Tue, 21 Jun 2022"
formatter.format (Time.new 1999 02 03 04 56 11 . internal_zoned_date_time) . should_equal "03/02/1999 04:56"
formatter.format (Time_Of_Day.new 13 55 . internal_local_time) . should_equal "1:55pm"

View File

@ -149,7 +149,7 @@ spec =
Test.specify 'should allow to always quote text and custom values, but for non-text primitves only if absolutely necessary' <|
format = File_Format.Delimited "," value_formatter=(Data_Formatter thousand_separator='"' date_formats=["E, d MMM y"]) . with_quotes always_quote=True quote_escape='\\'
table = Table.new [['The Column "Name"', ["foo","'bar'",'"baz"', 'one, two, three']], ["B", [1.0, 1000000.5, 2.2, -1.5]], ["C", ["foo", My_Type 44, (Date.new 2022 06 21 . internal_local_date), 42]], ["D", [1,2,3,4000]], ["E", [Nothing, (Time_Of_Day.new 13 55 . internal_local_time), Nothing, Nothing]]]
table = Table.new [['The Column "Name"', ["foo","'bar'",'"baz"', 'one, two, three']], ["B", [1.0, 1000000.5, 2.2, -1.5]], ["C", ["foo", My_Type 44, (Date.new 2022 06 21), 42]], ["D", [1,2,3,4000]], ["E", [Nothing, (Time_Of_Day.new 13 55 . internal_local_time), Nothing, Nothing]]]
file = (enso_project.data / "transient" / "quote_always.csv")
file.delete_if_exists
table.write file format on_problems=Report_Error . should_succeed

View File

@ -0,0 +1,54 @@
from Standard.Base import all
import Standard.Base.Data.Time.Date
import Standard.Table
import Standard.Table.Data.Column
import Standard.Table.Io.File_Format
from Standard.Table.Data.Data_Formatter as Data_Formatter_Module import Data_Formatter
from Standard.Table.Io.Line_Ending_Style import Unix_Line_Endings
import Standard.Test
import project.Util
spec =
c_number = ["Number", [71, 72, 73, 74, 75, 76, 77]]
c_party = ["Party", ["Conservative", "Conservative", "Labour", "Labour", "Conservative", "Conservative", "Conservative"]]
c_name = ["Title", ["Margaret Thatcher", "John Major", "Tony Blair", "Gordon Brown", "David Cameron", "Theresa May", "Boris Johnson"]]
c_from = ["From", [Date.new 1979 05 04, Date.new 1990 11 28, Date.new 1997 05 02, Date.new 2007 06 27, Date.new 2010 05 11, Date.new 2016 07 13, Date.new 2019 07 24]]
c_to = ["To", [Date.new 1990 11 28, Date.new 1997 05 02, Date.new 2007 06 27, Date.new 2010 05 11, Date.new 2016 07 13, Date.new 2019 07 24, Date.new 2022 07 07]]
expected = Table.new [c_number, c_party, c_name, c_from, c_to]
Test.group "File.read (Delimited) should work with Dates" <|
table = (enso_project.data / "prime_ministers.csv").read
Test.specify "should be able to read in a table with dates" <|
table.columns.length.should_equal 5
table.columns.map (.name) . should_equal ['Number','Party', 'Title', 'From', 'To']
table.row_count.should_equal 7
Test.specify "should be able to treat a single value as a Date" <|
from_column = table.at 'From'
from_column.at 6 . year . should_equal 2019
from_column.at 6 . should_equal (Date.new 2019 7 24)
Test.specify "should be able to compare columns and table" <|
table.at 'Number' . should_equal (Column.from_vector c_number.first c_number.second)
table.at 'Party' . should_equal (Column.from_vector c_party.first c_party.second)
table.at 'Title' . should_equal (Column.from_vector c_name.first c_name.second)
table.at 'From' . should_equal (Column.from_vector c_from.first c_from.second)
table.at 'To' . should_equal (Column.from_vector c_to.first c_to.second)
table.should_equal expected
Test.group "Should be able to serialise a table with Dates to Text" <|
Test.specify "should serialise back to input" <|
expected_text = (enso_project.data / "prime_ministers.csv").read_text
delimited = Text.from expected format=(File_Format.Delimited "," line_endings=Unix_Line_Endings)
delimited.should_equal expected_text
Test.specify "should serialise dates with format" <|
test_table = Table.new [c_from]
expected_text = 'From\n04.05.1979\n28.11.1990\n02.05.1997\n27.06.2007\n11.05.2010\n13.07.2016\n24.07.2019\n'
data_formatter = Data_Formatter . with_datetime_formats date_formats=["dd.MM.yyyy"]
delimited = Text.from test_table format=(File_Format.Delimited "," value_formatter=data_formatter line_endings=Unix_Line_Endings)
delimited.should_equal expected_text
main = Test.Suite.run_main spec

View File

@ -2,6 +2,7 @@ from Standard.Base import all
import Standard.Base.System
import Standard.Table
import Standard.Table.Data.Column
import Standard.Test
Table.Table.should_equal expected =
@ -10,6 +11,11 @@ Table.Table.should_equal expected =
self_cols.map .name . should_equal (that_cols.map .name) frames_to_skip=1
self_cols.map .to_vector . should_equal (that_cols.map .to_vector) frames_to_skip=1
Column.Column.should_equal expected =
self.name.should_equal expected.name
self.length.should_equal expected.length
self.to_vector.should_equal expected.to_vector
normalize_lines string line_separator=System.default_line_separator newline_at_end=True =
case newline_at_end of
True -> string.lines.join line_separator suffix=line_separator

View File

@ -7,58 +7,66 @@ import Standard.Base.Data.Time.Time_Of_Day
import Standard.Base.Data.Time.Zone
import Standard.Test
polyglot java import java.time.LocalDate
spec =
Test.group "Date" <|
specWith "Date" Date.new Date.parse
specWith "JavaScriptDate" js_date js_parse
specWith "JavaDate" java_date Date.parse
specWith "JavaScriptArrayWithADate" js_array_date Date.parse
specWith name create_new_date parse_date =
Test.group name <|
Test.specify "should create local date" <|
date = Date.new 2020 1 1
date = create_new_date 2020 1 1
date . year . should_equal 2020
date . month . should_equal 1
date . day . should_equal 1
Test.specify "should handle errors when creating local date" <|
case Date.new 2020 30 30 . catch of
case create_new_date 2020 30 30 . catch of
Time.Time_Error msg ->
msg . should_equal "Invalid value for MonthOfYear (valid values 1 - 12): 30"
result ->
Test.fail ("Unexpected result: " + result.to_text)
Test.specify "should format local date using provided pattern" <|
text = Date.new 2020 12 21 . format "yyyyMMdd"
text = create_new_date 2020 12 21 . format "yyyyMMdd"
text . should_equal "20201221"
Test.specify "should format local date using default pattern" <|
text = Date.new 2020 12 21 . to_text
text = create_new_date 2020 12 21 . to_text
text . should_equal "2020-12-21"
Test.specify "should parse default time format" <|
text = Date.new 2020 12 21 . to_text
date = Date.parse text
text = create_new_date 2020 12 21 . to_text
date = parse_date text
date . year . should_equal 2020
date . month . should_equal 12
date . day . should_equal 21
Test.specify "should throw error when parsing invalid date" <|
case Date.parse "birthday" . catch of
case parse_date "birthday" . catch of
Time.Time_Error msg ->
msg . should_equal "Text 'birthday' could not be parsed at index 0"
result ->
Test.fail ("Unexpected result: " + result.to_text)
Test.specify "should parse local date" <|
date = Date.parse "1999-01-01"
date = parse_date "1999-01-01"
date . year . should_equal 1999
date . month . should_equal 1
date . day . should_equal 1
Test.specify "should parse custom format" <|
date = Date.parse "1999 1 1" "yyyy M d"
date = parse_date "1999 1 1" "yyyy M d"
date . year . should_equal 1999
date . month . should_equal 1
date . day . should_equal 1
Test.specify "should throw error when parsing custom format" <|
date = Date.parse "1999-01-01" "yyyy M d"
date = parse_date "1999-01-01" "yyyy M d"
case date.catch of
Time.Time_Error msg ->
msg . should_equal "Text '1999-01-01' could not be parsed at index 4"
@ -66,7 +74,7 @@ spec =
Test.fail ("Unexpected result: " + result.to_text)
Test.specify "should convert to time" <|
time = Date.new 2000 12 21 . to_time (Time_Of_Day.new 12 30 45) Zone.utc
time = create_new_date 2000 12 21 . to_time (Time_Of_Day.new 12 30 45) Zone.utc
time . year . should_equal 2000
time . month . should_equal 12
time . day . should_equal 21
@ -77,45 +85,45 @@ spec =
time . zone . zone_id . should_equal Zone.utc.zone_id
Test.specify "should convert to Json" <|
date = Date.new 2001 12 21
date = create_new_date 2001 12 21
date.to_json.should_equal <|
Json.from_pairs [["type", "Date"], ["day", date.day], ["month", date.month], ["year", date.year]]
Test.specify "should add date-based interval" <|
date = Date.new 1970 + 1.day
date = create_new_date 1970 + 1.day
date . year . should_equal 1970
date . month . should_equal 1
date . day . should_equal 2
Test.specify "should subtract date-based interval" <|
date = Date.new 1970 - 1.year
date = create_new_date 1970 - 1.year
date . year . should_equal 1969
date . month . should_equal 1
date . day . should_equal 1
Test.specify "should support mixed interval operators" <|
date = Date.new 1970 + 1.month - 1.year
date = create_new_date 1970 + 1.month - 1.year
date . year . should_equal 1969
date . month . should_equal 2
date . day . should_equal 1
Test.specify "should throw error when adding time-based interval" <|
case (Date.new 1970 + 1.hour) . catch of
case (create_new_date 1970 + 1.hour) . catch of
Time.Time_Error message ->
message . should_equal "Date does not support time intervals"
result ->
Test.fail ("Unexpected result: " + result.to_text)
Test.specify "should throw error when subtracting time-based interval" <|
case (Date.new 1970 - (1.day - 1.minute)) . catch of
case (create_new_date 1970 - (1.day - 1.minute)) . catch of
Time.Time_Error message ->
message . should_equal "Date does not support time intervals"
result ->
Test.fail ("Unexpected result: " + result.to_text)
Test.specify "should be comparable" <|
date_1 = Date.parse "2021-01-02"
date_2 = Date.parse "2021-01-01"
date_1 = parse_date "2021-01-02"
date_2 = parse_date "2021-01-01"
(date_1 == date_2) . should_be_false
date_1==date_1 . should_be_true
date_1!=date_2 . should_be_true
@ -123,11 +131,40 @@ spec =
date_1<date_2 . should_be_false
Test.specify "should return the correct week of year" <|
date_1 = Date.parse "2021-08-01"
date_1 = parse_date "2021-08-01"
date_1.week_of_year Locale.mexico . should_equal 32
date_2 = Date.parse "2021-01-01"
date_2 = parse_date "2021-01-01"
date_2.week_of_year Locale.us . should_equal 1
date_2.week_of_year Locale.uk . should_equal 0
main = Test.Suite.run_main spec
parseNormally x y = (Date.parse x y) . to_text
js_parse text format=Nothing =
d = Date.parse text format
js_date d.year d.month d.day
js_date year month=1 day=1 =
Panic.catch Any (js_date_impl year month day) (err -> Error.throw (Time.Time_Error err.payload.cause))
js_array_date year month=1 day=1 =
arr = Panic.catch Any (js_array_dateCreate year month day) (err -> Error.throw (Time.Time_Error err.payload.cause))
arr.at(0)
java_date year month=1 day=1 =
Panic.catch Any (LocalDate.of year month day) (err -> Error.throw (Time.Time_Error <| err.payload.to_display_text.take (Range 16 Nothing)))
foreign js js_date_impl year month=1 day=1 = """
if (month > 12) {
throw `Invalid value for MonthOfYear (valid values 1 - 12): ${month}`;
}
return new Date(year, month - 1, day);
foreign js js_array_dateCreate year month day = """
if (month > 12) {
throw `Invalid value for MonthOfYear (valid values 1 - 12): ${month}`;
}
return [ new Date(year, month - 1, day) ];

View File

@ -1,11 +1,11 @@
polyglot java import java.time.LocalDate
polyglot java import java.time.format.DateTimeFormatter
import project.Polyglot
new year (month = 1) (day = 1) = LocalDate.of year month day
new year (month = 1) (day = 1) = (LocalDate.of year month day) . internal_local_date
type Date
type Date internal_local_date
year = self . internal_local_date . getYear
month = self . internal_local_date . getMonthValue
day = self . internal_local_date . getDayOfMonth
to_text = DateTimeFormatter.ISO_LOCAL_DATE.format self.internal_local_date
@Builtin_Type
type Date
year = @Builtin_Method "Date.year"
month = @Builtin_Method "Date.month"
day = @Builtin_Method "Date.day"