Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 33 additions & 4 deletions doc/api/fs.md
Original file line number Diff line number Diff line change
Expand Up @@ -4494,7 +4494,11 @@
atime: 2019-06-22T03:37:33.072Z,
mtime: 2019-06-22T03:36:54.583Z,
ctime: 2019-06-22T03:37:06.624Z,
birthtime: 2019-06-22T03:28:46.937Z
birthtime: 2019-06-22T03:28:46.937Z,
atimeInstant: 2019-06-22T03:37:33.071963Z,
mtimeInstant: 2019-06-22T03:36:54.5833518Z,
ctimeInstant: 2019-06-22T03:37:06.6235366Z,
birthtimeInstant: 2019-06-22T03:28:46.9372893Z
}
false
Stats {
Expand All @@ -4515,7 +4519,11 @@
atime: 2019-06-22T03:36:56.619Z,
mtime: 2019-06-22T03:36:54.584Z,
ctime: 2019-06-22T03:36:54.584Z,
birthtime: 2019-06-22T03:26:47.711Z
birthtime: 2019-06-22T03:26:47.711Z,
atimeInstant: 2019-06-22T03:36:56.6188555Z,
mtimeInstant: 2019-06-22T03:36:54.584Z,
ctimeInstant: 2019-06-22T03:36:54.5838145Z,
birthtimeInstant: 2019-06-22T03:26:47.7107478Z
}
```

Expand Down Expand Up @@ -7203,6 +7211,9 @@
<!-- YAML
added: v0.1.21
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/00000

Check warning on line 7215 in doc/api/fs.md

View workflow job for this annotation

GitHub Actions / lint-pr-url

pr-url doesn't match the URL of the current PR.
description: Added Temporal.Instant support.
- version:
- v22.0.0
- v20.13.0
Expand Down Expand Up @@ -7238,10 +7249,19 @@
mtimeMs: 1318289051000.1,
ctimeMs: 1318289051000.1,
birthtimeMs: 1318289051000.1,

// Instances of Date
atime: Mon, 10 Oct 2011 23:24:11 GMT,
mtime: Mon, 10 Oct 2011 23:24:11 GMT,
ctime: Mon, 10 Oct 2011 23:24:11 GMT,
birthtime: Mon, 10 Oct 2011 23:24:11 GMT }
birthtime: Mon, 10 Oct 2011 23:24:11 GMT,

// Instances of Temporal.Instant
atimeInstant: 2011-10-10T23:24:11.0001Z,
mtimeInstant: 2011-10-10T23:24:11.0001Z,
ctimeInstant: 2011-10-10T23:24:11.0001Z,
birthtimeInstant: 2011-10-10T23:24:11.0001Z
}
```

`bigint` version:
Expand All @@ -7266,10 +7286,19 @@
mtimeNs: 1318289051000000000n,
ctimeNs: 1318289051000000000n,
birthtimeNs: 1318289051000000000n,

// Instances of Date
atime: Mon, 10 Oct 2011 23:24:11 GMT,
mtime: Mon, 10 Oct 2011 23:24:11 GMT,
ctime: Mon, 10 Oct 2011 23:24:11 GMT,
birthtime: Mon, 10 Oct 2011 23:24:11 GMT }
birthtime: Mon, 10 Oct 2011 23:24:11 GMT,

