Days between dates

I would like to calculate the days between a fixed date and today.
Something like

rule “Time difference in day”
when
Time cron “0 1 1 * * ?”
then
var DateTime deadline = parse(“2017-03-30T16:00”)
var DateTime mitternacht = parse(now.getYear +"-"+now.getMonthOfYear+"-"+now.getDayOfMonth+“T0:00”)
var Integer dif_days
dif_days= (deadline.getTime()-mitternacht.getTime())/1000
end

Can someone provide code which is working?

What is it you are doing with dif_days? Do you do something different after a certain number of days have passed?

I ask because you don’t necessarily need to calculate the number of days at all in that case. You can use something like:

if(now.minusDays(days).isAfter(deadline)) {
    // do something
}

I want to realize a count down timer, that all.
regards Martin

There are probably a dozen ways to do it. Assuming you are using joda DateTime, personally I would get the times in milliseconds, subtract, and divide to get the number of days. As an aside, now is already a joda DateTime so you don’t have to parse it to get mitternacht.

val Integer dif_days = Math::round((deadline.calendar.millis - now.millis) / (86400000.0))

@Rich,

Thank you very much for this great tip.

I have include the import statements
import org.joda.time.*
import java.lang.Double
import java.lang.Math.*
import java.util.Date

potentially I forget some further imports

Designer is flagging calendar.millis as error." Cannot resolve referrence to … element"
I have to try whether it is working anyway.

I have meanwhile tested the proposal and it is failing due to an error that correponses to the designer syntax check error, meaning it cannot resolve “calendar”

with a lot of fiddling arround due to missing documenation and also my unability to understand the openhab classes, I came up with a solution.
If someone is interested:

var Date heute = new Date()
var Date deadline = new Date()
var SimpleDateFormat df = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss.S”)
deadline = df.parse(“2017-03-30 16:00:00.000”)
var dif_days = Math::round((deadline.getTime() - heute.getTime() )/ 86400000.0)

or:

var DateTime d1=parse("2017-03-30T16:00:00")
var dif_days =Math::round((d1.millis-now.millis)/86400000)

or 2nd line even shorter (lazy…):

var long dif_days =(d1.millis-now.millis)/86400000 // 86400000 = 24*60*60*1000

:wink:

3 Likes

Hi Udo,

it is strange, allthough it is seems to work, I am getting an error
something like
" … cannot cast BigDecimal to double"

I really do not understand those class types and the required conversation.
This is driving me nuts. Is there anywhere a decent documentation on this?

the cast-error is very strange, because I’ve tested and no error occured…:confused:
I’m struggling with types, too, but more or less Java seems to suit…

Maybe there is missing some import… I have
import org.openhab.core.library.types.* import org.openhab.model.script.actions.* import org.openhab.core.types.Command import java.lang.Math import java.util.Date import java.util.Calendar import java.util.GregorianCalendar import org.joda.time.DateTime
but I don’t know exactly, which ones are needed.

OK, that means that the DateTime you are using is not the joda date time. As you discovered, it is a matter of finding which method to call on whatever DateTime object you are using to get at the epoc time which, as you discovered, apparently getTime.

A trick to use in Designer to discover what methods are available is to type out the name of your variable, the ‘.’ and then press <ctrl><space>. This will bring up a dialog showing you every valid way to complete your statement. It is a great way to discover all the methods that are on a given object.

Unless you explicitly declare variables as double, Xtend (the programming language of rules) will treat all numbers as BigDecimal. When you see that error just call .doubleValue and it will give your the double instead of the BigDecimal.

Hi Rick,
Thanks for this valuable feedback and tips. This helped a lot.

Especially the trick with the designer displaying all the methods when pressing is really useful.

regards and great support Martin

Hi Rick,

You seem to be pretty knowledge about the “secrets” behind the DSL and the overall class definition and your previous advise help me quite a lot.
It would be great if you could answer another questions or at least share some thoughts.
Maybe we could also in the future create or extend the Openhab Wiki about the classes in openhab and their interplay with existing java Classes.

