Kamal Nayan
Kamal Nayan

Reputation: 1960

How to get last modified time of a file in perl in the format returned from diff command?

I want to display time in the same format as is displayed by the linux diff command.

D:\>diff -dub old.cfg new.cfg
--- old.cfg       2019-05-15 15:03:14.289950700 +0530
+++ new.cfg       2019-05-14 16:07:21.119695300 +0530

I want to show last modified time of a file as 2019-05-15 15:03:14.289950700 +0530.
I used stat but I am not getting that nanosecond part. Alo, need timezone in given format. I tried Time::HiRes too, but couldn't get.

my $dt = strftime("%Y-%m-%d %H:%M:%S", localtime((stat($old_file))[9]));

It returns: 2019-05-15 15:03:14. Can you please help me to append the other info?

Upvotes: 5

Views: 1647

Answers (2)

zdim
zdim

Reputation: 66964

I must first say, that's not going to be accurate. Even as your hardware likely supports nanosecond resolution by the time it gets back to software it's hundreds of times off. (With a file's timestamp, presumably used only as relative to other files, that may be less important.)

Having said that, the only way I find to claim nanoseconds is to use system's tools, so stat

my ($ts) = grep { /^\s*Access: [0-9]{4}/ } qx(stat $file);
$ts =~ s/^\s*Access:\s+//;

or, using map also as a filter

my ($ts) = map { /^\s*Access:\s+([0-9]{4}.*)/ ? $1 : () } qx(stat $file);

One problem here, of course, is that we have to parse output; so see your man pages and test your stat, and don't hope for much portability. Watch for system changes as output format of programs may change. But I don't find nanoseconds claim in Perl.

For reference, on my CentOS 7 (under both bash and tcsh)

perl -we'$f=shift; print grep { /^\s*Access: [0-9]{4}/ } qx(stat $f)' file

prints, for a random file

Access: 2019-05-15 13:21:57.723987422 -0700

Once again, the "nano"seconds aren't accurate at all.


Another option, for perhaps more reliable output parsing, is to use ls with its --full-time

my $ts = join ' ', (split ' ', qx(ls --full-time $file))[5..7];

As always, when running external commands build your variables with care and quote and escape appropriately. A good tool is String::ShellQuote. Or, better avoid the shell altogether unless it's specifically needed. Best do all this using a module for running external tools.

Upvotes: 3

TFBW
TFBW

Reputation: 1019

This is a multi-part challenge. The first challenge is to get a sub-second resolution on your file times, which is both OS and filesystem-dependent. The second part is to get the local timezone offset. All of this is doable on a modern system, however. You were on the right track with Time::HiRes, but strftime() is only going to get you part way. Here is an example program which takes files as arguments and prints the hi-res mtime per your requirements.

#!/usr/bin/perl

use strict;
use warnings;

use Time::HiRes qw(stat);
use Time::Piece;
use Time::Seconds;

sub timestr {
    my $t = localtime($_[0]);
    my $tstr = $t->strftime('%Y-%m-%d %H:%M:%S');
    if ($_[0] =~ /(\.\d+$)/) { $tstr .= $1 }
    my $off = $t->tzoffset;
    return sprintf('%s %+02d%02d', $tstr, int($off->hours), $off->minutes % 60);
}

for (@ARGV) {
    my @stat = stat($_);
    print "$_\n  ".($! ? "Error: $!" : timestr($stat[9]))."\n";
}
exit(0);

Example output:

$ ./stat.pl stat.pl
stat.pl
  2019-05-16 20:02:38.76708 +1000

A lot of the heavy lifting is being done by Time::Piece, but there is a little bit of hackery going on to extract the fractional part of the time, and convert the timezone to the desired format (and I haven't tested it extensively). You may want to use sprintf() on the original incoming value to coerce a particular number of decimal places. My code just honestly reproduces the original fractional part.

Bear in mind that not all timestamps will have a fractional part, even on a filesystem which supports sub-second resolution. The file may have been copied from a source which does not have sub-second resolution, for example.

Upvotes: 2

Related Questions