@absolutejs/calendar 0.1.0

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 ADDED
@@ -0,0 +1,13 @@
1
+ # @absolutejs/calendar
2
+
3
+ Framework-independent, date-only calendar mechanics for AbsoluteJS apps. Apps own event models, permissions, interactions and persistence.
4
+
5
+ Exports: `isCalendarDate`, `addDays`, `addMonths` (clamps the day), `dateInZone`, `weekDates`, `monthDates` (42 cells), `formatCalendarDate`, and `groupByDate` (includes an undated bucket). Dates use `YYYY-MM-DD`; arithmetic and display avoid local-time/DST shifts. Week starts are configurable from Sunday (0) through Saturday (6). `dateInZone` is the explicit bridge from an instant to a calendar day.
6
+
7
+ ```ts
8
+ import { monthDates, groupByDate } from '@absolutejs/calendar';
9
+ const cells = monthDates('2026-09-24');
10
+ const { days, undated } = groupByDate(tasks, task => task.due);
11
+ ```
12
+
13
+ No provider calendars, timers, React or storage dependencies are required. Invalid date input throws for arithmetic and is retained in the undated bucket when grouping.
@@ -0,0 +1,13 @@
1
+ /** Date-only arithmetic is UTC internally; it never treats a date as a local instant. */
2
+ export declare function isCalendarDate(value: unknown): value is string;
3
+ export declare function addDays(value: string, days: number): string;
4
+ export declare function addMonths(value: string, months: number): string;
5
+ export declare function dateInZone(now?: Date, timeZone?: string): string;
6
+ export declare function weekDates(value: string, weekStartsOn?: number): string[];
7
+ /** Six stable rows include adjacent-month days; default week starts Sunday. */
8
+ export declare function monthDates(value: string, weekStartsOn?: number): string[];
9
+ export declare function formatCalendarDate(value: string, options?: Intl.DateTimeFormatOptions, locale?: string): string;
10
+ export declare function groupByDate<T>(items: readonly T[], getDate: (item: T) => string | null | undefined): {
11
+ days: Map<string, T[]>;
12
+ undated: T[];
13
+ };
package/dist/index.js ADDED
@@ -0,0 +1,81 @@
1
+ // src/index.ts
2
+ function isCalendarDate(value) {
3
+ if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value))
4
+ return false;
5
+ const d = new Date(value + "T12:00:00Z");
6
+ return Number.isFinite(d.getTime()) && d.toISOString().slice(0, 10) === value;
7
+ }
8
+ function parse(value) {
9
+ if (!isCalendarDate(value))
10
+ throw new RangeError("Invalid calendar date");
11
+ return new Date(value + "T12:00:00Z");
12
+ }
13
+ function format(date) {
14
+ const value = date.toISOString().slice(0, 10);
15
+ if (!isCalendarDate(value))
16
+ throw new RangeError("Calendar date out of range");
17
+ return value;
18
+ }
19
+ function addDays(value, days) {
20
+ if (!Number.isInteger(days))
21
+ throw new RangeError("Days must be an integer");
22
+ const d = parse(value);
23
+ d.setUTCDate(d.getUTCDate() + days);
24
+ return format(d);
25
+ }
26
+ function addMonths(value, months) {
27
+ if (!Number.isInteger(months))
28
+ throw new RangeError("Months must be an integer");
29
+ const d = parse(value), day = d.getUTCDate();
30
+ d.setUTCDate(1);
31
+ d.setUTCMonth(d.getUTCMonth() + months);
32
+ const last = new Date(d);
33
+ last.setUTCMonth(last.getUTCMonth() + 1);
34
+ last.setUTCDate(0);
35
+ d.setUTCDate(Math.min(day, last.getUTCDate()));
36
+ return format(d);
37
+ }
38
+ function dateInZone(now = new Date, timeZone = "UTC") {
39
+ const parts = new Intl.DateTimeFormat("en-US", { timeZone, year: "numeric", month: "2-digit", day: "2-digit" }).formatToParts(now);
40
+ const p = (type) => parts.find((p2) => p2.type === type).value;
41
+ return `${p("year")}-${p("month")}-${p("day")}`;
42
+ }
43
+ function weekDates(value, weekStartsOn = 0) {
44
+ if (!Number.isInteger(weekStartsOn) || weekStartsOn < 0 || weekStartsOn > 6)
45
+ throw new RangeError("Week start must be 0–6");
46
+ const day = parse(value).getUTCDay();
47
+ const first = addDays(value, -((day - weekStartsOn + 7) % 7));
48
+ return Array.from({ length: 7 }, (_, i) => addDays(first, i));
49
+ }
50
+ function monthDates(value, weekStartsOn = 0) {
51
+ parse(value);
52
+ const first = weekDates(value.slice(0, 7) + "-01", weekStartsOn)[0];
53
+ return Array.from({ length: 42 }, (_, i) => addDays(first, i));
54
+ }
55
+ function formatCalendarDate(value, options = {}, locale = "en-US") {
56
+ return new Intl.DateTimeFormat(locale, { ...options, timeZone: "UTC" }).format(parse(value));
57
+ }
58
+ function groupByDate(items, getDate) {
59
+ const days = new Map, undated = [];
60
+ for (const item of items) {
61
+ const date = getDate(item);
62
+ if (!isCalendarDate(date)) {
63
+ undated.push(item);
64
+ continue;
65
+ }
66
+ const bucket = days.get(date) ?? [];
67
+ bucket.push(item);
68
+ days.set(date, bucket);
69
+ }
70
+ return { days, undated };
71
+ }
72
+ export {
73
+ addDays,
74
+ addMonths,
75
+ dateInZone,
76
+ formatCalendarDate,
77
+ groupByDate,
78
+ isCalendarDate,
79
+ monthDates,
80
+ weekDates
81
+ };
package/package.json ADDED
@@ -0,0 +1 @@
1
+ {"name":"@absolutejs/calendar","version":"0.1.0","description":"Date-only calendar ranges and layouts for AbsoluteJS applications","type":"module","license":"MIT","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"files":["dist","README.md"],"scripts":{"build":"bun build src/index.ts --outdir dist --target browser && tsc --declaration --emitDeclarationOnly --target ES2022 --module ESNext --moduleResolution bundler --skipLibCheck --outDir dist src/index.ts","test":"bun test"},"devDependencies":{"typescript":"5.9.3","@types/bun":"^1.3.14"}}