How can I convert ESPHome uptime in seconds to days, hours, minutes and seconds?

I have a sensor that does uptime. That comes in seconds. But I would like to have it in a neat format, with number of days, hours, minutes and seconds. I have tried to start something, but it doesn’t work. This is the code for the root sensor, uptime:

 - platform: uptime
    name: "Oppetidssensor"
    type: seconds
    update_interval: 10s
    id: uptime_sensor
    entity_category: "diagnostic"
    internal: true

I can convert that to hours (which is wonky without decimals, because when it is over 30 minutes it shows the full hour):

  - platform: copy
    source_id: uptime_sensor
    name: "Oppetid i timer"
    filters:
      - lambda: return x / 3600.0;
    unit_of_measurement: " H"
    entity_category: "diagnostic"
    device_class: ""
    id: oppetiditimer

Same thing for minutes:

  - platform: copy
    source_id: uptime_sensor
    name: "Oppetid i minutter"
    filters:
      - lambda: return x / 60.0;
    unit_of_measurement: " m"
    entity_category: "diagnostic"
    device_class: ""
    id: oppetidiminutter

And I made a copy sensor for seconds as well to have something to work out from and keep the original sensor internal:

  - platform: copy
    source_id: uptime_sensor
    name: "Oppetid i sekunder"
    filters:
      - lambda: return x / 1.0;
    accuracy_decimals: 0
    unit_of_measurement: " s"
    entity_category: "diagnostic"
    device_class: ""
    id: oppetidisekunder

But when I try to do math with these, as a first step to remove the hours and minutes, so only the seconds that are left when the minutes and hours are subtracted, it doesn’t work:

  - platform: template
    id: oppetid
    update_interval: 20s
    lambda: 'return id(int(oppetidisekunder).state) - (id(int(oppetiditimer).state) * 360) - (id(int(oppetidiminutter).state) * 60);'

Compiling .pioenvs/hekk/src/main.cpp.o
/config/hekk.yaml: In lambda function:
/config/hekk.yaml:787:39: error: request for member 'state' in '(int)((esphome::copy::CopySensor*)oppetidisekunder)', which is of non-class type 'int'
  787 |     lambda: 'return id(int(oppetidisekunder).state) - (id(int(oppetiditimer).state) * 360) - (id(int(oppetidiminutter).state) * 60);'
      |                                       ^~~~~
/config/hekk.yaml:787:71: error: request for member 'state' in '(int)((esphome::copy::CopySensor*)oppetiditimer)', which is of non-class type 'int'
  787 |     lambda: 'return id(int(oppetidisekunder).state) - (id(int(oppetiditimer).state) * 360) - (id(int(oppetidiminutter).state) * 60);'
      |                                                                       ^~~~~
/config/hekk.yaml:787:113: error: request for member 'state' in '(int)((esphome::copy::CopySensor*)oppetidiminutter)', which is of non-class type 'int'
  787 |     lambda: 'return id(int(oppetidisekunder).state) - (id(int(oppetiditimer).state) * 360) - (id(int(oppetidiminutter).state) * 60);'
      |                                                                                                                 ^~~~~
*** [.pioenvs/hekk/src/main.cpp.o] Error 1
========================= [FAILED] Took 33.89 seconds =========================

I’m obviously doing it all wrong, but I can’t understand why, and I have no idea how to fix it. Can somebody please help me?

1 Like

Suggestion.
Create template text or something like it and have it set to now() when the ESP boots.
Then use relative_time in HA to get the time.
Having a sensor that keeps updating isn’t necessary.

Example:

{{ relative_time(strptime('2026-01-01T00:00:00Z', '%Y-%m-%dT%H:%M:%S%z')) }}
1 Like

Thanks for the suggestion, but that would mean creating one for each of my more than ten ESPHome devices. I was hoping for something that could be put into each of the ESPHome yaml’s and that I won’t have to change if I remove/rename/add another ESP. Also I’d like to have it work when I check the ESP’s webserver.

But if it isn’t possible, yeah, then I’ll have a look at doing it in Hass.

What is the use case?
Is it going to be used on the dashboard or just for occasional debugging?

1 Like

Did you see this?

If that doesn’t do what you want I’m sure you could copy out bits from the underlying code.

  // Calculate all time units
  unsigned seconds = uptime % 60;
  uptime /= 60;
  unsigned minutes = uptime % 60;
  uptime /= 60;
  unsigned hours = uptime % 24;
  uptime /= 24;
  unsigned days = uptime;

