Yet another DateTime question (epoch seconds to DateTime in ECMA 2021)

I think that it has been like this every since jruby was officially adopted by openhab back in 2022 IIRC, and this is supported by the latest helper library version which works and tested on all openHAB versions starting from the latest 3.4.x, 4.0, 4.1, … all the way to 5.0 snapshot.

The reason for this particular case is that Epoch in seconds is the standard in Ruby, supported by Ruby’s native Time class. It also supports fractions for sub-second precision.

irb(main):014> now = Time.now
=> 2025-03-12 08:10:14.17885 +1000
irb(main):015> now + 86400
=> 2025-03-13 08:10:14.17885 +1000
irb(main):016> now + 86400.5
=> 2025-03-13 08:10:14.67885 +1000

So this is extended all the way to ZonedDateTime, Instant, DateTimeType, etc. Here are some more examples, including how it works with Duration, and comparisons.

JRuby> zdt = ZonedDateTime.now
=> 2025-03-12T08:14:40.250441488+10:00[Australia/Brisbane]
JRuby> zdt + 1
=> 2025-03-12T08:14:41.250441488+10:00[Australia/Brisbane]
JRuby> zdt + 1.second
=> 2025-03-12T08:14:41.250441488+10:00[Australia/Brisbane]
JRuby> zdt + 1.day
=> 2025-03-13T08:14:40.250441488+10:00[Australia/Brisbane]
JRuby> instant = Instant.now
=> 2025-03-11T22:14:48.832928160Z
JRuby> instant + 1
=> 2025-03-11T22:14:49.832928160Z
JRuby> instant + 1.second
=> 2025-03-11T22:14:49.832928160Z
JRuby> instant + 1.hour
=> 2025-03-11T23:14:48.832928160Z
JRuby> dtt = DateTimeType.new
=> 2025-03-12T08:15:02.131035486+1000
JRuby> dtt + 1
=> 2025-03-12T08:15:03.131035486+1000
JRuby> dtt > now
=> true
JRuby> dtt > instant
=> true
JRuby> zdt > instant
=> false
JRuby> dtt > 1.hour.ago
=> true
JRuby> dtt - zdt
=> 21.880593998000002

Feature request opened.

1 Like

I could not find this in the docs. Is this info anywhere?

I linked my DateTime item now to this channel but it stays at NULL. The log reports:

Transformation ignored, failed to parse [ | time.ZonedDateTime.ofInstant(time.Instant.ofEpochSeconds(parseInt(input)), time.ZonedId.systemDefault()).toString()]: The transformation pattern must be in the syntax of TYPE:PATTERN or TYPE(PATTERN)

Could you elaborate a bit more please @rlkoshak ?

What part?

Inline transformations?

Using the JS-Joda library to convert an epoch number to an Instant to a ZonedDateTime?

You never mention how or where you are applying this transformation. I assumed as a profile. If you are applying it elsewhere you need to follow the error message and tell it which rules language the transformation is written in.

JS: | time.ZonedDateTime.ofInstant(time.Instant.ofEpochSeconds(parseInt(input)), time.ZonedId.systemDefault()).toString()

or

JS(| time.ZonedDateTime.ofInstant(time.Instant.ofEpochSeconds(parseInt(input)), time.ZonedId.systemDefault()).toString())

True!

So, in the MQTT channel, where the item is linked, there is a section Profile. I have selected SCRIPT ECMAScript (ECMAScript 262 Edition 11) as the profile and pasted your suggested code; JS(| time.ZonedDateTime.ofInstant(time.Instant.ofEpochSeconds(parseInt(input)), time.ZonedId.systemDefault()).toString()).

I have also created a tempo String-item and tried doing JS(|(parseInt(input)/2).toString()) to see if had any effect. But nothing yet.

I have upgraded to latest stable. Lots of new things, including this bit. Once I get the hang of it, should be ok :wink:

It also matters if you are using the UI or doing this in .things or .items files.

In the UI for the Profile, you select the language to use for the script transformation. So you don’t need to supply it again. So there you would use:

|  time.ZonedDateTime.ofInstant(time.Instant.ofEpochSeconds(parseInt(input)), time.ZonedId.systemDefault()).toString()

For the MQTT Thing Channel config (for example) you don’t select the transfromation up front so you need to indicate it:

JS(|  time.ZonedDateTime.ofInstant(time.Instant.ofEpochSeconds(parseInt(input)), time.ZonedId.systemDefault()).toString())

In .things or .items files you don’t ever select anything so you always need to supply the transformation.

We are getting closer. At least the logs now say something I could elaborate with and the solution is very close but involved a typo.

| time.ZonedDateTime.ofInstant(time.Instant.ofEpochSecond(parseInt(input)), time.ZoneId.systemDefault()).toString()
ofEpochSeconds needed to be ofEpochSecond and ZonedId should have been ZoneId.

