Convert uptime to date and time

Yes, I want as I said in the previous post is, starting from an uptime in seconds by value XXXXXXXXX sg, convert it to an easily understandable value, it does not matter if it is with Mo+Days+Hours+Minutes+Seconds format or YYYY-MM-DD hh.mm.ss. I don’t know if I have explained myself well.

I understand the explanation, clarified.

Uptime is one absolute value and must do not consider leap years or timezone,
is the number of the seconds that CPU is up, regardeless any other conventional date and time rapresentation.
No matter if are seconds or text like 97w4d08:54:05.
Is also why is writed 97w4d08:54:05 and not like 1y10m3w4d08:54:05, because months and years do not are univocal.

EDIT: Ops! I did not know the :totime function. Much simpler, thanks.


..

Even better, taking the functions you created for the Mikrotik Uptime, it can work for me as well and is quite accurate.

http://forum.mikrotik.com/t/convert-uptime-to-date-and-time/157724/35

Now it would look better:

{
:global tokenParser
:global unixtodatetime
:global currdatetimestr
:local xmlData ([toarray [/tool fetch mode=https url="http://192.168.8.1/api/device/information" as-value output=user]] ->"data")
:local smsUptime [($tokenParser->"getTag") source=$xmlData tag="uptime"];  # uptime value e.g.:  618694
:put [$unixtodatetime ([$currdatetimestr] - $smsUptime)]
}
Result:  03/02/2023 09:40:27
(the date format I have modified for my internal use)

BR.