I never understood the reasons why there is a var type Number.
What is the difference to double?
Why was this type introduced anyway?
Also confusing for me, that there is also an Item type that is called Number.
I never understood why it makes sense to define an Item type with the same name as a variable. Why not clearly distingush between a “Number” which is a variable and maybe a “Number_item” which is an Item?
Also in rules, if itemnumber is Number Item and d is a Number variable
I have to use the statement:
“d = itemnumber as DecimalType” to get the contents of the Item into a Number variable.
I find this totally confusing. I would understand a notion
“d = itemnumber.Number” would make a lot of more sense.
on the other hand, if d is an Integer.
“d = itemnumber as Integer” is not working.

And there are further examples like the DateTime Type which is pretty much the same as Date.

Also some language constructs are not clear.
The repeating problem I have is that I am looking for functions not being defined in the various DSL examples. Since the DSL somehow is based on Java, I am searching for Java language examples. The problem is, that the code and functions cannot be used one to one but have to be syntacially “translated” into the DSL. And this translation is different and it is super hard to find the right syntax.
At some occasions Java language constructs can be used by using “::”.
f.e. Math::round(), on other occasions I have to use constructs like
birthday = transform(“MAP”, “birthday.map”,searchday).

Probably I am asking questions which are totally clear for a Java programmer but for me they are definitely not clear and since I am having new ideas to enhance my setup, I would find it worthwhile understanding the logic.
Also from seeing the various requests in this forum, I do see the need for others as well.
Do you have any idea how to deduce the logic behind this?

Please do not point to the infamous XText documentation, I have failed to understand this already before.

kind regards

Martin

1 Like

tl;dr: From the questions you either have no experience with programming or at a minimum have no experience with Java nor OO programming. You would greatly benefit from learning more about Java to give you a better foundation to understand OO concepts and how to go about discovering how and why you need to do certain things in a certain way. The DSL is built on the same predecessor as Xtend which, while built on Java is not Java. But most of your problems stem from not understanding OO which would be better learned by learning Java.

The MIT open courseware into to programming could be beneficial as well. They use Python but the same OO issues are discussed. And should you choose, you can program your rules in Python with the JSR233 script engine.

The openHAB developers have done a lot to make rules development as easy as possible, but rules development is still programming and it requires basic programming knowledge to get good at developing new rules and understand why and how you have to do things in certain ways.

All of the confusing inconsistencies you identified are there for very good and valid reasons and in a lot of cases are only inconsistencies because of a lack of understanding how openHAB works and general Object Oriented programming concepts.

Below would probably be covered in the first few weeks of a college level programming class. I’m just skimming and leaving a whole lot out.

That would be much too big of a job. Such an expansion would require thousands of pages of documents which really amount to duplication of stuff that is already documented elsewhere. Your best bet is to refer to the JavaDoc for a given class. You can almost always find them by searching for the fully qualified class name. For example searching Google for “org.joda.time.DateTime” will give you the JavaDoc for that class as the first result.

The only missing part is sometimes the documentation for the openHAB classes themselves can be a little harder to come by. For that you need to search the github repo and look at the actual source code if you can’t figure out what you need to using Designer and <ctrl><space>.

At the end of the day, having a little bit of experience programming in Java will be invaluable so if you don’t have any knowledge of Java, it would be a great benefit to you to go through a few tutorials until you get familiar with how Java works.

Java, Xtend, and the openHAB DLS are Object Oriented (OO) programming languages. In OO you have a hierarchy of definitions that go from the more generic to the more specific. These definitions are called classes. An instance of a class is called an object. For example, the class hierarchy for java.lang.Double is:

java.lang.Object
    java.lang.Number
        java.lang.Double

So this means that a Double is a Number and a Double is an Object. Number is also an Object. So anything you can do with an Object (the same methods and data members) you can also do with a Number or with a Double.

This can be advantageous because it allows you to create generic code that works at a higher level instead of needing to create separate copies of code that does the same thing for each of the lower level items. For example, you can create a method that operates on Number which will work for all Double, Float, Long, Integer, and Short instances instead of creating five different copies of this same method, one for each of the lower level class types.

Now the lowercase numbers (double, float, long, int, short) are called primitives. Primitives exist apart from the class hierarchy and it was an unfortunate design decision made when Java was first created. A “double” is a primitive floating point number which you can do math on but because it is a primitive it has no methods or data members (i.e. you can’t do something like .toString on a double). The capitalized numbers (Double, Float, etc.) are class types that let you do extra stuff that the primitives don’t allow, such as converting from one type to another, parsing a String to a double, etc.

