Timezone Helpers#

Timezone utilities for handling dates across different timezones. Uses the Intl API (built-in, zero-deps).

Timezone vs Date Library
These helpers convert instants across zones (parse, format, offset, grouping). For date-fns-style formatting tokens, distances, intervals, and 95 locales, see Date Library.

parseTime#

Parse a time string in a specific timezone:

typescript
import { parseTime } from "@buntok/core";

// Wall-clock string without zone info - interpreted in that timezone
const meeting = parseTime("2024-01-15 14:30", "America/New_York");
// → 2024-01-15T19:30:00.000Z (UTC instant, correct on any server TZ)

// Strings that already carry a zone are used as-is
parseTime("2024-01-15T14:30:00+07:00", "Asia/Jakarta").toISOString();
// "2024-01-15T07:30:00.000Z"

// Must include a date (bare times like "14:30" throw)
parseTime("14:30", "Asia/Jakarta");
// Error: Invalid date string: "14:30"

// Returns a UTC Date instant; print it with formatInTimezone below

formatInTimezone#

Format a Date object in a specific timezone:

typescript
import { formatInTimezone } from "@buntok/core";

const date = new Date("2024-01-15T14:30:00Z");

// 3rd arg is enum: "short" | "default" | "full" (not a custom pattern)
formatInTimezone(date, "Asia/Jakarta", "short");
// "2024-01-15 21:30" - YYYY-MM-DD HH:mm

formatInTimezone(date, "Asia/Jakarta", "default");
// "2024-01-15 21:30:00" - includes seconds

formatInTimezone(date, "Asia/Jakarta", "full");
// "2024-01-15 21:30:00.000" - adds milliseconds

toTimezoneParts#

Get individual timezone parts:

typescript
import { toTimezoneParts } from "@buntok/core";

const parts = toTimezoneParts(new Date(), "Asia/Jakarta");
// { year: 2024, month: 1, day: 15, hour: 14, minute: 30, second: 0 }
// (6 fields - no weekday)

nowInTimezone#

Get current time in a specific timezone:

typescript
import { nowInTimezone } from "@buntok/core";

const jakartaTime = nowInTimezone("Asia/Jakarta");
const nyTime = nowInTimezone("America/New_York");

getTimezoneOffset#

Get timezone offset in minutes from UTC:

typescript
import { getTimezoneOffset, getTimezoneOffsetString } from "@buntok/core";

getTimezoneOffset("Asia/Jakarta");      // -420 (UTC+7, negative = ahead)
getTimezoneOffset("America/New_York");  // 240 or 300 (DST, positive = behind)
getTimezoneOffset("UTC");               // 0

getTimezoneOffsetString("Asia/Jakarta"); // "+07:00"
getTimezoneOffsetString("America/New_York"); // "-05:00" or "-04:00" (DST)

isValidTimezone#

Check if a timezone string is valid:

typescript
import { isValidTimezone } from "@buntok/core";

isValidTimezone("Asia/Jakarta");     // true
isValidTimezone("America/New_York"); // true
isValidTimezone("Invalid/Zone");     // false

Additional Helpers#

FunctionDescription
getTimezoneOffsetString(tz)Offset as string (+07:00)
toISOWithTimezone(date, tz)ISO string with offset (2024-01-15T17:30:00+07:00)
groupByTimezone(items, field, tz, "day")Group items by hour/day/month/year
getGroupLabels(groupMap, groupBy, locale?)Map of group key → localized label
formatGroupLabel(key, groupBy, locale?)Localize a single key (default en-US)
typescript
import {
  toISOWithTimezone, groupByTimezone, getGroupLabels, formatGroupLabel,
} from "@buntok/core";
// GroupByKey = "hour"|"day"|"month"|"year"; GroupByTimezoneOptions { locale?, labelFormatter? }

toISOWithTimezone(new Date("2024-01-15T10:30:00Z"), "Asia/Jakarta");
// "2024-01-15T17:30:00+07:00"

const grouped = groupByTimezone(orders, "createdAt", "Asia/Jakarta", "day");
// Map<string, Order[]> - e.g. "2024-01-15" → [orders]

const labels = getGroupLabels(grouped, "day");
// Map { "2024-01-15" => "Monday, January 15, 2024" }
formatGroupLabel("2024-01-15", "day", "en-US"); // "Monday, January 15, 2024"

Full Example#

typescript
import {
  parseTime, formatInTimezone, nowInTimezone,
  getTimezoneOffset, getTimezoneOffsetString, isValidTimezone, toTimezoneParts,
} from "@buntok/core";

// API endpoint returning user's local time
app.get("/time/:timezone", (ctx) => {
  const { timezone } = ctx.params;

  if (!isValidTimezone(timezone)) {
    return ctx.error("Invalid timezone", 400);
  }

  return ctx.json({
    timezone,
    now: nowInTimezone(timezone),
    formatted: formatInTimezone(new Date(), timezone, "default"), // "short"|"default"|"full"
    offset: getTimezoneOffset(timezone), // -420 for Jakarta
    offsetStr: getTimezoneOffsetString(timezone), // "+07:00"
    parts: toTimezoneParts(new Date(), timezone), // { year, month, day, hour, minute, second }
  });
});