···11{
22 "name": "@kbilkis/cron-fast",
33- "version": "2.3.0",
33+ "version": "3.0.0",
44 "description": "Fast and tiny JavaScript/TypeScript cron parser with timezone support - works in Node.js, Deno, Bun, Cloudflare Workers, and browsers. Zero dependencies.",
55 "keywords": [
66 "javascript",
+1-1
package.json
···11{
22 "name": "cron-fast",
33- "version": "2.3.0",
33+ "version": "3.0.0",
44 "description": "Fast and tiny JavaScript/TypeScript cron parser with timezone support - works in Node.js, Deno, Bun, Cloudflare Workers, and browsers. Zero dependencies.",
55 "keywords": [
66 "browser",
-16
src/timezone.ts
···11-function isValidTimezone(timezone: string): boolean {
22- if (typeof timezone !== "string" || !timezone.trim()) return false;
33- try {
44- Intl.DateTimeFormat(undefined, { timeZone: timezone });
55- return true;
66- } catch {
77- return false;
88- }
99-}
1010-111/**
122 * Convert a UTC date to wall-clock time in the target timezone.
1313- * @throws {Error} If timezone is invalid
143 */
154export function convertToTimezone(date: Date, timezone: string): Date {
1616- if (!isValidTimezone(timezone)) throw new Error(`Invalid timezone: "${timezone}"`);
1717-185 const str = date.toLocaleString("en-US", {
196 timeZone: timezone,
207 year: "numeric",
···38253926/**
4027 * Convert a timezone-local date back to UTC (inverse of convertToTimezone).
4141- * @throws {Error} If timezone is invalid
4228 *
4329 * Note: During DST fall-back, multiple UTC times map to the same wall-clock time.
4430 * The result is implementation-defined. Avoid scheduling during DST transition hours
4531 * for predictable behavior.
4632 */
4733export function convertFromTimezone(date: Date, timezone: string): Date {
4848- if (!isValidTimezone(timezone)) throw new Error(`Invalid timezone: "${timezone}"`);
4949-5034 // Target time as a comparable number (for checking if we found it)
5135 const targetTime = Date.UTC(
5236 date.getUTCFullYear(),
+4-4
test/scheduler.test.ts
···14711471 it("should throw for invalid timezone in nextRun", () => {
14721472 const from = new Date("2026-03-15T14:00:00Z");
14731473 expect(() => nextRun("0 9 * * *", { from, timezone: "Invalid/Timezone" })).toThrow(
14741474- 'Invalid timezone: "Invalid/Timezone"',
14741474+ "Invalid time zone specified",
14751475 );
14761476 });
1477147714781478 it("should throw for invalid timezone in previousRun", () => {
14791479 const from = new Date("2026-03-15T14:00:00Z");
14801480 expect(() => previousRun("0 9 * * *", { from, timezone: "NotATimezone" })).toThrow(
14811481- 'Invalid timezone: "NotATimezone"',
14811481+ "Invalid time zone specified",
14821482 );
14831483 });
1484148414851485 it("should throw for invalid timezone in isMatch", () => {
14861486 const date = new Date("2026-03-15T09:00:00Z");
14871487 expect(() => isMatch("0 9 * * *", date, { timezone: "Fake/Zone" })).toThrow(
14881488- 'Invalid timezone: "Fake/Zone"',
14881488+ "Invalid time zone specified",
14891489 );
14901490 });
1491149114921492 it("should throw for empty timezone string in nextRun", () => {
14931493 const from = new Date("2026-03-15T14:00:00Z");
14941494- expect(() => nextRun("0 9 * * *", { from, timezone: "" })).toThrow('Invalid timezone: ""');
14941494+ expect(() => nextRun("0 9 * * *", { from, timezone: "" })).toThrow("Invalid time zone");
14951495 });
14961496 });
14971497 });