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

tz-zoned-time

v0.0.1

Published

Zero-dependency timezone math: convert wall-clock date and time in any IANA time zone to a UTC instant and back, DST-safe, using only Intl. Works in Cloudflare Workers, browsers, and Node.js.

Readme

tz-zoned-time

Zero-dependency timezone math for JavaScript and TypeScript. Convert a wall-clock date and time in any IANA time zone ("Asia/Seoul", "America/New_York") to a UTC instant and back, handle daylight saving time (DST) transitions predictably, and store date-only values without off-by-one-day bugs. It uses only the built-in Intl API, so the same code runs in Cloudflare Workers, edge and serverless runtimes, browsers, and Node.js. Use it as a small, isomorphic alternative to date-fns-tz or Luxon when you only need time zone conversion.

Why

Two things are easy to confuse:

  • Wall-clock time: the numbers a person reads off a clock ("9:00 AM"). Without a time zone it doesn't name a moment. 9:00 in Seoul and 9:00 in New York are different moments.
  • Instant: a single point in time, the same everywhere on Earth. You carry it around as a UTC Date.

If you convert between them with new Date(...) and local getters such as getDate(), the result depends on the runtime's time zone. Workers run in UTC and browsers run in the user's local zone, so the same code can shift dates by a day. tz-zoned-time gives the server and the client one shared, deterministic implementation.

Use cases

  • Scheduling: a user picks "Sep 1, 9:00 AM in Asia/Seoul". Turn it into a UTC instant for your database.
  • Displaying events: show a stored instant as the date and time in the viewer's time zone, for example in a calendar or a reminder list.
  • Date-only fields (due dates, birthdays): store them as a stable instant and read the same calendar date back from any time zone.
  • APIs, logs, and LLM prompts: format an instant as an ISO 8601 string with the local offset, such as 2026-09-02T10:30:00+09:00.

Install

npm install tz-zoned-time

Usage

Instant → wall-clock time in a time zone (for display)

import { dateInTz, timeInTz, tzOffsetMs } from "tz-zoned-time";

const instant = new Date("2026-08-19T15:30:00Z");

dateInTz(instant, "Asia/Seoul");   // → { year: 2026, month: 8, day: 20 }
timeInTz(instant, "Asia/Seoul");   // → { hour: 0, minute: 30 }
tzOffsetMs(instant, "Asia/Seoul"); // → 32400000 (+9h in ms)

Wall-clock time in a time zone → instant (for storage)

import { combineDateAndTime } from "tz-zoned-time";

combineDateAndTime(
  { year: 2026, month: 9, day: 1 },
  { time: { hour: 9, minute: 0 }, timezone: "Asia/Seoul" },
);
// → 2026-09-01T00:00:00.000Z

ISO 8601 string with the local offset

import { zonedIsoString } from "tz-zoned-time";

zonedIsoString(new Date("2026-09-02T01:30:00Z"), "Asia/Seoul");
// → "2026-09-02T10:30:00+09:00"

Date-only values

import { encodeCalendarDate, calendarDateOf } from "tz-zoned-time";

const stored = encodeCalendarDate({ year: 2026, month: 8, day: 20 });
// → 2026-08-20T00:00:00.000Z

calendarDateOf(stored); // → { year: 2026, month: 8, day: 20 }

API

| Function | Description | | --- | --- | | dateInTz(date, timezone) | Calendar date (CalendarDate) of an instant in the given time zone. | | timeInTz(date, timezone) | Wall-clock time (TimeOfDay) of an instant in the given time zone. | | tzOffsetMs(date, timezone) | How far the zone's clock is ahead of UTC at that instant, in ms (Asia/Seoul → 32400000). The sign is the opposite of Date#getTimezoneOffset(). | | combineDateAndTime(date, { time, timezone }) | The UTC instant for a calendar date plus a wall-clock time in a time zone. | | zonedIsoString(date, timezone) | ISO 8601 string with the local time and offset, to the second. | | encodeCalendarDate(date) | Encodes a calendar date as UTC midnight of that day for storage. | | calendarDateOf(instant) | Decodes a stored instant back into a calendar date. Doesn't depend on any time zone. |

Daylight saving time

combineDateAndTime computes the offset twice: once from a first guess, then again at the guessed moment. On DST transition days it follows one rule: pick the earlier instant for both skipped times (spring forward) and repeated times (fall back).

// 02:30 doesn't exist in New York on 2026-03-08 → 01:30 EST
combineDateAndTime(
  { year: 2026, month: 3, day: 8 },
  { time: { hour: 2, minute: 30 }, timezone: "America/New_York" },
); // → 2026-03-08T06:30:00.000Z

// 01:30 happens twice on 2026-11-01 → the first one (EDT)
combineDateAndTime(
  { year: 2026, month: 11, day: 1 },
  { time: { hour: 1, minute: 30 }, timezone: "America/New_York" },
); // → 2026-11-01T05:30:00.000Z

Decoding date-only values

calendarDateOf returns the UTC year, month, and day of instant + 12h ("round to noon"). Values from encodeCalendarDate always round-trip exactly. It also recovers older data saved as browser-local midnight, as long as the zone offset is in (−12h, +12h].

Known limitations: legacy local-midnight values written in UTC+13/+14 (for example Pacific/Kiritimati), and legacy values that aren't midnight and fall late in the local evening, can come back one day off.

Types

interface CalendarDate { year: number; month: number; day: number } // month is 1-based (1–12)
interface TimeOfDay { hour: number; minute: number }                // hour is 0–23
type ZonedTimeOfDay = { time: TimeOfDay; timezone: string }         // IANA zone name

month is 1-based, unlike the 0-based month in JavaScript's Date.

License

MIT