PHP file times: filemtime, fileatime and filectime
last modified November 14, 2026
Every file has three timestamps, and PHP has one function for each:
filemtime, fileatime and filectime.
In this article we show what each timestamp means, how they change, why PHP
sometimes returns an old value, and how to use file times for tasks such as
finding the newest file or deciding when a cache must be rebuilt.
The examples were run with PHP 8.5 on Linux.
The three timestamps
The functions share the same signature and return a Unix timestamp, or
false on failure:
filemtime(string $filename): int|false fileatime(string $filename): int|false filectime(string $filename): int|false
They differ in what they measure:
filemtime– modification time: when the file's content was last written,fileatime– access time: when the file's content was last read,filectime– change time: when the file's inode was last changed, that is its content or its metadata, such as permissions, owner or name.
A common mistake is to read filectime as the creation time. On
Unix-like systems it is not: any change of permissions or ownership updates
it. Only on Windows does it return the creation time. For most tasks the
modification time is the one you want.
Reading the timestamps
The first example sets the modification and access times of a file and reads all three timestamps back.
<?php
declare(strict_types=1);
date_default_timezone_set('UTC');
$file = 'notes.txt';
file_put_contents($file, "first line\n");
$mtime = strtotime('2026-03-01 09:30:00');
$atime = strtotime('2026-03-05 14:00:00');
touch($file, $mtime, $atime);
clearstatcache();
echo 'modified: ', date('Y-m-d H:i:s', filemtime($file)), "\n";
echo 'accessed: ', date('Y-m-d H:i:s', fileatime($file)), "\n";
echo 'changed: ', date('Y-m-d', filectime($file)), " (today)\n";
unlink($file);
The touch function sets the modification time to its second
argument and the access time to its third. We use fixed dates so that the
output does not depend on when the example runs.
echo 'modified: ', date('Y-m-d H:i:s', filemtime($file)), "\n";
The timestamps are integers, so we format them with date. We set
the time zone to UTC first, because date uses the default time
zone.
The change time cannot be set by any function. Calling touch
changes the inode, so the change time becomes the current time, the day the
example was run.
$ php basic.php modified: 2026-03-01 09:30:00 accessed: 2026-03-05 14:00:00 changed: 2026-11-14 (today)
Modification time versus change time
This example shows which operations update the modification time and which update only the change time.
<?php
declare(strict_types=1);
$file = 'report.txt';
file_put_contents($file, "draft\n");
touch($file, time() - 3600);
clearstatcache();
$show = function (string $label) use ($file): void {
clearstatcache();
$m = filemtime($file);
$c = filectime($file);
printf("%-18s mtime %s ctime %s\n", $label, date('H:i:s', $m), date('H:i:s', $c));
};
$show('start');
sleep(2);
chmod($file, 0600);
$show('after chmod');
sleep(2);
file_put_contents($file, "final\n");
$show('after write');
unlink($file);
We create a file and move its modification time an hour back. The
$show closure prints both timestamps.
chmod($file, 0600);
Changing the permissions does not touch the content, so the modification time stays the same. The change time moves forward, because the inode was changed.
file_put_contents($file, "final\n");
Writing new content updates both timestamps. The pauses of two seconds make the steps visible in the output.
$ php mtime_ctime.php start mtime 20:14:37 ctime 21:14:37 after chmod mtime 20:14:37 ctime 21:14:39 after write mtime 21:14:41 ctime 21:14:41
Access time and relatime
Updating the access time on every read would mean a disk write for every
read. Modern Linux systems therefore mount file systems with the
relatime option, which limits these updates.
<?php
declare(strict_types=1);
$file = 'readme.txt';
file_put_contents($file, "hello\n");
$old = strtotime('2026-01-01 00:00:00');
touch($file, $old, $old);
clearstatcache();
echo 'before read: ', date('Y-m-d H:i:s', fileatime($file)), "\n";
file_get_contents($file);
clearstatcache();
$first = fileatime($file);
echo 'first read: ', date('Y-m-d H:i:s', $first), "\n";
sleep(2);
file_get_contents($file);
clearstatcache();
$second = fileatime($file);
echo 'second read: ', date('Y-m-d H:i:s', $second), "\n";
echo 'atime updated by the second read: ', $second !== $first ? 'yes' : 'no', "\n";
unlink($file);
We set both times of a new file to January 1 and read the file twice, two seconds apart.
With relatime, the kernel updates the access time on a read only
if the old access time is older than the modification time or more than 24
hours old. The first read qualifies, so the access time jumps to the present.
The second read does not, so the access time stays the same. On a file system
mounted with noatime it never changes. Don't rely on
fileatime to tell exactly when a file was last read.
$ php access_time.php before read: 2026-01-01 00:00:00 first read: 2026-11-14 21:14:56 second read: 2026-11-14 21:14:56 atime updated by the second read: no
The stat cache
PHP caches the result of the last stat call. When another process
changes a file while our script is running, the time functions may return
the old value.
<?php
declare(strict_types=1);
$file = 'config.json';
file_put_contents($file, '{}');
touch($file, strtotime('2026-01-01 00:00:00'));
clearstatcache();
echo 'start: ', date('Y-m-d', filemtime($file)), "\n";
$editor = proc_open("sleep 1; touch -d 2026-06-15 $file", [], $pipes);
sleep(2);
echo 'cached: ', date('Y-m-d', filemtime($file)), "\n";
clearstatcache(true, $file);
echo 'after clear: ', date('Y-m-d', filemtime($file)), "\n";
proc_close($editor);
unlink($file);
We set the modification time of a file to January 1. Then
proc_open starts a separate process, which waits one second
and changes the modification time to June 15, while our script sleeps.
echo 'cached: ', date('Y-m-d', filemtime($file)), "\n";
The file has changed, but filemtime returns the cached value.
clearstatcache(true, $file);
The clearstatcache function empties the cache; with the second
argument it clears only the entry for one file. The next call reads the real
value. PHP clears the cache itself when the script changes the file, for
instance with touch, chmod or a write. You need
clearstatcache in long-running scripts that watch files changed
by other programs.
$ php stat_cache.php start: 2026-01-01 cached: 2026-01-01 after clear: 2026-06-15
Handling missing files
All three functions fail when the file does not exist.
<?php
declare(strict_types=1);
$file = 'missing.txt';
$mtime = filemtime($file);
var_dump($mtime);
$mtime = @filemtime($file);
if ($mtime === false) {
echo "Cannot read the modification time of $file\n";
}
if (is_file($file)) {
echo 'modified: ', date('Y-m-d', filemtime($file)), "\n";
} else {
echo "$file does not exist\n";
}
On failure the functions return false and emit an
E_WARNING. Compare the result with false using the
strict === operator: a timestamp of 0, the Unix epoch, is also
falsy, so a loose check would treat it as an error.
The @ operator suppresses the warning, but it also hides other
problems. It is cleaner to check the file with is_file first, as
the last part of the example does.
$ php errors.php Warning: filemtime(): stat failed for missing.txt in /app/errors.php on line 7 bool(false) Cannot read the modification time of missing.txt missing.txt does not exist
Reading all times at once with stat
When we need several attributes of a file, stat returns them in
one call.
<?php
declare(strict_types=1);
date_default_timezone_set('UTC');
$file = 'data.csv';
file_put_contents($file, "id,name\n1,Alice\n");
touch($file, strtotime('2026-04-10 08:15:00'), strtotime('2026-04-12 17:45:00'));
clearstatcache();
$st = stat($file);
echo 'size: ', $st['size'], " bytes\n";
echo 'modified: ', date('Y-m-d H:i', $st['mtime']), "\n";
echo 'accessed: ', date('Y-m-d H:i', $st['atime']), "\n";
$info = new SplFileInfo($file);
echo 'SplFileInfo modified: ', date('Y-m-d H:i', $info->getMTime()), "\n";
unlink($file);
The stat function returns an array with the size, the three
timestamps (mtime, atime, ctime) and
other inode data. The SplFileInfo class offers the same values
through the getMTime, getATime and
getCTime methods, which is convenient when we already work with
file objects, for example from a directory iterator.
$ php stat.php size: 16 bytes modified: 2026-04-10 08:15 accessed: 2026-04-12 17:45 SplFileInfo modified: 2026-04-10 08:15
Finding the newest file
Sorting files by modification time is a common task, for example to find the latest log or upload.
<?php
declare(strict_types=1);
date_default_timezone_set('UTC');
$dir = 'logs';
mkdir($dir);
$files = [
'app.log' => '2026-11-10 12:00',
'error.log' => '2026-11-13 08:30',
'access.log' => '2026-11-12 23:59',
];
foreach ($files as $name => $date) {
file_put_contents("$dir/$name", "...\n");
touch("$dir/$name", strtotime($date));
}
clearstatcache();
$paths = glob("$dir/*.log");
usort($paths, fn(string $a, string $b): int => filemtime($b) <=> filemtime($a));
foreach ($paths as $path) {
echo date('Y-m-d H:i', filemtime($path)), ' ', basename($path), "\n";
}
echo 'newest: ', basename($paths[0]), "\n";
array_map('unlink', $paths);
rmdir($dir);
We create three log files with different modification times. The
glob function lists them, and usort sorts the paths
by filemtime.
usort($paths, fn(string $a, string $b): int => filemtime($b) <=> filemtime($a));
Comparing $b with $a using the spaceship operator
sorts from the newest to the oldest, so the newest file ends up first.
$ php newest.php 2026-11-13 08:30 error.log 2026-11-12 23:59 access.log 2026-11-10 12:00 app.log newest: error.log
Deciding when to rebuild a cache
Build tools and template engines compare modification times to decide whether a generated file is out of date.
<?php
declare(strict_types=1);
function needsRebuild(string $source, string $cache): bool
{
return !is_file($cache) || filemtime($source) > filemtime($cache);
}
file_put_contents('template.html', '<h1>{{ title }}</h1>');
file_put_contents('template.cache', '<h1>Old title</h1>');
touch('template.html', strtotime('2026-11-01'));
touch('template.cache', strtotime('2026-11-05'));
clearstatcache();
echo 'cache newer than source: rebuild? ', var_export(needsRebuild('template.html', 'template.cache'), true), "\n";
touch('template.html', strtotime('2026-11-10'));
clearstatcache();
echo 'source edited afterwards: rebuild? ', var_export(needsRebuild('template.html', 'template.cache'), true), "\n";
unlink('template.cache');
clearstatcache();
echo 'cache file missing: rebuild? ', var_export(needsRebuild('template.html', 'template.cache'), true), "\n";
unlink('template.html');
The needsRebuild function returns true when the
cache file does not exist or when the source file was modified after the
cache was written.
We test three situations: a fresh cache, a source edited after the cache
was built, and a deleted cache. The is_file check comes first,
so filemtime is never called on a missing file.
$ php rebuild.php cache newer than source: rebuild? false source edited afterwards: rebuild? true cache file missing: rebuild? true
Source
PHP clearstatcache documentation
In this article we have used filemtime, fileatime
and filectime to read file timestamps, and shown how they change,
how the stat cache affects them and how to use them in practice. See also the
touch,
stat and
clearstatcache tutorials.