I wrote this ultimate time tool for all those shell scripts wanting to do time stuff - parsing, clock, offsets and formatted output. Complex questions might mean you call it more than once or in a loop, like what is the day of month of the next third thursday:
$ cat mysrc/tm2tm.c
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <time.h>
#include <errno.h>
#include <ctype.h>
static void usage_exit()
{
fputs(
"\n"
"Usage: tm2tm { -c | { - | <tm_str> } <in_fmt> } <delta> <out_fmt>\n"
" First, gets dates and time(s):\n"
" - when '-c' is used, from gettimeofday() (microsecond system clock)\n"
" - when '-' is used, from the front of each line of stdin per <in_fmt>\n"
" - else, from the front of <tm_str> per <in_fmt>\n"
" The default year is 2000, and for other elements, minimum values.\n"
" The <in_fmt> is either one of these or composed per strptime():\n"
" %s Absolute Unix time in integer seconds\n"
" %s.%F Unix time in integer and fractional 6 place seconds\n"
" %s.%f Unix time in integer and fractional 1-6 place seconds\n"
" Modify each time by <delta>, an optionally sign, integer number,\n"
" and optional suffix indicating the time unit (default seconds):\n"
" Y or y years m months\n"
" D or d days H or h hours\n"
" M minutes S or s seconds\n"
" Format the time by <out_fmt>, which supports all of the strftime() values,\n"
" plus the following:\n"
" %s Absolute Unix time in seconds\n"
" %F Fractional 6 place seconds\n"
" %f Fractional 1-6 place, zero suppressed seconds\n"
" Write the converted time plus any following input data to standard out.\n"
" For example: tm2tm '5/7/05 15:34' '%m/%d/%y %H:%M' -8h '%Y-%m-%d %r %Z'\n"
" prints '2005-05-07 07:34:00 AM EDT'\n",
stderr );
exit( 1 );
}
static void add_u_time( char *os, char *is, struct timeval *tv )
{
int st ;
int i ;
for ( st=0 ; *is ; os++, is++ )
{
switch ( *os = *is )
{
case '%':
st ^= 1 ;
continue ;
case 's':
if ( st )
{
st = 0 ;
os += sprintf( os - 1, "%u", tv->tv_sec ) ;
os -= 2 ;
}
continue ;
case 'f':
case 'F':
if ( st )
{
st = 0 ;
os += sprintf( os - 1, "%6.6u", tv->tv_usec );
os -= 2 ;
/* trim off up to 5 trailing 0's for %f */
for ( i = 0 ;
i < 5 && *os == '0' && *is == 'f' ;
i++ )
{
os-- ;
}
}
continue ;
default:
st = 0 ;
}
}
*os = NULL ;
}
int main( int argc, char **argv )
{
long td = 0L ;
struct tm tm ;
struct timeval tv = { 0, 0 };
int c = 0 ;
int sff = 0 ;
int dst_sav ;
char *td_sufx ;
char *in_sufx ;
char *tbufp ;
char *cp ;
char tbuf[65536];
char tbuf2[65536];
char tbuf3[65536];
if ( argc < 4 )
usage_exit();
if ( !strcmp( argv[1], "-c" ) )
{
if ( argc != 4 )
{
usage_exit();
}
c = 1 ;
in_sufx = "" ;
tbufp = "<system_clock>" ;
}
else
{
if ( argc != 5 )
{
usage_exit();
}
if ( !strcmp( argv[1], "-" ) )
{
tbufp = tbuf ;
}
else
{
tbufp = argv[1];
}
if ( !strcmp( argv[2], "%s" ) )
{
sff = 1 ;
}
else if ( !strcmp( argv[2], "%s.%F" ) )
{
sff = 2 ;
}
else if ( !strcmp( argv[2], "%s.%f" ) )
{
sff = 3 ;
}
}
td = strtol( argv[ 3 - c ], &td_sufx, 0 );
while ( *td_sufx
&& isspace( *td_sufx ) )
{
td_sufx++ ;
}
do
{
if ( c )
{
if ( gettimeofday( &tv, (void *)NULL ) )
{
perror( "gettimeofday()" );
exit( 1 );
}
localtime_r( &tv.tv_sec, &tm );
}
else
{
if ( tbufp == tbuf )
{
if ( !fgets( tbuf, sizeof( tbuf ), stdin ) )
{
if ( ferror( stdin ) )
{
perror( "stdin" );
exit( 1 );
}
exit( 0 );
}
}
if ( sff )
{
errno = 0 ;
tv.tv_sec = strtoul( tbufp, &in_sufx, 0 );
if ( errno )
{
fprintf( stderr,
"\nInput '%s' not format '%s'",
tbufp, argv[2] );
perror( "" );
continue ;
}
localtime_r( &tv.tv_sec, &tm );
if ( sff > 1 )
{
cp = in_sufx ;
errno = 0 ;
tv.tv_usec = strtoul( cp, &in_sufx, 0 );
if ( errno
|| *in_sufx != '.' )
{
fprintf( stderr,
"\nInput '%s' not format '%s'",
tbufp, argv[2] );
perror( "" );
continue ;
}
while ( ( in_sufx - cp ) < 6
&& sff > 2 )
{
tv.tv_usec *= 10 ;
cp-- ;
}
}
}
else
{
if ( !( in_sufx = strptime( tbufp,
argv[2], &tm ) ) )
{
fprintf( stderr,
"\nInput '%s' not format '%s'",
tbufp, argv[2] );
continue ;
}
if ( tm.tm_year < 69 )
{
tm.tm_year += 100 ;
}
if ( !tm.tm_mday )
{
tm.tm_mday = 1 ;
}
dst_sav = tm.tm_isdst ;
errno = 0 ;
tv.tv_sec = mktime( &tm );
if ( errno )
{
fprintf( stderr, "\n"
"Error converting date-time '%s' using format '%s': ",
tbufp, argv[ 2 ] );
perror( "" );
continue ;
}
if ( dst_sav < tm.tm_isdst )
{
tm.tm_hour-- ;
tv.tv_sec = mktime( &tm );
}
}
}
if ( td )
{
switch( *td_sufx )
{
case 'y':
case 'Y':
tm.tm_year += td ;
break ;
case 'm':
tm.tm_mon += td ;
break ;
case 'w':
case 'W':
tm.tm_mday += ( 7 * td );
break ;
case 'd':
case 'D':
tm.tm_mday += td ;
break ;
case 'h':
case 'H':
tm.tm_hour += td ;
break ;
case 'M':
tm.tm_min += td ;
break ;
case 's':
case 'S':
case NULL:
tm.tm_sec += td ;
break ;
default:
fprintf( stderr,
"\n"
"Fatal: Unknown delta unit %c in delta arg '%s'.\n",
*td_sufx, argv[ 3 - c ] );
exit( 1 );
}
errno = 0 ;
tv.tv_sec = mktime( &tm );
if ( errno )
{
fprintf( stderr, "\n"
"Error converting time + delta '%s' using ",
argv[ 3 - c ] );
perror( "mktime()" );
continue ;
}
} /* if td */
add_u_time( tbuf2, argv[ 4 - c ], &tv );
if ( !strftime( tbuf3, sizeof( tbuf3 ), tbuf2, &tm ) )
{
fprintf( stderr, "\n"
"String too long using format '%s' on time '%s'\n", tbuf2, tbufp );
continue ;
}
if ( 0 > printf( "%s%s%s", tbuf3, in_sufx,
( tbufp != tbuf ? "\n" : "" ) ) )
{
if ( ferror( stdout ) )
{
perror( "stdout" );
exit( 1 );
}
exit( 0 );
}
} while ( tbufp == tbuf ) ;
exit( 0 );
}
The input option for time at the front of a line fits many files, and I have another tool mystat that is a wrapper for the stat() call that can stream file names in (if not on the command line) and by default outputs (file mod times in decimal unix time TAB file name), so you can sort -n to find the oldest or newest of any number of files.