Easy way to do "2hours ago"

I’m just looking if someone have some nice approach how to display difference between times in human readable “2hours ago” “10min ago” etc.

In my mind I do have something like rule which will do TIME - NOW and then to have some mapping javascript to convert it to “…xx ago” ?

does somebody have something already please? :slight_smile:

https://www.google.com/search?channel=fs&client=ubuntu&q=javascript+epoch+differnece+to+days+minutes+hours

You got an answer from @Wolfgang_S resolving the more obvious problem (counting the difference in time). I do “sense” that your question was intended more to the difference between states from now and some time ago. Please have a look into “historicState”.

i can google search and I indeed have javascripts which converts times already.

I was asking more about how folks out there are doing it in OH in some fashionable and convenient way. Just dont like to reinvent the wheel where not needed.

nope, i’m not reffering to that

I simply asked about how anybody (if someone) is doing something like “Motion in Workroom 2hours ago” instead of “Motion in Workroom 2021-05-05:21:23:213+2000”
My take would be to calculate diff from now and convert it via javascript, but if there is a better way in OH let’s share

Wouldn’t that be the historic state?

WorkroomMotion.historicState(now().minusHours(2))

What prevents you from doing this? This is the straightforward way.

I think it’s more like my Timestamp=blah, calculate how long ago that was in ‘friendly form’. That’s fairly trivial, but when to calculate is more of a challenge. In a UI for example, a more or less continuous update.

If you would have given that example in the first place…

I use a transformation script for displaying the uptime of my raspi running openHAB. The actual value shows something like: 54 day(s) 12 hours 41 minutes.
Can’t get a VPN to my home ATM (Bavaria seems to be on the outskirts of mobile connectivity :thinking:) in order to post the script, however the used math and code isn’t using any black magic.

Convert the timestamp to seconds, do now.toEpochSecond - timestamp to get the duration as a long int, then divide it by the appropriate factors to get days/hrs/min/sec

Got a connection, here is my script

(function(i) {
    if(isNaN(i)) return "NA";
    var days = Math.floor(i/1440)
    var hours = Math.floor((i-days*1440)/60);
    var min = Math.floor((i-hours*60-days*1440)/1);
    var pad = "00";
    return days+ " Tag(e) " +hours+" Std "+min+" Min";
})(input)

Historic state gives you another timestamp, so that’s not the case in my opinion either.
I’m doing it as follows (let me know if that’s completely stupid way and there is better one :slight_smile: )

example of item:

Group gMotion_Entrance "Motion" <none> (GF_Entrance) ["MotionDetector"]
    Switch      Entrance_Motion     "Entrance Motion [MAP(motion.map):%s]"      <motion>    (gSecurity, gMotion, gMotion_Entrance)     ["Status", "Presence"]  { channel = "mqtt:topic:sensors_motion:entrance" }
    DateTime    Entrance_Motion_t   "Last Activity [%1$tA %1$tT]"               <time>      (gMotion_Entrance)              ["Status", "Timestamp"] { channel = "mqtt:topic:sensors_motion:entrance"[profile="timestamp-update"] }
    Number      Entrance_Motion_d   "Activity [JS(agodiff.js):%s]"              <time>

then I’ve created a 30s triggered rule

rule "Helpers: Calculate moveme/contacts ago value"
when
  Time cron "0/30 * * ? * * *"
then
    val newnow = now.toInstant.toEpochMilli

    // loop for all items in the group
    gSecurity.members.forEach[ ItemName |
        var Number diff = 0
        var Number prev = 0
        
        val orItem = ScriptServiceUtil.getItemRegistry.getItem(ItemName.name)
        val myItem = ScriptServiceUtil.getItemRegistry.getItem(orItem.name + "_t") as GenericItem
        if(myItem.state == NULL){
          diff = newnow - now.withYear(2020).minusMinutes(10).toInstant.toEpochMilli
        }else{
          prev = (myItem.state as DateTimeType).zonedDateTime.toInstant.toEpochMilli
          diff = newnow - prev
        }

        postUpdate(orItem.name+'_d',diff.toString)
    ]
end

and then I have map script