// Instances of Temporal.Instant
atimeInstant: 2011-10-10T23:24:11Z,
mtimeInstant: 2011-10-10T23:24:11Z,
ctimeInstant: 2011-10-10T23:24:11Z,
birthtimeInstant: 2011-10-10T23:24:11Z
}
```

#### `stats.isBlockDevice()`
Expand Down
130 changes: 121 additions & 9 deletions lib/internal/fs/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const {
DatePrototypeGetTime,
ErrorCaptureStackTrace,
FunctionPrototypeCall,
MathFloor,
MathMin,
MathRound,
Number,
Expand Down Expand Up @@ -63,6 +64,10 @@ const {
const pathModule = require('path');
const kType = Symbol('type');
const kStats = Symbol('stats');
const kPartialAtimeNs = Symbol('partialAtimeNs');
const kPartialMtimeNs = Symbol('partialMtimeNs');
const kPartialCtimeNs = Symbol('partialCtimeNs');
const kPartialBirthtimeNs = Symbol('kPartialBirthtimeNs');
const assert = require('internal/assert');

const {
Expand Down Expand Up @@ -431,6 +436,11 @@ function nsFromTimeSpecBigInt(sec, nsec) {
return sec * kNsPerSecBigInt + nsec;
}

function instantFromTimeSpecMs(msec, nsec) {
// TODO(LiviaMedeiros): TemporalInstant primordial
return new Temporal.Instant(BigInt(MathFloor(msec / kMsPerSec)) * kNsPerSecBigInt + BigInt(nsec));
Comment on lines +452 to +453
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're probably going to want to support building node without Temporal support for a while

Suggested change
// TODO(LiviaMedeiros): TemporalInstant primordial
return new Temporal.Instant(BigInt(MathFloor(msec / kMsPerSec)) * kNsPerSecBigInt + BigInt(nsec));
if (Temporal == null) throw 'informative error';
// TODO(LiviaMedeiros): TemporalInstant primordial
return new Temporal.Instant(BigInt(MathFloor(msec / kMsPerSec)) * kNsPerSecBigInt + BigInt(nsec));

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, especially with mandatory Rust requirements.
Added the error, the message text is subject to changes (if this may land before enabling Temporal by default, it should also suggest --harmony-temporal flag)

}

// The Date constructor performs Math.floor() on the absolute value
// of the timestamp: https://tc39.es/ecma262/#sec-timeclip
// Since there may be a precision loss when the timestamp is
Expand Down Expand Up @@ -491,6 +501,102 @@ const lazyDateFields = {
},
};

const lazyTemporalFields = {
__proto__: null,
atimeInstant: {
__proto__: null,
enumerable: true,
configurable: true,
get() {
return this.atimeInstant = instantFromTimeSpecMs(this.atimeMs, this[kPartialAtimeNs]);
},
set(value) {
ObjectDefineProperty(this, 'atimeInstant', { __proto__: null, value, writable: true });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ObjectDefineProperty(this, 'atimeInstant', { __proto__: null, value, writable: true });
setOwnProperty(this, 'atimeInstant', value);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAICT this will make the property enumerable, even though the initial getter is not? I don't mind it, but this better be aligned with how Date setters behave.

Copy link
Contributor

@aduh95 aduh95 Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably add an additional optional enumerable argument to setOwnProperty

},
},
mtimeInstant: {
__proto__: null,
enumerable: true,
configurable: true,
get() {
return this.mtimeInstant = instantFromTimeSpecMs(this.mtimeMs, this[kPartialMtimeNs]);
},
set(value) {
ObjectDefineProperty(this, 'mtimeInstant', { __proto__: null, value, writable: true });
},
},
ctimeInstant: {
__proto__: null,
enumerable: true,
configurable: true,
get() {
return this.ctimeInstant = instantFromTimeSpecMs(this.ctimeMs, this[kPartialCtimeNs]);;
},
set(value) {
ObjectDefineProperty(this, 'ctimeInstant', { __proto__: null, value, writable: true });
},
},
birthtimeInstant: {
__proto__: null,
enumerable: true,
configurable: true,
get() {
return this.birthtimeInstant = instantFromTimeSpecMs(this.birthtimeMs, this[kPartialBirthtimeNs]);;
},
set(value) {
ObjectDefineProperty(this, 'birthtimeInstant', { __proto__: null, value, writable: true });
},
},
};

const lazyTemporalBigIntFields = {
__proto__: null,
atimeInstant: {
__proto__: null,
enumerable: true,
configurable: true,
get() {
return this.atimeInstant = new Temporal.Instant(this.atimeNs);
},
set(value) {
ObjectDefineProperty(this, 'atimeInstant', { __proto__: null, value, writable: true });
},
},
mtimeInstant: {
__proto__: null,
enumerable: true,
configurable: true,
get() {
return this.mtimeInstant = new Temporal.Instant(this.mtimeNs);
},
set(value) {
ObjectDefineProperty(this, 'mtimeInstant', { __proto__: null, value, writable: true });
},
},
ctimeInstant: {
__proto__: null,
enumerable: true,
configurable: true,
get() {
return this.ctimeInstant = new Temporal.Instant(this.ctimeNs);
},
set(value) {
ObjectDefineProperty(this, 'ctimeInstant', { __proto__: null, value, writable: true });
},
},
birthtimeInstant: {
__proto__: null,
enumerable: true,
configurable: true,
get() {
return this.birthtimeInstant = new Temporal.Instant(this.birthtimeNs);
},
set(value) {
ObjectDefineProperty(this, 'birthtimeInstant', { __proto__: null, value, writable: true });
},
},
};

function BigIntStats(dev, mode, nlink, uid, gid, rdev, blksize,
ino, size, blocks,
atimeNs, mtimeNs, ctimeNs, birthtimeNs) {
Expand All @@ -510,6 +616,7 @@ function BigIntStats(dev, mode, nlink, uid, gid, rdev, blksize,
ObjectSetPrototypeOf(BigIntStats.prototype, StatsBase.prototype);
ObjectSetPrototypeOf(BigIntStats, StatsBase);
ObjectDefineProperties(BigIntStats.prototype, lazyDateFields);
ObjectDefineProperties(BigIntStats.prototype, lazyTemporalBigIntFields);

BigIntStats.prototype._checkModeProperty = function(property) {
if (isWindows && (property === S_IFIFO || property === S_IFBLK ||
Expand All @@ -521,18 +628,23 @@ BigIntStats.prototype._checkModeProperty = function(property) {

function Stats(dev, mode, nlink, uid, gid, rdev, blksize,
ino, size, blocks,
atimeMs, mtimeMs, ctimeMs, birthtimeMs) {
atimeS, atimeNs, mtimeS, mtimeNs, ctimeS, ctimeNs, birthtimeS, birthtimeNs) {
FunctionPrototypeCall(StatsBase, this, dev, mode, nlink, uid, gid, rdev,
blksize, ino, size, blocks);
this.atimeMs = atimeMs;
this.mtimeMs = mtimeMs;
this.ctimeMs = ctimeMs;
this.birthtimeMs = birthtimeMs;
this.atimeMs = msFromTimeSpec(atimeS, atimeNs);
this.mtimeMs = msFromTimeSpec(mtimeS, mtimeNs);
this.ctimeMs = msFromTimeSpec(ctimeS, ctimeNs);
this.birthtimeMs = msFromTimeSpec(birthtimeS, birthtimeNs);
this[kPartialAtimeNs] = atimeNs;
this[kPartialMtimeNs] = mtimeNs;
this[kPartialCtimeNs] = ctimeNs;
this[kPartialBirthtimeNs] = birthtimeNs;
}

ObjectSetPrototypeOf(Stats.prototype, StatsBase.prototype);
ObjectSetPrototypeOf(Stats, StatsBase);
ObjectDefineProperties(Stats.prototype, lazyDateFields);
ObjectDefineProperties(Stats.prototype, lazyTemporalFields);

Stats.prototype._checkModeProperty = function(property) {
if (isWindows && (property === S_IFIFO || property === S_IFBLK ||
Expand Down Expand Up @@ -565,10 +677,10 @@ function getStatsFromBinding(stats, offset = 0) {
stats[3 + offset], stats[4 + offset], stats[5 + offset],
stats[6 + offset], stats[7 + offset], stats[8 + offset],
stats[9 + offset],
msFromTimeSpec(stats[10 + offset], stats[11 + offset]),
msFromTimeSpec(stats[12 + offset], stats[13 + offset]),
msFromTimeSpec(stats[14 + offset], stats[15 + offset]),
msFromTimeSpec(stats[16 + offset], stats[17 + offset]),
stats[10 + offset], stats[11 + offset], // atime
stats[12 + offset], stats[13 + offset], // mtime
stats[14 + offset], stats[15 + offset], // ctime
stats[16 + offset], stats[17 + offset], // birthtime
);
}

Expand Down
12 changes: 8 additions & 4 deletions test/parallel/test-fs-watchfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,14 @@ const expectedStatObject = new fs.Stats(
0, // ino
0, // size
0, // blocks
Date.UTC(1970, 0, 1, 0, 0, 0), // atime
Date.UTC(1970, 0, 1, 0, 0, 0), // mtime
Date.UTC(1970, 0, 1, 0, 0, 0), // ctime
Date.UTC(1970, 0, 1, 0, 0, 0) // birthtime
0, // atimeS
0, // atimeNs
0, // mtimeS
0, // mtimeNs
0, // ctime
0, // ctimeNs
0, // birthtime
0, // birthtimeNs
);

tmpdir.refresh();
Expand Down