I did it! Script to compute UNIX time!

Do you find this script useful?

  • Yes
  • No
  • Maybe
  • I don’t know
0 voters

Hi there.
Since version 6.2 I try to make a script to compute the UNIX time (epoch time / posix time) from RB current date/time.
The script contains 2 functions: first one is JD which calculates Julian Days for specified date and the second one calculates the unix time based on difference between Julian Days and Julian date of ‘jan/01/1970’.
I guess is not flawless, but here it is :sunglasses: :

:global fncJD do={
:local months [:toarray "jan,feb,mar,apr,may,jun,jul,aug,sep,oct,nov,dec"];
:local jd
:local M [:pick $1 0 3];
:local D [:pick $1 4 6];
:local Y [:pick $1 7 11];
:for x from=0 to=([:len $months] - 1) do={
   :if ([:tostr [:pick $months $x]] = $M) do={:set M ($x + 1) } 
   }
:if ( $M = 1 || $M = 2) do={
    :set Y ($Y-1);
    :set M ($M+12);
}
:local A ($Y/100)
:local B ($A/4)
:local C (2-$A+$B)
:local E ((($Y+4716) * 36525)/100)
:local F ((306001*($M+1))/10000)
:local jd ($C+$D+$E+$F-1525)
:return $jd
};

:global timestamp do={
:global fncJD $fncJD
:local currtime [/system clock get time];
:local jdnow [$fncJD [/system clock get date]]
:local days ($jdnow - 2440587)
:local ore [:pick $currtime 0 2]
:local minute [:pick $currtime 3 5]
:local secunde [:pick $currtime 6 8]
:return (($days * 86400) + ($ore * 3600) + ($minute * 60) + $secunde - [/system clock get gmt-offset]);
}

After running the script just declare global function in your script and execute:

:global timestamp $timestamp
:put [$timestamp]

You can always post suggestions or enhancements to this script.

But why are you using Julian date as opposed to Gregorian?

Also, since 6.2 allows functions to accept arguments and :return, consider using that capability, since a timestamp is generally something to be used as part of a greater whole.

Julian date is the simplest algorithm I found that can be implemented to RouterOS in order to compute days since 1970.

Hello,

so I guess its impossible to get the time in seconds since 1970-01-01 00:00:00 UTC in any current RouterOS directly?

I need to count users’ session time and parsing rsyslog precision time is harder than log in date +%s.

Hello.

Many thanks to adeeadee.

This script was useful to me.
I use it when transferring GPS data to the monitoring site.

Regards,

Hello, I’m working on automation scripts that also need unix timestamp. Here are my preliminary functions:

:global "parse_date" do={
  :local year [:pick $str 7 11]
  :local month [:find [:toarray "jan,feb,mar,apr,may,jun,jul,aug,sep,oct,nov,dec"] [:pick $str 0 3]]
  :local day [:pick $str 4 6]

  :set month ($month - 1)
  :if ($month <= 0) do={
    :set month ($month + 12)
    :set year ($year - 1)
  }

  :return (( \
      365 * year + year / 4 - year / 100 + year / 400 \
    + 367 * month / 12 - 30 \
    + day - 1 \
    - 719527 + 31 + 28 \
    ) * 86400)
}

:global "parse_time" do={
  :local hour [:pick $str 0 2]
  :local min [:pick $str 3 5]
  :local sec [:pick $str 6 8]
  :return (hour * 3600 + min * 60 + sec)
}

:global "parse_timestamp" do={
  :global "parse_date"
  :global "parse_time"
  :return ([$"parse_date" str=[:pick $str 0 11]] + [$"parse_time" str=[:pick $str 12 20]])
}

:global "today" do={
  :global "parse_date"
  :return [$"parse_date" str=[/system clock get date]]
}

:global "time" do={
  :global "parse_time"
  :return [$"parse_time" str=[/system clock get time]]
}

:global "now" do={
  :global "today"
  :global "time"
  :return ([$"today"] + [$"time"])
}

My variant of epoch time, seems the shortest =)

