@gallop.software/canon 2.12.0 → 2.13.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gallop.software/canon",
3
- "version": "2.12.0",
3
+ "version": "2.13.0",
4
4
  "type": "module",
5
5
  "description": "Gallop Canon - Architecture patterns, ESLint plugin, and CLI for template governance",
6
6
  "main": "dist/index.js",
@@ -0,0 +1,132 @@
1
+ # Pattern 027: Luxon for Dates
2
+
3
+ **Canon Version:** 1.0
4
+ **Status:** Stable
5
+ **Category:** Components
6
+ **Enforcement:** Documentation
7
+
8
+ ## Decision
9
+
10
+ Use Luxon's `DateTime` for all date and time operations. Do not use the native JavaScript `Date` object.
11
+
12
+ ## Rationale
13
+
14
+ 1. **Timezone consistency** — Native `Date` operates in the user's local timezone, causing inconsistencies when users in different timezones interact with the same dates. A user in California viewing a New York restaurant's reservation system would see incorrect "today" calculations.
15
+ 2. **Explicit timezone handling** — Luxon's `.setZone()` method allows explicit timezone specification, ensuring dates are always interpreted in the intended timezone (e.g., the business's location).
16
+ 3. **Immutable API** — Luxon DateTime objects are immutable, preventing accidental mutation bugs common with native Date.
17
+ 4. **Better API** — Luxon provides clearer methods for formatting, parsing, and date arithmetic.
18
+
19
+ ## Usage
20
+
21
+ ### Import
22
+
23
+ ```tsx
24
+ import { DateTime } from 'luxon'
25
+ ```
26
+
27
+ ### Get Current Time in Specific Timezone
28
+
29
+ ```tsx
30
+ // Get "now" in New York, regardless of user's location
31
+ const now = DateTime.now().setZone('America/New_York')
32
+
33
+ // Get start of "today" in business timezone
34
+ const today = DateTime.now().setZone('America/Chicago').startOf('day')
35
+ ```
36
+
37
+ ### Compare Dates in Timezone
38
+
39
+ ```tsx
40
+ // Check if a date is in the past (relative to business timezone)
41
+ function isDatePast(year: number, month: number, day: number, timezone: string) {
42
+ const date = DateTime.fromObject(
43
+ { year, month: month + 1, day },
44
+ { zone: timezone }
45
+ ).startOf('day')
46
+ const today = DateTime.now().setZone(timezone).startOf('day')
47
+ return date < today
48
+ }
49
+ ```
50
+
51
+ ### Format Dates
52
+
53
+ ```tsx
54
+ // Format for display
55
+ const formatted = DateTime.now()
56
+ .setZone('America/New_York')
57
+ .toFormat('MMMM d, yyyy') // "January 19, 2026"
58
+
59
+ // Format for form submission (timezone-agnostic string)
60
+ const formValue = DateTime.fromObject({ year: 2026, month: 1, day: 19 })
61
+ .toFormat('yyyy-MM-dd') // "2026-01-19"
62
+ ```
63
+
64
+ ### Parse User Input
65
+
66
+ ```tsx
67
+ // Parse a date string and interpret in business timezone
68
+ const date = DateTime.fromFormat('2026-01-19', 'yyyy-MM-dd', {
69
+ zone: 'America/New_York'
70
+ })
71
+ ```
72
+
73
+ ## Examples
74
+
75
+ ### Good
76
+
77
+ ```tsx
78
+ import { DateTime } from 'luxon'
79
+
80
+ // Business timezone from config/props
81
+ const BUSINESS_TIMEZONE = 'America/Chicago'
82
+
83
+ // Get "today" in business timezone
84
+ const today = DateTime.now().setZone(BUSINESS_TIMEZONE).startOf('day')
85
+
86
+ // Check if user-selected date is valid
87
+ const selectedDate = DateTime.fromObject(
88
+ { year: 2026, month: 1, day: 19 },
89
+ { zone: BUSINESS_TIMEZONE }
90
+ )
91
+
92
+ if (selectedDate < today) {
93
+ console.log('Cannot select a date in the past')
94
+ }
95
+ ```
96
+
97
+ ### Bad
98
+
99
+ ```tsx
100
+ // Native Date uses user's local timezone
101
+ const today = new Date()
102
+ today.setHours(0, 0, 0, 0) // Still in user's timezone!
103
+
104
+ // This will be wrong for users in different timezones
105
+ const selectedDate = new Date(2026, 0, 19)
106
+ if (selectedDate < today) {
107
+ console.log('This comparison is timezone-dependent!')
108
+ }
109
+ ```
110
+
111
+ ## Common Timezone Identifiers
112
+
113
+ | City | IANA Timezone |
114
+ |------|---------------|
115
+ | New York | `America/New_York` |
116
+ | Chicago / Dallas / Houston | `America/Chicago` |
117
+ | Denver | `America/Denver` |
118
+ | Los Angeles / San Francisco | `America/Los_Angeles` |
119
+ | London | `Europe/London` |
120
+ | Paris | `Europe/Paris` |
121
+ | Tokyo | `Asia/Tokyo` |
122
+
123
+ ## Enforcement
124
+
125
+ - **Method:** Code review / documentation
126
+ - **Check:** Native `new Date()` should not be used for date comparisons or timezone-sensitive operations
127
+
128
+ ## References
129
+
130
+ - Luxon Documentation: https://moment.github.io/luxon/
131
+ - IANA Timezone Database: https://www.iana.org/time-zones
132
+ - Form components use `timezone` prop for business location
package/schema.json CHANGED
@@ -300,6 +300,16 @@
300
300
  "enforcement": "eslint",
301
301
  "rule": "gallop/prefer-list-components",
302
302
  "summary": "Use List/Li, not raw ul/li tags"
303
+ },
304
+ {
305
+ "id": "027",
306
+ "title": "Luxon for Dates",
307
+ "file": "patterns/027-luxon-dates.md",
308
+ "category": "components",
309
+ "status": "stable",
310
+ "enforcement": "documentation",
311
+ "rule": null,
312
+ "summary": "Use Luxon DateTime, not native JavaScript Date"
303
313
  }
304
314
  ],
305
315
  "guarantees": [