(id(int(oppetidiminutter).state)
^^At least one issue with that is the int position. At a minimum it should be on the outside. But I can’t recall exact syntax.

1 Like

You have the uptime sensor, which return the number of seconds.

It looks like you want the uptime text_sensor instead, which allows you to customise the display format somewhat.

1 Like

@Hellis81 For the dashboard, so I can see with a glance if any of the ESPs behave differently from the others, with reboots that I didn’t instigate. It’s in my boat, and even if I have extensive failsafe systems (one separate Pi with Node-RED that doubles the relay commands and a Phidgets board that doubles for the actual relays on the most important stuff, like mains power, sonar and lights) it can become a problem quite fast if one of the ESPs are wonky.

@Mahko_Mahko and @donburch888 That is exactly what I’m looking for, thank you! It’s almost perfect:

“Hekk” is the stern. I know it’s really only my OCD light talking (I have been working as a translator for 35 years, so I’m quite OCD about language in everything I program), but I’d love to remove the to (r)'s, meaning hour(s) and minute(s).

I couldn’t see anything in the sensor’s documentation that could make it possible to format so that I get “minute” and “hour” with one of each and “minutes” and “hours” when there are more. I just let the “sekunder”, “seconds” stay for now, since one second in the sensor would show very close to never. Is it possible, and in that case how?

1 Like

Since it is for the dashboard then a static sensor as I suggested before can work, if you add a markdown card with the relative time template.
And since it’s a template it’s easy to customize to what you want.

Yeah, but then I won’t be able to see it as easily if Home Assistant is down, on the webpage in the boat that connects to the ESP webservers.

That is true.

1 Like

Here’s what I’ve come up with. Based on a function I’ve found here in the forums, overengineered to add proper singular/plural distinctions. Only caveat so far is that there’s a leading space that isn’t easily removed because there’s no native trim() in C++, but that should not affect funtionality. To localize, change the const std::string values to your liking

text_sensor:
  # Send Uptime in human readable format
  - platform: template
    name: Uptime
    entity_category: "diagnostic"
    id: uptime_human
    icon: mdi:clock-start

sensor:
  # This is *the* OCD calming overkill version of turning the raw uptime sensor into a human readable form
  - platform: uptime
    name: Uptime
    id: uptime_internal
    internal: true
    entity_category: "diagnostic"
    update_interval: 5s
    on_raw_value:
      then:
        - text_sensor.template.publish:
            id: uptime_human
            # Custom C++ code to generate the result
            state: !lambda |-
              // define your localized strings here
              const std::string day_string = "day";
              const std::string hour_string = "hour";
              const std::string minute_string = "minute";
              const std::string second_string = "second";
              const std::string multiples_extension = "s";

              // the code itself
              int seconds = round(id(uptime_internal).get_raw_state());
              int days = seconds / (24 * 3600);
              seconds = seconds % (24 * 3600);
              int hours = seconds / 3600;
              seconds = seconds % 3600;
              int minutes = seconds / 60;
              seconds = seconds % 60;
              return (
                (days ? " " + to_string(days) + " " + day_string + (days == 1 ? "" : multiples_extension ) : "") +
                (hours ? " " + to_string(hours) + " " + hour_string + (hours == 1 ? "" : multiples_extension ) : "") +
                (minutes ? " " + to_string(minutes) + " " + minute_string + (minutes == 1 ? "" : multiples_extension ) : "") +
                (" " + to_string(seconds) + " " + second_string + (seconds == 1 ? "" : multiples_extension ))
              ).c_str();

Personally I would have just stuck with abbreviated units and then not sweated the plurals (personally I find the short form more legible), but well respect everyone’s right to overengineer a solution for their hobby and OCD;)

1d 2h 3m 4s

but this can already give built-in text sensor, right?
this:

text_sensor:
  - platform: uptime
    name: Uptime

will give:
Uptime: 1d5h15m

while somewhat modified:

text_sensor:
  - platform: uptime
    name: Uptime
    format:
      separator: ", "
      days: " dni"
      hours: " ur"
      minutes: " m"
      seconds: " s"

will give nicer look like (in slovenian language):
5 dni, 15ur, 16m

