~/recurring-dates
docs
New in v1.0.2

Timezone Support

Generate recurring dates in specific IANA timezones. Every standard timezone identifier is supported — from America/New_York to Asia/Kolkata to UTC.

Quick Start

Add TIMEZONE to any config object. Dates are still returned in your FORMAT, but the recurrence is resolved in that timezone's calendar day.

Basic timezone usage
import { generateRecurringDates } from "recurring-dates";

const result = generateRecurringDates({
  STARTS_ON: "01-03-2025",
  ENDS_ON:   "31-03-2025",
  FREQUENCY: "W",
  WEEK_DAYS: ["MON", "WED", "FRI"],
  TIMEZONE:  "America/New_York",   // <-- IANA timezone
});

console.log(result.text);  // "Every week on Monday, Wednesday and Friday"
console.log(result.dates); // ["03-03-2025", "05-03-2025", ...]

Timezone Utilities

A set of helper functions is exported from the package for common timezone operations.

isValidTimezone
isValidTimezone(timezone: string): boolean

Returns true if the string is a valid IANA timezone identifier. Handles null / undefined safely.

Example

isValidTimezone
import { isValidTimezone } from "recurring-dates";

isValidTimezone("America/New_York"); // true
isValidTimezone("Europe/London");    // true
isValidTimezone("Invalid/Zone");     // false
isValidTimezone(null);               // false
isValidTimezone(undefined);          // false
getUserTimezone
getUserTimezone(): string

Returns the user's current system timezone using the Intl API. Useful for automatically defaulting to the user's local timezone.

Example

getUserTimezone
import { getUserTimezone, generateRecurringDates } from "recurring-dates";

const userTz = getUserTimezone();
// e.g. "America/New_York", "Asia/Tokyo", "Europe/London"

// Auto-detect timezone for the user
const result = generateRecurringDates({
  STARTS_ON: "01-01-2025",
  ENDS_ON:   "31-01-2025",
  FREQUENCY: "D",
  TIMEZONE:  getUserTimezone(), // automatic
});
getTimezoneOffset
getTimezoneOffset(timezone: string): number

Returns the current UTC offset in hours for the timezone. Returns a decimal for half-hour offsets (e.g. 5.5 for Asia/Kolkata).

Example

getTimezoneOffset
import { getTimezoneOffset } from "recurring-dates";

getTimezoneOffset("UTC");            // 0
getTimezoneOffset("America/New_York"); // -5 (EST) or -4 (EDT)
getTimezoneOffset("Asia/Kolkata");   // 5.5
getTimezoneOffset("Asia/Tokyo");     // 9
getTimezoneOffsetString
getTimezoneOffsetString(timezone: string): string

Returns the offset as a formatted string suitable for display (e.g. "+05:30", "-08:00").

Example

getTimezoneOffsetString
import { getTimezoneOffsetString } from "recurring-dates";

getTimezoneOffsetString("UTC");              // "+00:00"
getTimezoneOffsetString("Asia/Kolkata");     // "+05:30"
getTimezoneOffsetString("America/New_York"); // "-05:00" or "-04:00"
formatDateInTimezone
formatDateInTimezone(date: Date, timezone: string, options?: Intl.DateTimeFormatOptions): string

Formats a JavaScript Date object for display in a specific timezone using the Intl API.

Example

formatDateInTimezone
import { formatDateInTimezone } from "recurring-dates";

const date = new Date("2025-03-05T00:00:00Z");

formatDateInTimezone(date, "UTC");              // "03/05/2025"
formatDateInTimezone(date, "Asia/Tokyo");       // "03/05/2025"
formatDateInTimezone(date, "America/New_York"); // "03/04/2025" (previous day!)
getTimezoneList
getTimezoneList(): Array<{ timezone: string; offset: string; offset_hours: number }>

Returns the full list of supported timezones with their current UTC offsets. Useful for building a timezone picker UI.

Example

