Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1414,6 +1414,7 @@ The `pino.stdTimeFunctions` object provides a very small set of common functions
* `pino.stdTimeFunctions.unixTime`: Seconds since Unix epoch
* `pino.stdTimeFunctions.nullTime`: Clears timestamp property (Used when `timestamp: false`)
* `pino.stdTimeFunctions.isoTime`: ISO 8601-formatted time in UTC
* `pino.stdTimeFunctions.isoTimeNano`: RFC 3339-formatted time in UTC with nanosecond precision

* See [`timestamp` option](#opt-timestamp)

Expand Down
30 changes: 29 additions & 1 deletion lib/time.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,32 @@ const unixTime = () => `,"time":${Math.round(Date.now() / 1000.0)}`

const isoTime = () => `,"time":"${new Date(Date.now()).toISOString()}"` // using Date.now() for testability

module.exports = { nullTime, epochTime, unixTime, isoTime }
const NS_PER_MS = 1_000_000n
const NS_PER_SEC = 1_000_000_000n

const startWallTimeNs = BigInt(Date.now()) * NS_PER_MS
const startHrTime = process.hrtime.bigint()

const isoTimeNano = () => {
const elapsedNs = process.hrtime.bigint() - startHrTime
const currentTimeNs = startWallTimeNs + elapsedNs

const secondsSinceEpoch = currentTimeNs / NS_PER_SEC
const nanosWithinSecond = currentTimeNs % NS_PER_SEC

const msSinceEpoch = Number(secondsSinceEpoch * 1000n + nanosWithinSecond / 1_000_000n)
const date = new Date(msSinceEpoch)

const year = date.getUTCFullYear()
const month = (date.getUTCMonth() + 1).toString().padStart(2, '0')
const day = date.getUTCDate().toString().padStart(2, '0')
const hours = date.getUTCHours().toString().padStart(2, '0')
const minutes = date.getUTCMinutes().toString().padStart(2, '0')
const seconds = date.getUTCSeconds().toString().padStart(2, '0')

return `,"time":"${year}-${month}-${day}T${hours}:${minutes}:${seconds}.${nanosWithinSecond
.toString()
.padStart(9, '0')}Z"`
}

module.exports = { nullTime, epochTime, unixTime, isoTime, isoTimeNano }
4 changes: 4 additions & 0 deletions pino.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,10 @@ declare namespace pino {
* Returns ISO 8601-formatted time in UTC
*/
isoTime: TimeFn;
/*
* Returns RFC 3339-formatted time in UTC
*/
isoTimeNano: TimeFn;
};

//// Exported functions
Expand Down
35 changes: 35 additions & 0 deletions test/timestamp-nano.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
'use strict'

/* eslint no-prototype-builtins: 0 */

const { test } = require('tap')
const { sink, once } = require('./helper')

test('pino.stdTimeFunctions.isoTimeNano returns RFC 3339 timestamps', async ({ equal }) => {
// Mock Date.now at module initialization time
const now = Date.now
Date.now = () => new Date('2025-08-01T15:03:45.000000000Z').getTime()

// Mock process.hrtime.bigint at module initialization time
const hrTimeBigint = process.hrtime.bigint
process.hrtime.bigint = () => 100000000000000n

const pino = require('../')

const opts = {
timestamp: pino.stdTimeFunctions.isoTimeNano
}
const stream = sink()

// Mock process.hrtime.bigint at invocation time, add 1 day to the timestamp
process.hrtime.bigint = () => 100000000000000n + 86400012345678n

const instance = pino(opts, stream)
instance.info('foobar')
const result = await once(stream, 'data')
equal(result.hasOwnProperty('time'), true)
equal(result.time, '2025-08-02T15:03:45.012345678Z')

Date.now = now
process.hrtime.bigint = hrTimeBigint
})
1 change: 1 addition & 0 deletions test/timestamp.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ test('pino exposes standard time functions', async ({ ok }) => {
ok(pino.stdTimeFunctions.unixTime)
ok(pino.stdTimeFunctions.nullTime)
ok(pino.stdTimeFunctions.isoTime)
ok(pino.stdTimeFunctions.isoTimeNano)
})

test('pino accepts external time functions', async ({ equal }) => {
Expand Down
1 change: 1 addition & 0 deletions test/types/pino-top-export.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ expectType<LevelMapping>(levels);
expectType<MultiStreamRes>(multistream(process.stdout));
expectType<SerializedError>(stdSerializers.err({} as any));
expectType<string>(stdTimeFunctions.isoTime());
expectType<string>(stdTimeFunctions.isoTimeNano());
expectType<string>(version);

// Can't test against `unique symbol`, see https://github.com/SamVerschueren/tsd/issues/49
Expand Down
4 changes: 4 additions & 0 deletions test/types/pino.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,10 @@ const withTimeFn = pino({
timestamp: pino.stdTimeFunctions.isoTime,
});

const withRFC3339TimeFn = pino({
timestamp: pino.stdTimeFunctions.isoTimeNano,
});

const withNestedKey = pino({
nestedKey: "payload",
});
Expand Down