npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@softations/flow-datetime

v1.0.5

Published

Date/time contract: instants vs calendar dates, timezone-safe by construction. Zero dependencies.

Readme

@softations/flow-datetime

A small date/time contract that makes the difference between an instant and a calendar date explicit, and keeps timezone handling correct by construction.

It is published publicly so it can be installed without registry authentication. It is maintained for internal use — the API is stable but driven by internal needs, so treat semver as a guide rather than a promise.

Why

Most timezone bugs are not caused by a missing date function. They are caused by having no agreed representation — nothing saying "a due date is a calendar day, an email timestamp is an absolute instant, and here is the one place a provider's exclusive end-date gets its ±1." Adding another date library does not fix that; agreeing on types does.

Install

npm install @softations/flow-datetime

Requires Node ≥ 20.19. The package is ESM-only (.mjs), with no build step.

The floor comes from CommonJS consumers, which load it via Node's require(esm) — verified as failing on 20.18.0 with ERR_REQUIRE_ESM and working from 20.19.0, where it was backported. ESM and bundler consumers have no such constraint: the code itself uses only Intl.DateTimeFormat with formatToParts/hourCycle, available since Node 14.

// ESM
import { todayIn, google } from "@softations/flow-datetime";

// CommonJS
const { todayIn, google } = require("@softations/flow-datetime");

The model

| Type | Representation | Meaning | |---|---|---| | Instant | "2026-08-04T13:00:00.000Z", or a Firestore Timestamp at rest | An absolute point on the timeline. A zone is applied only at render. | | PlainDate | "2026-08-04" — a string | A calendar day. No zone, no time. Identical for every viewer. | | PlainTime | "09:00" | Wall-clock time, no date. | | ZonedWallTime | { date, time, timeZone } | "9am in the user's zone". Becomes an Instant only at the provider boundary. | | DayRange | { start, end }, end inclusive | All-day events, multi-day tasks. |

Rules

  1. timeZone is always an explicit argument. Nothing here reads the host zone or process.env.TZ, so a result never depends on where the code runs. Pinning TZ=UTC on servers is still recommended: it makes any stray host-zone dependency fail identically everywhere instead of only in production.

  2. A PlainDate never round-trips through Date. new Date("2026-08-04") parses as UTC midnight; reading it back with local getters yields the previous day for every zone west of Greenwich. parsePlainDate throws on a Date argument specifically to catch this.

  3. End-exclusivity is a provider concern. Google Calendar's end.date, Microsoft Graph's all-day end and react-big-calendar's all-day end are all exclusive. Keep ranges inclusive in your domain and let toEventTimes apply the +1 exactly once.

  4. Never send an Instant and a timeZone together for the same field. Google lets an explicit offset win; Graph interprets the value in timeZone and ignores the offset — so an identical payload produces different absolute times per provider. Send either a Z instant with no zone sibling, or a naive datetime plus the zone. The adapters here send naive+zone, which both providers interpret identically.

  5. Nothing returns a bare Date except instantToDate, the deliberate escape hatch for calendar UI libraries and Timestamp.fromDate.

API

// zones
isValidTimeZone / requireTimeZone / zoneOffsetMs / wallTimeToInstantMs

// PlainDate
isPlainDate / parsePlainDate / plainDateFrom / plainDateParts / plainDateOf
plainDateWeekday / todayIn / startOfDayIn / addDays / daysBetween / comparePlainDates

// PlainTime
isPlainTime / parsePlainTime / plainTimeFrom / plainTimeParts

// ZonedWallTime
resolveWallTime / wallTimeOf

// Instant
toInstantMs / toInstantISO / instantToDate

// DayRange
dayRangeToExclusiveEnd / dayRangeFromExclusiveEnd

// natural language  (returns PlainDate | null, never a Date)
parseNaturalDate / parseNaturalTime

// normalisation
normalizeMailTimestamp / messageReceivedAt / messageReceivedMs
normalizeLegacyTaskKey / normalizeTaskBuckets / readTaskBuckets

// display  (timeZone is a REQUIRED argument)
formatDate / formatTime / formatDateTime / formatTimeRange
formatRelativeDay / formatDayAge / formatSyncAge / formatDateContext
isToday / isSameDayIn / isPastDay

// providers
google.toEventTimes / google.fromEventTimes
graph.toEventTimes  / graph.fromEventTimes / graph.PREFER_UTC_HEADER
providerDateTimeToInstant / eventPlainDate

TypeScript declarations ship with the package.

Zero dependencies

Only Intl, which carries the full IANA database and is the same primitive the major date libraries call underneath.

The tradeoff is real: roughly 60 lines of core.mjs do offset resolution and DST gap/ambiguity handling that Luxon or Temporal would provide. That is covered by the zone matrix below. Swapping the engine later is an internal change — the API surface does not move.

Tests

npm test         # one pass, host timezone
npm run test:tz  # the full matrix — what CI runs

Every test must pass in all six zones:

| Zone | Why | |---|---| | UTC | baseline | | America/New_York | negative offset + northern DST | | Australia/Sydney | positive offset + southern DST (transitions the other way) | | Asia/Kolkata | half-hour offset — catches whole-hour assumptions | | Asia/Karachi | whole-hour positive offset | | Pacific/Kiritimati | +14, where "today" is already "tomorrow" in UTC |

DST edge cases are asserted explicitly rather than assumed: 2026-03-08 02:30 America/New_York does not exist, 2026-11-01 01:30 happens twice, and a recurring 09:00 meeting across a transition must hold its wall clock while its UTC instant shifts.

Releasing

prepublishOnly runs the full zone matrix, so a publish cannot succeed with a failing timezone.

npm version patch|minor|major
npm publish

License

MIT