getTimezoneList
import { getTimezoneList } from "recurring-dates";

const zones = getTimezoneList();
// [
//   { timezone: "America/New_York", offset: "-05:00", offset_hours: -5 },
//   { timezone: "Europe/London",    offset: "+00:00", offset_hours: 0  },
//   { timezone: "Asia/Kolkata",     offset: "+05:30", offset_hours: 5.5 },
//   { timezone: "Asia/Tokyo",       offset: "+09:00", offset_hours: 9  },
//   ...
// ]

// Build a select dropdown
const options = zones.map((z) => ({
  value: z.timezone,
  label: `${z.timezone} (${z.offset})`,
}));
SUPPORTED_TIMEZONES
SUPPORTED_TIMEZONES: string[]

Array of 40+ commonly used IANA timezone identifiers covering all major world regions.

Example

SUPPORTED_TIMEZONES
import { SUPPORTED_TIMEZONES } from "recurring-dates";

// Americas
"America/New_York", "America/Chicago", "America/Denver",
"America/Los_Angeles", "America/Toronto", "America/Sao_Paulo",

// Europe
"Europe/London", "Europe/Paris", "Europe/Berlin", "Europe/Moscow",

// Asia
"Asia/Dubai", "Asia/Kolkata", "Asia/Bangkok", "Asia/Singapore",
"Asia/Tokyo", "Asia/Seoul", "Asia/Shanghai",

// Australia & Pacific
"Australia/Sydney", "Australia/Melbourne", "Pacific/Auckland",

// Africa & UTC
"Africa/Cairo", "Africa/Johannesburg", "UTC", ...

Real-World Examples

Global Team Meetings

Generate weekly standups at the same local time for each regional team:

Global team meetings
import { generateRecurringDates } from "recurring-dates";

const regions = [
  { name: "US East",  timezone: "America/New_York" },
  { name: "UK",       timezone: "Europe/London"    },
  { name: "India",    timezone: "Asia/Kolkata"     },
  { name: "Japan",    timezone: "Asia/Tokyo"       },
];

regions.forEach(({ name, timezone }) => {
  const meetings = generateRecurringDates({
    STARTS_ON: "01-03-2025",
    ENDS_ON:   "31-03-2025",
    FREQUENCY: "W",
    WEEK_DAYS: ["MON", "WED"],
    TIMEZONE:  timezone,
  });
  console.log(`${name}: ${meetings.dates.length} standups`);
});
// US East: 8 standups
// UK:      8 standups
// India:   8 standups
// Japan:   8 standups

Auto-Detect User Timezone (React)

Use getUserTimezone() so schedules always reflect the visitor's local timezone without any manual selection:

React: auto-detect timezone
import { useRecurringDates, getUserTimezone } from "recurring-dates";

export function LocalSchedule({ start, end }) {
  const userTz = getUserTimezone(); // e.g., "Asia/Tokyo"

  const { dates, text } = useRecurringDates({
    STARTS_ON: start,
    ENDS_ON:   end,
    FREQUENCY: "W",
    WEEK_DAYS: ["TUE", "THU"],
    TIMEZONE:  userTz,
    FORMAT:    "YYYY-MM-DD",
  });

  return (
    <section>
      <p className="caption">{text} — in {userTz}</p>
      <ul>
        {dates.map((d) => <li key={d}>{d}</li>)}
      </ul>
    </section>
  );
}

Monthly Billing Per Customer Timezone

Send invoices on the 1st of each month in the customer's local timezone so they always arrive at the right time:

Billing per timezone
import { generateRecurringDates } from "recurring-dates";

function getBillingSchedule(customerTimezone: string) {
  return generateRecurringDates({
    STARTS_ON:   "01-01-2025",
    ENDS_ON:     "31-12-2025",
    FREQUENCY:   "M",
    MONTH_DATES: [1],
    TIMEZONE:    customerTimezone,
    FORMAT:      "YYYY-MM-DD",
  });
}

