This is all in the docs but I don’t think many go back and review the docs when installing an update. This tutorial is just to provide a heads up about this feature that was added to openhab-js not too long ago to make creating, working with, and processing date times easier. It is not intended to be a replacement for the docs, just a place to have a few more examples.
Traditionally, working with and converting date times in openHAB has been a pain. Really, it’s a pain in any programming environment because there are few concepts that most people use every day that is as complex as dates and time.
First let’s set some groundwork.
joda-js
The JS Scripting add-on uses the js-joda library for all date time types. openHAB uses java.time.*
internally. The openhab-js library and add-on has a bunch of stuff built into it that automatically converts between the JavaScript and the Java versions of these classes so that you can use the pure JavaScript classes in your rules.
This means that you can use a JS-Joda ZonedDateTime in any call to an openHAB Java method that expects a java.time.ZonedDateTime
. Therefore, use the JS-Joda classes in your rules which are available via time
(e.g. time.ZonedDateTime
).
Time Utilities
openhab-js’s time
includes a number of additional utilities to make implementing the most common use cases of using dates and times in OH easier. Some of these are stand-alone features while others are additions to the ZonedDateTime
class (monkey patched).
Creating a ZonedDateTime
with an Offset
This is probably the most common use cases in all of openHAB because this is usually what one needs to do create an openHAB Timer which takes a ZonedDateTime
as the time for the timer to run. So the offset is almost always from now
.
time.toZDT(); // returns a ZonedDateTime of now, shorter to type than time.ZonedDateTime.now()
time.toZDT(12345); // passing any JavaScript or Java representation of a number will be treated as the number of milliseconds to add to now
time.toZDT(items.getItem('MyNumberTime')); // It will add the state of a Number:Time Item (i.e. QuantityType<Time>) to now
time.toZDT(items.getItem('MyNumberItem'); // Adds the state of the Item to now as milliseconds
time.toZDT('PT1h2m3.4s'); // Parses the ISO8601 duration string and adds it to now
time.tiZDT(time.Duration.ofSeconds(123)); // Adds the passed in Duration to now
I particularly like the duration string version because it makes the code very clear. For example:
ScriptExectution.createTimer(time.toZDT('PT5s'), ...
will schedule the timer for five seconds from now.
Using the state of a DateTime Item in a Rule
In order to compare or manipulate the state of a DateTime Item in a rule its state needs to be converted to a ZonedDateTime
.
time.toZDT(MyDateTimeItem); // Converts the state of the DateTime Item to a ZonedDateTime (can handle both the openhab-js Item Object or the native openHAB Java Item Object)
time.toZDT(items.getItem('MyDateTimeItem')); // if all you have is the name of the Item
Once you have it converted you can use all the standard JS-Joda operations to compare and manipulate it.
Other Conversions to ZonedDateTime
time.toZDT()
will also convert java.time.ZonedDateTime
, and native Java Date
to JS-Joda ZonedDateTime
. It can also parse the RFC formatted string produced by java.time.ZonedDateTime.toString()
but it cannot handle an ISO8601 formatted string with the timezone at this time.
Static Times of Day
Often we will want to create a ZonedDateTime
at a specific time but with today’s date.
time.toZDT('8:30:00 AM'); // 8:30 AM today
time.toZDT('13:02'); // 1:02 PM today
Moving a ZonedDateTime to Today’s Date
Sometimes one will have a date time where the time needs to be kept static but the date needs to be adjusted to today (e.g. an alarm clock). Calling toToday()
on a ZonedDateTime
will move the date to today but preserve the time, taking into account for DST change overs. For example, if the time is 13:02
with a date from three days ago and the DST changeover happened in between, calling toToday()
will result in 13:02
today, not 12:02
or 14:02`.
ScriptExecution.createTime(time.toZDT(items.getItem('AlarmClock')).toToday(), ...
Between Times
One of the more common but frustratingly common comparisons is to see if a ZonedDateTime
is between two times (ignoring the date). For example, one might want to do something different between 10:00 PM and 8:00 AM in a rule than what it does the rest of the time.
time.toZDT().betweenTimes('10:00 PM', '8:00 AM') // true if now is between 10:00 PM today and 8:00 AM tomorrow
time.toZDT(items.getItem('MyDateTime')).betweenTimes(items.getItem('MyDateTimeStart'), '22:00') // true if the state of MyDateTime is between the time carried by MyDateTimeStart and 10:00 PM
betweenTimes()
will call time.toZDT()
on both of its arguments so anything accepted by toZDT()
can be passed as an argument.
If the time in the first argument is later than the second argument, the time period is assumed to span midnight and the second argument will be advanced to tomorrow.
Are Two ZonedDateTimes Close to Each Other?
There are sometimes cases where we want to see whether or not two DateTime Items are within a certain amount of time of each other. For example, perhaps you want to see if a rule is triggered within a second of the last trigger and do something different.
timestamp.isClose(time.toZDT(), time.Duration.ofSeconds(1)); // true if the timestamp is within 1 second of now
time.toZDT(items.getItem('DoorOpenedLastUpdate').isClose(time.toZDT(), time.Duration.parse('PT1d12h')); // the state of DoorOpenedLastUpdate is no more than 36 hours ago
Note that it will return true if timestamp
is one second before or one second after now
.
Milliseconds from Now
The more native looking way to create timers in JS Scripting is by calling setTimeout()
. However this function only supports the number of milliseconds from now to run the timer. But it’s easy to convert a ZonedDateTime to the number of milliseconds from now.
setTimeout(toZDT(items.getItem('AlarmClock').millisFromNow(), () => { consol.log('Alarm clock has gone off!'); });
Conclusion
With these utilities, only rarely should one need to directly manipulate date time Objects or use a long string of method calls to get to something that can be converted to a ZonedDateTime
. With support for Duration strings and time of day strings your code should be just a little more human readable.
Common complicated comparisons like checking to see if a ZonedDateTime
is between two times of day, accounting for spanning midnight, or testing to see if two ZonedDateTimes
are within a certain amount of time of each other are now just a single function call.
Hopefully this post has made you aware of these utilities and shown you just a few of the many things you can do with them.
Note that openhab-rules-tools, my library of utilities that implement many of the design patterns, make heavy use of these functions. For example:
// Create a timer for the triggering Item to go off in 5 seconds and call runme,
// if a timer already exists reschedule it
timerManager.check(event.itemName, 'PT5s', runme, true);
// Create a timer that calls a function every 10 seconds until that function returns true
loopingTimer.loop(10000, loopFunction);
// If it's within a time specified by a Number:Time Item, queue up the calls until that amount of time has passed
// e.g. if you don't want to send commands to a bunch of devices too fast
items.getItem('HueLights').forEach( (i, index, arr) => {
gatekeeper.addCommand(time.toZDT(items.getItem('HueDelay')), () => { i.sendCommand(OFF) });
});