@lacspace/market-clock 1.0.4 → 1.0.6

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/README.md CHANGED
@@ -102,18 +102,24 @@ const custom = createClock({
102
102
 
103
103
  ## Licensing
104
104
 
105
- This package is **free** under the **[Lacspace Free Licence](https://lacspace.com/licenses/lacspace-free-1.0)** — MIT-equivalent freedoms. Use it in personal and commercial projects at no cost; just keep the notice.
105
+ This package is **free** under the **[Lacspace Free Licence](https://lacspace.com/licenses/lacspace-free-1.0)** — permissive freedoms. Use it in personal and commercial projects at no cost; just keep the notice.
106
106
 
107
107
  Not every Lacspace package is free. We also offer **Commercial** (paid), **Client-specific**, and **Private** (proprietary) packages under separate terms. See the full **[Lacspace Licence Centre](https://lacspace.com/licenses)**.
108
108
 
109
+ <!-- LACSPACE-DEV-PLATFORM -->
110
+
109
111
  ---
110
112
 
111
- <div align="center">
113
+ ## The Lacspace Developer Platform
112
114
 
113
- **Part of the Lacspace ecosystem — 35 zero-dependency, isomorphic TypeScript packages.**
115
+ `@lacspace/market-clock` is part of **63 zero-dependency, isomorphic TypeScript packages** — one standard library for the modern web. Explore the ecosystem:
114
116
 
115
- [All packages ↗](https://lacspace.com/packages) · [npm org ↗](https://www.npmjs.com/org/lacspace) · [Licence Centre ↗](https://lacspace.com/licenses) · [GitHub ↗](https://github.com/lacspace/npm-packages)
117
+ - 📦 **This package, documented** https://developer.lacspace.com/packages/market-clock
118
+ - 🗂️ **All 63 packages** — https://developer.lacspace.com/packages
119
+ - 🧭 **Developer handbook** — guides & runnable recipes — https://developer.lacspace.com/handbook
120
+ - 🧪 **Live playground** — run any package in your browser — https://developer.lacspace.com/playground
121
+ - 🖥️ **Finished app templates** — https://templates.lacspace.com
122
+ - 🚀 **Scaffold a full app** — `npm create lacspace-app@latest`
116
123
 
117
- </div>
124
+ Free under the **[Lacspace Free Licence](https://lacspace.com/licenses/lacspace-free-1.0)** — a permissive, free-to-use licence.
118
125
 
119
- <div align="center"><sub>Built with care by <a href="https://lacspace.com">Lacspace</a> · powers <a href="https://stockyatra.com">StockYatra</a> · Lacspace Free Licence · <a href="https://github.com/lacspace/npm-packages">source</a></sub></div>
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":"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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 * 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":"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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 * 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.6",
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",
@@ -46,7 +46,7 @@
46
46
  ],
47
47
  "author": "Lacspace <contact@lacspace.com>",
48
48
  "license": "SEE LICENSE IN LICENSE",
49
- "homepage": "https://lacspace.com/packages",
49
+ "homepage": "https://developer.lacspace.com/packages/market-clock",
50
50
  "repository": {
51
51
  "type": "git",
52
52
  "url": "git+https://github.com/lacspace/npm-packages.git",