OH3 Time Window Comparator

Hello,

Could someone please lend me 2 minutes to check/correct the two Time Window Comparator Statements below. I’m getting too old for learning Java now. The comparators are in the same rule so I cannot tell from the log which one is failing.

  1. Copy and past from the forum:
    if((LocalDateTime.now().isAfter(LocalDate.now().atTime(00, 00))) && (LocalDateTime.now().isBefore(LocalDate.now().atTime(05, 00)))) {

  2. Home made:
    if((LocalDateTime.now().isAfter(LocalDate.of(2022,05,01))) && (LocalDateTime.now().isBefore(LocalDate.of(2022,08,24)))) {

The error log shows:
2022-04-04 15:00:00.541 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID ‘default-13’ failed: An error occurred during the script execution: Could not invoke method: java.time.LocalDateTime.isAfter(java.time.chrono.ChronoLocalDateTime) on instance: 2022-04-04T15:00:00.537824 in default

Your two minutes would be very much appreciated, and get my OH3 going.
And YES, I have searched the forum ++.

The point is that LocalDate represents only a date, not a timestamp, while LocalDateTime represents a timestamp (i.e. date + time).
In the copied version, while LocalDate.now() still is a date, LocalDate.now().atTime(...) creates a timestamp from this, thus can be compared to other timestamp.
In the home made version, LocalDate.of(...) still is a date, not a timestamp, thus cannot be compared to timestamp.
Not sure what you actually want to do, but probably LocalDate.of(...).atTime(0,0) works for you?

Robert

1 Like

Hi Robert,
Thanks a million for your 2+ minutes. Very enlightening!!
From your explanation I assume that the following pure Date statement will also work?
if((LocalDate.now().isAfter(LocalDate.of(2022,04,10))) &&
(LocalDate.now().isBefore(LocalDate.of(2022,04,18)))) {
“Lower Temperatures during Easter Vacation”
}
No error messages so far.
Thanks again,
Bjorn

Hi,

yes this should work as you obviously tested in the meantime :slight_smile:

Robert

Hi,
During OH2 to OH3 time adjustments I have an issue, which searches has not yet solved.
I need to define a time window that ends at midnight like this:
(…).is.After(…) && (LocalDateTime.now().isBefore(LocalDate.now().atTime(24, 00))).
But, time (24, 00) is legal in JODA but illegal in Java, so how can I define true midnight in this Java case? (00, 00) is morning and not midnight if I’m right.
Anyone?

23:59:59 ?

1 Like

He he, good try! I can add nanoseconds too.
But the question was: How to define true Midnight in Java Time?
Is it impossible???

There isn’t such a thing as 24:00. At that point you start the new day and it becomes 00:00. There is no instant in time where it’s both today and tomorrow, which is what the existence of 24:00 as a valid time would imply.

However, some libraries like Joda supported referring to that instance in time as both 24:00 and 00:00 (i.e. two names for the same instant in time) for convenience but it causes all sorts of problems and ambiguities so not all date time libraries support that. “True Midnight”, as you call it, is the start of the day, not the end of the day.

But you could try to get clever but it’s going to get tricky around DST changeovers.

LocalDate.now().atTime(00, 00).plusDays(1)

I am pretty sure that plusDays can handle DST. But maybe it’s better to do it like this

LocalDate.now().plusDays(1).atTime(00, 00)

I think that deals with DST change overs nicely. That should represent the start of tomorrow which, of course, is the end of the day today.

Though you’d only be off by one nanosecond if you use @rossko57’s approach (plus the nanoseconds). If one nanosecond matters in your home automation, openHAB is probably not the right tool for you anyway. ;-)—

Rick,
Thanks a million!

Your good explanation and suggestion solved more than I payed for!
It allows me to also build time windows crossing the midnight/morning boundary, which simplifies some of my code.
Thanks for your friendly attitude! Very much appreciated.

Note, if you are using JS Scripting and a recent version of openHAB, there is now a bunch of time utilities I’ve added to the library. It will convert almost anything you can think of to a ZonedDateTime (see GitHub - openhab/openhab-js: openHAB Javascript Library) but for this case it also provides a betweenTimes(t1, t2) which returns a boolean if the current ZonedDateTime instance is between the two passed in times. It handles a time period that spans midnight for you. It can accept anything as t1 and t2 that can be converted to a ZonedDateTime also.

In short, a lot of these sorts of things are now handled in that library and you don’t have to mess with doing them yourself.

For example, if you want to determine if now is between the states of two DateTime Items:

if(time.ZonedDateTime.now().betweenTimes(items.getItem('T1').rawState, items.getItem('T2').rawState))

To see if the state of an Item is between five minutes ago and 13:25 today:

if(items.getItem('MyTime').rawState.getZonedDateTime.betweenTimes('-P5M', '13:25'))

Note that the syntax for “five minutes ago” is defined at Duration | js-joda.

If you want to schedule a Timer to go off in three seconds:

actions.ScriptExecution.createTimer(time.toZDT('P3S'), () => ...

There is also an isClose() method which returns true if the ZonedDateTime is within a Duration of a passed in time. So if you wanted to see if now is within five minutes of the state of a DateTime Item’s state:

if(time.toZDT().isClose(items.getItem('MyTime').rawState.getZonedDateTime(), time.Duration.ofMinutes(5))) 

Date Times are a pain to work with and I wanted to provide some utilities that cover most of the common use cases

Rich,
Thanks for the info.
Can I bother you one more minute please?

I get the error:
2022-05-24 17:00:25.507 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID ‘default-13’ failed: An error occurred during the script execution: Could not invoke method: java.time.LocalDate.of(int,java.time.Month,int) on instance: null in default

This error relates to one of the following two statements with LocalDate.of(… :

//Vacation dates
if((LocalDate.now().isAfter(LocalDate.of(vacYrOn,vacMoOn,vacDyOn)))
&& (LocalDate.now().isBefore(LocalDate.of(vacYrOff,vacMoOff,vacDyOff)))) {
Actions }
For testing the dates are:
//isAfter:
var Number vacYrOn = 2022
var Number vacMoOn = 05
var Number vacDyOn = 25
//isBefore:
var Number vacYrOff = 2022
var Number vacMoOff = 05
var Number vacDyOff = 27

//Summer Temperatures
if((LocalDate.now().isAfter(LocalDate.of(2022,05,15)))
&& (LocalDate.now().isBefore(LocalDate.of(2022,08,15)))) {
Actions }

Is there a hidden error here? For testing, one date is in the passed. Is that a problem for Java time? JODA does not care.

You are working with Java Classes and Objects. When in doubt, always refer to the JavaDocs. In this case we see that there are two LocalDate.of() methods. One takes three int arguments and the other one takes a java.time.Month as the second argument.

You are trying to pass three Number Objects to this function which probably has it confused. Use int instead of Number.

In Rules DSL it’s often best to never force the type of a variable until absolutely necessary. To do that here:

var vacYrOn = 2022
var vacMoOn = 05
var vacDyOn = 25

if((LocalDate.now().isAfter(LocalDate.of(vacYrOn.intValue,vacMoOn.intValue,vacDyOn.intValue)))

Rich,
Sorry again, but your suggested code generates the following Error:
2022-05-25 10:00:00.316 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID ‘default-13’ failed: An error occurred during the script execution: index=2, size=2 in default
I’m completely in the dark here, but hope you can shed some light?

I don’t think that’s coming from anything I wrote above. There is nothing referencing anything with an index in my code. That’s an error caused by trying to access a value in an array at an index that doesn’t exist (in this case the array is of size 2 but because arrays are 0 indexed there are no elements past index 1 and the code is trying to access index 2).

That has three parameters, so the last might be index 2. Check for variable name typos.

Rich,

So I tried the other method (2022,Month.MAY,25) and it works. No Errors!

A final stupid question: How do I now define Month.MAY as a string argument so I don’t have to edit the code itself. I have to date never used strings.

With this I think I’m ready to give OH3 a test run on my system.
So, thanks again for your expert help!!!
Take care.

You don’t. LocalDate requires either an int or a java.time.Month. You can’t just replace that with a String. The creator of the function being called gets to decide the types of the arguments that get passed to it, not the person calling the function. So you’d have to convert the String to a Month. Looking at the JavaDoc there appears to be a function for that.

But it should work with the three int arguments too so there is, as @rossko57 points out, probably a typo somewhere in the names of the arguments or a missing comma or the like.

There were no typos.
If you can explanation why 3 x int does not work I would rather work with integers than text.
Thanks.

First, It’s a long shot but var vacMoOn = 05 might be the problem with the int version. I’m not sure what Rules DSL does with that leading zero.

This is really become an XY Problem I think.

In general, I hate this type of code. It’s why I created the Time of Day design pattern and rule template, the Alarm Clock rule template, and added all those time utils to the JS Scripting helper library. Directly working with date times is really complex.

A really simple way to replace this whole mess would be as follows:

  1. Create a String Item, call it “Season” or something else appropriate.

  2. Create a rule with a cron trigger for May 15th and command Season with “SUMMER”

  3. Create similar rules for all the other dates/seasons you care about (e.g. one for August 15th that sets “Season” to “AUTUM” and o on.

  4. Now in your rule it’s as simple as

    if(Season.state == “SUMMER”) {

Easy peasy with out needing to mess with Date Times at all. You don’t even need to write code for the rules since these are simple enough to use simple UI rules to implement. The code that uses it is also way easier to read and understand.

If you want to follow the seasons of the year exactly there is even a Channel in the Astro Binding you can use. You don’t even need the rules.

Notice that we don’t mess with the year. That’s because we don’t want to have to change the code every new year to keep this code working.

In an openHAB context, I’ve never seen the utility of using LocalDate, LocalTime, or LocalDateTime. Everything that needs to interact with OH will need a ZonedDateTime anyway so I find it easier to deal with ZonedDateTimes in those rare cases where I need to mess with the actual date times directly. So if I wanted to see if now was between two dates and I didn’t want to set up a state machine for that (i.e. that Season Item from above) I would do the following.

    if(now.isAfter(now.withMonth(5).withDay(15)) && now.isBefore(now.withMonth(8).withDay(15))) {

We don’t mess with the year because we want it to work next year too. In Rules DSL we already have ZonedDateTime.now() “aliased” to just now so why add all that extra stuff just to work with Local?

In JS Scripting that would look something like

  if(time.toZDT().isBetweenTimes(time.toZDT().withMonth(5).withDay(15), time.toZDT().withMonth(8).withDay(15)) {

But even that pails in the simplicity and understandability of

if(Season.state == "SUMMER"){

Thanks Rich,

022-05-25 19:20:41.232 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID ‘default-1’ failed: ‘withDay’ is not a member of ‘java.time.ZonedDateTime’; line 278, column 23, length 28 in default

Is there a typo in the above statement?