Merge tag 'upstream/0.58'
[moreutils.git] / ts
blob63baf18030d399f956b54afc35906da83cd72f4c
1 #!/usr/bin/perl
3 =head1 NAME
5 ts - timestamp input
7 =head1 SYNOPSIS
9 ts [-r] [-i | -s] [format]
11 =head1 DESCRIPTION
13 ts adds a timestamp to the beginning of each line of input.
15 The optional format parameter controls how the timestamp is formatted,
16 as used by L<strftime(3)>. The default format is "%b %d %H:%M:%S". In
17 addition to the regular strftime conversion specifications, "%.S" and "%.s"
18 are like "%S" and "%s", but provide subsecond resolution
19 (ie, "30.00001" and "1301682593.00001").
21 If the -r switch is passed, it instead converts existing timestamps in
22 the input to relative times, such as "15m5s ago". Many common timestamp
23 formats are supported. Note that the Time::Duration and Date::Parse perl
24 modules are required for this mode to work. Currently, converting localized
25 dates is not supported.
27 If both -r and a format is passed, the existing timestamps are
28 converted to the specified format.
30 If the -i or -s switch is passed, ts timestamps incrementally instead. In case
31 of -i, every timestamp will be the time elapsed since the last timestamp. In
32 case of -s, the time elapsed since start of the program is used.
33 The default format changes to "%H:%M:%S", and "%.S" and "%.s" can be used
34 as well.
36 =head1 ENVIRONMENT
38 The standard TZ environment variable controls what time zone dates
39 are assumed to be in, if a timezone is not specified as part of the date.
41 =head1 AUTHOR
43 Copyright 2006 by Joey Hess <id@joeyh.name>
45 Licensed under the GNU GPL.
47 =cut
49 use warnings;
50 use strict;
51 use POSIX q{strftime};
52 no warnings 'utf8';
54 $|=1;
56 my $rel=0;
57 my $inc=0;
58 my $sincestart=0;
59 use Getopt::Long;
60 GetOptions("r" => \$rel, "i" => \$inc, "s" => \$sincestart) || die "usage: ts [-r] [-i | -s] [format]\n";
62 if ($rel) {
63 eval q{
64 use Date::Parse;
65 use Time::Duration;
67 die $@ if $@;
70 my $use_format=@ARGV;
71 my $format="%b %d %H:%M:%S";
72 if ($inc || $sincestart) {
73 $format="%H:%M:%S";
74 $ENV{TZ}='GMT';
76 $format=shift if @ARGV;
78 # For subsecond resolution, Time::HiRes is needed.
79 my $hires=0;
80 if ($format=~/\%\.[Ss]/) {
81 require Time::HiRes;
82 $hires=1;
85 my $lastseconds = 0;
86 my $lastmicroseconds = 0;
88 if ($hires) {
89 ($lastseconds, $lastmicroseconds) = Time::HiRes::gettimeofday();
90 } else {
91 $lastseconds = time;
95 while (<>) {
96 if (! $rel) {
97 if ($hires) {
98 my $f=$format;
99 my ($seconds, $microseconds) = Time::HiRes::gettimeofday();
100 if ($inc || $sincestart) {
101 my $deltaseconds = $seconds - $lastseconds;
102 my $deltamicroseconds = $microseconds - $lastmicroseconds;
103 if ($deltamicroseconds < 0) {
104 $deltaseconds -= 1;
105 $deltamicroseconds += 1000000;
107 if ($inc) {
108 $lastseconds = $seconds;
109 $lastmicroseconds = $microseconds;
111 $seconds = $deltaseconds;
112 $microseconds = $deltamicroseconds;
114 my $s=sprintf("%06i", $microseconds);
115 $f=~s/\%\.([Ss])/%$1.$s/g;
116 print strftime($f, localtime($seconds));
118 else {
119 if ($inc || $sincestart) {
120 my $seconds = time;
121 my $deltaseconds = $seconds - $lastseconds;
122 if ($inc) {
123 $lastseconds = $seconds;
125 print strftime($format, localtime($deltaseconds));
126 } else {
127 print strftime($format, localtime);
130 print " ".$_;
132 else {
133 s{\b(
134 \d\d[-\s\/]\w\w\w # 21 dec 17:05
135 (?:\/\d\d+)? # 21 dec/93 17:05
136 [\s:]\d\d:\d\d # (time part of above)
137 (?::\d\d)? # (optional seconds)
138 (?:\s+[+-]\d\d\d\d)? # (optional timezone)
140 \w{3}\s+\d{1,2}\s+\d\d:\d\d:\d\d # syslog form
142 \d\d\d[-:]\d\d[-:]\d\dT\d\d:\d\d:\d\d.\d+ # ISO-8601
144 (?:\w\w\w,?\s+)? # (optional Day)
145 \d+\s+\w\w\w\s+\d\d+\s+\d\d:\d\d:\d\d
146 # 16 Jun 94 07:29:35
147 (?:\s+\w\w\w|\s[+-]\d\d\d\d)?
148 # (optional timezone)
150 \w\w\w\s+\w\w\w\s+\d\d\s+\d\d:\d\d
151 # lastlog format
154 $use_format
155 ? strftime($format, localtime(str2time($1)))
156 : concise(ago(time - str2time($1), 2))
157 }exg;
159 print $_;