What gets tricky is that Java and even more so Xtend does a lot of magic behind the scenes to convert things from one type to another for you. So if you have two Doubles that you want to add together, it will automatically cast them to doubles, do the math, and then give you a BigDecimal as the result (BigDecimal is an object that is able to more accurately store and represent large floating point numbers).

One of the problems with the openHAB DSL is, in my opinion, that this automatic conversion of types does not work as expected (e.g. why the heck does it give me a BigDecimal when I add two Doubles? I expect it to give me a Double or a double as the result). This causes a lot of confusion. But there is a reason why they give you BigDecimal in this case. BigDecimal is able to represent any type of number (double, int, short, etc) so the DSL by default uses BigDecimal so it can treat all numbers the same. They chose BigDecimal because it has more useful capabilities than Number does.

There are three separate contexts in openHAB: items, sitemap, and rules. Each of these contexts have different rules, different syntax, different naming conventions, and ways to define things.

So, if you are in a .items file you can define a Number item. If want to put that item on your sitemap you would use a Text field and use the string formatter to control how it appears (i.e. the stull in [] that is part of the label). When you are in a rule, a Number item is an instance that is of the class NumberItem. The class hierarchy for a NumberItem is:

java.lang.Object
    org.openhab.core.items.Item
        org.openhab.core.items.GenericItem
            org.openhab.core.library.items.NumberItem

As you can see, there is nothing in common between java.lang.Number and org.openhab.core.library.items.NumberItem, not even the name (in the Rules context). You can’t do math with a NumberItem. NumberItem, like every other org.openhab.core.items.GenericItem carries an org.openhab.core.types.State and what makes NumberItem different from, for example SwitchItem or ContactItem is that it carries a State that is of type DecimalType. You can get the primitive value (lower case double, float, etc) stored by DecimalType by calling one of the “value” methods (e.g. doubleValue) that only exist for DecimalType, and do not exist on State.

So to answer your questions directly:

  • The Item Number is only named Number in the context of the items file. It has a different name in the rules context
  • Number is not a variable, it is a class which can represent any numerical value.
  • They do clearly distinguish between java.lang.Number and org.openhab.core.library.item.NumberItem in the rules context. But the piece you were missing is that the items context and the rules context are completely separate, operating under different rules and different naming conventions and in the rules context a Number Item is called NumberItem.

As a developer I understand why and how this happened. And there is a balancing act that has to be made. Do you impose a burden on the creators of Items files by using less intuitive names for Item types that may share the same name of something that means something else in the rules context, or do you use the intuitive name and potentially confuse those users who don’t understand that the contexts for items and rules (and the sitemap) are completely separate. Developers are REALLY good at switching between contexts like this so they opted to make the Items context easier over providing consistency across contexts.

So, as discussed above, a NumberItem has nothing in common with a Number. It carries a State and that State carries the value. This is why to get the value from a NumberItem so you can do math with it or whatever you have to call:

val int d = (myNumberItem.state as DecimalType).intValue

What’s the deal with that “as DecimalType”? Well, as discussed above, you can operate on an object as if it were one of the classes higher up in its hierarchy. When you are referencing Items in your rules, you are referencing them as an org.openhab.core.items.Item. But this class is completely generic and when you call “.state” you get back an org.openhab.core.types.State instance. The State type doesn’t know anything about numbers so we need to cast it to a DecimalType which does. We know we can do this because we, the developer, knows that myNumberItem is a NumberItem which will have a DecimalType state. If myNumberItem were actually a SwitchItem we would get an error. This is what the “as DecimalType” part does. It lets us get at the methods and data members of the lower down more specific class.

So, why can’t you do “myNumberItem.state as Integer”? Because NumberItem carries a State that is of type DecimalType and DecimalType has nothing to do with Integer. They are completely different classes so you can’t treat one as the other.

The theme we keep coming back to is that you must understand what type/class everything is. Once you understand the type you can understand how to convert from one class to another, how to get at the data you want, and ultimately generate the code you want. Without that the code looks like arcane magical spells recited because they work but providing no ability to fix it if something goes wrong.

