Skip to content

Obtenir la taille et la date de modification d'un fichier snippet

Demander au système de fichiers la taille d'un fichier et sa date de dernière modification en un seul appel stat — l'entrée brute de l'invalidation de cache (« retélécharge sauf si le fichier distant est plus récent »), des bandeaux d'obsolescence, des contrôles de rotation de logs et des listages de répertoires.

Demander au système de fichiers la taille d'un fichier et sa date de dernière modification en un seul appel stat — l'entrée brute de l'invalidation de cache (« retélécharge sauf si le fichier distant est plus récent »), des bandeaux d'obsolescence, des contrôles de rotation de logs et des listages de répertoires. Les pièges récurrents : les horodatages n'ont que la précision du système de fichiers et de l'API (FAT arrondit à 2 secondes, les champs à la seconde entière perdent silencieusement les changements sub-seconde — compare des champs nanoseconde ou utilise une tolérance, jamais l'égalité exacte), mtime est indicatif plutôt qu'une preuve (touch le réécrit ; une copie peut ou non le préserver — vérifie par checksum quand ça compte), et plusieurs API rapportent un fichier manquant comme 0 octet / temps zéro au lieu d'échouer (File.length() de Java, File.GetLastWriteTimeUtc de .NET renvoie 1601-01-01), transformant un chemin mort en données plausibles. SQL est omis : aucun langage de requête ne statue sur des chemins arbitraires du système de fichiers client — les métadonnées de fichier relèvent de l'hôte applicatif, pas de la session de base.

Recette exécutable · 14 langages
Files & Datafilesmetadatastatmtimefile-size

Every language

14 langages, copy-ready. One at a time with syntax highlighting, or all inline.

JSJavaScript
import { stat } from 'node:fs/promises';

// One call, two facts. Rejects with ENOENT for a missing path —
// catching is cheaper AND safer than testing existence first.
let info;
try {
  info = await stat('data.csv');
} catch (err) {
  if (err.code === 'ENOENT') throw new Error('data.csv not found');
  throw err;
}

console.log(info.size);                 // bytes
console.log(info.mtime.toISOString());  // UTC, ISO 8601

// Freshness check: mtimeMs keeps the precision the filesystem reported
// (a Date object may round it away on nanosecond filesystems).
const staleAfterMs = 60 * 60 * 1000;
console.log(Date.now() - info.mtimeMs > staleAfterMs ? 'stale' : 'fresh');

stat follows symlinks; lstat (fs/promises) stats the link itself. The stat result is a snapshot — a second read of the file can change underneath you, so treat size/mtime as hints for decisions, not locks. statSync is the sync twin for one-shot scripts.

TSTypeScript
import { stat } from 'node:fs/promises';

interface FileFacts {
  size: number;        // bytes
  modifiedMs: number;  // epoch milliseconds
}

/** Returns null for a missing path instead of throwing. */
async function fileFacts(path: string): Promise<FileFacts | null> {
  try {
    const s = await stat(path);
    return { size: s.size, modifiedMs: s.mtimeMs };
  } catch (err) {
    if ((err as NodeJS.ErrnoException).code === 'ENOENT') return null;
    throw err;
  }
}

const facts = await fileFacts('data.csv');
console.log(
  facts ? `${facts.size} bytes, mtime ${new Date(facts.modifiedMs).toISOString()}` : 'missing',
);

Narrowing err.code with a cast is the idiomatic escape hatch until Node ships typed error classes. Everything else is plain JS — the win here is the FileFacts contract making the epoch-milliseconds convention explicit at the boundary.

GoGo
package main

import (
	"fmt"
	"log"
	"os"
)

func main() {
	fi, err := os.Stat("data.csv")
	if err != nil {
		log.Fatal(err) // missing file arrives here as os.ErrNotExist
	}

	fmt.Println(fi.Size())            // bytes (int64)
	fmt.Println(fi.ModTime().UTC())   // time.Time, ready to print
	fmt.Println(fi.ModTime().Unix())  // epoch seconds for wire formats
}

ModTime() returns a time.Time carrying the resolution the platform reported — compare two of them with .Equal (== compares monotonic clocks and location too). os.Lstat is the no-symlink-follow spelling; errors.Is(err, os.ErrNotExist) detects a missing file.

RsRust
use std::fs;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let meta = fs::metadata("data.csv")?;

    println!("{}", meta.len()); // bytes (u64)

    let modified = meta.modified()?; // SystemTime
    println!("modified {}s ago", modified.elapsed()?.as_secs());

    Ok(())
}