:global EpochTime do={
   :local ds [/system clock get date];
   :local months;
   :if ((([:pick $ds 9 11]-1)/4) != (([:pick $ds 9 11])/4)) do={
      :set months {"an"=0;"eb"=31;"ar"=60;"pr"=91;"ay"=121;"un"=152;"ul"=182;"ug"=213;"ep"=244;"ct"=274;"ov"=305;"ec"=335};
   } else={
      :set months {"an"=0;"eb"=31;"ar"=59;"pr"=90;"ay"=120;"un"=151;"ul"=181;"ug"=212;"ep"=243;"ct"=273;"ov"=304;"dec"=334};
   }
   :set ds (([:pick $ds 9 11]*365)+(([:pick $ds 9 11]-1)/4)+($months->[:pick $ds 1 3])+[:pick $ds 4 6]);
   :local ts [/system clock get time];
   :set ts (([:pick $ts 0 2]*60*60)+([:pick $ts 3 5]*60)+[:pick $ts 6 8]);
   :return ($ds*24*60*60 + $ts + 946684800 - [/system clock get gmt-offset]);
}

How to use:

:put [$EpochTime] 
1588575464

Thanks for the script.
This is some I have complained to MT support about. Some like this should be built in to the RouterOS software.
As it is now, its impossible to handle time in RouterOS.
Example: From Cli, look at log time. For event less then 24 hour, only time is shown.
For event more than 24, moth in text and date + time is shown. How to then calculate some from this. Impossible..

Could this script be used for Log time as well?

/log print detail

time=apr/29 21:05:26 topics=system,error,critical message=“login failure for user xxxx from 192.168.10.178 via ssh”

I would lime this time in epoc and not apr/29 21:05:26

PS, you do not need semicolon ; after each line, only between multiple commands on same lines.
This script sets 2100 as a leap year, but it is not. So far inn to the future that it should not be a problem :slight_smile:

Here is an updated version that could be used to both get current time or convert data from input.

Example current time

:put [$EpochTime]
1588597644

Convert time

:put [$EpochTime "may/01 16:23:50"]
1588343030

EpochTime "15:23:50"]
1588598630

When date not give, it uses current day/month/year


:global EpochTime do={
	# Usage
	# $EpochTime [time input]
	# -----
	# Get current time
	# :put [$EpochTime]
	# 
	# Read log time in one of these two format "may/01 16:23:50" or "12:02:23" for log number *323
	# :put [$EpochTime [:log get *323 time]]

	:local ds
	:local ts
	:if ([:len $1]=0) do={
		# Get "now time"
		:set ds [/system clock get date]
		:set ts [/system clock get time]
	} else={
	
		if ([:len $1]>8) do={
			# Use remote date and time and convert date
			:set ds "$[:pick $1 0 6]/$[:pick [/system clock get date] 7 11]"
			:set ts [:pick $1 7 15]
		} else={
			# Use remote time and get date
			:set ds [/system clock get date]
			:set ts $1
		}
	}
	:local months
	:if ((([:pick $ds 9 11]-1)/4) != (([:pick $ds 9 11])/4)) do={

		:set months {"an"=0;"eb"=31;"ar"=60;"pr"=91;"ay"=121;"un"=152;"ul"=182;"ug"=213;"ep"=244;"ct"=274;"ov"=305;"ec"=335}
	} else={
		:set months {"an"=0;"eb"=31;"ar"=59;"pr"=90;"ay"=120;"un"=151;"ul"=181;"ug"=212;"ep"=243;"ct"=273;"ov"=304;"ec"=334}
	}
	:set ds (([:pick $ds 9 11]*365)+(([:pick $ds 9 11]-1)/4)+($months->[:pick $ds 1 3])+[:pick $ds 4 6])
	:set ts (([:pick $ts 0 2]*60*60)+([:pick $ts 3 5]*60)+[:pick $ts 6 8])
	:return ($ds*24*60*60 + $ts + 946684800 - [/system clock get gmt-offset])
}

NB, not sure how it handles log around new year change, when year is missing.
NB time laps year 2100, 2200 etc are not handled. But should not be a problem :wink:

Of course,
i’m create the “shortest variant” for years between 2000 and 2099.
But for my purpose it’s enough =)

Should do :slight_smile:

“dec” in this line:

else={
    :set months {"an"=0;"eb"=31;"ar"=59;"pr"=90;"ay"=120;"un"=151;"ul"=181;"ug"=212;"ep"=243;"ct"=273;"ov"=304;"dec"=334}
}

should be “ec”:

else={
    :set months {"an"=0;"eb"=31;"ar"=59;"pr"=90;"ay"=120;"un"=151;"ul"=181;"ug"=212;"ep"=243;"ct"=273;"ov"=304;"ec"=334}
}

Good catch, fixed the original post.

