OH 4 -> How to add or subtract hours and minute from DateTime item?

Hi to all!
After upgrading to OH 4 I am trying to implement the following:

I am downloading my rooster via ical-binding. The DateTime item “next_outbound_begin” contains the date and time to start my shift.
However, I have to leave the house about 90 minutes before my shift starts and that’s when I want my heaters to lower the temperature.
Consequently, I need to subtract about 90 minutes from the DateTime item “next_outbound_begin”.
All approaches I tried are for openHAB 2 or 3 and don’t work.

Does anyone have an idea how a rule has to look where subtract (or add) a few hours and minutes from/to a DateTime item?

I am on openHAB 4.2.3, running on a Raspberry Pi 4.

Thank you in advance!

In Javascript:

var aantalminuten = 4

var lastseenstring = items["Camera_tuin_en_terras_G5_Bullet_Last_Seen"].state
var lastseen = new Date (lastseenstring)
var aantalminutengeleden = new Date(new Date().getTime() - (aantalminuten * 60 * 1000))

var unifiprotectbreukgemeld = items["unifiprotectbreukgemeld"]

if ((lastseen < aantalminutengeleden || lastseen == "Invalid Date") && unifiprotectbreukgemeld.state == "OFF") {
    var boodschap = 'De camera heeft al minstens '+ aantalminuten +' minuten geen teken van leven meer gegeven (sinds '+ lastseen +').'
    console.log(boodschap)
    
    unifiprotectbreukgemeld.postUpdate("ON")
}

Thank you so much for your quick reply.

Since I am not as advanced as you, I am not able to suit your rule to my needs.
I have a DateTime item called “next_outbound_begin”, which is the start of my shift.
From this DateTime item I need to subtract 90 minutes, which the should be stored in a DateTime item named “EDT”
So basically it should look like this

EDT=next_outbound_begin.minusMinutes(90)

of course this doesn’t work but the DateTime item “EDT” should then, at the newly calculated time, turn off my heaters.

Any idea?

I’m not at all advanced :slight_smile: My knowledge is pretty basic, and Google is my friend. But also places like these, so I figured I’d give back now that I can. :slight_smile:

I would assume this works:

Make an DateTime item, e.g. ETD_item.

Run this every morning at 00h01 or something like that:

var minutestosubstract = 90

var timetosubstractfromstring = items["next_outbound_begin"].state
var timetosubstractfrom = new Date (timetosubstractfromstring)
var ETD = new Date(timetosubstractfrom.getTime() - (minutestosubstract * 60 * 1000))
items["ETD_item"].sendCommand(ETD)

Add a rule using this trigger:


with code like this:

items["heaterswitchitem"].sendCommand("OFF")

But this is on the top of my head. It might need some tweaking. Or, there might be an actual advanced guru with a better approach :wink:

Hello again!

Unfortunately this doesn’t work.
Here is my item definition:

DateTime next_outbound_begin   "first at [%1$tT, %1$tY-%1$tm-%1$td]"      <calendar> { channel="icalendar:eventfilter:outbound:result_0#begin" }

and this is what the error log says:

[ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'test-1' failed: The name 'items' cannot be resolved to an item or type; line 7, column 33, length 28 in test

So the item is clearly defined, but to my understanding the error log says the item is not defined or might be a different type?
So I googled a little bit and modified Line 7 a little bit:

var timetosubstractfromstring = items["next_outbound_begin"].state.toString

with the same result. Error “The name ‘items’ cannot be resolved to an item or type…”

Is it that I am using a DateTime item and the rule works with String? I have no idea…

But thank you for putting so much effort into it!

Try

`var timetosubstractfromstring = items.next_outbound_begin.state.toString`

Thank you, hmerk,

but this gives me the same error message as before. I am starting to assume that there’s something wrong with my item definition. However, the definition comes directly from the openHAB documentation, so actually it should be correct. I am already at google page 8 with my research but so far, nothing has worked.

Sorry, was too much in Widget expressions. Please remove the „items.“ part.

Example in Blockly:

items.getItem('updated_item').sendCommand((time.toZDT(items.getItem('original_item'))).minusMinutes(90))

Ok, I removed the items. part, just to make sure it’s supposed to look like this:

var timetosubstractfromstring = next_outbound_begin.state.toString

This doesn’t work either, however, the Error-log message has changed to:

[ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'test-1' failed: An error occurred during the script execution: Cannot invoke "org.eclipse.xtext.common.types.JvmType.eIsProxy()" because "type" is null in test

So I googled this error which obviously says that the item is "null, but it isn’t. When I use the API explorer to get the state of an item, this is the result:

{
  "link": "http://192.168.4.10:8080/rest/items/next_outbound_begin",
  "state": "2024-11-29T16:50:00.000+0100",
  "stateDescription": {
    "pattern": "%1$tT, %1$tY-%1$tm-%1$td",
    "readOnly": true,
    "options": []
  },
  "editable": false,
  "type": "DateTime",
  "name": "next_outbound_begin",
  "label": "first at",
  "category": "calendar",
  "tags": [],
  "groupNames": []
}

Maybe I have to convert the DateTime item to a String, do the math and then convert it back to a DateTime item. I do not care about the date, but the time part is important.

At least the error is different now :wink:
Why not use @hafniumzinc‘s Blockly example ?

I have coded everything by text and stored it in files. However, I will try the Blockly-Thing. Could take a few days since I have to go to work tomorrow…

Thank you, guys!

You’re using Javascript, right? Not Rules DSL? (Although that might not matter, since no-one else brought this up…)

I honestly have no idea. How can I find out. Nevertheless I used a Blockly script, when I run the script within the OH user interface, it works. As soon as I copy the code into a file (test.rules), it won’t work. So may the issue with with Javascript ↔ Rules DSL? I assume I am using rules DSL, that’s the result of my research I did this morning.

In JS it’s better to use the classes in time.* than using the native Date class.

var minutestosubstract = 90

var timetosubstractfromstring = items["next_outbound_begin"].state
var timetosubstractfrom = new Date (timetosubstractfromstring)
var ETD = new Date(timetosubstractfrom.getTime() - (minutestosubstract * 60 * 1000))
items["ETD_item"].sendCommand(ETD)

would better be implemented as

var ETD = time.toZDT(items["next_outbound_begin"]).minusMinutes(90);
items["ETD_item"].sendCommand(ETD);

This might be the root of you’re problem.

The overall subtract and format of a managed rule (I e. in the UI) is different from a done based rule and you absolutely must put the code in the type of file the language calls for.

Nothing in this thread thus far had been Rules DSL but .rules files and only be Rules DSL.

Blocky code can only work as a managed rule. You cannot copy the code to a file and expect it to work as is. It needs a lot of work to convert it to something that can work on a file

In openhab 4.3 you can remove your EDT item, and create a time trigger based on next_outbound_begin with a -5400 second offset.

1 Like

Thank you for your reply. I still receive an error message:

[ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'test-1' failed: The name 'time' cannot be resolved to an item or type; line 6, column 13, length 4 in test

Anyways, using Blockly as suggested by @hafniumzinc, openHAB now calculates my departure time correctly. I normally would like to understand how and why (or why not) this works and I usually use files to configure my openHAB, but finally it works and I can continue coding and do not need to bother you guys any further. Thank you for your support!

This is the reason it doesn’t work. You need a .js file. (That is my understanding at least. :))

@rlkoshak, what do you mean with it being ‘better’ to use time.*?

As I said above and @ErikDB said, this code is JS code. You do not put JS code into a .rules file. See JavaScript Scripting - Automation | openHAB for everything you need to know to write JS rules.

Working with epoch and JS’s Date class is arcane, error prone, and unnecessarily long and involved.

JS Scripting’s helper lkibrary (i.e. openhab-js) includes the entirety of the joda-jd library which is made available in the time name space (e.g. time.ZonedDateTime). In addition, there is a helper method called time.toZDT() which converts almost anything that can be converted into a time.ZonedDateTime. You never really need to call new to get a ZonedDateTime, just use time.toZDT().

For example:

Code What you get
time.toZDT() now
time.toZDT(items.MyDateTimeItem) the state of the Item as a ZonedDateTime
time.toZDT(6000) now plus six seconds
time.toZDT('PT5H2M') now plus five hours two minutes
time.toZDT('08:00') today at 08:00
time.toZDT('9:00 pm') today at 21:00

There’s more, these are just some of the ones I use more often.

Once you have a ZonedDateTime you have all the plusMinutes and minusSeconds type methods. The time.Duration class can determine the difference between two ZonedDateTimes.
See JavaScript Scripting - Automation | openHAB for details.

All the calls to openHAB’s API (createTimer, postUpdate, sendCommand, executeCommandLine, etc.) exepects a time.ZonedDateTime or time.Duration.

So, instead of the five lines of code required to add 90 minutes to the current state of a DateTime Item:

var minutestosubstract = 90

var timetosubstractfromstring = items["next_outbound_begin"].state
var timetosubstractfrom = new Date (timetosubstractfromstring)
var ETD = new Date(timetosubstractfrom.getTime() - (minutestosubstract * 60 * 1000))
items["ETD_item"].sendCommand(ETD)

we have two

var ETD = time.toZDT(items["next_outbound_begin"]).minusMinutes(90);
items["ETD_item"].sendCommand(ETD);

And those two lines of code requires less mental gymnastics to understand (e.g. .minusMinutes(90) as opposed to - (minutestosubteract * 60 * 1000)).

Since using the time.* classes are going to be required anyway some of the time and using them results in shorter and easier to understand code, using them in most circumstances is better than using the native JS Date class with is far less capable and requires a lot more effort to work with. And it’s going to be converted to a ZonedDateTime for you anyway before it can be used in that sendCommand.

1 Like

Or use Jruby

ETD_item << next_outbound_begin.state - 90.minutes