Std-only, no crates. metadata() follows symlinks, symlink_metadata() does not. Avoid the mtime()/atime() extension-trait methods — raw i64 seconds with no portability story; modified() returns SystemTime and errors (rather than lies) on filesystems without mtime.

PHPPHP
<?php
$path = 'data.csv';

// PHP caches stat results per path for the whole request — clear it
// when the file may have changed since the last filesize()/filemtime().
clearstatcache(true, $path);

$size = filesize($path);    // bytes
$mtime = filemtime($path);  // Unix timestamp

if ($size === false || $mtime === false) {
    throw new RuntimeException("stat failed for {$path}");
}

echo $size, ' bytes, modified ', date('c', $mtime), PHP_EOL;

filesize()/filemtime() emit a warning and return false for missing paths — the explicit check matters because 0 is also a legitimate size. clearstatcache() is load-bearing in long requests that rewrite files; one-shot scripts never notice the cache. OO spelling: (new SplFileInfo($path))->getSize() / ->getMTime().

PyPython
import datetime
import os

st = os.stat("data.csv")  # follows symlinks; os.lstat does not
print(st.st_size)         # bytes
print(st.st_mtime_ns)     # epoch nanoseconds — full filesystem precision
print(datetime.datetime.fromtimestamp(st.st_mtime, datetime.timezone.utc))

os.path.getsize()/getmtime() are the one-liner wrappers. st_mtime is a float — fine for display, lossy for equality after two writes inside the same second; compare st_mtime_ns instead. pathlib: Path('data.csv').stat() returns the same struct.

CC
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <sys/stat.h>
#include <time.h>

int main(void) {
    struct stat st;
    if (stat("data.csv", &st) == -1) { /* lstat() skips the symlink hop */
        perror("stat");
        return 1;
    }

    printf("%lld bytes\n", (long long)st.st_size);

    /* st_mtime is whole seconds; st_mtim is the timespec with .tv_nsec */
    char buf[32];
    struct tm tm;
    gmtime_r(&st.st_mtime, &tm);
    strftime(buf, sizeof buf, "%Y-%m-%dT%H:%M:%SZ", &tm);
    printf("modified %s\n", buf);
    return 0;
}

st_size is off_t; long long keeps it honest on 32-bit. st_mtim.tv_nsec carries the sub-second part on POSIX 2008 systems. On Windows use _stat64/_wstat64 and _gmtime64_s; FAT filesystems only give 2-second mtime granularity.

C++C++
#include <chrono>
#include <cstdint>
#include <filesystem>
#include <iomanip>
#include <iostream>
#include <system_error>

namespace fs = std::filesystem;

int main() {
    std::error_code ec; // the *_overloads never throw

    fs::file_time_type mtime = fs::last_write_time("data.csv", ec);
    if (ec) {
        std::cerr << "stat failed: " << ec.message() << '\n';
        return 1;
    }
    std::uintmax_t size = fs::file_size("data.csv", ec);

    std::cout << size << " bytes\n";

    // file_time_type has its own clock and epoch — clock_cast (C++20)
    // is the blessed bridge to system_clock, then to_time_t for display.
    auto sys = std::chrono::clock_cast<std::chrono::system_clock>(mtime);
    std::time_t tt = std::chrono::system_clock::to_time_t(sys);
    std::cout << std::put_time(std::gmtime(&tt), "modified %Y-%m-%d %H:%M:%S UTC\n");
}

C++17 for <filesystem>, C++20 for clock_cast — before it, converting file_time_type meant hand-rolling the epoch offset, which is exactly why people avoided last_write_time. The error_code overloads never throw; the plain ones throw filesystem_error. std::format("{:%F %T}", sys) is the chrono-formatting spelling on GCC 13+/MSVC.

C#C#
using System;
using System.IO;

var info = new FileInfo("data.csv");

Console.WriteLine($"{info.Length} bytes");
Console.WriteLine(info.LastWriteTimeUtc.ToString("u"));