Is not my choice, but logical choice, represent date and time on logic way: on the left the higher values, like on numbers.
(assuming 3 is day, and 2 the month)
The “ISO” date 2023-02-03 (or without the “-” 20230203) is directly sortable,
any other format (regardless the separator is “/”, “.”, “-” or others, NOT… :laughing:

Hi @rextended!

With this "approximate" data, I think there is a small mismatch in your script, because there is something strange in the result:

Uptime: 00:25:39
Local Date: 28/02/2023 01:39:10

:put [$unixtodatetime ([$currdatetimestr] - [$timetoseconds [/system resource get uptime]])]

current output: 00/03/2023 01:13:39

The date should be: 28/02/2023 01:13:39

Can you check it? Thanks.

Ok, I found two bug, fixed.
Update previous script.

For curiosity, added this:
:if ($tmpsec < (86400 * $tmpbissex)) do={
:set yearStart ($yearStart - 1)
:set tmpdays ($tmpdays + 365)
}
When years are calculated, if are not passed already the number of bissextile days, is still the previous year…
And I can not know the bissexstile years passed until I do not calc the total number of years :laughing: ..

and added = after >
:local mnthStart 12 ; :while (($prMntDays->$mnthStart) >= tmpdays) do={:set mnthStart ($mnthStart - 1)}because not exist the month day “0”, must go on previous month.

I hope is not present other bugs.

The script works fine now, thanks!

BR.

Thanks to you.

Please, if you find a bug on any of my scripts, notify me.
Thanks.

Ciao rextended!

After all these years, I'm still using your wonderful scripts and I'm very happy with them (thanks). But RouterOS has changed a lot, and I'm optimizing the related scripts, especially those concerning Date/Time, for the latest RouterOS versions.

Regarding "datetime2epoch," I'd like to reduce it as much as possible to adapt it only to the current format, "2023-04-13 09:30:00." What do you think?

Original version by rextended (all versions)

:global datetime2epoch do={
    :local dtime [:tostr $1]
    /system clock
    :local cyear [get date] ; :if ($cyear ~ "....-..-..") do={:set cyear [:pick $cyear 0 4]} else={:set cyear [:pick $cyear 7 11]}
    :if (([:len $dtime] = 10) or ([:len $dtime] = 11)) do={:set dtime "$dtime 00:00:00"}
    :if ([:len $dtime] = 15) do={:set dtime "$[:pick $dtime 0 6]/$cyear $[:pick $dtime 7 15]"}
    :if ([:len $dtime] = 14) do={:set dtime "$cyear-$[:pick $dtime 0 5] $[:pick $dtime 6 14]"}
    :if ([:len $dtime] =  8) do={:set dtime "$[get date] $dtime"}
    :if ([:tostr $1] = "") do={:set dtime ("$[get date] $[get time]")}
    :local vdate [:pick $dtime 0 [:find $dtime " " -1]]
    :local vtime [:pick $dtime ([:find $dtime " " -1] + 1) [:len $dtime]]
    :local vgmt  [get gmt-offset]; :if ($vgmt > 0x7FFFFFFF) do={:set vgmt ($vgmt - 0x100000000)}
    :if ($vgmt < 0) do={:set vgmt ($vgmt * -1)}
    :local arrm  [:toarray "0,0,31,59,90,120,151,181,212,243,273,304,334"]
    :local vdoff [:toarray "0,4,5,7,8,10"]
    :local MM    [:pick $vdate ($vdoff->2) ($vdoff->3)]
    :local M     [:tonum $MM]
    :if ($vdate ~ ".../../....") do={
        :set vdoff [:toarray "7,11,1,3,4,6"]
        :set M     ([:find "xxanebarprayunulugepctovecANEBARPRAYUNULUGEPCTOVEC" [:pick $vdate ($vdoff->2) ($vdoff->3)] -1] / 2)
        :if ($M>12) do={:set M ($M - 12)}
    }
    :if ($vdate ~ "....-...-..") do={
        :set vdoff [:toarray "0,4,6,8,9,11"]
        :set M     ([:find "xxanebarprayunulugepctovecANEBARPRAYUNULUGEPCTOVEC" [:pick $vdate ($vdoff->2) ($vdoff->3)] -1] / 2)
        :if ($M>12) do={:set M ($M - 12)}
    }
    :local yyyy  [:pick $vdate ($vdoff->0) ($vdoff->1)] ; :if ((($yyyy - 1968) % 4) = 0) do={:set ($arrm->1) -1; :set ($arrm->2) 30}
    :local totd  ((($yyyy - 1970) * 365) + (($yyyy - 1968) / 4) + ($arrm->$M) + ([:pick $vdate ($vdoff->4) ($vdoff->5)] - 1))
    :return      (((((($totd * 24) + [:pick $vtime 0 2]) * 60) + [:pick $vtime 3 5]) * 60) + [:pick $vtime 6 8] - $vgmt)
}

Optimized version only for v7.10+ (Is that correct?)

:global datetime2epoch do={
    :local dtime [:tostr $1]
    /system clock
    :if ([:tostr $1] = "") do={:set dtime ("$[get date] $[get time]")}
    :local vdate [:pick $dtime 0 [:find $dtime " " -1]]
    :local vtime [:pick $dtime ([:find $dtime " " -1] + 1) [:len $dtime]]
    :local vgmt  [get gmt-offset]; :if ($vgmt > 0x7FFFFFFF) do={:set vgmt ($vgmt - 0x100000000)}
    :if ($vgmt < 0) do={:set vgmt ($vgmt * -1)}
    :local arrm  [:toarray "0,0,31,59,90,120,151,181,212,243,273,304,334"]
    :local vdoff [:toarray "0,4,5,7,8,10"]
    :local MM    [:pick $vdate ($vdoff->2) ($vdoff->3)]; :local M [:tonum $MM]
    :local yyyy  [:pick $vdate ($vdoff->0) ($vdoff->1)] ; :if ((($yyyy - 1968) % 4) = 0) do={:set ($arrm->1) -1; :set ($arrm->2) 30}
    :local totd  ((($yyyy - 1970) * 365) + (($yyyy - 1968) / 4) + ($arrm->$M) + ([:pick $vdate ($vdoff->4) ($vdoff->5)] - 1))
    :return      (((((($totd * 24) + [:pick $vtime 0 2]) * 60) + [:pick $vtime 3 5]) * 60) + [:pick $vtime 6 8] - $vgmt)
}

Cioa e Grazie,

In RouterOS v7 they implemented a lot of the features I wrote for v6... for example...

datetime2epoch: (from 7.16)
:put [:tonum [:totime "2023-04-13 09:30:00"]]

Does RouterOS script support floats ?
365.25 ? To get the leapday

The results match your script only if I set the time to UTC format (Spain, now is UTC+2). Example:

[admin@MikroTik] > :put [$datetime2epoch "2023-04-13 09:30:00"]
1681371000 <= Your script already takes daylight saving time into account.

[admin@MikroTik] > :put [:tonum [:totime "2023-04-13 09:30:00"]]
1681378200

[admin@MikroTik] > :put [:tonum [:totime "2023-04-13 07:30:00"]]
1681371000

Is this solution valid?

:global dt2epoch do={    
   :local dtime [:tostr $1]    
   :local vgmt  [/system clock get gmt-offset]    
   :return ([:tonum [:totime $dtime]] - $vgmt)
}

It does not. The "floating point numbers" will either be considered as an IP address or a time value (converted from seconds) depending on whether it's suitable to be an IPv4 address. You can try in the CLI:

:ping count=5 1.2
:put 123.456
:put [:typeof 123.45]
:put [:typeof 123.456]
:put [:typeof 1234.56]

Some controls should be left...
Called without parameters,
and a fix for some 32/64 bit errors on some devices with negative time zone... the result must be UTC evrytime.

:global datetime2epochV7 do={
    :local dtime [:tostr $1]
    /system clock
    :if ([:tostr $1] = "") do={:set dtime ("$[get date] $[get time]")}
    :local vgmt  [get gmt-offset]; :if ($vgmt > 0x7FFFFFFF) do={:set vgmt ($vgmt - 0x100000000)}
    :return ([:tonum [:totime $dtime]] - $vgmt)
}

And does this function have any abbreviated form (for v7.10+) using Mikrotik functions?

:global unixtodatetime do={
    :local ux [:tonum $1]
    :local Fzerofill do={:return [:pick (100 + $1) 1 3]}
    :local prMntDays [:toarray "0,0,31,59,90,120,151,181,212,243,273,304,334"]
    :local vgmt      [:tonum [/system clock get gmt-offset]]; :if ($vgmt > 0x7FFFFFFF) do={:set vgmt ($vgmt - 0x100000000)}
    :if ($vgmt < 0) do={:set vgmt ($vgmt * -1)}
    :local tzepoch   ($ux + $vgmt)
    :if ($tzepoch < 0) do={:set tzepoch 0} ; # unsupported negative unix epoch
    :local yearStart (1970 + ($tzepoch / 31536000))
    :local tmpbissex (($yearStart - 1968) / 4) ; :if ((($yearStart - 1968) % 4) = 0) do={:set ($prMntDays->1) -1 ; :set ($prMntDays->2) 30}
    :local tmpsec    ($tzepoch % 31536000)
    :local tmpdays   (($tmpsec / 86400) - $tmpbissex)
    :if (($tmpsec < (86400 * $tmpbissex)) and ((($yearStart - 1968) % 4) = 0)) do={
        :set tmpbissex ($tmpbissex - 1) ; :set ($prMntDays->1) 0 ; :set ($prMntDays->2) 31 ; :set tmpdays  ($tmpdays + 1)
    }
    :if ($tmpsec < (86400 * $tmpbissex)) do={:set yearStart ($yearStart - 1) ; :set tmpdays   ($tmpdays + 365)}
    :local mnthStart 12 ; :while (($prMntDays->$mnthStart) > $tmpdays) do={:set mnthStart ($mnthStart - 1)}
    :local dayStart  [$Fzerofill (($tmpdays + 1) - ($prMntDays->$mnthStart))]
    :local timeStart (00:00:00 + [:totime ($tmpsec % 86400)])
    :return "$yearStart/$[$Fzerofill $mnthStart]/$[$Fzerofill $dayStart] $timeStart"
}

Thank's!

Till now, not...

We'll wait for MikroTik to implement the ":todate" function :wink::slightly_smiling_face:

(not really... :todate = yyyy-mm-dd, and :todt = yyyy-mm-dd hh:mm:ss)

Or simply... If I'm the programmer...

>:put ("1970-01-01 00:00:00" + [:timestamp])
2026-07-30 12:36:00Z
>

>:put ("2026-07-30 14:36:00" + 2w)
2026-08-15 14:36:00
>

>:put ([:now] + 2d1h)
2026-08-01 16:36:00
>

You can use serialize to=json which converts basic types too.

So:

:put [:serialize to=json [:timestamp]]
"2026-08-06 01:15:53"

or even math on dates:

:put [:serialize to=json ([:timestamp] + 2w12h)]
"2026-08-20 13:18:29"

Perfect, I remembered there was something...

And the reverse also works, with the caveat that the quotes need be in the string to be valid JSON.

:put [:deserialize from=json "\"2026-01-01 01:01:01\""]
2922w01:01:01

On the orginal "uptime" to date/time in title...

:put "RouterOS booted on $[:serialize to=json ([:timestamp] - [/system/resource/get uptime])]"

RouterOS booted on "2026-07-27 18:21:22"

You'll note that the " " quote are part of the routeros string, so you have strip that if you care...

{ 
:local uptimestr [:serialize to=json ([:timestamp] - [/system/resource/get uptime])]
:put [:pick $uptimestr 1 ([:len $uptimestr]-1)] 
}

2026-07-27 18:21:23