isBefore no longer works?

2.5M5

one of my rules which worked now throws an error on isBefore
here is rule:

rule "WaspTimerExpired"
when
    Item BoxTimer changed from ON to OFF
then
    var tally = WaspConf.state as Number
    if (tally > 74) return;
    val openTime = new DateTimeType(LastBoxOpen.state.toString)
    val motionTime = new DateTimeType(LastBoxBuzz.state.toString)
    if (DaddysPhone.state = ON) {tally = (tally + 20)}
    if (DaddysPhone.state = OFF) {tally = (tally - 20)}
    if (openTime.isBefore(motionTime)) {tally = (tally + 60)}
    if (openTime.isAfter(motionTime)) {tally = (tally - 5)}
    if (tally >= 100) {WaspConf.sendCommand(100)}
    if (tally <= 0) {WaspConf.sendCommand(0)}
    if (tally > 0 && tally < 100) {WaspConf.sendCommand(tally)}
    if (tally < 75 && tally > 0) {BoxTimer.sendCommand(ON)}
end

here is error

18:45:48.189 [ERROR] [untime.internal.engine.RuleEngineImpl] - Rule 'WaspTimerExpired': 'isBefore' is not a member of 'org.eclipse.smarthome.core.library.types.DateTimeType'; line 41, column 9, length 29

is here in java docs
https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html

These two lines are incorrect:

    if (DaddysPhone.state = ON) {tally = (tally + 20)}
    if (DaddysPhone.state = OFF) {tally = (tally - 20)}

And should be:

    if (DaddysPhone.state == ON) {tally = (tally + 20)}
    if (DaddysPhone.state == OFF) {tally = (tally - 20)}

I don’t know about the isBefore sorry

I’m on openHAB 2.5.0~M5-1 on OpenHABian, and I’m not seeing this problem. Here’s my rule.

rule "Presence timer expired"
when
	Item PresenceTimer received command OFF
then
	logInfo("WaspInBox.rules", "Presence timer expired.")
	var confidence = PresenceConfidence.state as Number

	if (confidence > 0 && confidence < 100) { // we haven't made up our mind yet

		val TimerExpired = new DateTime(LastTimerExpired.state.toString)
		val JonsPhoneLastSeen = new DateTime(JonsPhone_LastSeen.state.toString)
		val AlsPhoneLastSeen = new DateTime(AlsPhone_LastSeen.state.toString)

		// Check if a phone was seen since the last timer expired
		if (TimerExpired.isBefore(JonsPhoneLastSeen) || TimerExpired.isBefore(AlsPhoneLastSeen)) { confidence = confidence + 20 }
		else { confidence = confidence - 5 }

		if (confidence >= 100) { confidence = 100 }
		else if (confidence <= 0) { confidence = 0 }
		PresenceTimer.sendCommand(ON)
		PresenceConfidence.sendCommand(confidence)
	}
	var time_now = new DateTimeType(now.toString) 
	LastTimerExpired.sendCommand(time_now)
end

My Java version:

openjdk version "1.8.0_232"
OpenJDK Runtime Environment (Zulu 8.42.0.23-CA-linux64) (build 1.8.0_232-b18)
OpenJDK 64-Bit Server VM (Zulu 8.42.0.23-CA-linux64) (build 25.232-b18, mixed mode)

Thought you might be on to something Jon

 java -version
openjdk version "11.0.4" 2019-07-16
OpenJDK Runtime Environment (build 11.0.4+11-post-Ubuntu-1ubuntu218.04.3)
OpenJDK 64-Bit Server VM (build 11.0.4+11-post-Ubuntu-1ubuntu218.04.3, mixed mode, sharing)

so down graded using:

sudo apt-get install openjdk-8-jre

then

sudo update-alternatives --config java

which I got from here:

worked like a charm

java -version
openjdk version "1.8.0_222"
OpenJDK Runtime Environment (build 1.8.0_222-8u222-b10-1ubuntu1~18.04.1-b10)
OpenJDK 64-Bit Server VM (build 25.222-b10, mixed mode)

restarted OpenHAB… still no joy

19:30:13.155 [ERROR] [untime.internal.engine.RuleEngineImpl] - Rule 'WaspTimerExpired': 'isBefore' is not a member of 'org.eclipse.smarthome.core.library.types.DateTimeType'; line 41, column 9, length 29

restarted computer and still no joy
version of java is still

java -version
openjdk version "1.8.0_222"
OpenJDK Runtime Environment (build 1.8.0_222-8u222-b10-1ubuntu1~18.04.1-b10)
OpenJDK 64-Bit Server VM (build 25.222-b10, mixed mode)

good catch Vincent, didn’t throw any errors in vscode so I missed it. I had to stare at your two examples for a couple minutes to even see the difference :crazy_face:

Well, I can confirm that it’s working OK for me, so it’s not a 2.5M5 thing. I’m only guessing that it’s Java related. I note that I’m running Zulu java, rather than the default Ubuntu OpenJDK.

On a separate note, I have found using the on/off state for phone presence unreliable. I’m using iPhones and the network binding. I find that phone presence state is frequently “off”, even when the phone is present. I found it more reliable when using the phone “last seen” item.

… and just another guess: are your LastBoxOpen and LastBoxBuzz items defined as DateTime types and initialised somewhere? My phone things were configured through paper UI, but my timers are defined in .items files:

Switch      PresenceTimer           "timer used for wasp in box"        { expire="60s,command=OFF" }
Number      PresenceConfidence      "Wasp in Box confidence level"      <none>  (Home)
DateTime    LastTimerExpired        "Last timer expired at [%1$tI:%1$tM %1$Tp]"     <none>  (Home)

and my Wasp in Box rules initialise the items on startup.

rule "initializeSys"
when
    System started
then
	// initialize wasp in box unbound item values from null. Note the assumption is that no-one is
    // present, and let the house correct that by itself

	logInfo("WaspInBox.rules", "Wasp in Box items initialised.")
	PresenceConfidence.postUpdate(50)
	PresenceTimer.postUpdate(OFF)
	var time_now = new DateTimeType(now.toString)
	LastTimerExpired.sendCommand(time_now)
	JonsPhone_LastSeen.sendCommand(time_now)
	AlsPhone_LastSeen.sendCommand(time_now)
end

I recently discovered that if OpenHAB restarts when one of the phones isn’t present, and without the LastSeen item initialised (and it was therefore NULL) my presence rules were broken.

no iphone, works fine for me

items

DateTime LastBoxOpen "last time door opened" <none> (gHome, gPersist)
DateTime LastBoxBuzz "last time motion detected" <none> (gHome)

initialization rule

//Wasp start up rule
// initialize wasp in box unbound item values from null
rule "WaspSystemStart"
when
    System started
then
    WaspInBox.postUpdate(OFF)
    WaspConf.postUpdate(0)
    LastBoxOpen.postUpdate(new DateTimeType(now.toString))
    LastBoxBuzz.postUpdate(new DateTimeType(now.toString))
    BoxTimer.postUpdate(OFF)
end

you do release this is my thread right :wink:


thanks for your help Jon. I was just starting clean to test 2.5M5 and copying my rules over from the old install.

well changed the rule to this instead and works with no error

    //val openTime = new DateTimeType(LastBoxOpen.state.toString)
    val openTime = (LastBoxOpen.state as DateTimeType).zonedDateTime.toInstant.toEpochMilli
    ///val motionTime = new DateTimeType(LastBoxBuzz.state.toString)
    val motionTime = (LastBoxBuzz.state as DateTimeType).zonedDateTime.toInstant.toEpochMilli
    //if (openTime.isBefore(motionTime)) {tally = (tally + 60)}
    if (openTime < motionTime) {tally = (tally + 60)}
    //if (openTime.isAfter(motionTime)) {tally = (tally - 5)}

that was my old version before rud and rich helped me in this thread


which I used to develop this rule example

isBefore() and isAfter() are not methods of org.eclipse.smarthome.core.library.types.DateTimeType, but they are methods of org.joda.time.DateTime. Your issue should be solved with this change…

val openTime = new DateTime(LastBoxOpen.state.toString)
val motionTime = new DateTime(LastBoxBuzz.state.toString)

There have been some recent PRs for DateTimeType that may have changed it’s behavior in the rules DSL. I doubt that the functionality that previously worked for you (using DateTimeType as joda.time.DateTime) would be something that would be restored, since it was not intended functionality and so unlikely to be considered a regression. But you may want to create an issue to ask in GH. I’m not sure how many users will be affected by this, but to be safe, it might be something to consider adding to the update.lst. This is a good item to make note of in the 2.5M5 issues topic.

In that case, I’d rather not file an issue then. I’d rather figure out how to convert the time properly. You see the hack I’ve implemented in the post above yours to get my rules running again. Here is link to the thread where Rich taught me the hack. And the rule example in the post above yours also shows an example of using the same hack so that will need fixed.

Well, after looking at my rule I realized I was doing things a little wrong
My original line was as follows

val openTime = new DateTimeType(LastBoxOpen.state.toString)

