@gobrand/calendar-core 0.0.9 → 0.0.10

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.
Files changed (2) hide show
  1. package/README.md +443 -0
  2. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,443 @@
1
+ # @gobrand/calendar-core
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@gobrand/calendar-core.svg)](https://www.npmjs.com/package/@gobrand/calendar-core)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ Framework-agnostic calendar utilities built with the [Temporal API](https://tc39.es/proposal-temporal/docs/). Simple, composable functions for building month, week, and day views with full timezone support.
7
+
8
+ > **For React users:** Check out [@gobrand/react-calendar](https://www.npmjs.com/package/@gobrand/react-calendar) for a ready-to-use hook with state management built on top of this core library.
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ npm install @gobrand/calendar-core
14
+ # or
15
+ pnpm add @gobrand/calendar-core
16
+ ```
17
+
18
+ ## Why @gobrand/calendar-core?
19
+
20
+ Building calendars is complex: timezone handling, DST transitions, date arithmetic, and data mapping. **@gobrand/calendar-core** provides pure, framework-agnostic functions for calendar logic:
21
+
22
+ - **🌍 Timezone-aware** - Native timezone support with Temporal API primitives
23
+ - **🎯 Data-agnostic** - Works with any data type through accessor pattern
24
+ - **⚡️ Type-safe** - Full TypeScript support with proper Temporal types
25
+ - **📦 Minimal** - Simple, composable functions with no unnecessary abstractions
26
+ - **🔧 Zero config** - Sensible defaults, customize only what you need
27
+ - **🪄 Framework-agnostic** - Use with React, Vue, Angular, Svelte, or vanilla JavaScript
28
+
29
+ **Key features:**
30
+ - ✅ Built exclusively on Temporal API (no Date objects, no moment.js, no date-fns)
31
+ - ✅ Automatic DST handling and timezone conversions
32
+ - ✅ Calendar-aware arithmetic (leap years, month-end dates)
33
+ - ✅ Flexible accessor pattern for any data structure
34
+ - ✅ Polyfill included for browser compatibility
35
+
36
+ **Perfect for:**
37
+ - Building custom calendar UIs in any framework
38
+ - Server-side calendar generation
39
+ - Event calendars and schedulers
40
+ - Booking systems and appointment managers
41
+ - Task management with due dates
42
+ - Analytics dashboards with date ranges
43
+
44
+ ## Quick Start
45
+
46
+ ```typescript
47
+ import { buildMonth, createCalendarAccessor, getWeekdays, getMonthName } from '@gobrand/calendar-core';
48
+ import { Temporal } from '@js-temporal/polyfill';
49
+
50
+ type Event = {
51
+ id: string;
52
+ date: Temporal.PlainDate;
53
+ title: string;
54
+ };
55
+
56
+ const events: Event[] = [
57
+ { id: '1', date: Temporal.PlainDate.from('2025-01-20'), title: 'Team Meeting' },
58
+ { id: '2', date: Temporal.PlainDate.from('2025-01-22'), title: 'Code Review' },
59
+ ];
60
+
61
+ const accessor = createCalendarAccessor<Event>({
62
+ getDate: (event) => event.date,
63
+ });
64
+
65
+ const month = buildMonth(2025, 1, {
66
+ weekStartsOn: 1,
67
+ data: events,
68
+ accessor,
69
+ });
70
+
71
+ console.log(getMonthName(month.month)); // "January"
72
+ console.log(getWeekdays(1)); // ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
73
+
74
+ month.weeks.forEach(week => {
75
+ week.forEach(day => {
76
+ console.log(
77
+ day.date.toString(),
78
+ day.isCurrentMonth,
79
+ day.isToday,
80
+ `${day.items.length} events`
81
+ );
82
+ });
83
+ });
84
+ ```
85
+
86
+ ## API
87
+
88
+ ### Accessor Pattern
89
+
90
+ #### `createCalendarAccessor(accessor)`
91
+
92
+ Create a type-safe accessor for mapping your data to calendar dates. This is a type-identity function for TypeScript inference.
93
+
94
+ **Parameters:**
95
+ - `accessor` (CalendarAccessor<TItem>): Accessor configuration
96
+ - `getDate` (required): Extract PlainDate from item
97
+ - `getStart` (optional): Extract ZonedDateTime start time
98
+ - `getEnd` (optional): Extract ZonedDateTime end time
99
+
100
+ **Returns:** Same accessor object with proper types
101
+
102
+ **Example:**
103
+ ```tsx
104
+ // Simple date-based items (tasks, posts)
105
+ type Task = {
106
+ id: string;
107
+ name: string;
108
+ dueDate: Temporal.PlainDate;
109
+ };
110
+
111
+ const taskAccessor = createCalendarAccessor<Task>({
112
+ getDate: (task) => task.dueDate,
113
+ });
114
+
115
+ // Time-based items with start time (events, appointments)
116
+ type Event = {
117
+ id: string;
118
+ title: string;
119
+ start: Temporal.ZonedDateTime;
120
+ };
121
+
122
+ const eventAccessor = createCalendarAccessor<Event>({
123
+ getDate: (event) => event.start.toPlainDate(),
124
+ getStart: (event) => event.start,
125
+ });
126
+
127
+ // Items with start and end times (meetings, bookings)
128
+ type Meeting = {
129
+ id: string;
130
+ title: string;
131
+ start: Temporal.ZonedDateTime;
132
+ end: Temporal.ZonedDateTime;
133
+ };
134
+
135
+ const meetingAccessor = createCalendarAccessor<Meeting>({
136
+ getDate: (meeting) => meeting.start.toPlainDate(),
137
+ getStart: (meeting) => meeting.start,
138
+ getEnd: (meeting) => meeting.end,
139
+ });
140
+ ```
141
+
142
+ ### Building Calendars
143
+
144
+ Low-level functions for building calendar grids without state management.
145
+
146
+ #### `buildMonth(year, month, options?)`
147
+
148
+ Build a month grid for any year and month.
149
+
150
+ **Parameters:**
151
+ - `year` (number): Year (e.g., 2025)
152
+ - `month` (number): Month (1-12)
153
+ - `options` (object, optional):
154
+ - `weekStartsOn` (0-6): First day of week
155
+ - `today` (PlainDate): Override today's date
156
+ - `data` (TItem[]): Items to include
157
+ - `accessor` (CalendarAccessor<TItem>): Data accessor
158
+
159
+ **Returns:** `CalendarMonth<TItem>`
160
+
161
+ **Example:**
162
+ ```typescript
163
+ import { buildMonth, createCalendarAccessor } from '@gobrand/calendar-core';
164
+
165
+ const month = buildMonth(2025, 1, {
166
+ weekStartsOn: 1,
167
+ data: events,
168
+ accessor: createCalendarAccessor({
169
+ getDate: (event) => event.date
170
+ })
171
+ });
172
+ ```
173
+
174
+ #### `buildWeek(date, options?)`
175
+
176
+ Build a week view for a specific date.
177
+
178
+ **Parameters:**
179
+ - `date` (PlainDate): Any date in the target week
180
+ - `options` (object, optional):
181
+ - `weekStartsOn` (0-6): First day of week
182
+ - `startHour` (number): Start hour for time slots
183
+ - `endHour` (number): End hour for time slots
184
+ - `slotDuration` (number): Minutes per slot
185
+ - `today` (PlainDate): Override today's date
186
+ - `data` (TItem[]): Items to include
187
+ - `accessor` (CalendarAccessor<TItem>): Data accessor
188
+
189
+ **Returns:** `CalendarWeekView<TItem>`
190
+
191
+ #### `buildDay(date, options?)`
192
+
193
+ Build a day view with time slots.
194
+
195
+ **Parameters:**
196
+ - `date` (PlainDate): The target date
197
+ - `options` (object, optional):
198
+ - `startHour` (number): Start hour for time slots
199
+ - `endHour` (number): End hour for time slots
200
+ - `slotDuration` (number): Minutes per slot
201
+ - `today` (PlainDate): Override today's date
202
+ - `data` (TItem[]): Items to include
203
+ - `accessor` (CalendarAccessor<TItem>): Data accessor
204
+
205
+ **Returns:** `CalendarDayView<TItem>`
206
+
207
+ ### Formatting Utilities
208
+
209
+ #### `getWeekdays(weekStartsOn?, locale?, format?)`
210
+
211
+ Get localized weekday names.
212
+
213
+ **Parameters:**
214
+ - `weekStartsOn` (0-6, optional): First day of week (default: 0 = Sunday)
215
+ - `locale` (string, optional): BCP 47 locale (default: system locale)
216
+ - `format` ('long' | 'short' | 'narrow', optional): Name format (default: 'short')
217
+
218
+ **Returns:** `string[]` - Array of 7 weekday names
219
+
220
+ **Example:**
221
+ ```typescript
222
+ import { getWeekdays } from '@gobrand/calendar-core';
223
+
224
+ getWeekdays(1); // ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
225
+ getWeekdays(0); // ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
226
+ getWeekdays(1, 'es-ES'); // ["lun", "mar", "mié", "jue", "vie", "sáb", "dom"]
227
+ getWeekdays(1, 'en-US', 'long'); // ["Monday", "Tuesday", ...]
228
+ getWeekdays(1, 'en-US', 'narrow'); // ["M", "T", "W", "T", "F", "S", "S"]
229
+ ```
230
+
231
+ #### `getMonthName(month, locale?)`
232
+
233
+ Get localized month name.
234
+
235
+ **Parameters:**
236
+ - `month` (PlainYearMonth): The month to format
237
+ - `locale` (string, optional): BCP 47 locale
238
+
239
+ **Returns:** `string` - Formatted month name
240
+
241
+ **Example:**
242
+ ```typescript
243
+ import { getMonthName } from '@gobrand/calendar-core';
244
+ import { Temporal } from '@js-temporal/polyfill';
245
+
246
+ const month = Temporal.PlainYearMonth.from('2025-01');
247
+ getMonthName(month); // "January"
248
+ getMonthName(month, 'es-ES'); // "enero"
249
+ getMonthName(month, 'ja-JP'); // "1月"
250
+ ```
251
+
252
+ #### `formatTime(time, locale?)`
253
+
254
+ Format a PlainTime as a localized time string.
255
+
256
+ **Parameters:**
257
+ - `time` (PlainTime): Time to format
258
+ - `locale` (string, optional): BCP 47 locale
259
+
260
+ **Returns:** `string` - Formatted time
261
+
262
+ **Example:**
263
+ ```typescript
264
+ import { formatTime } from '@gobrand/calendar-core';
265
+ import { Temporal } from '@js-temporal/polyfill';
266
+
267
+ const time = Temporal.PlainTime.from('14:30');
268
+ formatTime(time); // "2:30 PM" (en-US)
269
+ formatTime(time, 'en-GB'); // "14:30"
270
+ formatTime(time, 'es-ES'); // "14:30"
271
+ ```
272
+
273
+ ### Timezone Utilities
274
+
275
+ #### `getMonthRange(timeZone?, weekStartsOn?)`
276
+
277
+ Get the date range for the current month, week-aligned in a specific timezone.
278
+
279
+ **Parameters:**
280
+ - `timeZone` (string, optional): IANA timezone (default: system timezone)
281
+ - `weekStartsOn` (0-6, optional): First day of week (default: 1)
282
+
283
+ **Returns:** `{ start: Temporal.PlainDate; end: Temporal.PlainDate }` - Start/end dates for the week-aligned month
284
+
285
+ **Example:**
286
+ ```typescript
287
+ import { getMonthRange } from '@gobrand/calendar-core';
288
+
289
+ const range = getMonthRange('America/New_York', 1);
290
+ // Returns week-aligned range for current month in New York time
291
+ console.log(range.start, range.end);
292
+ ```
293
+
294
+ #### `getWeekRange(timeZone?, weekStartsOn?)`
295
+
296
+ Get the date range for the current week in a specific timezone.
297
+
298
+ **Parameters:**
299
+ - `timeZone` (string, optional): IANA timezone (default: system timezone)
300
+ - `weekStartsOn` (0-6, optional): First day of week (default: 1)
301
+
302
+ **Returns:** `{ start: Temporal.PlainDate; end: Temporal.PlainDate }` - Start/end dates for the week
303
+
304
+ #### `getDayRange(timeZone?)`
305
+
306
+ Get the date range for today in a specific timezone.
307
+
308
+ **Parameters:**
309
+ - `timeZone` (string, optional): IANA timezone (default: system timezone)
310
+
311
+ **Returns:** `{ start: Temporal.PlainDate; end: Temporal.PlainDate }` - Start/end dates for today (same date)
312
+
313
+ #### `getCurrentTimeZone()`
314
+
315
+ Get the system's current IANA timezone identifier.
316
+
317
+ **Returns:** `string` - IANA timezone (e.g., "America/New_York")
318
+
319
+ #### `convertToTimezone(zdt, timeZone)`
320
+
321
+ Convert a ZonedDateTime to a different timezone.
322
+
323
+ **Parameters:**
324
+ - `zdt` (ZonedDateTime): Source ZonedDateTime
325
+ - `timeZone` (string): Target IANA timezone
326
+
327
+ **Returns:** `ZonedDateTime` - Same instant in new timezone
328
+
329
+ #### `createZonedDateTime(date, time, timeZone)`
330
+
331
+ Create a ZonedDateTime from a PlainDate and PlainTime.
332
+
333
+ **Parameters:**
334
+ - `date` (PlainDate): The date
335
+ - `time` (PlainTime): The time
336
+ - `timeZone` (string): IANA timezone
337
+
338
+ **Returns:** `ZonedDateTime`
339
+
340
+ ## Real World Examples
341
+
342
+ ### Example 1: HTML Calendar Generator
343
+
344
+ Using core functions without React for maximum flexibility.
345
+
346
+ ```typescript
347
+ import {
348
+ buildMonth,
349
+ createCalendarAccessor,
350
+ getWeekdays,
351
+ getMonthName
352
+ } from '@gobrand/calendar-core';
353
+ import { Temporal } from '@js-temporal/polyfill';
354
+
355
+ type BlogPost = {
356
+ id: string;
357
+ title: string;
358
+ publishedAt: Temporal.PlainDate;
359
+ };
360
+
361
+ const posts: BlogPost[] = [
362
+ { id: '1', title: 'Getting Started with Temporal', publishedAt: Temporal.PlainDate.from('2025-01-15') },
363
+ { id: '2', title: 'Building Calendars', publishedAt: Temporal.PlainDate.from('2025-01-20') },
364
+ ];
365
+
366
+ const accessor = createCalendarAccessor<BlogPost>({
367
+ getDate: (post) => post.publishedAt,
368
+ });
369
+
370
+ // Build January 2025 calendar
371
+ const month = buildMonth(2025, 1, {
372
+ weekStartsOn: 1,
373
+ data: posts,
374
+ accessor,
375
+ });
376
+
377
+ // Render to HTML
378
+ const weekdays = getWeekdays(1);
379
+ const monthName = getMonthName(month.month);
380
+
381
+ let html = `<h2>${monthName} ${month.month.year}</h2>`;
382
+ html += '<table><thead><tr>';
383
+ weekdays.forEach(day => {
384
+ html += `<th>${day}</th>`;
385
+ });
386
+ html += '</tr></thead><tbody>';
387
+
388
+ month.weeks.forEach(week => {
389
+ html += '<tr>';
390
+ week.forEach(day => {
391
+ const className = day.isCurrentMonth ? '' : 'other-month';
392
+ html += `<td class="${className}">`;
393
+ html += `<div>${day.date.day}</div>`;
394
+ day.items.forEach(post => {
395
+ html += `<div class="post">${post.title}</div>`;
396
+ });
397
+ html += '</td>';
398
+ });
399
+ html += '</tr>';
400
+ });
401
+
402
+ html += '</tbody></table>';
403
+ document.getElementById('calendar')!.innerHTML = html;
404
+ ```
405
+
406
+ ## Browser Support
407
+
408
+ The Temporal API is a Stage 3 TC39 proposal. The polyfill `@js-temporal/polyfill` is included as a dependency, ensuring compatibility across all modern browsers and Node.js environments.
409
+
410
+ ```typescript
411
+ import { Temporal } from '@js-temporal/polyfill';
412
+ ```
413
+
414
+ ## Development
415
+
416
+ ```bash
417
+ # Install dependencies
418
+ pnpm install
419
+
420
+ # Build all packages
421
+ pnpm build
422
+
423
+ # Run tests
424
+ pnpm test --run
425
+
426
+ # Type check
427
+ pnpm typecheck
428
+
429
+ # Release new version
430
+ pnpm release <patch|minor|major>
431
+ ```
432
+
433
+ ## Contributing
434
+
435
+ Contributions are welcome! Please feel free to submit a Pull Request.
436
+
437
+ ## License
438
+
439
+ MIT
440
+
441
+ ## Built by Go Brand
442
+
443
+ temporal-calendar is built and maintained by [Go Brand](https://gobrand.app) - a modern social media management platform.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobrand/calendar-core",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
4
4
  "description": "Lightweight utility library for building calendars using the Temporal API",
5
5
  "private": false,
6
6
  "publishConfig": {