15

I would like to view a text file with awk/grep which contains a Unix timestamp in the first column. How can I convert it to a human-readable time while viewing?

  • It would be nice to have a portable answer to this not relying on perl or gawk. – R.. Oct 23 at 19:06
  • 5
    @R.. isn’t Perl portable enough? – Stephen Kitt Oct 23 at 20:54
  • @StephenKitt: No, not at all. From both a standpoint of whether it's installed, and whether it even can be built for your system. But if you're happy with having to build/install software, the portable answer is just a one-line C program. – R.. Oct 23 at 21:55
  • @R Is there any Unix-variants (actually used) left for which Perl is not available as a binary? – Thorbjørn Ravn Andersen Oct 25 at 9:55
  • @ThorbjørnRavnAndersen: Saying "not available" is a bit unfair, but SmartOS doesn't come with perl installed. – Jesse_b Oct 25 at 14:45
19

If the line starts with the unix timestamp, then this should do it:

perl -pe 's/^(\d+)/localtime $1/e' inputfilename

perl -p invokes a loop over the expression passed with -e which is executed for each line of the input, and prints the buffer at the end of the loop.

The expression uses the substition command s/// to match and capture a sequence of digits at the beginning of each line, and replace it with the local time representation of those digits interpreted as a unix timestamp. /e indicates that the replacement pattern is to be evaluated as an expression.

  • 3
    Or perl -MPOSIX -pe 's/^\s*\K\d+/strftime "%FT%T%z", localtime $&/e' to get a more useful timestamp format (unambiguous and sorts lexically the same as chronologically) – Stéphane Chazelas Oct 23 at 10:18
18

If your AWK is Gawk, you can use strftime:

gawk '{ print strftime("%c", $1) }'

will convert the timestamp in the first column to the current locale’s default date/time representation.

You can use this to transform the content before viewing it:

gawk '{ print gensub($1, strftime("%c", $1), 1) }' inputfile
8

As Dan mentions in his answer, the Gnu date(1) command can be given a -d parameter with a date or timestamp that it will treat as the date to be output. (This is not available in the POSIX or BSD date(1) commands, however.) @1571806800 is the format used to specify a time_t integer.

Here's a Bash shell function that acts as a filter, reading lines from the input, assuming any word starting a line that's all digits is a timestamp, and converting that to human-readable output in one of my favourite formats.

ts2hr() {
    while read n rest; do
        if [[ $n =~ ^[0-9][0-9]*$ ]]; then
            echo "$(date -d @"$n" +"%Y-%m-%d %T")" "$rest"
        else
            echo "$n" "$rest"
        fi
    done
}

Here's what some input and output look like:

ts2hr <<____
1571806123 first line
# A comment?
1571720456 second date's here
Just a regular sentence.
1571547789 The last line.
____
2019-10-23 13:48:43 first line
# A comment?
2019-10-22 14:00:56 second date's here
Just a regular sentence.
2019-10-20 14:03:09 The last line.

You can tweak the function as necessary to handle the particular input and output formats you require, as well as the tools you have available. The above function is Bash mainly because the =~ operator made the pattern match easier; if you need this in Bourne shell you'll have to code the check differently, if indeed you need it at all.

4

use "date"

You don't need perl, awk, php, or a long arithmetic expression for this. You can do it with gnu "date", which is really quite useful for this sort of thing. For other versions of "date", your YMMV.

$ date -d '1/1/1970 GMT 1571889039 seconds'
Wed Oct 23 20:50:39 PDT 2019

What's going on here is that you are specifying a time and an adjustment to it. The time is midnight January 1st, 1970 GMT, the unix epoch. (Midnight is not explicitly given; it's used implicitly any time you omit the time of day.)

The adjustment, 1571889039 seconds, is added to the time. Use your timestamp instead, obviously. The docs use the word "relative" for this feature.

There's a more concise version, documented only by an example in the manpage, which works for newer versions of gnu date:

$ date -d @2147483647
Mon Jan 18 19:14:07 PST 2038

If you need to manipulate dates often, get to know your date command. It's more than just a clock; it's a date/time library for the shell.

  • 1
    So how do you extract the first column of a text file and convert it with date? – Freddy Oct 24 at 5:34
  • The obvious way is with s///e in GNU sed, I think. – Toby Speight Oct 24 at 18:31
  • Or even simpler, use cut. – Psychonaut Oct 25 at 13:13
2

For a portable version of this, in the worst case you can do the math yourself. Unix timestamps are defined in POSIX XBD 4.16 Seconds Since the Epoch:

tm_sec + tm_min*60 + tm_hour*3600 + tm_yday*86400 +
    (tm_year-70)*31536000 + ((tm_year-69)/4)*86400 -
    ((tm_year-1)/100)*86400 + ((tm_year+299)/400)*86400

This is the inverse of the mapping you want, so a little bit of work* is required to reverse it. This will get you to broken-down "POSIX UTC", but then you may need a way to format it in the current timezone. It appears date -d is not portable, so I'm not clear on whether there's any good way to do this.

* Ok that may be an understatement.

2

If you don't have gnu date or access to a programming language with strftime or equivalent, you have to do the math yourself, which goes into code golf territory.

unix_to_iso_utc () {
    local day_unix=$(($1 / 86400))
    local day_offset=$(($1 % 86400))
    local d=$(($day_unix + 719468))
    local y=$((($d - ($d+2+3*$d/146097)/1461 + ($d-$d/146097)/36524 - ($d+1)/146097)/365))
    local y_d=$((365*$y + $y/4 - $y/100 + $y/400))
    local y_offset=$(($d - $y_d))
    local m=$((($y_offset - ($y_offset+20)/50) / 30))
    local m_d=$((30*$m + 3*($m+4)/5 - 2))
    local day=$(($y_offset - $m_d + 1))
    local m_cont=$((12*$y + $m + 2))
    local year=$(($m_cont/12))
    local month=$(($m_cont%12 + 1))
    local hour=$(($day_offset / 3600))
    local hour_offset=$(($day_offset % 3600))
    local minute=$(($hour_offset / 60))
    local second=$(($hour_offset % 60))
    DATE=$(printf "%04d-%02d-%02dT%02d:%02d:%02d" $year $month $day $hour $minute $second)
}

You also probably don't have a timezone database at that point, which can be a huge practical problem, unless you can hardcode what you need.

0

As has been said, theres not really a portable way to do this using shell, or even AWK. GAWK can do it, but GAWK strftime is not availble with certain versions of AWK, namely MAWK. And MAWK is used with some popular distros, for example Debian.

I agree that a portable shell or AWK solution should be available for this. But as its not, and likely wont be for many years, if ever, the best solution is to just use a programming language. Perl has been suggested, another option is PHP:

$ cat input.txt
1571806800 blue
1571720400 green
1571634000 orange
1571547600 red
1571461200 yellow

$ cat app.php
<?php
$r1 = fopen('input.txt', 'r');
while (true) {
   $s1 = fgets($r1);
   if ($s1 === false) {
      break;
   }
   $a1 = explode(' ', $s1);
   $n1 = + $a1[0];
   echo date(DATE_W3C, $n1), "\n";
}

$ php app.php
2019-10-23T05:00:00+00:00
2019-10-22T05:00:00+00:00
2019-10-21T05:00:00+00:00
2019-10-20T05:00:00+00:00
2019-10-19T05:00:00+00:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.