Friday, June 02, 2006

Solaris Date command and epoch time

I can't count over the years how often I've wanted to output a date stamp in seconds since the epoch to make duration calculations simple.

Just to prove that I'm not 100% biased towards Solaris let me point out that my Linux scripts all enjoy the ability to call /bin/date with a simple switch that outputs time in my desired format: date +%s.

In Solaris, neither /usr/bin/date nor /usr/xpg4/bin/date support output in the "seconds since epoch" format. This is what we call low hanging fruit as enhancements go. Unfortunately, the fruit still hangs.

Perl does a very nice job of handling date math and epoch conversion, but that requires a separate interpreter, and when I'm in shell I don't like to jump in an out of other interpreters. I found a pretty cool hack that seems to avoid an external interpreter, and gets me what I want...

Since we know that the system keeps track in the format we want, we need to find a utility that uses a system call... In this case I made a crazy guess and found the time() call. Here's what it looks like:


testbox# man -s 2 time
System Calls time(2)

NAME
time - get time

SYNOPSIS
time_t time(time_t *tloc);

DESCRIPTION
The time() function returns the value of time in seconds
since 00:00:00 UTC, January 1, 1970.

So, making a second wild guess I assumed that our beloved /usr/bin/date command uses the time() system call. Let's take a look... If we use the truss command to check out system calls and returns we should find what we're looking for. We'll use the grep command to look for the time() call.

There's a catch though... Truss is going to dump output to stderr, and grep looks for input on stdin. Those paths won't cross. So, we need to redirect stderr into the stdout stream before piping it all over to grep.


testbox# truss /usr/bin/date 2>&1 | grep ^time
time() = 1149275766


Cool! You can see the 2>&1 take stderr (2) and redirects (>) to catenate (&) with stdout (1). This cuts the 40+ lines of system calls down to the one we care about.

It's not a standard interface, so any time we use it we run the risk of any OS patch breaking our algorithm. Perl would be a safer way to go, but it does require more overhead in terms of firing up the interpreter for such a simple thing. You'll have to decide for yourself whether or not this hack is useful to you, but I think it's a good one.

To clean it up and make a bit better behaved we'll need to get rid of leading and trailing spaces, and output just what we need. Here's a quick script you can call or source...


#!/bin/sh
/usr/bin/truss /usr/bin/date 2>&1 | nawk -F= '/^time\(\)/ {gsub(/ /,"",$2);print $2}'
exit $?


And finally, let's see it in action:

testbox# ./edate
1149276150
testbox#

So there you have it. A way to get epoch time without writing a single line of C.

29 comments:

Ragesh Moyan said...

Thanks for the code snippet....It really help when you want to calculate the difference in times.
Ragesh

Aleks said...

whats about perl -e "print time;"?

cghubbell said...

Perl of course outshines the shell, but Perl is not always a viable option in a script. For example, the Solaris packaging system does not (technically) support Perl scripts for postinstall, preinstall, etc.

I try to use Perl when I can, but there's a lot of places it turns out not to be the best choice.

Alex said...

Thanks a lot!
i was lookin for something like that for a while.

Berk said...

Thank you!
I usually have various GNU utilities installed from Blastwave, but I was in a situation where I needed this fix.

bernardthered said...

That's awesome, thanks!

-bernardthered (3/21/08)

@b34tf00t said...

I guy,
i'm a problem. In linux have a:
DATE_LDAPSEC=`date +%s -d "$LDAP_DATE"`
but the "-d" option is not replicable whit your script.
Any ideas for substitution???

S.A.M said...

Thanks! Very smart and useful. About using perl instead, if you are running a loop within a ksh script that requires the current epoch time on every loop, which command would be less taxing? a truss/date/grep/nawk or a perl interpreter invocation?
You would think that staying "within" the shell would be faster. Not sure! Will probably need to test/measure it to find out! What are your thoughyts?Anyway, I like the technique, Smart!

Mark Lindsey said...

Thanks! This was a big help in a crunch. It's completely unclear to me why Solaris still ships with an ancient date(1) that doesn't support %s as on GNU Date.

Steven said...