according to Rud’s great time conversion thread, what I seem to be doing here is taking a OpenHAB item (a DateTimeType) and converting it to a string to then create another OpenHAB DateTimeType from it??? Obviously stupid (as Scott pointed out)

So new version (just remove the Type from DateTimeType)

val openTime = new DateTime(LastBoxOpen.state.toString)

Converts OH item (DateTimeType) to a string and then from the string creates a Joda time which works with isBefore just fine. I’ll have to look at my old thread but I’m amazed my old version worked at all.
Is this still as you say Scott, unintended behavior? Is there a cleaner way of comparing OH DateTimeTypes or converting them to Joda?

Edit to add: This is really kind of crazy… When I convert the rule to use millis, I changed the comparison line to this because millis is just a number

if (openTime < motionTime) {tally = (tally + 60)}

And when I just now tested it with the Joda time… the above still worked???
No isBefore needed???

If in fact your rules were working the way you describe, my guess is that the DSL was able to figure out what you were trying to do and converted DateTimeType to DateTime. Yes, I’d call it unexpected behavior, but I’m tempted to test it out to see first hand. The DSL is very good at figuring out what types you are going after, but there must have been a change in OHC, if this no longer works for you.

The DateTime conversion topic should replace org.joda.time with pure Java, since Joda will be going bye-bye, unless you use scripted automation, where you can import 3rd party libs. This also lets you use core.date for conversions. If you really want to future-proof yourself, use Java libraries.

I don’t think we can/should do that until now is no longer a Joda DateTime and createTimer no longer requires a Joda DateTime. Most of the time one is working with a Joda DateTime it is in these situations where it’s still required. It’s bad enough that we have to have two DateTime types we have to use (I’m sure there is a reason, but why is DateTimeType not an instance of Joda DateTime in the first place, then there’d be no need to convert anything) I’m hesitant to bring in a third approach. I’d much rather provide “Change the joda DateTimes to Java DateTime using these steps” in a migration tutorial and update the official docs for OH 3 to drop Joda at that time. Doing it now will lead to complexity and confusion.

I tend to agree with this, but with OH3 approaching, I feel it is time for us to change tacks and start prepping the documentation and socializing the change in the forum.

1 Like

2.5 release isn’t even out yet. If we stick to the same release schedule OH3 is still six months away, though based on the experience with the 2.0 release I’m skeptical we can stick to that release cycle. So it is approaching but it’s not like we are mere weeks or even a few months away.

And we don’t know if the devs will do something to make the transition from Joda to Java DateTime easier or just let it break everyone’s Rules (this was attempted at some point and we discovered that Java is not a drop in replacement for Joda and it broke Rules). I could see (and would absolutely love) that everything get’s normalized on DateTimeType so there is no longer a need to do any of these conversions in the first place. (I’ve added a comment asking about this to the Issue).

I don’t think we know enough yet about what the impact to users for this change will be so it’s hard to advise changing how it’s currently done. At least not in the official docs for sure. When we know more I’ll be on board but I still think it’s premature to modify the official docs or to recommend changing how we do things with DateTime. I’d wait for the PR to be submitted for review before doing that.

As unexperienced as I am in these matters, I have a gut feeling that a transition period would be very helpful…? Because porting won’t happen overnight. For myself I can say that I have a very varying time budget I can put in this hobby. This may be 0 for months and then again larger at some other time… That’s why 6 months may not be that much…
But of course the target setup should be available for that.

Don’t worry Alex, you will have plenty of time to transition.

I think this is how you do it using a Java ZonedDateTime:

rule "WaspTimerExpired"
when
    Item BoxTimer changed from ON to OFF
then
    var tally = WaspConf.state as Number
    if (tally > 74) return;
    val openTime = (LastBoxOpen.state as DateTimeType).zonedDateTime
    val motionTime = (LastBoxBuzz.state as DateTimeType).zonedDateTime
    if (DaddysPhone.state = ON) {tally = (tally + 20)}
    if (DaddysPhone.state = OFF) {tally = (tally - 20)}
    if (openTime.isBefore(motionTime)) {tally = (tally + 60)}
    if (openTime.isAfter(motionTime)) {tally = (tally - 5)}
    if (tally >= 100) {WaspConf.sendCommand(100)}
    if (tally <= 0) {WaspConf.sendCommand(0)}
    if (tally > 0 && tally < 100) {WaspConf.sendCommand(tally)}
    if (tally < 75 && tally > 0) {BoxTimer.sendCommand(ON)}
end

or if you prefer using getStateAs use:

val openTime = LastBoxOpen.getStateAs(DateTimeType).zonedDateTime

No need for now or toString conversions, this should also work:

var time_now = new DateTimeType()
2 Likes

nice Wouter, thank you!