Overall a big thanks. See, the ‘trick’ with the UI and file based difference wasn’t clear to me. Now all working as expected!

Thanks all!

I’m sorry, I got caught up in all the JavaScript, time.ZonedDateTime, time.Instant etc.

If you link your channel directly to a DateTime item, this is all you need to do. DateTimeType has a String constructor that understands epoch seconds, so you don’t need JavaScript and/or a profile:

items/test.items:

DateTime DateTimeTest

Console:

openhab> openhab:update DateTimeTest "1741895780"
Update has been sent successfully.

Result:

2025-03-13 21:05:15.478 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'DateTimeTest' changed from NULL to 2025-03-13T20:56:20.000+0100
2 Likes

Can’t remember when I stumbled on the solution by @laursen. (I’m sure I didn’t read it in the docs :wink:) But I timestamp my battery devices;

  - id: Last_furnace_flood_n
    channelTypeUID: mqtt:datetime
    label: Last Activity Furnace flood
    configuration:
      stateTopic: zwave2/Furnace_Area_Leak/lastActive
      transformationPattern:
        - JSONPATH:$.value


with the following result

I do have this state description
%1$tb-%1$td %1$tl:%1$tM %Tp
no mess, no fuss.

Just to extend the explanation in my previous post: In this latest example you have microseconds since epoch, and this is supported as well. If the length of the string is less than 12 characters, seconds are assumed.

Will you be damned! That works indeed! I didn’t manage to get that working earlier hence the ‘difficult way’ to achieve the same result.

Only thing now is, I can somehow not select ‘Standard’ in the profile list anymore. As a workaround I selected the debounce with a value of 0

To hack my own topic, but keeping the title ‘another DateTime question’…
Why does this code:

var trashDayDt = time.toZDT(items.nextTrashTime.state)
var isTrashDay = trashDayDt.toLocalDate() == time.ZonedDateTime.now().toLocalDate()
console.log(trashDayDt)
console.log(trashDayDt.toLocalDate() + " " + time.ZonedDateTime.now().toLocalDate())
console.log(isTrashDay)

Yield these log results

2025-03-16 14:33:16.213 [INFO ] [nhab.automation.script.ui.b9a578bddc] - 2025-03-16T14:35+01:00[Europe/Amsterdam]

2025-03-16 14:33:16.216 [INFO ] [nhab.automation.script.ui.b9a578bddc] - 2025-03-16 2025-03-16

2025-03-16 14:33:16.217 [INFO ] [nhab.automation.script.ui.b9a578bddc] - false

The dates are the same, right?
Just as when I move the calendar-item in my calendar to tomorrow, so the DateTime reflect tomorrows date for nextTrashTime and I do:

var isTrashTomorrow = trashDayDt.minusDays(1).toLocalDate() == time.ZonedDateTime.now().toLocalDate()
console.log(trashDayDt.minusDays(1).toLocalDate() + " " + time.ZonedDateTime.now().toLocalDate())
console.log(isTrashTomorrow)

Produces

2025-03-16 14:40:01.286 [INFO ] [nhab.automation.script.ui.b9a578bddc] - 2025-03-17T10:15+01:00[Europe/Amsterdam]

2025-03-16 14:40:01.288 [INFO ] [nhab.automation.script.ui.b9a578bddc] - 2025-03-16 2025-03-16

2025-03-16 14:40:01.291 [INFO ] [nhab.automation.script.ui.b9a578bddc] - false

Why does this equation never result in true? Do I need the same time as well?
And if the syntax is wrong, ie remove the () after toLocalDate, I get for both today and tomorrow a true while only 1 should be true!

Because you can’t use == to compare two Object in JS. You have to use .equals().

var isTrashDay = trashDayDt.toLocalDate().equals(time.ZonedDateTime.now().toLocalDate())

Because JS doesn’t allow operator overloading. Therefore == is strictly a boolean mathematical operator that only works with strings and numbers. If you have anything more complicated than that, such as a joda-js LocalDate Object, you have to call the .equals function.

This is the same reason why you have to use the methods to do math or comparisons with Quantity Objects.

Ahhhhh! It works (didn’t expect anything else). Never thought about the .equals()

Never to old to learn and learning everyday. Thanks!

So just FYI, I have added support for converting a time.Instant to time.ZonedDateTime in JS Scripting using time.toZDT.
This will be part of the upcoming openhab-js release, which will provide an even better way to work with Instants:

time.toInstant

You can just to MyItem.sendCommand(time.toInstant(500)) to send a Instant of epoch milli 500 to a DateTime Item.

I have just released openhab-js 5.9.0 that contains these changes :partying_face:

1 Like