ZetCode

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:

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.

basic.php
<?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.

mtime_ctime.php
<?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.

access_time.php
<?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.

stat_cache.php
<?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.

errors.php
<?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.

stat.php
<?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.

newest.php
<?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.

rebuild.php
<?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 filemtime documentation

PHP fileatime documentation

PHP filectime documentation

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.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all PHP Filesystem functions.