if (DateTime.UtcNow - info.LastWriteTimeUtc > TimeSpan.FromHours(24))
    Console.WriteLine("stale");

FileInfo caches what it read at construction — call info.Refresh() before re-reading after an external change. Prefer the *Utc members: local LastWriteTime shifts with DST and the machine's zone. The static File.GetLastWriteTimeUtc(path) returns 1601-01-01 for a MISSING file instead of throwing — always pair it with File.Exists.

JvJava
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;

public class FileFacts {
    public static void main(String[] args) throws IOException {
        Path p = Path.of("data.csv");
        BasicFileAttributes attrs = Files.readAttributes(p, BasicFileAttributes.class);

        System.out.println(attrs.size() + " bytes");

        FileTime mtime = attrs.lastModifiedTime();
        System.out.println(mtime.toInstant()); // e.g. 2026-08-26T09:41:00Z

        // FileTime is Comparable — freshness check without Instant math
        FileTime hourAgo = FileTime.fromMillis(System.currentTimeMillis() - 3_600_000L);
        System.out.println(mtime.compareTo(hourAgo) < 0 ? "stale" : "fresh");
    }
}

The legacy java.io.File.length() returns 0L for a missing file — silent nonsense, no exception; Files.readAttributes throws NoSuchFileException instead, which is why NIO wins. One call also yields creationTime and lastAccessTime. Path.of is Java 11+.

SwSwift
import Foundation

let url = URL(fileURLWithPath: "data.csv")

do {
    // One fetch for every key you ask for — not one syscall per fact.
    let values = try url.resourceValues(
        forKeys: [.fileSizeKey, .contentModificationDateKey])

    print(values.fileSize ?? 0, "bytes")
    if let mtime = values.contentModificationDate {
        print(mtime.formatted(.iso8601))
    }
} catch {
    fatalError("stat failed: \(error)")
}

resourceValues fetches every requested key in one syscall; the older FileManager.attributesOfItem is the dictionary-based spelling. fileSizeKey is logical bytes — fileAllocatedSizeKey is what the disk actually holds. formatted(.iso8601) needs macOS 12/iOS 15+.

KtKotlin
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.attribute.BasicFileAttributes

fun main() {
    val path = Path.of("data.csv")
    val attrs = Files.readAttributes(path, BasicFileAttributes::class.java)

    println("${attrs.size()} bytes")
    println(attrs.lastModifiedTime().toInstant())

    val hourAgo = System.currentTimeMillis() - 3_600_000L
    println(if (attrs.lastModifiedTime().toMillis() < hourAgo) "stale" else "fresh")
}

Same JVM caveats as the Java impl: java.io.File.length() silently returns 0 for dead paths — readAttributes throws NoSuchFileException instead. Path.of needs JDK 11+ (or Kotlin 1.5 JVM target); File("data.csv").toPath() is the older spelling.

RbRuby
require 'time' # Time#iso8601

st = File.stat('data.csv') # File.lstat skips the symlink hop

puts st.size              # bytes
puts st.mtime             # Time in the local zone
puts st.mtime.utc.iso8601 # wire format

puts st.mtime > Time.now - 3600 ? 'fresh' : 'stale'

File.size/File.mtime are the one-shot shortcuts; File::Stat is a single syscall carrying everything, including st_ctime-style fields via the struct-like methods. mtime is a Time with sub-second precision where the filesystem provides it.

ZigZig
const std = @import("std");

pub fn main() !void {
    const st = try std.fs.cwd().statFile("data.csv");

    std.debug.print("{d} bytes\n", .{st.size});

    // mtime is i128 NANOSECONDS since the Unix epoch — all the
    // precision the filesystem gave, before any API rounds it away.
    std.debug.print("mtime ns: {d}\n", .{st.mtime});

    const secs = @divTrunc(st.mtime, std.time.ns_per_s);
    std.debug.print("modified {d} seconds after the epoch\n", .{secs});
}

Dir.statFile avoids opening the file (File.stat() is the open-handle twin). st.mtime is i128 nanoseconds since epoch — print or compare it directly rather than narrowing to seconds first, and mind the 2038-adjacent truncations you avoid by never storing i32/i64 seconds. Zig 0.12+ API spelling.