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

Date literal as @ notation #416

Merged
merged 4 commits into from
Apr 25, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 20 additions & 4 deletions docs/docs/reference/language-guide/feel-data-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ A date value without a time component.

```js
date("2017-03-10")

@"2017-03-10"
```

### Time
Expand All @@ -75,10 +77,13 @@ A local or zoned time. The time can have an offset or time zone id.
```js
time("11:45:30")
time("13:30")

time("11:45:30+02:00")

time("10:31:10@Europe/Paris")

@"11:45:30"
@"13:30"
@"11:45:30+02:00"
@"10:31:10@Europe/Paris"
```

### Date-Time
Expand All @@ -90,10 +95,12 @@ A date with a local or zoned time component. The time can have an offset or time

```js
date and time("2015-09-18T10:31:10")

date and time("2015-09-18T10:31:10+01:00")

date and time("2015-09-18T10:31:10@Europe/Paris")

@"2015-09-18T10:31:10"
@"2015-09-18T10:31:10+01:00"
@"2015-09-18T10:31:10@Europe/Paris"
```

### Days-Time-Duration
Expand All @@ -108,6 +115,11 @@ duration("P4D")
duration("PT2H")
duration("PT30M")
duration("P1DT6H")

@"P4D"
@"PT2H"
@"PT30M"
@"P1DT6H"
```

### Years-Months-Duration
Expand All @@ -121,6 +133,10 @@ A duration based on the calendar. It can contain years and months.
duration("P2Y")
duration("P6M")
duration("P1Y6M")

@"P2Y"
@"P6M"
@"P1Y6M"
```

### List
Expand Down
16 changes: 15 additions & 1 deletion docs/docs/reference/language-guide/feel-temporal-expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,38 @@ title: Temporal Expressions

### Literal

Creates a new temporal value.
Creates a new temporal value. A value can be written in one of the following ways:

* using a temporal function (e.g. `date("2020-04-06")`)
* using the `@` - notation (e.g. `@"2020-04-06"`)

```js
date("2020-04-06")
@"2020-04-06"

time("08:00:00")
time("08:00:00+02:00")
time("08:00:00@Europe/Berlin")
@"08:00:00"
@"08:00:00+02:00"
@"08:00:00@Europe/Berlin"

date and time("2020-04-06T08:00:00")
date and time("2020-04-06T08:00:00+02:00")
date and time("2020-04-06T08:00:00@Europe/Berlin")
@"2020-04-06T08:00:00"
@"2020-04-06T08:00:00+02:00"
@"2020-04-06T08:00:00@Europe/Berlin"

duration("P5D")
duration("PT6H")
@"P5D"
@"PT6H"

duration("P1Y6M")
duration("P3M")
@"P1Y6M"
@"P3M"
```

### Addition
Expand Down
26 changes: 25 additions & 1 deletion src/main/scala/org/camunda/feel/impl/parser/FeelParser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ import org.camunda.feel.{
isOffsetTime,
isValidDate,
isYearMonthDuration,
isLocalDateTime,
isDayTimeDuration,
stringToDate,
stringToDateTime,
stringToDayTimeDuration,
Expand Down Expand Up @@ -410,12 +412,14 @@ object FeelParser {

private def temporal[_: P]: P[Exp] =
P(
("duration" | "date and time" | "date" | "time").! ~ "(" ~ stringWithQuotes ~ ")"
(("duration" | "date and time" | "date" | "time").! ~ "(" ~ stringWithQuotes ~ ")") |
("@".! ~ stringWithQuotes)
).map {
Comment on lines 414 to 417
Copy link
Contributor

Choose a reason for hiding this comment

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

❓ Just verifying I understand what this does correctly. I'm not sure what the P is, but it seems to be part of some parsing library (and it's different from the generic P used in the function definition).

I suppose it is matching an input string to see if it contains either duration, date and time, date or time followed by a string with quotes between parentheses. E.g. duration("P1Y").
Or it contains an @ followed by a string with quotes. E.g. @"P1Y".

I'm not sure what this P function returns but we match this against the temporal and find the correct parsing function for it.

Copy link
Member Author

Choose a reason for hiding this comment

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

You can read more about the underlying parser library here: https://com-lihaoyi.github.io/fastparse/
The docs are nicely written. But I'm also happy to talk about it and how it is used in the FEEL engine. Just ping me 🛎️

case ("duration", value) => parseDuration(value)
case ("date and time", value) => parseDateTime(value)
case ("date", value) => parseDate(value)
case ("time", value) => parseTime(value)
case ("@", value) => parseTemporalValue(value)
}

private def list[_: P]: P[Exp] =
Expand Down Expand Up @@ -656,4 +660,24 @@ object FeelParser {
}
}.getOrElse(ConstNull)
}

