Compare two file modification dates

Given that you're using the stat (similar functionality, but different output format on BSDs and GNU), you could also use the test utility, which does this comparison directly:

   FILE1 -nt FILE2
          FILE1 is newer (modification date) than FILE2

   FILE1 -ot FILE2
          FILE1 is older than FILE2

In your example,

if [ "$source_file" -nt "$target_file" ]
then
    printf '%s\n' "$source_file is newer than $target_file"
fi

The feature is not available in POSIX (see its documentation for test), which provides as a rationale:

Some additional primaries newly invented or from the KornShell appeared in an early proposal as part of the conditional command ([[]]): s1 > s2, s1 < s2, str = pattern, str != pattern, f1 -nt f2, f1 -ot f2, and f1 -ef f2. They were not carried forward into the test utility when the conditional command was removed from the shell because they have not been included in the test utility built into historical implementations of the sh utility.

That might change in the future though as the feature is widely supported.

Note that when operands are symlinks, it's the modification time of the target of the symlink that is considered (which is generally what you want, use find -newer instead if not). When symlinks cannot be resolved, the behaviour between implementations (some consider an existing file as always newer than one that can't resolve, some will always report false if any of the operands can't be resolved).

Also note that not all implementations support sub-second granularity (bash's test/[ builtin as of version 4.4 still doesn't for instance, GNU test and the test builtin of zsh or ksh93 do, at least on GNU/Linux).

For reference:

  • For the GNU test utility implementation (though note that your shell, if fish or Bourne-like, will also have a test/[ builtin that typically shadows it, use env test instead of test to bypass it), get_mtime in test.c reads struct timespec, and
  • option -nt uses that data

In testing on this linux system. The usual way to test file times is the shell:

[ file1 -nt file2 ] && echo "yes"

Seems to work with seconds. This, which will touch the files with a time difference less than a second, doesn't detect that difference:

$ touch file2; sleep 0.1; touch file1; [ file1 -nt file2 ] && echo "yes"

To confirm the issue (time after the dot is nanoseconds):

$ ls --time-style=full-iso -l file?
-rw-r--r-- 1 user user 0 2017-06-23 01:37:01.707387495 -0400 file1
-rw-r--r-- 1 user user 0 2017-06-23 01:37:01.599392538 -0400 file2

The file1 is (a bit) newer than file2.

The problem now will be to correctly process the time value.

One solution is to use a formatted output of ls:

$ ls --time-style=+%s.%N -l file?
-rw-r--r-- 1 user user 0 1498196221.707387495 file1
-rw-r--r-- 1 user user 0 1498196221.599392538 file2

Extracting the time to two variables (without the dot):

$ file1time=$(ls --time-style=+%s%N -l file1 | awk "{print(\$6)}")
$ file2time=$(ls --time-style=+%s%N -l file2 | awk "{print(\$6)}")

And compare the times (times with nanoseconds just barely fit in a 64 bit value. If your system does not use 64 bit, this comparison will fail):

$ [ $file1time -gt $file2time ] && echo "yes"
yes

That shows that file1 is newer than file2


If ls fails to have the format needed, then you may try stat.

$ stat file1
  File: file1
  Size: 0               Blocks: 0          IO Block: 4096   regular file
Device: 805h/2053d      Inode: 9180838     Links: 1
Access: (0644/-rw-r--r--)  Uid: ( 1000/    user)   Gid: ( 1000/    user)
Access: 2017-06-23 01:37:01.707387495 -0400
Modify: 2017-06-23 01:37:01.707387495 -0400
Change: 2017-06-23 01:37:01.707387495 -0400
 Birth: -

If the output shows nanoseconds, the we will need date to parse (and format) the time.

$ stat --printf='%y\n' file1
2017-06-23 01:37:01.707387495 -0400

$ date +'%s%N' -d "$(stat --printf='%y\n' file1)" 
1498196221707387495

The rest is the same, assign the results of file1 and file2 to two variables and numerically compare them.