search tag # rextended mikrotik date to ISO:8601 date date2iso epoch unix time week day date2epoch date2weekday

A first pass to rewrite the function, is try to convert mikrotik “get date, time and gmt-offset” to valid ISO:8601 date like
2021-09-01T21:10:26**+00:00** (Great Britain) = 2021-09-01T21:10:26Z (UTC) = 2021-09-01T23:10:26**+02:00** (Italy)

The get date return lowercase months name, the date on file is with months on lowercase, etc.
instead last user access date, last link down/link up, date on log lines, and other, start with first letter uppercase,
(nice solution remove first letter on compare, probably I do the same later)
MikroTik programmers do not like uniformity… Like 3 different type of date on logs…
Why do not use international date 2021-09-01??? Is clear where is the year, the month and the day…

I everytime use global on that examples for rapidly work on terminal, on script replace all “global” occurencies with “local”

/system clock
:global strDate [get date]
:global strTime [get time]
:global intGoff [:tonum [get gmt-offset]]
:if ($intGoff > 0x7FFFFFFF) do={:set intGoff ($intGoff - 0x100000000)}
:global arrMonths {jan="01";feb="02";mar="03";apr="04";may="05";jun="06";jul="07";aug="08";sep="09";oct="10";nov="11";dec="12"}
:global intYear   [:tonum [:pick $strDate 7 11]]
:global strMonth  ($arrMonths->[:pick $strDate 0 3])
:global strDay    [:pick $strDate 4 6]
:global strHour   [:pick $strTime 0 2]
:global strMinute [:pick $strTime 3 5]
:global strSecond [:pick $strTime 6 8]
:global strOffsig "+"
:global strGoff   $intGoff
:if ($intGoff < 0) do={:set strOffsig "-"; :set strGoff ($intGoff * -1)}
:global strHoff  [:pick [:totime $strGoff] 0 2]
:global strMoff  [:pick [:totime $strGoff] 3 5]
:global strISOdate "$intYear-$strMonth-$strDay\54$strHour:$strMinute:$strSecond$strOffsig$strHoff:$strMoff"
:put "$strDate $strTime $strOffsig$intGoff converted to ISO format is $strISOdate"

(EDIT later: upgraded version of this script https://forum.mikrotik.com/viewtopic.php?p=960070#p960070 )

and after that, this is an addon to calculate UNIX “epoch” time. This cont timezione of the routerboard.
I do not count TimeZone seconds because, at the time I write, do not exist any timezone with “seconds”
The script include the calculaton of lap year and the week day.
Still precise until 2100, but probably at that date we are all dead. Eh…

:global arrPreMonDays {"01"=0;"02"=31;"03"=59;"04"=90;"05"=120;"06"=151;"07"=181;"08"=212;"09"=243;"10"=273;"11"=304;"12"=334}
# 1970-01-01 is a Thursday
:global arrWeekDays   {"Thu";"Fri";"Sat";"Sun";"Mon";"Tue";"Wed"}
# first bixestile year immediately before 1970 is 1968
:global numTotalDays  (($intYear - 1968) / 4)
:global bolLeapYear   false
:if ((($intYear - 1968) % 4) = 0) do={:set bolLeapYear true; :set ($arrPreMonDays->"01") -1; :set ($arrPreMonDays->"02") 30}
:set numTotalDays  ($numTotalDays + (($intYear - 1970) * 365))
:set numTotalDays  ($numTotalDays + ($arrPreMonDays->$strMonth))
:set numTotalDays  ($numTotalDays + ([:tonum $strDay] - 1))
:global strWeekDay ($arrWeekDays->($numTotalDays % 7))
:global numTotHours   (($numTotalDays * 24) + [:tonum $strHour])
:global numTotMinutes (($numTotHours * 60) + [:tonum $strMinute])
:global numTotSeconds (($numTotMinutes * 60) + [:tonum $strSecond] - $intGoff)
:put "For $strISOdate UNIX time is $numTotSeconds the current year is a lap year:$bolLeapYear and the Week Day is $strWeekDay"

(EDIT later: upgraded version of this script https://forum.mikrotik.com/viewtopic.php?p=960070#p960070 )


Next step is convert all to 3 single functions: date2iso, date2epoch, date2weekday.
Function to check if year is leap year or not is useless. (simply check if this ((($checkYear - 1968) % 4) = 0) is true or not)
The functions already internally must check if the passed date is valid or not,
and accept any “log format type”.

Do you need all these global variable, cant local be used.

I do not understand :frowning: ,
I already wrote

I everytime use global on that examples for rapidly work on terminal, on script replace all “global” occurencies with “local”

You mean than local can’t be used, global can’t be replaced on script?

Its to early in the morning to read all :slight_smile:
:smiling_face_with_sunglasses: :+1:

search tag # rextended lowercase uppercase chr2lcase chr2ucase

Functions to convert single character on lowercase and to uppercase.
Any other character passed remain unchanged.

:global chr2lcase do={
    :local charsString ""
    :for x from=0 to=15 step=1 do={ :for y from=0 to=15 step=1 do={
        :local tmpHex "$[:pick "0123456789ABCDEF" $x ($x+1)]$[:pick "0123456789ABCDEF" $y ($y+1)]"
        :set $charsString "$charsString$[[:parse "(\"\\$tmpHex\")"]]"
    } }
    :local chrValue [:find $charsString $1 -1]
    :if (($chrValue > 64) and ($chrValue < 91)) do={
        :return [:pick $charsString ($chrValue + 32) ($chrValue + 33)]
    } else={
        :return $1
    }
}

:global chr2ucase do={
    :local charsString ""
    :for x from=0 to=15 step=1 do={ :for y from=0 to=15 step=1 do={
        :local tmpHex "$[:pick "0123456789ABCDEF" $x ($x+1)]$[:pick "0123456789ABCDEF" $y ($y+1)]"
        :set $charsString "$charsString$[[:parse "(\"\\$tmpHex\")"]]"
    } }
    :local chrValue [:find $charsString $1 -1]
    :if (($chrValue > 96) and ($chrValue < 123)) do={
        :return [:pick $charsString ($chrValue - 32) ($chrValue - 31)]
    } else={
        :return $1
    }
}
:put [$chr2lcase "A"]

