@lacspace/market-clock 1.0.4 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -60,7 +60,7 @@ var MarketClock = class {
60
60
  if (this.isPreOpen(at)) return "pre-open";
61
61
  return "closed";
62
62
  }
63
- /** The next moment the regular session opens, at or after `from`. */
63
+ /** The next moment the regular session opens, strictly after `from`. */
64
64
  nextOpen(from = /* @__PURE__ */ new Date()) {
65
65
  const start = this.parts(from);
66
66
  for (let i = 0; i < 500; i++) {
@@ -74,7 +74,7 @@ var MarketClock = class {
74
74
  }
75
75
  throw new Error("no trading day found within 500 days");
76
76
  }
77
- /** The next moment the regular session closes, at or after `from`. */
77
+ /** The next moment the regular session closes, strictly after `from`. */
78
78
  nextClose(from = /* @__PURE__ */ new Date()) {
79
79
  const start = this.parts(from);
80
80
  for (let i = 0; i < 500; i++) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @lacspace/market-clock\n * Is the market open right now? When does it next open / close?\n *\n * A holiday-aware, timezone-correct trading clock for stock exchanges. Ships\n * with NSE / BSE (India) presets — India has no DST, so a fixed IST offset is\n * exact. Bring your own exchange spec or override the holiday list any time.\n *\n * Zero dependencies · isomorphic · fully typed.\n */\n\nexport interface Session {\n /** \"HH:MM\" 24-hour, in exchange-local time. */\n open: string;\n close: string;\n}\n\nexport interface ExchangeSpec {\n name: string;\n /** Minutes ahead of UTC (IST = +330). Exchanges without DST only. */\n offsetMinutes: number;\n regular: Session;\n preOpen?: Session;\n /** Weekday numbers that are always closed (0 = Sunday … 6 = Saturday). */\n weekend: number[];\n /** Full-day holidays as \"YYYY-MM-DD\" in exchange-local dates. */\n holidays: string[];\n}\n\nexport type MarketStatus = \"pre-open\" | \"open\" | \"closed\";\n\nfunction toMinutes(hhmm: string): number {\n const [h, m] = hhmm.split(\":\").map(Number);\n return (h || 0) * 60 + (m || 0);\n}\n\nfunction pad(n: number): string {\n return n < 10 ? \"0\" + n : String(n);\n}\n\ninterface LocalParts {\n y: number;\n m: number;\n d: number;\n day: number;\n minute: number;\n}\n\nexport class MarketClock {\n private readonly regOpen: number;\n private readonly regClose: number;\n private readonly preOpen: number | null;\n private readonly preClose: number | null;\n\n constructor(public readonly spec: ExchangeSpec) {\n this.regOpen = toMinutes(spec.regular.open);\n this.regClose = toMinutes(spec.regular.close);\n this.preOpen = spec.preOpen ? toMinutes(spec.preOpen.open) : null;\n this.preClose = spec.preOpen ? toMinutes(spec.preOpen.close) : null;\n }\n\n /** Exchange-local calendar parts for an instant. */\n private parts(date: Date): LocalParts {\n const local = new Date(date.getTime() + this.spec.offsetMinutes * 60000);\n return {\n y: local.getUTCFullYear(),\n m: local.getUTCMonth() + 1,\n d: local.getUTCDate(),\n day: local.getUTCDay(),\n minute: local.getUTCHours() * 60 + local.getUTCMinutes(),\n };\n }\n\n /** UTC epoch millis for an exchange-local calendar day + minute-of-day. */\n private utcFor(y: number, m: number, d: number, minute: number): number {\n return Date.UTC(y, m - 1, d) + minute * 60000 - this.spec.offsetMinutes * 60000;\n }\n\n private isoDate(p: { y: number; m: number; d: number }): string {\n return `${p.y}-${pad(p.m)}-${pad(p.d)}`;\n }\n\n isWeekend(at: Date = new Date()): boolean {\n return this.spec.weekend.includes(this.parts(at).day);\n }\n\n isHoliday(at: Date = new Date()): boolean {\n return this.spec.holidays.includes(this.isoDate(this.parts(at)));\n }\n\n /** A day the exchange trades (not weekend, not a holiday). */\n isTradingDay(at: Date = new Date()): boolean {\n return !this.isWeekend(at) && !this.isHoliday(at);\n }\n\n isOpen(at: Date = new Date()): boolean {\n if (!this.isTradingDay(at)) return false;\n const { minute } = this.parts(at);\n return minute >= this.regOpen && minute < this.regClose;\n }\n\n isPreOpen(at: Date = new Date()): boolean {\n if (this.preOpen === null || this.preClose === null) return false;\n if (!this.isTradingDay(at)) return false;\n const { minute } = this.parts(at);\n return minute >= this.preOpen && minute < this.preClose;\n }\n\n status(at: Date = new Date()): MarketStatus {\n if (this.isOpen(at)) return \"open\";\n if (this.isPreOpen(at)) return \"pre-open\";\n return \"closed\";\n }\n\n /** The next moment the regular session opens, at or after `from`. */\n nextOpen(from: Date = new Date()): Date {\n const start = this.parts(from);\n for (let i = 0; i < 500; i++) {\n const dayMs = Date.UTC(start.y, start.m - 1, start.d) + i * 86400000;\n const dp = new Date(dayMs);\n const p = { y: dp.getUTCFullYear(), m: dp.getUTCMonth() + 1, d: dp.getUTCDate() };\n if (this.spec.weekend.includes(dp.getUTCDay())) continue;\n if (this.spec.holidays.includes(this.isoDate(p))) continue;\n const openUtc = this.utcFor(p.y, p.m, p.d, this.regOpen);\n if (openUtc > from.getTime()) return new Date(openUtc);\n }\n throw new Error(\"no trading day found within 500 days\");\n }\n\n /** The next moment the regular session closes, at or after `from`. */\n nextClose(from: Date = new Date()): Date {\n const start = this.parts(from);\n for (let i = 0; i < 500; i++) {\n const dayMs = Date.UTC(start.y, start.m - 1, start.d) + i * 86400000;\n const dp = new Date(dayMs);\n const p = { y: dp.getUTCFullYear(), m: dp.getUTCMonth() + 1, d: dp.getUTCDate() };\n if (this.spec.weekend.includes(dp.getUTCDay())) continue;\n if (this.spec.holidays.includes(this.isoDate(p))) continue;\n const closeUtc = this.utcFor(p.y, p.m, p.d, this.regClose);\n if (closeUtc > from.getTime()) return new Date(closeUtc);\n }\n throw new Error(\"no trading day found within 500 days\");\n }\n\n /** Milliseconds until the session closes, or 0 if not currently open. */\n msToClose(at: Date = new Date()): number {\n if (!this.isOpen(at)) return 0;\n return this.nextClose(at).getTime() - at.getTime();\n }\n\n /** Milliseconds until the next open, or 0 if already open. */\n msToOpen(at: Date = new Date()): number {\n if (this.isOpen(at)) return 0;\n return this.nextOpen(at).getTime() - at.getTime();\n }\n}\n\nexport function createClock(spec: ExchangeSpec): MarketClock {\n return new MarketClock(spec);\n}\n\n/**\n * NSE trading holidays. Nationally-fixed days are reliable; the rest follow the\n * annual NSE circular and may shift year to year — verify and extend as needed\n * (spread via `new MarketClock({ ...NSE, holidays: [...NSE.holidays, ...] })`).\n */\nconst NSE_HOLIDAYS: string[] = [\n // 2025\n \"2025-02-26\", \"2025-03-14\", \"2025-03-31\", \"2025-04-10\", \"2025-04-14\",\n \"2025-04-18\", \"2025-05-01\", \"2025-08-15\", \"2025-08-27\", \"2025-10-02\",\n \"2025-10-21\", \"2025-10-22\", \"2025-11-05\", \"2025-12-25\",\n // 2026 (nationally-fixed observances; confirm exchange circular for the rest)\n \"2026-01-26\", \"2026-03-04\", \"2026-04-01\", \"2026-05-01\", \"2026-10-02\",\n \"2026-11-09\", \"2026-12-25\",\n];\n\n/** National Stock Exchange of India. Pre-open 09:00–09:15, regular 09:15–15:30 IST. */\nexport const NSE: ExchangeSpec = {\n name: \"NSE\",\n offsetMinutes: 330,\n preOpen: { open: \"09:00\", close: \"09:15\" },\n regular: { open: \"09:15\", close: \"15:30\" },\n weekend: [0, 6],\n holidays: NSE_HOLIDAYS,\n};\n\n/** Bombay Stock Exchange — same session timings and holiday calendar as NSE. */\nexport const BSE: ExchangeSpec = {\n ...NSE,\n name: \"BSE\",\n};\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":";;;AAmCA,SAAS,UAAU,IAAA,EAAsB;AACvC,EAAA,MAAM,CAAC,GAAG,CAAC,CAAA,GAAI,KAAK,KAAA,CAAM,GAAG,CAAA,CAAE,GAAA,CAAI,MAAM,CAAA;AACzC,EAAA,OAAA,CAAQ,CAAA,IAAK,CAAA,IAAK,EAAA,IAAM,CAAA,IAAK,CAAA,CAAA;AAC/B;AAEA,SAAS,IAAI,CAAA,EAAmB;AAC9B,EAAA,OAAO,CAAA,GAAI,EAAA,GAAK,GAAA,GAAM,CAAA,GAAI,OAAO,CAAC,CAAA;AACpC;AAUO,IAAM,cAAN,MAAkB;AAAA,EAMvB,YAA4B,IAAA,EAAoB;AAApB,IAAA,IAAA,CAAA,IAAA,GAAA,IAAA;AAC1B,IAAA,IAAA,CAAK,OAAA,GAAU,SAAA,CAAU,IAAA,CAAK,OAAA,CAAQ,IAAI,CAAA;AAC1C,IAAA,IAAA,CAAK,QAAA,GAAW,SAAA,CAAU,IAAA,CAAK,OAAA,CAAQ,KAAK,CAAA;AAC5C,IAAA,IAAA,CAAK,UAAU,IAAA,CAAK,OAAA,GAAU,UAAU,IAAA,CAAK,OAAA,CAAQ,IAAI,CAAA,GAAI,IAAA;AAC7D,IAAA,IAAA,CAAK,WAAW,IAAA,CAAK,OAAA,GAAU,UAAU,IAAA,CAAK,OAAA,CAAQ,KAAK,CAAA,GAAI,IAAA;AAAA,EACjE;AAAA;AAAA,EAGQ,MAAM,IAAA,EAAwB;AACpC,IAAA,MAAM,KAAA,GAAQ,IAAI,IAAA,CAAK,IAAA,CAAK,SAAQ,GAAI,IAAA,CAAK,IAAA,CAAK,aAAA,GAAgB,GAAK,CAAA;AACvE,IAAA,OAAO;AAAA,MACL,CAAA,EAAG,MAAM,cAAA,EAAe;AAAA,MACxB,CAAA,EAAG,KAAA,CAAM,WAAA,EAAY,GAAI,CAAA;AAAA,MACzB,CAAA,EAAG,MAAM,UAAA,EAAW;AAAA,MACpB,GAAA,EAAK,MAAM,SAAA,EAAU;AAAA,MACrB,QAAQ,KAAA,CAAM,WAAA,EAAY,GAAI,EAAA,GAAK,MAAM,aAAA;AAAc,KACzD;AAAA,EACF;AAAA;AAAA,EAGQ,MAAA,CAAO,CAAA,EAAW,CAAA,EAAW,CAAA,EAAW,MAAA,EAAwB;AACtE,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,CAAA,GAAI,CAAA,EAAG,CAAC,CAAA,GAAI,MAAA,GAAS,GAAA,GAAQ,IAAA,CAAK,IAAA,CAAK,aAAA,GAAgB,GAAA;AAAA,EAC5E;AAAA,EAEQ,QAAQ,CAAA,EAAgD;AAC9D,IAAA,OAAO,CAAA,EAAG,CAAA,CAAE,CAAC,CAAA,CAAA,EAAI,GAAA,CAAI,CAAA,CAAE,CAAC,CAAC,CAAA,CAAA,EAAI,GAAA,CAAI,CAAA,CAAE,CAAC,CAAC,CAAA,CAAA;AAAA,EACvC;AAAA,EAEA,SAAA,CAAU,EAAA,mBAAW,IAAI,IAAA,EAAK,EAAY;AACxC,IAAA,OAAO,IAAA,CAAK,KAAK,OAAA,CAAQ,QAAA,CAAS,KAAK,KAAA,CAAM,EAAE,EAAE,GAAG,CAAA;AAAA,EACtD;AAAA,EAEA,SAAA,CAAU,EAAA,mBAAW,IAAI,IAAA,EAAK,EAAY;AACxC,IAAA,OAAO,IAAA,CAAK,IAAA,CAAK,QAAA,CAAS,QAAA,CAAS,IAAA,CAAK,QAAQ,IAAA,CAAK,KAAA,CAAM,EAAE,CAAC,CAAC,CAAA;AAAA,EACjE;AAAA;AAAA,EAGA,YAAA,CAAa,EAAA,mBAAW,IAAI,IAAA,EAAK,EAAY;AAC3C,IAAA,OAAO,CAAC,KAAK,SAAA,CAAU,EAAE,KAAK,CAAC,IAAA,CAAK,UAAU,EAAE,CAAA;AAAA,EAClD;AAAA,EAEA,MAAA,CAAO,EAAA,mBAAW,IAAI,IAAA,EAAK,EAAY;AACrC,IAAA,IAAI,CAAC,IAAA,CAAK,YAAA,CAAa,EAAE,GAAG,OAAO,KAAA;AACnC,IAAA,MAAM,EAAE,MAAA,EAAO,GAAI,IAAA,CAAK,MAAM,EAAE,CAAA;AAChC,IAAA,OAAO,MAAA,IAAU,IAAA,CAAK,OAAA,IAAW,MAAA,GAAS,IAAA,CAAK,QAAA;AAAA,EACjD;AAAA,EAEA,SAAA,CAAU,EAAA,mBAAW,IAAI,IAAA,EAAK,EAAY;AACxC,IAAA,IAAI,KAAK,OAAA,KAAY,IAAA,IAAQ,IAAA,CAAK,QAAA,KAAa,MAAM,OAAO,KAAA;AAC5D,IAAA,IAAI,CAAC,IAAA,CAAK,YAAA,CAAa,EAAE,GAAG,OAAO,KAAA;AACnC,IAAA,MAAM,EAAE,MAAA,EAAO,GAAI,IAAA,CAAK,MAAM,EAAE,CAAA;AAChC,IAAA,OAAO,MAAA,IAAU,IAAA,CAAK,OAAA,IAAW,MAAA,GAAS,IAAA,CAAK,QAAA;AAAA,EACjD;AAAA,EAEA,MAAA,CAAO,EAAA,mBAAW,IAAI,IAAA,EAAK,EAAiB;AAC1C,IAAA,IAAI,IAAA,CAAK,MAAA,CAAO,EAAE,CAAA,EAAG,OAAO,MAAA;AAC5B,IAAA,IAAI,IAAA,CAAK,SAAA,CAAU,EAAE,CAAA,EAAG,OAAO,UAAA;AAC/B,IAAA,OAAO,QAAA;AAAA,EACT;AAAA;AAAA,EAGA,QAAA,CAAS,IAAA,mBAAa,IAAI,IAAA,EAAK,EAAS;AACtC,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA;AAC7B,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,GAAA,EAAK,CAAA,EAAA,EAAK;AAC5B,MAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,GAAA,CAAI,KAAA,CAAM,CAAA,EAAG,KAAA,CAAM,CAAA,GAAI,CAAA,EAAG,KAAA,CAAM,CAAC,CAAA,GAAI,CAAA,GAAI,KAAA;AAC5D,MAAA,MAAM,EAAA,GAAK,IAAI,IAAA,CAAK,KAAK,CAAA;AACzB,MAAA,MAAM,CAAA,GAAI,EAAE,CAAA,EAAG,EAAA,CAAG,gBAAe,EAAG,CAAA,EAAG,EAAA,CAAG,WAAA,EAAY,GAAI,CAAA,EAAG,CAAA,EAAG,EAAA,CAAG,YAAW,EAAE;AAChF,MAAA,IAAI,KAAK,IAAA,CAAK,OAAA,CAAQ,SAAS,EAAA,CAAG,SAAA,EAAW,CAAA,EAAG;AAChD,MAAA,IAAI,IAAA,CAAK,KAAK,QAAA,CAAS,QAAA,CAAS,KAAK,OAAA,CAAQ,CAAC,CAAC,CAAA,EAAG;AAClD,MAAA,MAAM,OAAA,GAAU,IAAA,CAAK,MAAA,CAAO,CAAA,CAAE,CAAA,EAAG,EAAE,CAAA,EAAG,CAAA,CAAE,CAAA,EAAG,IAAA,CAAK,OAAO,CAAA;AACvD,MAAA,IAAI,UAAU,IAAA,CAAK,OAAA,IAAW,OAAO,IAAI,KAAK,OAAO,CAAA;AAAA,IACvD;AACA,IAAA,MAAM,IAAI,MAAM,sCAAsC,CAAA;AAAA,EACxD;AAAA;AAAA,EAGA,SAAA,CAAU,IAAA,mBAAa,IAAI,IAAA,EAAK,EAAS;AACvC,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA;AAC7B,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,GAAA,EAAK,CAAA,EAAA,EAAK;AAC5B,MAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,GAAA,CAAI,KAAA,CAAM,CAAA,EAAG,KAAA,CAAM,CAAA,GAAI,CAAA,EAAG,KAAA,CAAM,CAAC,CAAA,GAAI,CAAA,GAAI,KAAA;AAC5D,MAAA,MAAM,EAAA,GAAK,IAAI,IAAA,CAAK,KAAK,CAAA;AACzB,MAAA,MAAM,CAAA,GAAI,EAAE,CAAA,EAAG,EAAA,CAAG,gBAAe,EAAG,CAAA,EAAG,EAAA,CAAG,WAAA,EAAY,GAAI,CAAA,EAAG,CAAA,EAAG,EAAA,CAAG,YAAW,EAAE;AAChF,MAAA,IAAI,KAAK,IAAA,CAAK,OAAA,CAAQ,SAAS,EAAA,CAAG,SAAA,EAAW,CAAA,EAAG;AAChD,MAAA,IAAI,IAAA,CAAK,KAAK,QAAA,CAAS,QAAA,CAAS,KAAK,OAAA,CAAQ,CAAC,CAAC,CAAA,EAAG;AAClD,MAAA,MAAM,QAAA,GAAW,IAAA,CAAK,MAAA,CAAO,CAAA,CAAE,CAAA,EAAG,EAAE,CAAA,EAAG,CAAA,CAAE,CAAA,EAAG,IAAA,CAAK,QAAQ,CAAA;AACzD,MAAA,IAAI,WAAW,IAAA,CAAK,OAAA,IAAW,OAAO,IAAI,KAAK,QAAQ,CAAA;AAAA,IACzD;AACA,IAAA,MAAM,IAAI,MAAM,sCAAsC,CAAA;AAAA,EACxD;AAAA;AAAA,EAGA,SAAA,CAAU,EAAA,mBAAW,IAAI,IAAA,EAAK,EAAW;AACvC,IAAA,IAAI,CAAC,IAAA,CAAK,MAAA,CAAO,EAAE,GAAG,OAAO,CAAA;AAC7B,IAAA,OAAO,KAAK,SAAA,CAAU,EAAE,EAAE,OAAA,EAAQ,GAAI,GAAG,OAAA,EAAQ;AAAA,EACnD;AAAA;AAAA,EAGA,QAAA,CAAS,EAAA,mBAAW,IAAI,IAAA,EAAK,EAAW;AACtC,IAAA,IAAI,IAAA,CAAK,MAAA,CAAO,EAAE,CAAA,EAAG,OAAO,CAAA;AAC5B,IAAA,OAAO,KAAK,QAAA,CAAS,EAAE,EAAE,OAAA,EAAQ,GAAI,GAAG,OAAA,EAAQ;AAAA,EAClD;AACF;AAEO,SAAS,YAAY,IAAA,EAAiC;AAC3D,EAAA,OAAO,IAAI,YAAY,IAAI,CAAA;AAC7B;AAOA,IAAM,YAAA,GAAyB;AAAA;AAAA,EAE7B,YAAA;AAAA,EAAc,YAAA;AAAA,EAAc,YAAA;AAAA,EAAc,YAAA;AAAA,EAAc,YAAA;AAAA,EACxD,YAAA;AAAA,EAAc,YAAA;AAAA,EAAc,YAAA;AAAA,EAAc,YAAA;AAAA,EAAc,YAAA;AAAA,EACxD,YAAA;AAAA,EAAc,YAAA;AAAA,EAAc,YAAA;AAAA,EAAc,YAAA;AAAA;AAAA,EAE1C,YAAA;AAAA,EAAc,YAAA;AAAA,EAAc,YAAA;AAAA,EAAc,YAAA;AAAA,EAAc,YAAA;AAAA,EACxD,YAAA;AAAA,EAAc;AAChB,CAAA;AAGO,IAAM,GAAA,GAAoB;AAAA,EAC/B,IAAA,EAAM,KAAA;AAAA,EACN,aAAA,EAAe,GAAA;AAAA,EACf,OAAA,EAAS,EAAE,IAAA,EAAM,OAAA,EAAS,OAAO,OAAA,EAAQ;AAAA,EACzC,OAAA,EAAS,EAAE,IAAA,EAAM,OAAA,EAAS,OAAO,OAAA,EAAQ;AAAA,EACzC,OAAA,EAAS,CAAC,CAAA,EAAG,CAAC,CAAA;AAAA,EACd,QAAA,EAAU;AACZ;AAGO,IAAM,GAAA,GAAoB;AAAA,EAC/B,GAAG,GAAA;AAAA,EACH,IAAA,EAAM;AACR","file":"index.cjs","sourcesContent":["/**\n * @lacspace/market-clock\n * Is the market open right now? When does it next open / close?\n *\n * A holiday-aware, timezone-correct trading clock for stock exchanges. Ships\n * with NSE / BSE (India) presets — India has no DST, so a fixed IST offset is\n * exact. Bring your own exchange spec or override the holiday list any time.\n *\n * Note: the bundled holiday lists are hand-maintained and only cover the years\n * listed. Extend them per year from the official exchange circular (e.g.\n * `new MarketClock({ ...NSE, holidays: [...NSE.holidays, \"2027-...\"] })`).\n *\n * Zero dependencies · isomorphic · fully typed.\n */\n\nexport interface Session {\n /** \"HH:MM\" 24-hour, in exchange-local time. */\n open: string;\n close: string;\n}\n\nexport interface ExchangeSpec {\n name: string;\n /** Minutes ahead of UTC (IST = +330). Exchanges without DST only. */\n offsetMinutes: number;\n regular: Session;\n preOpen?: Session;\n /** Weekday numbers that are always closed (0 = Sunday … 6 = Saturday). */\n weekend: number[];\n /** Full-day holidays as \"YYYY-MM-DD\" in exchange-local dates. */\n holidays: string[];\n}\n\nexport type MarketStatus = \"pre-open\" | \"open\" | \"closed\";\n\nfunction toMinutes(hhmm: string): number {\n const [h, m] = hhmm.split(\":\").map(Number);\n return (h || 0) * 60 + (m || 0);\n}\n\nfunction pad(n: number): string {\n return n < 10 ? \"0\" + n : String(n);\n}\n\ninterface LocalParts {\n y: number;\n m: number;\n d: number;\n day: number;\n minute: number;\n}\n\nexport class MarketClock {\n private readonly regOpen: number;\n private readonly regClose: number;\n private readonly preOpen: number | null;\n private readonly preClose: number | null;\n\n constructor(public readonly spec: ExchangeSpec) {\n this.regOpen = toMinutes(spec.regular.open);\n this.regClose = toMinutes(spec.regular.close);\n this.preOpen = spec.preOpen ? toMinutes(spec.preOpen.open) : null;\n this.preClose = spec.preOpen ? toMinutes(spec.preOpen.close) : null;\n }\n\n /** Exchange-local calendar parts for an instant. */\n private parts(date: Date): LocalParts {\n const local = new Date(date.getTime() + this.spec.offsetMinutes * 60000);\n return {\n y: local.getUTCFullYear(),\n m: local.getUTCMonth() + 1,\n d: local.getUTCDate(),\n day: local.getUTCDay(),\n minute: local.getUTCHours() * 60 + local.getUTCMinutes(),\n };\n }\n\n /** UTC epoch millis for an exchange-local calendar day + minute-of-day. */\n private utcFor(y: number, m: number, d: number, minute: number): number {\n return Date.UTC(y, m - 1, d) + minute * 60000 - this.spec.offsetMinutes * 60000;\n }\n\n private isoDate(p: { y: number; m: number; d: number }): string {\n return `${p.y}-${pad(p.m)}-${pad(p.d)}`;\n }\n\n isWeekend(at: Date = new Date()): boolean {\n return this.spec.weekend.includes(this.parts(at).day);\n }\n\n isHoliday(at: Date = new Date()): boolean {\n return this.spec.holidays.includes(this.isoDate(this.parts(at)));\n }\n\n /** A day the exchange trades (not weekend, not a holiday). */\n isTradingDay(at: Date = new Date()): boolean {\n return !this.isWeekend(at) && !this.isHoliday(at);\n }\n\n isOpen(at: Date = new Date()): boolean {\n if (!this.isTradingDay(at)) return false;\n const { minute } = this.parts(at);\n return minute >= this.regOpen && minute < this.regClose;\n }\n\n isPreOpen(at: Date = new Date()): boolean {\n if (this.preOpen === null || this.preClose === null) return false;\n if (!this.isTradingDay(at)) return false;\n const { minute } = this.parts(at);\n return minute >= this.preOpen && minute < this.preClose;\n }\n\n status(at: Date = new Date()): MarketStatus {\n if (this.isOpen(at)) return \"open\";\n if (this.isPreOpen(at)) return \"pre-open\";\n return \"closed\";\n }\n\n /** The next moment the regular session opens, strictly after `from`. */\n nextOpen(from: Date = new Date()): Date {\n const start = this.parts(from);\n for (let i = 0; i < 500; i++) {\n const dayMs = Date.UTC(start.y, start.m - 1, start.d) + i * 86400000;\n const dp = new Date(dayMs);\n const p = { y: dp.getUTCFullYear(), m: dp.getUTCMonth() + 1, d: dp.getUTCDate() };\n if (this.spec.weekend.includes(dp.getUTCDay())) continue;\n if (this.spec.holidays.includes(this.isoDate(p))) continue;\n const openUtc = this.utcFor(p.y, p.m, p.d, this.regOpen);\n if (openUtc > from.getTime()) return new Date(openUtc);\n }\n throw new Error(\"no trading day found within 500 days\");\n }\n\n /** The next moment the regular session closes, strictly after `from`. */\n nextClose(from: Date = new Date()): Date {\n const start = this.parts(from);\n for (let i = 0; i < 500; i++) {\n const dayMs = Date.UTC(start.y, start.m - 1, start.d) + i * 86400000;\n const dp = new Date(dayMs);\n const p = { y: dp.getUTCFullYear(), m: dp.getUTCMonth() + 1, d: dp.getUTCDate() };\n if (this.spec.weekend.includes(dp.getUTCDay())) continue;\n if (this.spec.holidays.includes(this.isoDate(p))) continue;\n const closeUtc = this.utcFor(p.y, p.m, p.d, this.regClose);\n if (closeUtc > from.getTime()) return new Date(closeUtc);\n }\n throw new Error(\"no trading day found within 500 days\");\n }\n\n /** Milliseconds until the session closes, or 0 if not currently open. */\n msToClose(at: Date = new Date()): number {\n if (!this.isOpen(at)) return 0;\n return this.nextClose(at).getTime() - at.getTime();\n }\n\n /** Milliseconds until the next open, or 0 if already open. */\n msToOpen(at: Date = new Date()): number {\n if (this.isOpen(at)) return 0;\n return this.nextOpen(at).getTime() - at.getTime();\n }\n}\n\nexport function createClock(spec: ExchangeSpec): MarketClock {\n return new MarketClock(spec);\n}\n\n/**\n * NSE trading holidays. Nationally-fixed days are reliable; the rest follow the\n * annual NSE circular and may shift year to year — verify and extend as needed\n * (spread via `new MarketClock({ ...NSE, holidays: [...NSE.holidays, ...] })`).\n */\nconst NSE_HOLIDAYS: string[] = [\n // 2025\n \"2025-02-26\", \"2025-03-14\", \"2025-03-31\", \"2025-04-10\", \"2025-04-14\",\n \"2025-04-18\", \"2025-05-01\", \"2025-08-15\", \"2025-08-27\", \"2025-10-02\",\n \"2025-10-21\", \"2025-10-22\", \"2025-11-05\", \"2025-12-25\",\n // 2026 (nationally-fixed observances; confirm exchange circular for the rest)\n \"2026-01-26\", \"2026-03-04\", \"2026-04-01\", \"2026-05-01\", \"2026-10-02\",\n \"2026-11-09\", \"2026-12-25\",\n];\n\n/** National Stock Exchange of India. Pre-open 09:00–09:15, regular 09:15–15:30 IST. */\nexport const NSE: ExchangeSpec = {\n name: \"NSE\",\n offsetMinutes: 330,\n preOpen: { open: \"09:00\", close: \"09:15\" },\n regular: { open: \"09:15\", close: \"15:30\" },\n weekend: [0, 6],\n holidays: NSE_HOLIDAYS,\n};\n\n/** Bombay Stock Exchange — same session timings and holiday calendar as NSE. */\nexport const BSE: ExchangeSpec = {\n ...NSE,\n name: \"BSE\",\n};\n"]}
package/dist/index.d.cts CHANGED
@@ -6,6 +6,10 @@
6
6
  * with NSE / BSE (India) presets — India has no DST, so a fixed IST offset is
7
7
  * exact. Bring your own exchange spec or override the holiday list any time.
8
8
  *
9
+ * Note: the bundled holiday lists are hand-maintained and only cover the years
10
+ * listed. Extend them per year from the official exchange circular (e.g.
11
+ * `new MarketClock({ ...NSE, holidays: [...NSE.holidays, "2027-..."] })`).
12
+ *
9
13
  * Zero dependencies · isomorphic · fully typed.
10
14
  */
11
15
  interface Session {
@@ -44,9 +48,9 @@ declare class MarketClock {
44
48
  isOpen(at?: Date): boolean;
45
49
  isPreOpen(at?: Date): boolean;
46
50
  status(at?: Date): MarketStatus;
47
- /** The next moment the regular session opens, at or after `from`. */
51
+ /** The next moment the regular session opens, strictly after `from`. */
48
52
  nextOpen(from?: Date): Date;
49
- /** The next moment the regular session closes, at or after `from`. */
53
+ /** The next moment the regular session closes, strictly after `from`. */
50
54
  nextClose(from?: Date): Date;
51
55
  /** Milliseconds until the session closes, or 0 if not currently open. */
52
56
  msToClose(at?: Date): number;
package/dist/index.d.ts CHANGED
@@ -6,6 +6,10 @@
6
6
  * with NSE / BSE (India) presets — India has no DST, so a fixed IST offset is
7
7
  * exact. Bring your own exchange spec or override the holiday list any time.
8
8
  *
9
+ * Note: the bundled holiday lists are hand-maintained and only cover the years
10
+ * listed. Extend them per year from the official exchange circular (e.g.
11
+ * `new MarketClock({ ...NSE, holidays: [...NSE.holidays, "2027-..."] })`).
12
+ *
9
13
  * Zero dependencies · isomorphic · fully typed.
10
14
  */
11
15
  interface Session {
@@ -44,9 +48,9 @@ declare class MarketClock {
44
48
  isOpen(at?: Date): boolean;
45
49
  isPreOpen(at?: Date): boolean;
46
50
  status(at?: Date): MarketStatus;
47
- /** The next moment the regular session opens, at or after `from`. */
51
+ /** The next moment the regular session opens, strictly after `from`. */
48
52
  nextOpen(from?: Date): Date;
49
- /** The next moment the regular session closes, at or after `from`. */
53
+ /** The next moment the regular session closes, strictly after `from`. */
50
54
  nextClose(from?: Date): Date;
51
55
  /** Milliseconds until the session closes, or 0 if not currently open. */
52
56
  msToClose(at?: Date): number;
package/dist/index.js CHANGED
@@ -58,7 +58,7 @@ var MarketClock = class {
58
58
  if (this.isPreOpen(at)) return "pre-open";
59
59
  return "closed";
60
60
  }
61
- /** The next moment the regular session opens, at or after `from`. */
61
+ /** The next moment the regular session opens, strictly after `from`. */
62
62
  nextOpen(from = /* @__PURE__ */ new Date()) {
63
63
  const start = this.parts(from);
64
64
  for (let i = 0; i < 500; i++) {
@@ -72,7 +72,7 @@ var MarketClock = class {
72
72
  }
73
73
  throw new Error("no trading day found within 500 days");
74
74
  }
75
- /** The next moment the regular session closes, at or after `from`. */
75
+ /** The next moment the regular session closes, strictly after `from`. */
76
76
  nextClose(from = /* @__PURE__ */ new Date()) {
77
77
  const start = this.parts(from);
78
78
  for (let i = 0; i < 500; i++) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":";AA+BA,SAAS,UAAU,IAAA,EAAsB;AACvC,EAAA,MAAM,CAAC,GAAG,CAAC,CAAA,GAAI,KAAK,KAAA,CAAM,GAAG,CAAA,CAAE,GAAA,CAAI,MAAM,CAAA;AACzC,EAAA,OAAA,CAAQ,CAAA,IAAK,CAAA,IAAK,EAAA,IAAM,CAAA,IAAK,CAAA,CAAA;AAC/B;AAEA,SAAS,IAAI,CAAA,EAAmB;AAC9B,EAAA,OAAO,CAAA,GAAI,EAAA,GAAK,GAAA,GAAM,CAAA,GAAI,OAAO,CAAC,CAAA;AACpC;AAUO,IAAM,cAAN,MAAkB;AAAA,EAMvB,YAA4B,IAAA,EAAoB;AAApB,IAAA,IAAA,CAAA,IAAA,GAAA,IAAA;AAC1B,IAAA,IAAA,CAAK,OAAA,GAAU,SAAA,CAAU,IAAA,CAAK,OAAA,CAAQ,IAAI,CAAA;AAC1C,IAAA,IAAA,CAAK,QAAA,GAAW,SAAA,CAAU,IAAA,CAAK,OAAA,CAAQ,KAAK,CAAA;AAC5C,IAAA,IAAA,CAAK,UAAU,IAAA,CAAK,OAAA,GAAU,UAAU,IAAA,CAAK,OAAA,CAAQ,IAAI,CAAA,GAAI,IAAA;AAC7D,IAAA,IAAA,CAAK,WAAW,IAAA,CAAK,OAAA,GAAU,UAAU,IAAA,CAAK,OAAA,CAAQ,KAAK,CAAA,GAAI,IAAA;AAAA,EACjE;AAAA;AAAA,EAGQ,MAAM,IAAA,EAAwB;AACpC,IAAA,MAAM,KAAA,GAAQ,IAAI,IAAA,CAAK,IAAA,CAAK,SAAQ,GAAI,IAAA,CAAK,IAAA,CAAK,aAAA,GAAgB,GAAK,CAAA;AACvE,IAAA,OAAO;AAAA,MACL,CAAA,EAAG,MAAM,cAAA,EAAe;AAAA,MACxB,CAAA,EAAG,KAAA,CAAM,WAAA,EAAY,GAAI,CAAA;AAAA,MACzB,CAAA,EAAG,MAAM,UAAA,EAAW;AAAA,MACpB,GAAA,EAAK,MAAM,SAAA,EAAU;AAAA,MACrB,QAAQ,KAAA,CAAM,WAAA,EAAY,GAAI,EAAA,GAAK,MAAM,aAAA;AAAc,KACzD;AAAA,EACF;AAAA;AAAA,EAGQ,MAAA,CAAO,CAAA,EAAW,CAAA,EAAW,CAAA,EAAW,MAAA,EAAwB;AACtE,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,CAAA,GAAI,CAAA,EAAG,CAAC,CAAA,GAAI,MAAA,GAAS,GAAA,GAAQ,IAAA,CAAK,IAAA,CAAK,aAAA,GAAgB,GAAA;AAAA,EAC5E;AAAA,EAEQ,QAAQ,CAAA,EAAgD;AAC9D,IAAA,OAAO,CAAA,EAAG,CAAA,CAAE,CAAC,CAAA,CAAA,EAAI,GAAA,CAAI,CAAA,CAAE,CAAC,CAAC,CAAA,CAAA,EAAI,GAAA,CAAI,CAAA,CAAE,CAAC,CAAC,CAAA,CAAA;AAAA,EACvC;AAAA,EAEA,SAAA,CAAU,EAAA,mBAAW,IAAI,IAAA,EAAK,EAAY;AACxC,IAAA,OAAO,IAAA,CAAK,KAAK,OAAA,CAAQ,QAAA,CAAS,KAAK,KAAA,CAAM,EAAE,EAAE,GAAG,CAAA;AAAA,EACtD;AAAA,EAEA,SAAA,CAAU,EAAA,mBAAW,IAAI,IAAA,EAAK,EAAY;AACxC,IAAA,OAAO,IAAA,CAAK,IAAA,CAAK,QAAA,CAAS,QAAA,CAAS,IAAA,CAAK,QAAQ,IAAA,CAAK,KAAA,CAAM,EAAE,CAAC,CAAC,CAAA;AAAA,EACjE;AAAA;AAAA,EAGA,YAAA,CAAa,EAAA,mBAAW,IAAI,IAAA,EAAK,EAAY;AAC3C,IAAA,OAAO,CAAC,KAAK,SAAA,CAAU,EAAE,KAAK,CAAC,IAAA,CAAK,UAAU,EAAE,CAAA;AAAA,EAClD;AAAA,EAEA,MAAA,CAAO,EAAA,mBAAW,IAAI,IAAA,EAAK,EAAY;AACrC,IAAA,IAAI,CAAC,IAAA,CAAK,YAAA,CAAa,EAAE,GAAG,OAAO,KAAA;AACnC,IAAA,MAAM,EAAE,MAAA,EAAO,GAAI,IAAA,CAAK,MAAM,EAAE,CAAA;AAChC,IAAA,OAAO,MAAA,IAAU,IAAA,CAAK,OAAA,IAAW,MAAA,GAAS,IAAA,CAAK,QAAA;AAAA,EACjD;AAAA,EAEA,SAAA,CAAU,EAAA,mBAAW,IAAI,IAAA,EAAK,EAAY;AACxC,IAAA,IAAI,KAAK,OAAA,KAAY,IAAA,IAAQ,IAAA,CAAK,QAAA,KAAa,MAAM,OAAO,KAAA;AAC5D,IAAA,IAAI,CAAC,IAAA,CAAK,YAAA,CAAa,EAAE,GAAG,OAAO,KAAA;AACnC,IAAA,MAAM,EAAE,MAAA,EAAO,GAAI,IAAA,CAAK,MAAM,EAAE,CAAA;AAChC,IAAA,OAAO,MAAA,IAAU,IAAA,CAAK,OAAA,IAAW,MAAA,GAAS,IAAA,CAAK,QAAA;AAAA,EACjD;AAAA,EAEA,MAAA,CAAO,EAAA,mBAAW,IAAI,IAAA,EAAK,EAAiB;AAC1C,IAAA,IAAI,IAAA,CAAK,MAAA,CAAO,EAAE,CAAA,EAAG,OAAO,MAAA;AAC5B,IAAA,IAAI,IAAA,CAAK,SAAA,CAAU,EAAE,CAAA,EAAG,OAAO,UAAA;AAC/B,IAAA,OAAO,QAAA;AAAA,EACT;AAAA;AAAA,EAGA,QAAA,CAAS,IAAA,mBAAa,IAAI,IAAA,EAAK,EAAS;AACtC,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA;AAC7B,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,GAAA,EAAK,CAAA,EAAA,EAAK;AAC5B,MAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,GAAA,CAAI,KAAA,CAAM,CAAA,EAAG,KAAA,CAAM,CAAA,GAAI,CAAA,EAAG,KAAA,CAAM,CAAC,CAAA,GAAI,CAAA,GAAI,KAAA;AAC5D,MAAA,MAAM,EAAA,GAAK,IAAI,IAAA,CAAK,KAAK,CAAA;AACzB,MAAA,MAAM,CAAA,GAAI,EAAE,CAAA,EAAG,EAAA,CAAG,gBAAe,EAAG,CAAA,EAAG,EAAA,CAAG,WAAA,EAAY,GAAI,CAAA,EAAG,CAAA,EAAG,EAAA,CAAG,YAAW,EAAE;AAChF,MAAA,IAAI,KAAK,IAAA,CAAK,OAAA,CAAQ,SAAS,EAAA,CAAG,SAAA,EAAW,CAAA,EAAG;AAChD,MAAA,IAAI,IAAA,CAAK,KAAK,QAAA,CAAS,QAAA,CAAS,KAAK,OAAA,CAAQ,CAAC,CAAC,CAAA,EAAG;AAClD,MAAA,MAAM,OAAA,GAAU,IAAA,CAAK,MAAA,CAAO,CAAA,CAAE,CAAA,EAAG,EAAE,CAAA,EAAG,CAAA,CAAE,CAAA,EAAG,IAAA,CAAK,OAAO,CAAA;AACvD,MAAA,IAAI,UAAU,IAAA,CAAK,OAAA,IAAW,OAAO,IAAI,KAAK,OAAO,CAAA;AAAA,IACvD;AACA,IAAA,MAAM,IAAI,MAAM,sCAAsC,CAAA;AAAA,EACxD;AAAA;AAAA,EAGA,SAAA,CAAU,IAAA,mBAAa,IAAI,IAAA,EAAK,EAAS;AACvC,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA;AAC7B,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,GAAA,EAAK,CAAA,EAAA,EAAK;AAC5B,MAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,GAAA,CAAI,KAAA,CAAM,CAAA,EAAG,KAAA,CAAM,CAAA,GAAI,CAAA,EAAG,KAAA,CAAM,CAAC,CAAA,GAAI,CAAA,GAAI,KAAA;AAC5D,MAAA,MAAM,EAAA,GAAK,IAAI,IAAA,CAAK,KAAK,CAAA;AACzB,MAAA,MAAM,CAAA,GAAI,EAAE,CAAA,EAAG,EAAA,CAAG,gBAAe,EAAG,CAAA,EAAG,EAAA,CAAG,WAAA,EAAY,GAAI,CAAA,EAAG,CAAA,EAAG,EAAA,CAAG,YAAW,EAAE;AAChF,MAAA,IAAI,KAAK,IAAA,CAAK,OAAA,CAAQ,SAAS,EAAA,CAAG,SAAA,EAAW,CAAA,EAAG;AAChD,MAAA,IAAI,IAAA,CAAK,KAAK,QAAA,CAAS,QAAA,CAAS,KAAK,OAAA,CAAQ,CAAC,CAAC,CAAA,EAAG;AAClD,MAAA,MAAM,QAAA,GAAW,IAAA,CAAK,MAAA,CAAO,CAAA,CAAE,CAAA,EAAG,EAAE,CAAA,EAAG,CAAA,CAAE,CAAA,EAAG,IAAA,CAAK,QAAQ,CAAA;AACzD,MAAA,IAAI,WAAW,IAAA,CAAK,OAAA,IAAW,OAAO,IAAI,KAAK,QAAQ,CAAA;AAAA,IACzD;AACA,IAAA,MAAM,IAAI,MAAM,sCAAsC,CAAA;AAAA,EACxD;AAAA;AAAA,EAGA,SAAA,CAAU,EAAA,mBAAW,IAAI,IAAA,EAAK,EAAW;AACvC,IAAA,IAAI,CAAC,IAAA,CAAK,MAAA,CAAO,EAAE,GAAG,OAAO,CAAA;AAC7B,IAAA,OAAO,KAAK,SAAA,CAAU,EAAE,EAAE,OAAA,EAAQ,GAAI,GAAG,OAAA,EAAQ;AAAA,EACnD;AAAA;AAAA,EAGA,QAAA,CAAS,EAAA,mBAAW,IAAI,IAAA,EAAK,EAAW;AACtC,IAAA,IAAI,IAAA,CAAK,MAAA,CAAO,EAAE,CAAA,EAAG,OAAO,CAAA;AAC5B,IAAA,OAAO,KAAK,QAAA,CAAS,EAAE,EAAE,OAAA,EAAQ,GAAI,GAAG,OAAA,EAAQ;AAAA,EAClD;AACF;AAEO,SAAS,YAAY,IAAA,EAAiC;AAC3D,EAAA,OAAO,IAAI,YAAY,IAAI,CAAA;AAC7B;AAOA,IAAM,YAAA,GAAyB;AAAA;AAAA,EAE7B,YAAA;AAAA,EAAc,YAAA;AAAA,EAAc,YAAA;AAAA,EAAc,YAAA;AAAA,EAAc,YAAA;AAAA,EACxD,YAAA;AAAA,EAAc,YAAA;AAAA,EAAc,YAAA;AAAA,EAAc,YAAA;AAAA,EAAc,YAAA;AAAA,EACxD,YAAA;AAAA,EAAc,YAAA;AAAA,EAAc,YAAA;AAAA,EAAc,YAAA;AAAA;AAAA,EAE1C,YAAA;AAAA,EAAc,YAAA;AAAA,EAAc,YAAA;AAAA,EAAc,YAAA;AAAA,EAAc,YAAA;AAAA,EACxD,YAAA;AAAA,EAAc;AAChB,CAAA;AAGO,IAAM,GAAA,GAAoB;AAAA,EAC/B,IAAA,EAAM,KAAA;AAAA,EACN,aAAA,EAAe,GAAA;AAAA,EACf,OAAA,EAAS,EAAE,IAAA,EAAM,OAAA,EAAS,OAAO,OAAA,EAAQ;AAAA,EACzC,OAAA,EAAS,EAAE,IAAA,EAAM,OAAA,EAAS,OAAO,OAAA,EAAQ;AAAA,EACzC,OAAA,EAAS,CAAC,CAAA,EAAG,CAAC,CAAA;AAAA,EACd,QAAA,EAAU;AACZ;AAGO,IAAM,GAAA,GAAoB;AAAA,EAC/B,GAAG,GAAA;AAAA,EACH,IAAA,EAAM;AACR","file":"index.js","sourcesContent":["/**\n * @lacspace/market-clock\n * Is the market open right now? When does it next open / close?\n *\n * A holiday-aware, timezone-correct trading clock for stock exchanges. Ships\n * with NSE / BSE (India) presets — India has no DST, so a fixed IST offset is\n * exact. Bring your own exchange spec or override the holiday list any time.\n *\n * Zero dependencies · isomorphic · fully typed.\n */\n\nexport interface Session {\n /** \"HH:MM\" 24-hour, in exchange-local time. */\n open: string;\n close: string;\n}\n\nexport interface ExchangeSpec {\n name: string;\n /** Minutes ahead of UTC (IST = +330). Exchanges without DST only. */\n offsetMinutes: number;\n regular: Session;\n preOpen?: Session;\n /** Weekday numbers that are always closed (0 = Sunday … 6 = Saturday). */\n weekend: number[];\n /** Full-day holidays as \"YYYY-MM-DD\" in exchange-local dates. */\n holidays: string[];\n}\n\nexport type MarketStatus = \"pre-open\" | \"open\" | \"closed\";\n\nfunction toMinutes(hhmm: string): number {\n const [h, m] = hhmm.split(\":\").map(Number);\n return (h || 0) * 60 + (m || 0);\n}\n\nfunction pad(n: number): string {\n return n < 10 ? \"0\" + n : String(n);\n}\n\ninterface LocalParts {\n y: number;\n m: number;\n d: number;\n day: number;\n minute: number;\n}\n\nexport class MarketClock {\n private readonly regOpen: number;\n private readonly regClose: number;\n private readonly preOpen: number | null;\n private readonly preClose: number | null;\n\n constructor(public readonly spec: ExchangeSpec) {\n this.regOpen = toMinutes(spec.regular.open);\n this.regClose = toMinutes(spec.regular.close);\n this.preOpen = spec.preOpen ? toMinutes(spec.preOpen.open) : null;\n this.preClose = spec.preOpen ? toMinutes(spec.preOpen.close) : null;\n }\n\n /** Exchange-local calendar parts for an instant. */\n private parts(date: Date): LocalParts {\n const local = new Date(date.getTime() + this.spec.offsetMinutes * 60000);\n return {\n y: local.getUTCFullYear(),\n m: local.getUTCMonth() + 1,\n d: local.getUTCDate(),\n day: local.getUTCDay(),\n minute: local.getUTCHours() * 60 + local.getUTCMinutes(),\n };\n }\n\n /** UTC epoch millis for an exchange-local calendar day + minute-of-day. */\n private utcFor(y: number, m: number, d: number, minute: number): number {\n return Date.UTC(y, m - 1, d) + minute * 60000 - this.spec.offsetMinutes * 60000;\n }\n\n private isoDate(p: { y: number; m: number; d: number }): string {\n return `${p.y}-${pad(p.m)}-${pad(p.d)}`;\n }\n\n isWeekend(at: Date = new Date()): boolean {\n return this.spec.weekend.includes(this.parts(at).day);\n }\n\n isHoliday(at: Date = new Date()): boolean {\n return this.spec.holidays.includes(this.isoDate(this.parts(at)));\n }\n\n /** A day the exchange trades (not weekend, not a holiday). */\n isTradingDay(at: Date = new Date()): boolean {\n return !this.isWeekend(at) && !this.isHoliday(at);\n }\n\n isOpen(at: Date = new Date()): boolean {\n if (!this.isTradingDay(at)) return false;\n const { minute } = this.parts(at);\n return minute >= this.regOpen && minute < this.regClose;\n }\n\n isPreOpen(at: Date = new Date()): boolean {\n if (this.preOpen === null || this.preClose === null) return false;\n if (!this.isTradingDay(at)) return false;\n const { minute } = this.parts(at);\n return minute >= this.preOpen && minute < this.preClose;\n }\n\n status(at: Date = new Date()): MarketStatus {\n if (this.isOpen(at)) return \"open\";\n if (this.isPreOpen(at)) return \"pre-open\";\n return \"closed\";\n }\n\n /** The next moment the regular session opens, at or after `from`. */\n nextOpen(from: Date = new Date()): Date {\n const start = this.parts(from);\n for (let i = 0; i < 500; i++) {\n const dayMs = Date.UTC(start.y, start.m - 1, start.d) + i * 86400000;\n const dp = new Date(dayMs);\n const p = { y: dp.getUTCFullYear(), m: dp.getUTCMonth() + 1, d: dp.getUTCDate() };\n if (this.spec.weekend.includes(dp.getUTCDay())) continue;\n if (this.spec.holidays.includes(this.isoDate(p))) continue;\n const openUtc = this.utcFor(p.y, p.m, p.d, this.regOpen);\n if (openUtc > from.getTime()) return new Date(openUtc);\n }\n throw new Error(\"no trading day found within 500 days\");\n }\n\n /** The next moment the regular session closes, at or after `from`. */\n nextClose(from: Date = new Date()): Date {\n const start = this.parts(from);\n for (let i = 0; i < 500; i++) {\n const dayMs = Date.UTC(start.y, start.m - 1, start.d) + i * 86400000;\n const dp = new Date(dayMs);\n const p = { y: dp.getUTCFullYear(), m: dp.getUTCMonth() + 1, d: dp.getUTCDate() };\n if (this.spec.weekend.includes(dp.getUTCDay())) continue;\n if (this.spec.holidays.includes(this.isoDate(p))) continue;\n const closeUtc = this.utcFor(p.y, p.m, p.d, this.regClose);\n if (closeUtc > from.getTime()) return new Date(closeUtc);\n }\n throw new Error(\"no trading day found within 500 days\");\n }\n\n /** Milliseconds until the session closes, or 0 if not currently open. */\n msToClose(at: Date = new Date()): number {\n if (!this.isOpen(at)) return 0;\n return this.nextClose(at).getTime() - at.getTime();\n }\n\n /** Milliseconds until the next open, or 0 if already open. */\n msToOpen(at: Date = new Date()): number {\n if (this.isOpen(at)) return 0;\n return this.nextOpen(at).getTime() - at.getTime();\n }\n}\n\nexport function createClock(spec: ExchangeSpec): MarketClock {\n return new MarketClock(spec);\n}\n\n/**\n * NSE trading holidays. Nationally-fixed days are reliable; the rest follow the\n * annual NSE circular and may shift year to year — verify and extend as needed\n * (spread via `new MarketClock({ ...NSE, holidays: [...NSE.holidays, ...] })`).\n */\nconst NSE_HOLIDAYS: string[] = [\n // 2025\n \"2025-02-26\", \"2025-03-14\", \"2025-03-31\", \"2025-04-10\", \"2025-04-14\",\n \"2025-04-18\", \"2025-05-01\", \"2025-08-15\", \"2025-08-27\", \"2025-10-02\",\n \"2025-10-21\", \"2025-10-22\", \"2025-11-05\", \"2025-12-25\",\n // 2026 (nationally-fixed observances; confirm exchange circular for the rest)\n \"2026-01-26\", \"2026-03-04\", \"2026-04-01\", \"2026-05-01\", \"2026-10-02\",\n \"2026-11-09\", \"2026-12-25\",\n];\n\n/** National Stock Exchange of India. Pre-open 09:00–09:15, regular 09:15–15:30 IST. */\nexport const NSE: ExchangeSpec = {\n name: \"NSE\",\n offsetMinutes: 330,\n preOpen: { open: \"09:00\", close: \"09:15\" },\n regular: { open: \"09:15\", close: \"15:30\" },\n weekend: [0, 6],\n holidays: NSE_HOLIDAYS,\n};\n\n/** Bombay Stock Exchange — same session timings and holiday calendar as NSE. */\nexport const BSE: ExchangeSpec = {\n ...NSE,\n name: \"BSE\",\n};\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @lacspace/market-clock\n * Is the market open right now? When does it next open / close?\n *\n * A holiday-aware, timezone-correct trading clock for stock exchanges. Ships\n * with NSE / BSE (India) presets — India has no DST, so a fixed IST offset is\n * exact. Bring your own exchange spec or override the holiday list any time.\n *\n * Note: the bundled holiday lists are hand-maintained and only cover the years\n * listed. Extend them per year from the official exchange circular (e.g.\n * `new MarketClock({ ...NSE, holidays: [...NSE.holidays, \"2027-...\"] })`).\n *\n * Zero dependencies · isomorphic · fully typed.\n */\n\nexport interface Session {\n /** \"HH:MM\" 24-hour, in exchange-local time. */\n open: string;\n close: string;\n}\n\nexport interface ExchangeSpec {\n name: string;\n /** Minutes ahead of UTC (IST = +330). Exchanges without DST only. */\n offsetMinutes: number;\n regular: Session;\n preOpen?: Session;\n /** Weekday numbers that are always closed (0 = Sunday … 6 = Saturday). */\n weekend: number[];\n /** Full-day holidays as \"YYYY-MM-DD\" in exchange-local dates. */\n holidays: string[];\n}\n\nexport type MarketStatus = \"pre-open\" | \"open\" | \"closed\";\n\nfunction toMinutes(hhmm: string): number {\n const [h, m] = hhmm.split(\":\").map(Number);\n return (h || 0) * 60 + (m || 0);\n}\n\nfunction pad(n: number): string {\n return n < 10 ? \"0\" + n : String(n);\n}\n\ninterface LocalParts {\n y: number;\n m: number;\n d: number;\n day: number;\n minute: number;\n}\n\nexport class MarketClock {\n private readonly regOpen: number;\n private readonly regClose: number;\n private readonly preOpen: number | null;\n private readonly preClose: number | null;\n\n constructor(public readonly spec: ExchangeSpec) {\n this.regOpen = toMinutes(spec.regular.open);\n this.regClose = toMinutes(spec.regular.close);\n this.preOpen = spec.preOpen ? toMinutes(spec.preOpen.open) : null;\n this.preClose = spec.preOpen ? toMinutes(spec.preOpen.close) : null;\n }\n\n /** Exchange-local calendar parts for an instant. */\n private parts(date: Date): LocalParts {\n const local = new Date(date.getTime() + this.spec.offsetMinutes * 60000);\n return {\n y: local.getUTCFullYear(),\n m: local.getUTCMonth() + 1,\n d: local.getUTCDate(),\n day: local.getUTCDay(),\n minute: local.getUTCHours() * 60 + local.getUTCMinutes(),\n };\n }\n\n /** UTC epoch millis for an exchange-local calendar day + minute-of-day. */\n private utcFor(y: number, m: number, d: number, minute: number): number {\n return Date.UTC(y, m - 1, d) + minute * 60000 - this.spec.offsetMinutes * 60000;\n }\n\n private isoDate(p: { y: number; m: number; d: number }): string {\n return `${p.y}-${pad(p.m)}-${pad(p.d)}`;\n }\n\n isWeekend(at: Date = new Date()): boolean {\n return this.spec.weekend.includes(this.parts(at).day);\n }\n\n isHoliday(at: Date = new Date()): boolean {\n return this.spec.holidays.includes(this.isoDate(this.parts(at)));\n }\n\n /** A day the exchange trades (not weekend, not a holiday). */\n isTradingDay(at: Date = new Date()): boolean {\n return !this.isWeekend(at) && !this.isHoliday(at);\n }\n\n isOpen(at: Date = new Date()): boolean {\n if (!this.isTradingDay(at)) return false;\n const { minute } = this.parts(at);\n return minute >= this.regOpen && minute < this.regClose;\n }\n\n isPreOpen(at: Date = new Date()): boolean {\n if (this.preOpen === null || this.preClose === null) return false;\n if (!this.isTradingDay(at)) return false;\n const { minute } = this.parts(at);\n return minute >= this.preOpen && minute < this.preClose;\n }\n\n status(at: Date = new Date()): MarketStatus {\n if (this.isOpen(at)) return \"open\";\n if (this.isPreOpen(at)) return \"pre-open\";\n return \"closed\";\n }\n\n /** The next moment the regular session opens, strictly after `from`. */\n nextOpen(from: Date = new Date()): Date {\n const start = this.parts(from);\n for (let i = 0; i < 500; i++) {\n const dayMs = Date.UTC(start.y, start.m - 1, start.d) + i * 86400000;\n const dp = new Date(dayMs);\n const p = { y: dp.getUTCFullYear(), m: dp.getUTCMonth() + 1, d: dp.getUTCDate() };\n if (this.spec.weekend.includes(dp.getUTCDay())) continue;\n if (this.spec.holidays.includes(this.isoDate(p))) continue;\n const openUtc = this.utcFor(p.y, p.m, p.d, this.regOpen);\n if (openUtc > from.getTime()) return new Date(openUtc);\n }\n throw new Error(\"no trading day found within 500 days\");\n }\n\n /** The next moment the regular session closes, strictly after `from`. */\n nextClose(from: Date = new Date()): Date {\n const start = this.parts(from);\n for (let i = 0; i < 500; i++) {\n const dayMs = Date.UTC(start.y, start.m - 1, start.d) + i * 86400000;\n const dp = new Date(dayMs);\n const p = { y: dp.getUTCFullYear(), m: dp.getUTCMonth() + 1, d: dp.getUTCDate() };\n if (this.spec.weekend.includes(dp.getUTCDay())) continue;\n if (this.spec.holidays.includes(this.isoDate(p))) continue;\n const closeUtc = this.utcFor(p.y, p.m, p.d, this.regClose);\n if (closeUtc > from.getTime()) return new Date(closeUtc);\n }\n throw new Error(\"no trading day found within 500 days\");\n }\n\n /** Milliseconds until the session closes, or 0 if not currently open. */\n msToClose(at: Date = new Date()): number {\n if (!this.isOpen(at)) return 0;\n return this.nextClose(at).getTime() - at.getTime();\n }\n\n /** Milliseconds until the next open, or 0 if already open. */\n msToOpen(at: Date = new Date()): number {\n if (this.isOpen(at)) return 0;\n return this.nextOpen(at).getTime() - at.getTime();\n }\n}\n\nexport function createClock(spec: ExchangeSpec): MarketClock {\n return new MarketClock(spec);\n}\n\n/**\n * NSE trading holidays. Nationally-fixed days are reliable; the rest follow the\n * annual NSE circular and may shift year to year — verify and extend as needed\n * (spread via `new MarketClock({ ...NSE, holidays: [...NSE.holidays, ...] })`).\n */\nconst NSE_HOLIDAYS: string[] = [\n // 2025\n \"2025-02-26\", \"2025-03-14\", \"2025-03-31\", \"2025-04-10\", \"2025-04-14\",\n \"2025-04-18\", \"2025-05-01\", \"2025-08-15\", \"2025-08-27\", \"2025-10-02\",\n \"2025-10-21\", \"2025-10-22\", \"2025-11-05\", \"2025-12-25\",\n // 2026 (nationally-fixed observances; confirm exchange circular for the rest)\n \"2026-01-26\", \"2026-03-04\", \"2026-04-01\", \"2026-05-01\", \"2026-10-02\",\n \"2026-11-09\", \"2026-12-25\",\n];\n\n/** National Stock Exchange of India. Pre-open 09:00–09:15, regular 09:15–15:30 IST. */\nexport const NSE: ExchangeSpec = {\n name: \"NSE\",\n offsetMinutes: 330,\n preOpen: { open: \"09:00\", close: \"09:15\" },\n regular: { open: \"09:15\", close: \"15:30\" },\n weekend: [0, 6],\n holidays: NSE_HOLIDAYS,\n};\n\n/** Bombay Stock Exchange — same session timings and holiday calendar as NSE. */\nexport const BSE: ExchangeSpec = {\n ...NSE,\n name: \"BSE\",\n};\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lacspace/market-clock",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Holiday-aware, timezone-correct trading clock — is NSE/BSE open now, next open/close, pre-open, holidays. Bring your own exchange spec. Zero-dependency.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",