Skip to content

Obtener el tamaño de un archivo y su fecha de modificación snippet

Pregunta al filesystem por el tamaño de un archivo y su última modificación en una sola llamada a stat — el insumo bruto de la invalidación de caché («redescarga salvo que el archivo remoto sea más nuevo»), los avisos de contenido obsoleto, las comprobaciones de rotación de logs y los listados de directorio.

Pregunta al filesystem por el tamaño de un archivo y su última modificación en una sola llamada a stat — el insumo bruto de la invalidación de caché («redescarga salvo que el archivo remoto sea más nuevo»), los avisos de contenido obsoleto, las comprobaciones de rotación de logs y los listados de directorio. Las trampas recurrentes: los timestamps solo son tan precisos como el filesystem y la API (FAT redondea a 2 segundos, los campos de segundos enteros descartan en silencio los cambios sub-segundo — compara campos de nanosegundos o usa una tolerancia, nunca igualdad exacta), el mtime es orientativo y no una prueba (`touch` lo reescribe; copiar puede o no preservarlo — verifica con un checksum cuando importe), y varias APIs reportan un archivo ausente como 0 bytes / tiempo cero en lugar de fallar (el File.length() de Java, el File.GetLastWriteTimeUtc de .NET devuelve 1601-01-01), convirtiendo una ruta muerta en datos plausibles. SQL se omite: ningún lenguaje de consultas hace stat de rutas arbitrarias del filesystem del cliente — los metadatos de archivos pertenecen al host de la aplicación, no a la sesión de la base de datos.

Receta ejecutable · 14 lenguajes
Files & Datafilesmetadatastatmtimefile-size

Every language

14 lenguajes, 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.