:put [$chr2ucase "a"]

search tag # rextended str2case string lowercase uppercase proper case

Functions to convert a string to lowercase, uppercase or proper case (only the first letter up, the other low)
Any other non A-Z a-z character passed remain unchanged.

Optional parameter:

  1. no parameter convert the first letter uppercase, all the others lowercase.
  2. “U” convert to upper case
  3. “L” convert to lower case
  4. “P” convert to proper case: only the first letter on each word are uppercase, the remaining lower.


:global str2case do={
    :local input   [:tostr "$1"]
    :local options "$2"
    :local letters "[A-Za-z]"

    :local charsString ""
    :for x from=0 to=15 step=1 do={ :for y from=0 to=15 step=1 do={
        :local tmpHex "$[:pick "0123456789ABCDEF" $x ($x+1)]$[:pick "0123456789ABCDEF" $y ($y+1)]"
        :set $charsString "$charsString$[[:parse "(\"\\$tmpHex\")"]]"
    } }

    :local position 0 ; :local chrValue 0; :local what "U" ; :local output "" ; :local work "" ; :local previous ""
    :while ($position < [:len $input]) do={
        :set work     [:pick $input $position ($position + 1)]
        :set chrValue [:find $charsString $work -1]
        :if ($options~"(p|P)") do={:set what "U"}
        :if (($options~"(l|L)") or ((!($options~"(l|L|u|U)")) and ($previous~$letters))) do={:set what "L"}
        :if (($what = "L") and (($chrValue > 64) and ($chrValue <  91))) do={
            :set work [:pick $charsString ($chrValue + 32) ($chrValue + 32 + 1)]
        }
        :if (($what = "U") and (($chrValue > 96) and ($chrValue < 123))) do={
            :set work [:pick $charsString ($chrValue - 32) ($chrValue - 32 + 1)]
        }
        :set output   "$output$work"
        :set previous $work
        :set position ($position + 1)
    }
    :return $output
}

> :put [$str2case “this iS a teSt ,a teSt is it”]
This is a test ,a test is it

> :put [$str2case “this iS a teSt ,a teSt is it” “U”]
THIS IS A TEST ,A TEST IS IT

> :put [$str2case “this iS a teSt ,a teSt is it” “L”]
this is a test ,a test is it

> :put [$str2case “this iS a teSt ,a teSt is it” “P”]
This Is A Test ,A Test Is It

Interesting, thank you!

I’ve tried it and something is wrong here:

[] > :put [$str2case "this iS a teSt ,a teSt is it"]
This is a test ,a test is it

U and L works fine.

BR.