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.

18 comments:

Ragesh said...

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

google said...

whats about perl -e "print time;"?

Christopher Hubbell 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)

webmasterAT8100 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 G. Harms said...

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

Christopher Hubbell 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.

dan said...

Thanks, that was helpful.

Dan

karthick_3d said...

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

DaWaBZ 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 !

Christian 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!

Christian 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.