But it isn’t “pretty much the same”. The hierarchy for Date is:

java.lang.Object
    java.util.Date

The hierarchy for DateTime is:

java.lang.Object
    org.joda.time.base.AbstractInstant
        org.joda.time.base.AbstractDateTime
            org.joda.time.base.BaseDateTime
                org.joda.time.DateTime

Despite the fact that they do similar things (i.e. hold the date and time and let you manipulate it), they have nothing in common. So you have to go through gymnastics to convert one to the other and they are not compatible with each other. The developers of openHAB decided to use joda’s DateTime as the default way to deal with dates because it provides more capabilities than the Date built into Java. But because Xtend makes everything in Java available to you, you end up with both.

While I have been harping on understanding Java and pointing at Java examples, the DSL is NOT Java. It is not even Xtend, Xtext, or Xbase. To work in the DSL you need to know Java to understand what classes (technical term for a type) and capabilities are available to you and understand the OO nature of the language. But for syntax you need look at the Xtend documentation because the syntax is in a lot of cases vastly different. Java examples are unlikely to work because Xtend is a completely different language with different rules and forms. It is built on Java and all of the problems you listed above can be better understood if you know Java, but for syntax you have to look at the Xtend docs or the examples on the wiki.

Also, realize that not everything you can do in Xtend can be done in the DSL (e.g. you can’t create your own classes or define your own arrays).

In the DSL, to refer to static parts of a class you use “::”. To refer to dynamic parts of an Object you use “.”. When a programmer defines a type (i.e. a class) they can create certain parts of it as static. This means that it will always be there and always have the same value. And good coding practice is to access these members and fields by using the class name, not through an instance. So java.lang.Math is a class name and round is static so you have to refer to it using “Math::round”. But birthday is an instance of some class that defines a map method so you use “.”.

This is a tough concept to understand but it is an important one, and one you will be able to pick up if you learn some basic OO programming.

I do not know why Xtend, Xbase, or whatever requires this distinction (in Java you just use “.”). I’m sure there was a technical problem that this distinction solved.

Unfortunately openHAB makes the rules development just easy enough to make it possible for non-programmers to jump right in and start to develop working rules. This is both a positive and a negative. It is a positive because it gives newcomers a win and sense of accomplishment right away. It is a negative because it hides the fact that to really be successful at developing complex rules you must have some programming skills. Understanding Java is not required but you at least need to know and understand OO and be able to read examples and other documentation to figure out how to do more advanced things. Knowing Java can help a lot though.

And I will point you to the Xtend documentation because the next time you run into a syntax problem, that is the only place you will find the answer. But most of the problems sited above stem from not understanding the OO nature of the languages.

7 Likes

Hi Rick,

Thank you so much for taking your time to write this tutorial.
Yes, i am aware that I should learn Java.

But anyway your post helped a lot and gave valuable hints.

kind regards and again a big thanks

Martin

@rlkoshak, thank you very much for this, I’ll keep it bookmarked :ribbon:

So do I :slight_smile:

Thank you Udo for the fine solution.

I’m setting up a rule to trace in habpanel the waist pickup.

I’ve got one items that stores the days to the pickup “GiorniAlRitiro_Carta”
And this is my rule:

// CARTA
//add array
var DateTime carta1=parse("2020-11-03T08:00:00")
var long carta1dif_days =(carta1.millis-now.millis)/86400000 // 86400000 = 24*60*60*1000

logInfo("test", "Note in the log files, variable is: " + dif_days)
sendCommand(GiorniAlRitiro_Carta , carta1dif_days)

I will have lots of pickup date, so I need to setup a loop, the question is: how can I choose only the next pickup in the array?

For example: if I have these pickup dates:
2020-10-30
2020-12-01
2020-12-31
and, let’s say, today its the 20th of November, the code should do the action only on the second item of the array.

Sorry but I’m not a good sw developer

Well, I think you have to calculate the minimum above zero. Where do you get the pickup dates?

Hi, I’m trying to use this in a rule (OH 2.5)

var day = "2021-01-01"
var long dif_days =(day.millis-now.millis)/86400000

but I get this error
'millis' is not a member of 'java.lang.String'
Where am I wrong?
Thanks