(function(i) {
    if (i == 'NULL') { return i; }
    if (i == '-') { return 'Unknown'; }

    var msPerMinute = 60 * 1000;
    var msPerHour = msPerMinute * 60;
    var msPerDay = msPerHour * 24;
    var msPerMonth = msPerDay * 30;
    var msPerYear = msPerDay * 365;

    var elapsed = i;

    if (elapsed == 0){
        return 'Now';
    }
    else if (elapsed < msPerMinute) {
         return Math.round(elapsed/1000) + ' s ago';   
    }
    else if (elapsed < msPerHour) {
         return Math.round(elapsed/msPerMinute) + ' min ago';   
    }
    else if (elapsed < msPerDay ) {
         return Math.round(elapsed/msPerHour ) + ' h ago';   
    }
    else if (elapsed < msPerMonth) {
        return Math.round(elapsed/msPerDay) + ' days ago';   
    }
    else if (elapsed < msPerYear) {
        return Math.round(elapsed/msPerMonth) + ' months ago';   
    }
    else {
        return Math.round(elapsed/msPerYear ) + ' year ago';   
    }

})(input)

on the sitemap I’m displaying *_d item, which is being populated every 30s for each item in the group.

and output is like this:

It feels bit over engineered, but works… so that’s why I asked if anyone has got a nicer solution :wink:

ok so i was not really happy to have another items and rules and stuff…

so i’ve reworked JS transformation so it’s now using original DateTime item which is updated by mqtt item automatically.

Only thing I needed to do is to convert OH timeformat to something JS can work with … not sure how to do it directly as
2021-05-13T11:47:26.209413+02:00 is not recognized in JS
2021-05-13T11:47:26Z+02:00 is

note: why we need to have miliseonds in datetime item?? (can i get rid of them?)

    DateTime    Contact_Window_GF_Bathroom_t "Activity [JS(ago.js):%s]"                 <time>      (gContacts_GF_Bathroom)                            ["Status","Timestamp"]  { channel="mqtt:topic:sensors_contacts:window_gfbathroom"[profile="timestamp-update"] }
(function(i) {
    if (i == 'NULL') { return i; }
    if (i == '-') { return 'Unknown'; }

    var msPerMinute = 60 * 1000;
    var msPerHour = msPerMinute * 60;
    var msPerDay = msPerHour * 24;
    var msPerMonth = msPerDay * 30;
    var msPerYear = msPerDay * 365;

    i = i.replace(/\..*\+/, 'Z+')
    var time = new Date(i);
    var timestamp = time.getTime();

    var now = Date.now();
    var elapsed = now - timestamp;

    if (elapsed < 30000){
        return 'Now';
    }
    else if (elapsed < msPerMinute) {
         return Math.round(elapsed/1000) + 's ago';   
    }
    else if (elapsed < msPerHour) {
         return Math.round(elapsed/msPerMinute) + 'm ago';   
    }
    else if (elapsed < msPerDay ) {
         return Math.round(elapsed/msPerHour ) + 'h ago';   
    }
    else if (elapsed < msPerMonth) {
        return Math.round(elapsed/msPerDay) + 'd ago';   
    }
    else if (elapsed < msPerYear) {
        return Math.round(elapsed/msPerMonth) + ' months ago';   
    }
    else {
        return Math.round(elapsed/msPerYear ) + ' year ago';   
    }

})(input)

Who cares? Just don’t display them.

well for example javascript cares … :wink: so i need to do bit of transformation

i = i.replace(/\..*\+/, 'Z+')
    var time = new Date(i);

which makes this 2021-05-13T11:47:26.209413+02:00 onto this 2021-05-13T11:47:26Z+02:00

but maybe JS can handle that first format somehow as well, I’m not so great javascript programmer :wink:

I wanted to do this in a DSL rule so inspired by this thread (thanks everyone!) and a Stack Exchange post here, https://stackoverflow.com/a/19667994

Here’s what I have running. Produces a human readable ‘since timestamp x…’ string which is used to get Alexa to announce a status.

Any comments very welcome!

var openSince = (myItem.state as DateTimeType).zonedDateTime.toInstant.toEpochMilli
var now = now.toInstant.toEpochMilli
var diff = now - openSince

var long seconds = diff / 1000;
var long minutes = seconds / 60;
var long hours = minutes / 60;
var long days = hours / 24;

var String daysLbl = if (days == 0 || days > 1) " days, " else " day, "
var String hoursLbl = if (hours == 0 || hours % 24 > 1) " hours, " else " hour, "
var String minutesLbl = if (minutes == 0 || minutes % 60 > 1) " minutes " else " minute "
var String secondsLbl = if (seconds == 0 || seconds % 60 > 1) " seconds" else " second"

var String timeDiffDetail = days + daysLbl + hours % 24 + hoursLbl + minutes % 60 + minutesLbl + "and " + seconds % 60 + secondsLbl

logInfo("TimeDiffTest", myItem.state.toString)
logInfo("TimeDiffTest", diff.toString)
logInfo("TimeDiffTest", timeDiffDetail)