private def parseTemporalValue(value: String): Exp = {
Try {
if (isValidDate(value)) {
ConstDate(value)
} else if (isOffsetTime(value)) {
ConstTime(value)
} else if (isOffsetDateTime(value)) {
ConstDateTime(value)
} else if (isLocalDateTime(value)) {
ConstLocalDateTime(value)
} else if (isYearMonthDuration(value)) {
ConstYearMonthDuration(value)
} else if (isDayTimeDuration(value)) {
ConstDayTimeDuration(value)
} else {
ConstLocalTime(value)
}
}.getOrElse(ConstNull)
}
}
5 changes: 5 additions & 0 deletions src/main/scala/org/camunda/feel/syntaxtree/ZonedTime.scala
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,11 @@ object ZonedTime {
ZonedTime(time, offset, None)
}

def of(time: LocalTime, zoneId: ZoneId): ZonedTime = {
val offset = zoneId.getRules.getStandardOffset(Instant.now)
ZonedTime(time, offset, Some(zoneId))
}

def of(offsetTime: OffsetTime): ZonedTime = {
val localTime = offsetTime.toLocalTime()
val offset = offsetTime.getOffset
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ class DateTimeDurationPropertiesTest
"No property found with name 'x' of value 'ValDate(2020-09-30)'. Available properties:")
}

it should "has properties with @-notation" in {
eval(""" @"2017-03-10".year """) should be(ValNumber(2017))
eval(""" @"2017-03-10".month """) should be(ValNumber(3))
eval(""" @"2017-03-10".day """) should be(ValNumber(10))
}

///// -----

"A time" should "has a hour property" in {
Expand Down Expand Up @@ -96,6 +102,12 @@ class DateTimeDurationPropertiesTest
"No property found with name 'x' of value 'ValTime(ZonedTime(11:45:30,+02:00,None))'. Available properties:")
}

it should "has properties with @-notation" in {
eval(""" @"11:45:30+02:00".hour """) should be(ValNumber(11))
eval(""" @"11:45:30+02:00".minute """) should be(ValNumber(45))
eval(""" @"11:45:30+02:00".second """) should be(ValNumber(30))
}

///// -----

"A local time" should "has a hour property" in {
Expand Down Expand Up @@ -197,6 +209,12 @@ class DateTimeDurationPropertiesTest
"No property found with name 'x' of value 'ValDateTime(2020-09-30T22:50:30+02:00)'. Available properties:")
}

it should "has properties with @-notation" in {
eval(""" @"2017-03-10T11:45:30+02:00".year """) should be(ValNumber(2017))
eval(""" @"2017-03-10T11:45:30+02:00".month """) should be(ValNumber(3))
eval(""" @"2017-03-10T11:45:30+02:00".day """) should be(ValNumber(10))
}

///// -----

"A local date-time" should "has a year property" in {
Expand Down Expand Up @@ -280,6 +298,11 @@ class DateTimeDurationPropertiesTest
"No property found with name 'x' of value 'ValYearMonthDuration(P2Y3M)'. Available properties:")
}

it should "has properties with @-notation" in {
eval(""" @"P2Y3M".years """) should be(ValNumber(2))
eval(""" @"P2Y3M".months """) should be(ValNumber(3))
}

///// -----

"A day-time-duration" should "has a days property" in {
Expand Down Expand Up @@ -310,4 +333,11 @@ class DateTimeDurationPropertiesTest
"No property found with name 'x' of value 'ValDayTimeDuration(PT26H10M30S)'. Available properties:")
}

it should "has properties with @-notation" in {
eval(""" @"P1DT2H10M30S".days """) should be(ValNumber(1))
eval(""" @"P1DT2H10M30S".hours """) should be(ValNumber(2))
eval(""" @"P1DT2H10M30S".minutes """) should be(ValNumber(10))
eval(""" @"P1DT2H10M30S".seconds """) should be(ValNumber(30))
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ import org.camunda.feel.syntaxtree._
import org.scalatest.matchers.should.Matchers
import org.scalatest.flatspec.AnyFlatSpec

import java.time.{
Duration,
LocalDate,
LocalDateTime,
LocalTime,
Period,
ZoneId,
ZoneOffset,
ZonedDateTime
}

/**
* @author Philipp Ossler
*/
Expand Down Expand Up @@ -107,4 +118,147 @@ class InterpreterLiteralExpressionTest
eval("[ [1], [2] ]") should be(
ValList(List(ValList(List(ValNumber(1))), ValList(List(ValNumber(2))))))
}