in order to get seconds shown (that’s sneaky one…) you must set update interval quicker than 30 seconds, so:

text_sensor:
  - platform: uptime
    name: Uptime
    update_interval: 29s #(or less)
    format:
      separator: ", "
      days: " dni"
      hours: " ur"
      minutes: " m"
      seconds: " s"

which will give:
5 dni, 15ur, 16m, 35s

Yes, the formatting of the uptime text_sensor is so limited, especially compared with the formatting available in C’s strftime functio (referenced in the time component).

I only have passing familiarity with C, so not sure whether you could use it in a lambda with your uptime sensor (the one in number of seconds) instead of time.now() which is used in the examples.

To get the plural for hours and minutes I think you would have to go with DukeSniper’s suggestion.

Yes that’s what I was basically hinting at.

You’ll see I pointed to the text sensor back here.

The built-in text sensor can’t do this, in Norwegian, with a comma after the hour and “og” (and) between minutes and seconds:

image

@DukeSniper I have changed your code a bit to get it to where I wanted it:

sensor:
 # Make uptime readable
  - platform: uptime
    name: Oppetidssensor
    id: oppetid_intern
    internal: true
    entity_category: "diagnostic"
    update_interval: 5s
    on_raw_value:
      then:
        - text_sensor.template.publish:
            id: oppetidssensor
            # Custom C++ code to generate the result
            state: !lambda |-
              // define your localized strings here
              const std::string day_string = "dag";
              const std::string hour_string = "time";
              const std::string minute_string = "minutt";
              const std::string second_string = "sekund";
              const std::string multiples_extension = "er";
              const std::string multiples_extension_hours = "r";

              // the code itself
              int seconds = round(id(oppetid_intern).get_raw_state());
              int days = seconds / (24 * 3600);
              seconds = seconds % (24 * 3600);
              int hours = seconds / 3600;
              seconds = seconds % 3600;
              int minutes = seconds / 60;
              seconds = seconds % 60;
              return (
                (days ? " " + to_string(days) + " " + day_string + (days == 1 ? "" : multiples_extension ) : "") +
                (hours ? " " + to_string(hours) + " " + hour_string + (hours == 1 ? "" : multiples_extension_hours ) + ", " : "") +
                (minutes ? " " + to_string(minutes) + " " + minute_string + (minutes == 1 ? "" : multiples_extension ) + " og": "") +
                (" " + to_string(seconds) + " " + second_string + (seconds == 1 ? "" : multiples_extension ))
              ).c_str();

text_sensor:
  - platform: template
    name: Oppetidssensor
    entity_category: "diagnostic"
    id: oppetidssensor
    icon: mdi:clock-start

I have so far implemented it in two ESPs and run into a very weird problem. Home Assistant uses the same uptime for both, the latest reboot it seems. Look at the difference between the stern ESP in the ESPHome web server and in Home Assistant (webpage seen through VNC server, it’s one screenshot so taken at exactly the same time):

The sensor “baug Oppetidssensor i sekunder” is the regular uptime sensor, in seconds, so I can have statistics, which of course isn’t possible with the formatted version. That’s identical too in Hass, but totally different in the webpage. Renaming the sensors adding “in bough” and “in stern” at the end fixed it. Still weird that Home Assistant couldn’t separate them, since the MQTT messages in were different:

madmax/baug/sensor/oppetidssensor_i_sekunder/state
madmax/hekk/sensor/oppetidssensor_i_sekunder/state

But it’s working out now. :grin:

OCD light satisfied! :joy:

can’t say for sure (didn’ try), but i assume it can this way:

  - platform: uptime
    name: Uptime
    update_interval: 29s
    format:
      separator: ", "
      hours: " time"
      minutes: " minutter og"
      seconds: " sekunder"

or without comma separator:

  - platform: uptime
    name: Uptime
    update_interval: 29s
    format:
      separator: " "
      hours: " time,"
      minutes: " minutter og"
      seconds: " sekunder"

But can it separate between “time” and “timer”, and “minutt” and “minutter” (singular and plural)? I didn’t get that impression.

Yeah, i know what you mean… i guess not, but it doesn’t really bother me, since i only look this sensor when something goes wrong, which is veeeery rarely…so it’s not worthed to complicate things (at least for me).
I guess you can “trick” it with short names, like 1d, 1h, 1m, 1s … :wink:

OCD light, remember… :laughing: