diff --git a/_tools/check_docs.ts b/_tools/check_docs.ts index a1b9e6dd9812..959b34bacb08 100644 --- a/_tools/check_docs.ts +++ b/_tools/check_docs.ts @@ -78,6 +78,7 @@ const ENTRY_POINTS = [ "../fs/unstable_symlink.ts", "../fs/unstable_truncate.ts", "../fs/unstable_types.ts", + "../fs/unstable_utime.ts", "../html/mod.ts", "../html/unstable_is_valid_custom_element_name.ts", "../http/mod.ts", diff --git a/_tools/node_test_runner/run_test.mjs b/_tools/node_test_runner/run_test.mjs index 893496015eb3..c97b972d9792 100644 --- a/_tools/node_test_runner/run_test.mjs +++ b/_tools/node_test_runner/run_test.mjs @@ -63,6 +63,7 @@ import "../../fs/unstable_symlink_test.ts"; import "../../fs/unstable_truncate_test.ts"; import "../../fs/unstable_lstat_test.ts"; import "../../fs/unstable_chmod_test.ts"; +import "../../fs/unstable_utime_test.ts"; for (const testDef of testDefinitions) { test(testDef.name, testDef.fn); diff --git a/fs/deno.json b/fs/deno.json index cadc852511d4..5007721f4610 100644 --- a/fs/deno.json +++ b/fs/deno.json @@ -26,6 +26,7 @@ "./unstable-real-path": "./unstable_real_path.ts", "./unstable-rename": "./unstable_rename.ts", "./unstable-stat": "./unstable_stat.ts", + "./unstable-utime": "./unstable_utime.ts", "./unstable-symlink": "./unstable_symlink.ts", "./unstable-truncate": "./unstable_truncate.ts", "./unstable-types": "./unstable_types.ts", diff --git a/fs/unstable_utime.ts b/fs/unstable_utime.ts new file mode 100644 index 000000000000..47bb01505964 --- /dev/null +++ b/fs/unstable_utime.ts @@ -0,0 +1,95 @@ +// Copyright 2018-2025 the Deno authors. MIT license. + +import { getNodeFs, isDeno } from "./_utils.ts"; +import { mapError } from "./_map_error.ts"; + +/** Changes the access (`atime`) and modification (`mtime`) times of a file + * system object referenced by `path`. Given times are either in seconds + * (UNIX epoch time) or as `Date` objects. + * + * Requires `allow-write` permission for the target path + * + * @example Usage + * + * ```ts + * import { assert } from "@std/assert" + * import { utime } from "@std/fs/unstable-utime"; + * import { stat } from "@std/fs/unstable-stat" + * + * const newAccessDate = new Date() + * const newModifiedDate = new Date() + * + * const fileBefore = await Deno.stat("README.md") + * await Deno.utime("README.md", newAccessDate, newModifiedDate) + * const fileAfter = await Deno.stat("README.md") + * + * assert(fileBefore.atime !== fileAfter.atime) + * assert(fileBefore.mtime !== fileAfter.mtime) + * ``` + * @tags allow-write + * @category File System + * @param path The path to the file to be updated + * @param atime The new access time + * @param mtime The new modification time + */ +export async function utime( + path: string | URL, + atime: number | Date, + mtime: number | Date, +): Promise { + if (isDeno) { + await Deno.utime(path, atime, mtime); + } else { + try { + await getNodeFs().promises.utimes(path, atime, mtime); + return; + } catch (error) { + throw mapError(error); + } + } +} + +/** Synchronously changes the access (`atime`) and modification (`mtime`) + * times of the file stream resource. Given times are either in seconds + * (UNIX epoch time) or as `Date` objects. + * + * Requires `allow-write` permission for the target path + * + * @example Usage + * + * ```ts + * import { assert } from "@std/assert" + * import { utimeSync } from "@std/fs/unstable-utime"; + * import { stat } from "@std/fs/unstable-stat" + * + * const newAccessDate = new Date() + * const newModifiedDate = new Date() + * + * const fileBefore = await Deno.stat("README.md") + * Deno.utimeSync("README.md", newAccessDate, newModifiedDate) + * const fileAfter = await Deno.stat("README.md") + * + * assert(fileBefore.atime !== fileAfter.atime) + * assert(fileBefore.mtime !== fileAfter.mtime) + * ``` + * @tags allow-write + * @category File System + * @param path The path to the file to be updated + * @param atime The new access time + * @param mtime The new modification time + */ +export function utimeSync( + path: string | URL, + atime: number | Date, + mtime: number | Date, +): void { + if (isDeno) { + return Deno.utimeSync(path, atime, mtime); + } else { + try { + getNodeFs().utimesSync(path, atime, mtime); + } catch (error) { + throw mapError(error); + } + } +} diff --git a/fs/unstable_utime_test.ts b/fs/unstable_utime_test.ts new file mode 100644 index 000000000000..6f8de67128af --- /dev/null +++ b/fs/unstable_utime_test.ts @@ -0,0 +1,47 @@ +// Copyright 2018-2025 the Deno authors. MIT license. +import { assert, assertRejects, assertThrows } from "@std/assert"; +import { utime, utimeSync } from "./unstable_utime.ts"; +import { NotFound } from "./unstable_errors.js"; + +import { statSync } from "node:fs"; + +const now = new Date(); +const filePath = "fs/testdata/copy_file.txt"; + +Deno.test("utime() change atime and mtime date", async () => { + const fileBefore = statSync(filePath); + + await utime(filePath, now, now); + + const fileAfter = statSync(filePath); + + assert(fileBefore.atime != fileAfter.atime); + assert(fileBefore.mtime != fileAfter.mtime); +}); + +Deno.test("utime() fail on NotFound file", async () => { + const randomFile = "foo.txt"; + + await assertRejects(async () => { + await utime(randomFile, now, now); + }, NotFound); +}); + +Deno.test("utimeSync() change atime and mtime data", () => { + const fileBefore = statSync(filePath); + + utimeSync(filePath, now, now); + + const fileAfter = statSync(filePath); + + assert(fileBefore.atime != fileAfter.atime); + assert(fileBefore.mtime != fileAfter.mtime); +}); + +Deno.test("utimeSync() fail on NotFound file", () => { + const randomFile = "foo.txt"; + + assertThrows(() => { + utimeSync(randomFile, now, now); + }, NotFound); +});