"A date literal" should "be defined" in {
eval(""" date("2021-09-08") """) should be(
ValDate(
LocalDate.parse("2021-09-08")
))
}

it should "be defined with '@'" in {
eval(""" @"2021-09-08" """) should be(
ValDate(
LocalDate.parse("2021-09-08")
))
}

"A time literal" should "be defined without offset" in {
eval(""" time("10:30:00") """) should be(
ValLocalTime(
LocalTime.parse("10:30:00")
))
}

it should "be defined with offset" in {
eval(""" time("10:30:00+02:00") """) should be(
ValTime(
ZonedTime.of(time = LocalTime.parse("10:30:00"),
offset = ZoneOffset.ofHours(2))
))
}

it should "be defined with timezone" in {
eval(""" time("10:30:00@Europe/Berlin") """) should be(
ValTime(
ZonedTime.of(
time = LocalTime.parse("10:30:00"),
zoneId = ZoneId.of("Europe/Berlin")
)
))
}

it should "be defined with '@' and no offset" in {
eval(""" @"10:30:00" """) should be(
ValLocalTime(
LocalTime.parse("10:30:00")
))
}

it should "be defined with '@' and offset" in {
eval(""" @"10:30:00+02:00" """) should be(
ValTime(
ZonedTime.of(time = LocalTime.parse("10:30:00"),
offset = ZoneOffset.ofHours(2))
))
}

it should "be defined with '@' and timezone" in {
eval(""" @"10:30:00@Europe/Berlin" """) should be(
ValTime(
ZonedTime.of(
time = LocalTime.parse("10:30:00"),
zoneId = ZoneId.of("Europe/Berlin")
)
))
}

"A date-time literal" should "be defined without offset" in {
eval(""" date and time("2021-09-08T10:30:00") """) should be(
ValLocalDateTime(
LocalDateTime.parse("2021-09-08T10:30:00")
))
}

it should "be defined with offset" in {
eval(""" date and time("2021-09-08T10:30:00+02:00") """) should be(
ValDateTime(
ZonedDateTime.of(LocalDateTime.parse("2021-09-08T10:30:00"),
ZoneOffset.ofHours(2))
))
}

it should "be defined with timezone" in {
eval(""" date and time("2021-09-08T10:30:00@Europe/Berlin") """) should be(
ValDateTime(
ZonedDateTime.of(
LocalDateTime.parse("2021-09-08T10:30:00"),
ZoneId.of("Europe/Berlin")
)
))
}

it should "be defined with '@' and no offset" in {
eval(""" @"2021-09-08T10:30:00" """) should be(
ValLocalDateTime(
LocalDateTime.parse("2021-09-08T10:30:00")
))
}

it should "be defined with '@' and offset" in {
eval(""" @"2021-09-08T10:30:00+02:00" """) should be(
ValDateTime(
ZonedDateTime.of(LocalDateTime.parse("2021-09-08T10:30:00"),
ZoneOffset.ofHours(2))
))
}

it should "be defined with '@' and timezone" in {
eval(""" @"2021-09-08T10:30:00@Europe/Berlin" """) should be(
ValDateTime(
ZonedDateTime.of(
LocalDateTime.parse("2021-09-08T10:30:00"),
ZoneId.of("Europe/Berlin")
)
))
}

"A years-months duration" should "be defined" in {
eval(""" duration("P1Y6M") """) should be(
ValYearMonthDuration(
Period.ofYears(1).withMonths(6)
))
}

it should "be defined with '@'" in {
eval(""" @"P1Y6M" """) should be(
ValYearMonthDuration(
Period.ofYears(1).withMonths(6)
))
}

"A days-time duration" should "be defined" in {
eval(""" duration("P1DT12H30M") """) should be(
ValDayTimeDuration(
Duration.parse("P1DT12H30M")
))
}

it should "be defined with '@'" in {
eval(""" @"P1DT12H30M" """) should be(
ValDayTimeDuration(
Duration.parse("P1DT12H30M")
))
}

}