It is absolutely embarrassing that solaris date() doesn't have this. Good tip.

cghubbell said...

s.a.m.,

That's a great question. Perl is actually very light weight, and I would imagine that its overhead could be even lighter than my hack. So in your described scenario I would put the odds in favor of Perl.

The biggest reason I don't go that direction for general use is that if I have a requirement that dictates shell for the primary routine, I find it tends to complicate maintainability to call perl from within shell. I prefer to pick the tool that best solves the overall problem.

That being said, most of the time Perl makes the best hammer, and I always prefer to write Perl over shell. Someday I hope some of the incompatibilities (like the packaging system) can be addressed.

Anonymous said...

Thanks, that was helpful.

Dan

Karthick said...

Hi,
Is it possible to subtract 600 seconds from this value and then convert the same into date once again?
Regards
Karthick

Unknown said...

Thanks a lot
you're genious :)
perl does the same with this command : perl -e 'print time, "\n"'

But yours is more portable

Thanks !

Unknown said...

Thank you.

nVerted said...

Perl does actually eclipse the hack in terms of sheer speed, but the hack offered works pretty slick if I might say so myself:

/tmp
-> time truss date 2>&1 |grep "^time" |sed "s/[ \t]/ /g" |cut -d' ' -f3
1273517121

real 0m0.08s
user 0m0.01s
sys 0m0.06s

/tmp
-> time truss date 2>&1 |grep ^time |awk '{print $3;}'
1273517125

real 0m0.09s
user 0m0.01s
sys 0m0.05s

/tmp
-> time perl -e 'print time; '
1273517128
real 0m0.03s
user 0m0.00s
sys 0m0.01s

I'd venture a guess that the cause for Solaris to ignore it is that it's low-hanging, but can be hacked such as this...

Thanks!

Unknown said...

found that:

nawk 'BEGIN{print srand()}'

# nawk 'BEGIN{print srand()}'
1275407044

BulletRip said...

Thanks. This really works. But how do I get epoch seconds for a date other than now? e.g. I want to find epoch seconds till 19th June 2006.

artsx said...

sed -n 3p mmm.ttt
will return the following:
May 4 2011 3:00PM

I need to convert it to seconds since epoch and then subtract it from the current date (again in seconds since epoch), to see the time difference.

Another word: i've a file which contains a date like described above. I need to read this date and compare it with current date, to see if the difference is more than 3 hours or not.

Could you please help me with a script?

JagRut said...

Thanks, this is really helpfull

attoparsec said...

Here's a pure POSIX solution without the need for the truss hack.

#!/bin/sh
: > x
echo "ibase=8;$(pax -wx cpio x | cut -c 48-59)" | bc
rm x

Lets see what this prints:

$ ./x.sh; date +%s
1314820066
1314820066

JT said...

This will allow you to calculate values in the future or in the past. This takes the value from epoch time creates it as an integer for calculation.

Epochtime=$(nawk 'BEGIN{print srand()}')

echo $Epochtime

echo "0t${Epochtime}=Y" | /usr/bin/adb

Epoch=`expr $Epochtime + 43200`

echo "after 12 hrs = $Epoch"

echo "0t${Epoch}=Y" | /usr/bin/adb

Unknown said...

To get the epoch time for current date and time is not a big deal but to get epoch time for a user given date like "Tue Nov 11 11:08:46 IST 2011"

Unknown said...

Finding epoch time for current date and time is not a big deal. But how do we get epoch seconds for a user given date like "Fri Dec 11 11:08:46 IST 2011"

trbot said...

Thanks, man. Nice snippet; saved me from having to write some C/Java code.

Nestor Urquiza said...

Almost 6 years after your post and still no +%s in Solaris 10 date. I keep coming back to this and I said I better leave at least a BIG THANK YOU!

Artsphere said...


nawk 'BEGIN{print srand()}'

# nawk 'BEGIN{print srand()}'
1275407044

Works for both Solaris 10 and 11.

but "truss date" only works for Solaris 10 or below.

Melani said...

Thats really a great post. Thanks for the post.

Nestor Urquiza said...

Just to report that in Solaris 11 "date +%s" does work now.