// Australian customer — first of month in AEST
const { dates } = getBillingSchedule("Australia/Sydney");
// ["2025-01-01", "2025-02-01", ..., "2025-12-01"]

Staggered Maintenance Windows

Schedule monthly maintenance at 2 AM local time for each server region so customers are never all affected at once:

Maintenance windows
import { generateRecurringDates } from "recurring-dates";

const regions = {
  "us-east-1": "America/New_York",
  "eu-west-1":  "Europe/London",
  "ap-north-1": "Asia/Tokyo",
};

const schedule = Object.entries(regions).map(([region, tz]) => ({
  region,
  maintenance: generateRecurringDates({
    STARTS_ON:   "01-01-2025",
    ENDS_ON:     "31-12-2025",
    FREQUENCY:   "M",
    MONTH_DATES: [28],   // last few days of month
    TIMEZONE:    tz,
    FORMAT:      "YYYY-MM-DD",
  }).dates,
}));

Validation & Error Handling

When an invalid timezone is supplied the function returns the standard error shape — no exceptions thrown.

Error handling
const result = generateRecurringDates({
  STARTS_ON: "01-01-2025",
  ENDS_ON:   "31-01-2025",
  FREQUENCY: "D",
  TIMEZONE:  "Not/ATimezone",
});

console.log(result);
// {
//   dates: [],
//   error: "Invalid timezone: 'Not/ATimezone'. Use a valid IANA timezone
//           identifier (e.g., 'America/New_York', 'Europe/London', 'Asia/Tokyo')"
// }

// Omitting TIMEZONE (or passing null/undefined) is perfectly fine —
// the library falls back to local calendar day resolution.
const ok = generateRecurringDates({
  STARTS_ON: "01-01-2025",
  ENDS_ON:   "31-01-2025",
  FREQUENCY: "D",
  // TIMEZONE not set — no error
});

Supported Timezones

Any valid IANA timezone identifier works. The table below shows the 40+ timezones exported as SUPPORTED_TIMEZONES.

RegionTimezoneOffset
AmericasAmerica/New_YorkUTC-5 / UTC-4
AmericasAmerica/ChicagoUTC-6 / UTC-5
AmericasAmerica/DenverUTC-7 / UTC-6
AmericasAmerica/Los_AngelesUTC-8 / UTC-7
AmericasAmerica/AnchorageUTC-9 / UTC-8
AmericasPacific/HonoluluUTC-10
AmericasAmerica/TorontoUTC-5 / UTC-4
AmericasAmerica/Mexico_CityUTC-6 / UTC-5
AmericasAmerica/Sao_PauloUTC-3
AmericasAmerica/Buenos_AiresUTC-3
EuropeEurope/LondonUTC+0 / UTC+1
EuropeEurope/ParisUTC+1 / UTC+2
EuropeEurope/BerlinUTC+1 / UTC+2
EuropeEurope/MadridUTC+1 / UTC+2
EuropeEurope/RomeUTC+1 / UTC+2
EuropeEurope/MoscowUTC+3
EuropeEurope/IstanbulUTC+3
AsiaAsia/DubaiUTC+4
AsiaAsia/KolkataUTC+5:30
AsiaAsia/BangkokUTC+7
AsiaAsia/SingaporeUTC+8
AsiaAsia/ShanghaiUTC+8
AsiaAsia/TokyoUTC+9
AsiaAsia/SeoulUTC+9
OceaniaAustralia/SydneyUTC+10 / UTC+11
OceaniaAustralia/PerthUTC+8
OceaniaPacific/AucklandUTC+12 / UTC+13
AfricaAfrica/CairoUTC+2 / UTC+3
AfricaAfrica/JohannesburgUTC+2
AfricaAfrica/LagosUTC+1
UTCUTCUTC+0

Config Key Used

TIMEZONE — optional string. Accepts any valid IANA timezone identifier. Defaults to local time when omitted or null.