Provides a basic check whether an RFC 3339 timestamp is a leap second.
npm install is-leap-secondimport { isLeapSecond } from 'is-leap-second'
isLeapSecond('2016-12-31T23:59:60Z') // true
isLeapSecond('2017-01-01T00:59:60+01:00') // true (same UTC moment)
isLeapSecond('2016-12-31T23:59:60.5Z') // true (fractional seconds accepted)
isLeapSecond('2020-06-30T23:59:60Z') // false (not a known leap second date)
isLeapSecond('2016-12-31T23:59:59Z') // false (seconds != 60)
isLeapSecond('not-a-timestamp') // falseReturns true if timestamp is a known positive leap second, false
otherwise.
Parameters
| Name | Type | Description |
|---|---|---|
timestamp |
string |
An RFC 3339 timestamp with an explicit timezone offset, e.g. "2016-12-31T23:59:60Z". |
Returns boolean
Throws TypeError if timestamp is not a string.
Converts an RFC 3339 / ISO 8601 timestamp string (with explicit timezone offset)
to a Unix nanosecond timestamp as a bigint, handling leap seconds correctly.
- For ordinary timestamps, returns the equivalent of
new Date(timestamp).getTime() * 1_000_000n. - Fractional seconds are preserved up to 9 digits (nanoseconds). Fractions with more than 9 digits are truncated to nanosecond precision.
- For known leap seconds (e.g.,
"2016-12-31T23:59:60Z"), returns the nanosecond value for the leap second (i.e., one second after23:59:59). - For known leap-second timestamps, any fractional part is ignored (same behavior
as
isLeapSecond). - Returns
nullfor invalid or non-leap-second timestamps withseconds=60. - Throws
TypeErrorif the input is not a string.
import { toTime } from 'is-leap-second'
toTime('2016-12-31T23:59:60Z') // millisecond value for the leap second
toTime('2024-03-15T12:30:00+02:00') // same as BigInt(new Date(...).getTime()) * 1_000_000n
toTime('not-a-timestamp') // null
toTime(42) // throws TypeErrorParameters
| Name | Type | Description |
|---|---|---|
timestamp |
string |
An RFC 3339 timestamp with an explicit timezone offset, e.g. "2016-12-31T23:59:60Z". |
Returns bigint | null — Nanoseconds since the Unix epoch, or null if invalid.
Throws TypeError if timestamp is not a string.
Rounds a leap-second RFC 3339 timestamp down to the nearest valid instant accepted by the Temporal API.
Because leap seconds (:60) are not representable in the Temporal API, this
function normalises them to the last nanosecond of the preceding second:
<UTC-date>T23:59:59.999999999Z.
If the input is not a leap second (seconds field ≠ 60, or the
UTC-normalised time is not 23:59, or the date is not a known leap second),
the timestamp is returned unchanged.
import { roundLeapSecond } from 'is-leap-second'
roundLeapSecond('2016-12-31T23:59:60Z') // '2016-12-31T23:59:59.999999999Z'
roundLeapSecond('2017-01-01T00:59:60+01:00') // '2016-12-31T23:59:59.999999999Z'
roundLeapSecond('2016-12-31T23:59:60.5Z') // '2016-12-31T23:59:59.999999999Z'
roundLeapSecond('2016-12-31T23:59:59Z') // '2016-12-31T23:59:59Z' (unchanged)
roundLeapSecond('2020-06-30T23:59:60Z') // '2020-06-30T23:59:60Z' (not a known leap second, unchanged)
roundLeapSecond('not-a-timestamp') // 'not-a-timestamp' (unchanged)Parameters
| Name | Type | Description |
|---|---|---|
timestamp |
string |
An RFC 3339 timestamp with an explicit timezone offset, e.g. "2016-12-31T23:59:60Z". |
Returns string — A Temporal-compatible UTC instant string, or the original timestamp if it is not a leap second.
Throws TypeError if timestamp is not a string.
A timestamp is considered a leap second when both conditions hold:
- The seconds field equals
60. - After normalising to UTC, the date and time (
23:59:60 UTC) match one of the leap seconds announced by the IERS.
Timestamps without an explicit timezone (e.g. "2016-12-31T23:59:60") are
rejected — RFC 3339 requires a timezone designator.
Fractional seconds (e.g. "23:59:60.5Z") are accepted; only the integer part of
the seconds field is evaluated.
The library contains a hardcoded list of all 27 positive leap seconds up to and including 2016-12-31.
Source: IERS Bulletin C / NIST
When a new leap second is announced, run the following command to fetch the
latest data from the IANA time zone database and regenerate lib/leapSeconds.js:
npm run update-leap-secondsPackage sources (index.js, lib/) are plain JavaScript with
JSDoc type annotations. No compilation step is needed
before publishing — what you author is what gets distributed.
The key benefit of this approach is that the package can be linked directly into
other projects with npm link without releasing a new version first. This makes
it practical to test new features end-to-end in a consuming project before
committing to a release.
The test suite (index.test.ts) and maintenance scripts (scripts/update.ts)
are dev-only and never published, so they use real TypeScript syntax.
TypeScript checks the JavaScript sources via checkJs: true and emits
declaration files to dist/ with emitDeclarationOnly: true. No .js output
is produced. The prepublishOnly script in package.json runs this
automatically before every npm publish, but you can also run it manually to
verify types:
tsc -b tsconfig.src.jsonJSDoc cannot express every TypeScript feature (e.g. mapped types, template
literal types). When you need them, define the types in a dedicated .ts
type-only file and import them into JSDoc annotations via @import or
@typedef {import('./types.js').MyType} MyType. This keeps the distributed
files as plain JavaScript while still supporting the full TypeScript type system.
The test suite uses the Node.js built-in test runner (node:test) together with
the built-in assertion library (node:assert/strict). No third-party test
framework is installed.
Run the tests with coverage:
npm testThis executes c8 node --test, collecting coverage in the same step without a
separate runner invocation. The familiar describe / it API is used throughout
— the style will be recognisable to developers coming from Jest or Mocha.
Because node:test is part of Node.js itself, there is no test-framework
dependency to install, update, or audit. The minimum required Node.js version is
20, where node:test became stable.