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