Skip to content

Commit

Permalink
Date literal as @ notation (#416)
Browse files Browse the repository at this point in the history
* test(engine): verify temporal literals

* feat(engine): create time with zone-id

* feat(parser): parse temporal literals with @-notation

* docs: mention the new @ notation for temporal literals
  • Loading branch information
saig0 committed Apr 25, 2022
1 parent d0a5b3c commit 8552b02
Show file tree
Hide file tree
Showing 6 changed files with 249 additions and 6 deletions.
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 {
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")
))
}

}

0 comments on commit 8552b02

Please sign in to comment.