1#!/usr/local/bin/perl 2 3=head1 NAME 4 5ts - timestamp input 6 7=head1 SYNOPSIS 8 9ts [-r] [-i | -s] [-m] [format] 10 11=head1 DESCRIPTION 12 13ts adds a timestamp to the beginning of each line of input. 14 15The optional format parameter controls how the timestamp is formatted, 16as used by L<strftime(3)>. The default format is "%b %d %H:%M:%S". In 17addition to the regular strftime conversion specifications, 18"%.S" and "%.s" and "%.T" 19are like "%S" and "%s" and "%T", but provide subsecond resolution 20(ie, "30.00001" and "1301682593.00001" and "1:15:30.00001"). 21 22If the -r switch is passed, it instead converts existing timestamps in 23the input to relative times, such as "15m5s ago". Many common timestamp 24formats are supported. Note that the Time::Duration and Date::Parse perl 25modules are required for this mode to work. Currently, converting localized 26dates is not supported. 27 28If both -r and a format is passed, the existing timestamps are 29converted to the specified format. 30 31If the -i or -s switch is passed, ts reports incremental timestamps instead of 32absolute ones. The default format changes to "%H:%M:%S", and "%.S" and "%.s" can 33be used as well. In case of -i, every timestamp will be the time elapsed since 34the last timestamp. In case of -s, the time elapsed since start of the program 35is used. 36 37The -m switch makes the system's monotonic clock be used. 38 39=head1 ENVIRONMENT 40 41The standard TZ environment variable controls what time zone dates 42are assumed to be in, if a timezone is not specified as part of the date. 43 44=head1 AUTHOR 45 46Copyright 2006 by Joey Hess <id@joeyh.name> 47 48Licensed under the GNU GPL. 49 50=cut 51 52use warnings; 53use strict; 54use POSIX q{strftime}; 55no warnings 'utf8'; 56 57$|=1; 58 59my $rel=0; 60my $inc=0; 61my $sincestart=0; 62my $mono=0; 63use Getopt::Long; 64GetOptions( 65 "r" => \$rel, 66 "i" => \$inc, 67 "s" => \$sincestart, 68 "m" => \$mono 69) || die "usage: ts [-r] [-i | -s] [-m] [format]\n"; 70 71if ($rel) { 72 eval q{ 73 use Date::Parse; 74 use Time::Duration; 75 }; 76 die $@ if $@; 77} 78 79my $use_format=@ARGV; 80my $format="%b %d %H:%M:%S"; 81if ($inc || $sincestart) { 82 $format="%H:%M:%S"; 83 $ENV{TZ}='GMT'; 84} 85$format=shift if @ARGV; 86 87# For subsecond resolution, Time::HiRes is needed. 88my $hires=0; 89if ($format=~/\%\.[SsT]/ || $mono) { 90 require Time::HiRes; 91 use Time::HiRes qw(CLOCK_MONOTONIC); 92 $hires=1; 93} 94 95my $lastseconds = 0; 96my $lastmicroseconds = 0; 97my $monodelta; 98 99if ($mono) { 100 my $raw_time = Time::HiRes::clock_gettime(CLOCK_MONOTONIC); 101 $lastseconds = time; 102 $lastmicroseconds = int(1000000 * ($raw_time - int($raw_time))); 103 $monodelta = $lastseconds - int($raw_time); 104} 105elsif ($hires) { 106 ($lastseconds, $lastmicroseconds) = Time::HiRes::gettimeofday(); 107} 108else { 109 $lastseconds = time; 110} 111 112 113while (<>) { 114 if (! $rel) { 115 if ($hires) { 116 my $f=$format; 117 my $seconds; 118 my $microseconds; 119 if ($mono) { 120 my $raw_time = 121 Time::HiRes::clock_gettime(CLOCK_MONOTONIC) + 122 $monodelta; 123 $seconds = int($raw_time); 124 $microseconds = int(1000000 * ($raw_time - $seconds)); 125 } 126 else { 127 ($seconds, $microseconds) = Time::HiRes::gettimeofday(); 128 } 129 130 if ($inc || $sincestart) { 131 my $deltaseconds = $seconds - $lastseconds; 132 my $deltamicroseconds = $microseconds - $lastmicroseconds; 133 if ($deltamicroseconds < 0) { 134 $deltaseconds -= 1; 135 $deltamicroseconds += 1000000; 136 } 137 if ($inc) { 138 $lastseconds = $seconds; 139 $lastmicroseconds = $microseconds; 140 } 141 $seconds = $deltaseconds; 142 $microseconds = $deltamicroseconds; 143 } 144 my $s=sprintf("%06i", $microseconds); 145 $f=~s/\%\.([SsT])/%$1.$s/g; 146 print strftime($f, localtime($seconds)); 147 } 148 else { 149 if ($inc || $sincestart) { 150 my $seconds = time; 151 my $deltaseconds = $seconds - $lastseconds; 152 if ($inc) { 153 $lastseconds = $seconds; 154 } 155 print strftime($format, localtime($deltaseconds)); 156 } 157 else { 158 print strftime($format, localtime); 159 } 160 } 161 print " ".$_; 162 } 163 else { 164 s{\b( 165 \d\d[-\s\/]\w\w\w # 21 dec 17:05 166 (?:\/\d\d+)? # 21 dec/93 17:05 167 [\s:]\d\d:\d\d # (time part of above) 168 (?::\d\d)? # (optional seconds) 169 (?:\s+[+-]\d\d\d\d)? # (optional timezone) 170 | 171 \w{3}\s+\d{1,2}\s+\d\d:\d\d:\d\d # syslog form 172 | 173 \d\d\d\d[-:]\d\d[-:]\d\dT\d\d:\d\d:\d\d.\d+Z? # ISO-8601 174 | 175 (?:\w\w\w,?\s+)? # (optional Day) 176 \d+\s+\w\w\w\s+\d\d+\s+\d\d:\d\d:\d\d 177 # 16 Jun 94 07:29:35 178 (?:\s+\w\w\w|\s[+-]\d\d\d\d)? 179 # (optional timezone) 180 | 181 \w\w\w\s+\w\w\w\s+\d\d\s+\d\d:\d\d 182 # lastlog format 183 )\b 184 }{ 185 $use_format 186 ? strftime($format, localtime(str2time($1))) 187 : concise(ago(time - str2time($1), 2)) 188 }exg; 189 190 print $_; 191 } 192} 193