OH 5 DateTime Conversion Graphic

Hello Everyone,

I need to start by saying that this is a hobby for me. I don’t code as a profession and OH has been a fun way to learn and play. There is a post that explains DateTime Conversions very well HERE. 4.3 logs will show a deprecated error in one of my rules that calculates runtime of a duct booster regarding zonedDateTime. I was able to find the solution which from the original linked post’s graphic would be #8 that changed as shown below.

val Number MyEpochFromDateTimeTypeItem = (MyDateTimeTypeItem.state as DateTimeType).zonedDateTime.toInstant.toEpochMilli
val Number MyEpochFromDateTimeTypeItem = (MyDateTimeTypeItem.state as DateTimeType).instant.toEpochMilli

My question is, now that OH 5 is out would it be worthwhile to remake that original post, but for v5? I would certainly do it, but given my skill level I feel it’s not my place. Is this needed? Is there someone willing to take this on if so?

Thanks!

It wouldn’t be the worst thing in the world to update that post for OH 5.

I will note that it almost always easier to say working with ZonedDateTime or LocalDateTime and Duration than it is to fall back to epoch and then converting from there. The code is almost always shorter and easier to read and understand. There are very few cases where conversion to epoch is the best approach.

Working with date times in all the other rules languages tend to be more straight forward, but it would probably be useful to show how all that looks in at least Blockly, JS, and jRuby.

You can always ask for help on the parts that you don’t know. It’s been many years since anyone has addressed that topic so I doubt there’s anyone else out there willing to volunteer.

Well, this is the code I was using. It works so I dont really need it changed other than learning a cleaner method. If I remember correctly this would persist in RRD, which is why I went the EpochMilli route. This is very much a hobby for me and I don’t get to play as much as I’d like to. I’m sure there is a much prettier way of doing it.

I hesitated to make the post as you and a handful of others whom are endlessly posting helpful responses don’t need another thing to do. To the purpose of assisting entry level folks like me though, I know I used the original graphic often. Maybe I’ll take a crack at it and will see what I can come up with.

rule "Calculate MBR Runtime"
when
   Item Rly_FF_MBR_DuctBooster_State changed
then
    //set now as starttime
    if(Rly_FF_MBR_DuctBooster_State.state == ON){
        Calc_FF_MBR_DuctBooster_Start.postUpdate(DateTimeType.valueOf(now.toLocalDateTime().toString()))
    }
    //set now as endtime and calculate runtime 
    if(Rly_FF_MBR_DuctBooster_State.state == OFF){
        Calc_FF_MBR_DuctBooster_Stop.postUpdate(DateTimeType.valueOf(now.toLocalDateTime().toString()))
        var MbrStartTime = (Calc_FF_MBR_DuctBooster_Start.state as DateTimeType).instant.toEpochMilli
        var MbrStopTime =  now.toInstant.toEpochMilli
        var MbrSecondsOn = (MbrStopTime - MbrStartTime) / 1000
        Calc_FF_MBR_DuctBooster_LastRun.postUpdate(MbrSecondsOn)
        if(Calc_FF_MBR_DuctBooster_Runtime.state == NULL){
            Calc_FF_MBR_DuctBooster_Runtime.postUpdate(MbrSecondsOn)
        }
        else{
            var MbrSecondsNowTotal = Calc_FF_MBR_DuctBooster_Runtime.state as Number + MbrSecondsOn
            Calc_FF_MBR_DuctBooster_Runtime.postUpdate(MbrSecondsNowTotal)
        }
    }
end

Converting to epoch and persisting in RRD4J are unrelated to each other. This code calculates the number of seconds between the ON and the OFF of a Switch Item. You can get the number of seconds between two timestamps in a more straight forward way.

Could be written as

        var MbrStartTime = (Calc_FF_MBR_DuctBooster_Start.state as DateTimeType).instant
        var MbrSecondsOn = Duration.between(now, MbrStartTime).seconds

I think the second options is better because it’s shorter, there are fewer conversion steps, and the operations used tell you what it’s doing. Notice how we get .instant from the Item but don’t need to do anything to now. Duration knows how to handle it so we don’t have to. And then instead of needing to convert from millis to seconds, we just ask for seconds.

That’s why I mention that converting to epoch is almost always unnecessary.

In JavaScript we don’t even need to get the instant from the Item.

  var MbrSecondsOn = time.Duration.between(time.toZDT(), time.toZDT(items.Calc_FF_MBR_DuctBooster_Start)).seconds();

I understood RRD only persisted numbers, right?

Sure but you don’t need epoch to get the number of seconds between two timestamps. Duration.between(now, MbrStartTime).seconds returns a number.

1 Like
Duration.between(now, MbrStartTime).seconds

Causes this error?

Script execution of rule with UID 'DuctBooster-5' failed: Unable to obtain ZonedDateTime from TemporalAccessor: 2025-07-26T00:53:40.955309299Z of type java.time.Instant in DuctBooster
Duration.between(Instant.now, MbrStartTime).seconds

Would this be correct? It works and doesn’t throw any errors and is the best I could come up with…

That’s weird. The docs say Duration takes two Temporals and a ZonedDateTime is a Temporal, as is instant. Maybe it requires them both to be the same type (e.g. both Instants or both ZonedDateTimes) but that would be an odd requirement.

Though it might be in weekend an an Instant isn’t a Temporal. I’m that case there must be another berween function that wheels with Instants.

The following should work also I think. Instead of getting the instant from the DateTimeType, get the ZonedDateTime and then both operands will be ZDTs.