@northguild/gmt 1.14.2 → 1.15.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.
Files changed (137) hide show
  1. package/README.md +43 -13
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.js +1 -0
  4. package/dist/plain/calculate/diffDateAsDuration.js +1 -1
  5. package/dist/plain/calculate/index.d.ts +2 -1
  6. package/dist/plain/calculate/index.js +2 -1
  7. package/dist/plain/calculate/{weekOfYear.d.ts → weekOfYearForDate.d.ts} +0 -14
  8. package/dist/plain/calculate/weekOfYearForDate.js +28 -0
  9. package/dist/plain/calculate/weekOfYearForDateTime.d.ts +14 -0
  10. package/dist/plain/calculate/{weekOfYear.js → weekOfYearForDateTime.js} +1 -27
  11. package/dist/plain/format/formatCalendar.d.ts +2 -2
  12. package/dist/plain/format/formatDate.d.ts +3 -2
  13. package/dist/plain/format/formatDate.js +1 -1
  14. package/dist/plain/format/formatDateRange.d.ts +3 -2
  15. package/dist/plain/format/formatDateRange.js +1 -1
  16. package/dist/plain/format/formatDateTime.d.ts +3 -2
  17. package/dist/plain/format/formatDateTime.js +1 -1
  18. package/dist/plain/format/formatDateTimeRange.d.ts +3 -2
  19. package/dist/plain/format/formatDateTimeRange.js +1 -1
  20. package/dist/plain/format/formatDateTimeToParts.d.ts +3 -2
  21. package/dist/plain/format/formatDateTimeToParts.js +1 -1
  22. package/dist/plain/format/formatDateToParts.d.ts +3 -2
  23. package/dist/plain/format/formatDateToParts.js +1 -1
  24. package/dist/plain/format/formatRelativeDate.d.ts +3 -14
  25. package/dist/plain/format/formatRelativeDateTime.d.ts +3 -14
  26. package/dist/plain/format/formatRelativeTime.d.ts +3 -14
  27. package/dist/plain/format/formatTime.d.ts +2 -1
  28. package/dist/plain/get/getNowUnit.d.ts +2 -3
  29. package/dist/plain/interval/splitIntervalByUnitDate.js +1 -1
  30. package/dist/regex/calendar-date.d.ts +19 -0
  31. package/dist/regex/calendar-date.js +19 -10
  32. package/dist/regex/calendar-zoned-date-time.d.ts +45 -0
  33. package/dist/regex/calendar-zoned-date-time.js +45 -40
  34. package/dist/regex/date-time.d.ts +18 -0
  35. package/dist/regex/date-time.js +18 -1
  36. package/dist/regex/date.d.ts +16 -0
  37. package/dist/regex/date.js +16 -1
  38. package/dist/regex/http-date.d.ts +15 -0
  39. package/dist/regex/http-date.js +15 -6
  40. package/dist/regex/leap-second.d.ts +12 -0
  41. package/dist/regex/leap-second.js +12 -1
  42. package/dist/regex/rfc-2822.d.ts +18 -0
  43. package/dist/regex/rfc-2822.js +18 -8
  44. package/dist/regex/rfc-3339.d.ts +19 -0
  45. package/dist/regex/rfc-3339.js +19 -6
  46. package/dist/regex/sql-date-time.d.ts +14 -0
  47. package/dist/regex/sql-date-time.js +14 -6
  48. package/dist/regex/time-zone-like.d.ts +21 -0
  49. package/dist/regex/time-zone-like.js +21 -0
  50. package/dist/regex/time.d.ts +59 -0
  51. package/dist/regex/time.js +59 -3
  52. package/dist/regex/unix.d.ts +21 -0
  53. package/dist/regex/unix.js +21 -0
  54. package/dist/regex/utc-date-time.d.ts +20 -0
  55. package/dist/regex/utc-date-time.js +20 -1
  56. package/dist/types/calendar-options.d.ts +11 -0
  57. package/dist/types/calendar-options.js +0 -0
  58. package/dist/types/datetime-format-options.d.ts +6 -0
  59. package/dist/types/datetime-format-options.js +0 -0
  60. package/dist/types/duration-unit.d.ts +5 -0
  61. package/dist/types/duration-unit.js +0 -0
  62. package/dist/types/index.d.ts +11 -0
  63. package/dist/types/index.js +11 -0
  64. package/dist/types/now-unit.d.ts +5 -0
  65. package/dist/types/now-unit.js +0 -0
  66. package/dist/types/relative-date-time-unit.d.ts +6 -0
  67. package/dist/types/relative-date-time-unit.js +0 -0
  68. package/dist/types/relative-date-unit.d.ts +5 -0
  69. package/dist/types/relative-date-unit.js +0 -0
  70. package/dist/types/relative-time-options.d.ts +18 -0
  71. package/dist/types/relative-time-options.js +0 -0
  72. package/dist/types/relative-time-unit.d.ts +5 -0
  73. package/dist/types/relative-time-unit.js +0 -0
  74. package/dist/types/relative-unit.d.ts +6 -0
  75. package/dist/types/relative-unit.js +0 -0
  76. package/dist/types/unix-now-unit.d.ts +5 -0
  77. package/dist/types/unix-now-unit.js +0 -0
  78. package/dist/types/utc-now-unit.d.ts +5 -0
  79. package/dist/types/utc-now-unit.js +0 -0
  80. package/dist/unix/calculate/startOrEndOfUnix.d.ts +18 -0
  81. package/dist/unix/calculate/startOrEndOfUnix.js +18 -0
  82. package/dist/unix/format/formatCalendarUnix.d.ts +27 -1
  83. package/dist/unix/format/formatRelativeUnix.d.ts +1 -8
  84. package/dist/unix/format/formatUnix.d.ts +19 -0
  85. package/dist/unix/format/formatUnix.js +19 -0
  86. package/dist/unix/get/getUnixNowUnit.d.ts +2 -3
  87. package/dist/unix/parse/parseUnitFromUnix.d.ts +24 -0
  88. package/dist/unix/validate/isValidUnixUnit.d.ts +15 -0
  89. package/dist/utc/calculate/startOrEndOfUtc.d.ts +17 -0
  90. package/dist/utc/calculate/startOrEndOfUtc.js +17 -0
  91. package/dist/utc/format/formatCalendarUtc.d.ts +2 -2
  92. package/dist/utc/format/formatRelativeUtc.d.ts +2 -12
  93. package/dist/utc/format/formatUtc.d.ts +33 -0
  94. package/dist/utc/format/formatUtc.js +17 -0
  95. package/dist/utc/get/getUtcNowUnit.d.ts +2 -3
  96. package/dist/utc/parse/parseUnitFromUtc.d.ts +23 -0
  97. package/dist/zoned/format/formatRelativeZoned.d.ts +1 -8
  98. package/dist/zoned/format/formatTimeZoneName.d.ts +15 -0
  99. package/dist/zoned/format/formatZonedDateTime.d.ts +2 -1
  100. package/dist/zoned/format/formatZonedRange.d.ts +2 -1
  101. package/dist/zoned/format/formatZonedToParts.d.ts +2 -1
  102. package/dist/zoned/get/getDstTransitions.d.ts +18 -2
  103. package/dist/zoned/get/getZonedNowUnit.d.ts +24 -0
  104. package/dist/zoned/interval/intervalDifferenceZoned.d.ts +3 -3
  105. package/dist/zoned/interval/intervalDifferenceZoned.js +3 -3
  106. package/dist/zoned/interval/intervalXorZoned.d.ts +6 -4
  107. package/dist/zoned/interval/intervalXorZoned.js +6 -4
  108. package/dist/zoned/parse/getZonedOffsetAs.d.ts +11 -0
  109. package/dist/zoned/parse/parseUnitFromZoned.d.ts +24 -0
  110. package/package.json +25 -7
  111. package/skills/gmt-arithmetic/SKILL.md +83 -0
  112. package/skills/gmt-basics/SKILL.md +80 -0
  113. package/skills/gmt-integration/SKILL.md +73 -0
  114. package/skills/gmt-timezone/SKILL.md +80 -0
  115. package/skills/_artifacts/domain_map.yaml +0 -547
  116. package/skills/_artifacts/skill_spec.md +0 -67
  117. package/skills/_artifacts/skill_tree.yaml +0 -278
  118. package/skills/api-expansion-workflow/SKILL.md +0 -52
  119. package/skills/app-integration/SKILL.md +0 -38
  120. package/skills/calculate-dates/SKILL.md +0 -432
  121. package/skills/compare-dates/SKILL.md +0 -412
  122. package/skills/convert-types/SKILL.md +0 -305
  123. package/skills/durations/SKILL.md +0 -418
  124. package/skills/format-date-time/SKILL.md +0 -486
  125. package/skills/format-date-time/references/format-api.md +0 -34
  126. package/skills/format-relative-time/SKILL.md +0 -223
  127. package/skills/get-current/SKILL.md +0 -229
  128. package/skills/interval-ops/SKILL.md +0 -432
  129. package/skills/issue-creation/SKILL.md +0 -118
  130. package/skills/lint-package-suggestion/SKILL.md +0 -37
  131. package/skills/new-method-implementation/SKILL.md +0 -71
  132. package/skills/parse-date-time/SKILL.md +0 -349
  133. package/skills/pr-contribution/SKILL.md +0 -180
  134. package/skills/unit-test-generation/SKILL.md +0 -56
  135. package/skills/validate-dates/SKILL.md +0 -438
  136. package/skills/zoned-date-ops/SKILL.md +0 -422
  137. package/skills/zoned-date-ops/references/timezones.md +0 -13
@@ -1,432 +0,0 @@
1
- ---
2
- name: calculate-dates
3
- description: >
4
- Add or subtract time from dates. Use addDays, addMonths, subtractTime for date
5
- arithmetic. Use addBusinessDays/subtractBusinessDays for business-day
6
- arithmetic (skips weekends). Use diffDate for differences. Use clampDate to
7
- restrict a date to a range, or closestDateTo for the nearest candidate by
8
- calendar distance. add*/subtract* accept optional overflow ("constrain" |
9
- "reject"); diff* accept optional smallestUnit/roundingIncrement/roundingMode.
10
- Use getLocaleStartOfWeek/getLocaleEndOfWeek for locale-driven week boundaries
11
- instead of startOfDate/endOfDate's ISO-biased weekStartsOn. Use
12
- setDate/setDateTime/setTime to set fields atomically (safer than composing
13
- add* calls field-by-field). Use cycleDate/cycleDateTime/cycleTime to wrap a
14
- single field instead of carrying into the next (month +1 from December stays
15
- in the same year, unlike addMonths).
16
- sources:
17
- - 'northguild/gmt:packages/gmt/src/plain/calculate/index.ts'
18
- - 'northguild/gmt:packages/gmt/src/plain/calculate/clampDate.ts'
19
- - 'northguild/gmt:packages/gmt/src/plain/calculate/closestDateTo.ts'
20
- - 'northguild/gmt:packages/gmt/src/plain/calculate/setDate.ts'
21
- - 'northguild/gmt:packages/gmt/src/plain/calculate/setDateTime.ts'
22
- - 'northguild/gmt:packages/gmt/src/plain/calculate/setTime.ts'
23
- - 'northguild/gmt:packages/gmt/src/plain/calculate/cycleDate.ts'
24
- - 'northguild/gmt:packages/gmt/src/plain/calculate/cycleDateTime.ts'
25
- - 'northguild/gmt:packages/gmt/src/plain/calculate/cycleTime.ts'
26
- metadata:
27
- type: core
28
- library: '@northguild/gmt'
29
- library_version: '1.14.2'
30
- ---
31
-
32
- # Calculate Dates
33
-
34
- Use this skill when you need to perform date arithmetic (add, subtract, diff).
35
-
36
- ## Setup
37
-
38
- ```ts
39
- import { addDays, addMonths, addYears } from "@northguild/gmt";
40
- import { subtractTime, diffDate } from "@northguild/gmt";
41
- import { startOfDate, endOfDate } from "@northguild/gmt";
42
- ```
43
-
44
- ## Core Patterns
45
-
46
- ### Add days to a date
47
-
48
- ```ts
49
- const result = addDays("2024-03-15", 5); // "2024-03-20"
50
- ```
51
-
52
- ### Add months to a date
53
-
54
- ```ts
55
- const result = addMonths("2024-03-15", 2); // "2024-05-15"
56
- ```
57
-
58
- ### Add years to a date
59
-
60
- ```ts
61
- const result = addYears("2024-03-15", 1); // "2025-03-15"
62
- ```
63
-
64
- ### Subtract time from date
65
-
66
- ```ts
67
- import { subtractTime } from "@northguild/gmt";
68
-
69
- const result = subtractTime("2024-03-15T14:30:45", { hours: 2 }); // "2024-03-15T12:30:45"
70
- ```
71
-
72
- ### Add business days (skip weekends)
73
-
74
- ```ts
75
- import { addBusinessDays, subtractBusinessDays } from "@northguild/gmt";
76
-
77
- addBusinessDays("2024-03-15", 1); // "2024-03-18" — skips Sat/Sun
78
- addBusinessDays("2024-03-16", 1); // "2024-03-18" — Saturday start still skips to Monday
79
-
80
- subtractBusinessDays("2024-03-18", 1); // "2024-03-15"
81
- subtractBusinessDays("2024-03-17", 1); // "2024-03-15" — Sunday start still skips to Friday
82
- ```
83
-
84
- ### Calculate difference between dates (in days)
85
-
86
- ```ts
87
- import { diffDate } from "@northguild/gmt";
88
-
89
- const diff = diffDate("2024-03-15", "2024-03-20", "day"); // 5
90
- ```
91
-
92
- Need a full multi-unit breakdown (e.g. `"P26D"`) instead of a single-unit number? See the `durations` skill's `diffDateAsDuration`/`diffDateTimeAsDuration`/`diffZonedAsDuration`/`diffUnixAsDuration`/`diffUtcAsDuration` bridge functions.
93
-
94
- ### Get start of day
95
-
96
- ```ts
97
- import { startOfDate } from "@northguild/gmt";
98
-
99
- const start = startOfDate("2024-03-15T14:30:45"); // "2024-03-15T00:00:00"
100
- ```
101
-
102
- ### Get end of day
103
-
104
- ```ts
105
- import { endOfDate } from "@northguild/gmt";
106
-
107
- const end = endOfDate("2024-03-15T14:30:45"); // "2024-03-15T23:59:59.999999999"
108
- ```
109
-
110
- ### Get start of month
111
-
112
- ```ts
113
- import { startOfDate } from "@northguild/gmt";
114
-
115
- const start = startOfDate("2024-03-15", "month"); // "2024-03-01"
116
- ```
117
-
118
- ### Get end of month
119
-
120
- ```ts
121
- import { endOfDate } from "@northguild/gmt";
122
-
123
- const end = endOfDate("2024-03-15", "month"); // "2024-03-31"
124
- ```
125
-
126
- ### Get quarter boundaries
127
-
128
- ```ts
129
- import { startOfQuarterForDate, endOfQuarterForDate } from "@northguild/gmt";
130
-
131
- const q1Start = startOfQuarterForDate("2024-03-15"); // "2024-01-01"
132
- const q1End = endOfQuarterForDate("2024-03-15"); // "2024-03-31"
133
- ```
134
-
135
- ### Clamp a date to a range
136
-
137
- ```ts
138
- import { clampDate } from "@northguild/gmt";
139
-
140
- clampDate("2024-03-15", "2024-03-01", "2024-03-31");
141
- // "2024-03-15"
142
-
143
- clampDate("2024-02-01", "2024-03-01", "2024-03-31");
144
- // "2024-03-01" (below min)
145
-
146
- clampDate("2024-05-01", "2024-03-01", "2024-03-31");
147
- // "2024-03-31" (above max)
148
-
149
- clampDate("2024-03-15", "2024-03-31", "2024-03-01");
150
- // "" (min > max is invalid)
151
- ```
152
-
153
- ### Find the nearest date to a target
154
-
155
- ```ts
156
- import { closestDateTo } from "@northguild/gmt";
157
-
158
- closestDateTo("2024-03-15", ["2024-03-01", "2024-03-20", "2024-03-18"]);
159
- // "2024-03-18"
160
-
161
- closestDateTo("2024-03-15", ["2024-03-01", "2024-03-29"]);
162
- // "2024-03-01" (tie-breaking favors first in array order when equidistant)
163
-
164
- closestDateTo("2024-03-15", []);
165
- // null (empty candidates)
166
-
167
- closestDateTo("invalid", ["2024-03-01"]);
168
- // null (invalid target)
169
- ```
170
-
171
- Distance is measured in whole calendar days via `Temporal.PlainDate.until()`. On a tie between two equidistant candidates, the first one in array order wins.
172
-
173
- ### Get locale-aware week boundaries
174
-
175
- ```ts
176
- import { getLocaleStartOfWeek, getLocaleEndOfWeek } from "@northguild/gmt";
177
-
178
- getLocaleStartOfWeek("2024-02-29", "en-US"); // "2024-02-25" (Sunday, en-US weeks start Sunday)
179
- getLocaleStartOfWeek("2024-02-29", "fr-FR"); // "2024-02-26" (Monday, fr-FR weeks start Monday)
180
-
181
- getLocaleEndOfWeek("2024-02-29", "en-US"); // "2024-03-02" (Saturday)
182
- getLocaleEndOfWeek("2024-02-29", "fr-FR"); // "2024-03-03" (Sunday)
183
- ```
184
-
185
- Unlike `startOfDate(value, "week", { weekStartsOn })`/`endOfDate(value, "week", { weekStartsOn })`, which take an explicit ISO-biased `weekStartsOn` (`"monday"` | `"sunday"`, default `"monday"`), `getLocaleStartOfWeek`/`getLocaleEndOfWeek` derive the week's first day automatically from the locale via `Intl.Locale.prototype.weekInfo`, falling back to Monday if the runtime can't resolve `weekInfo` for the locale. Both return `""` for invalid `value` or an unresolvable `locale`. Zoned equivalents (`getLocaleZonedStartOfWeek`/`getLocaleZonedEndOfWeek`) live in the `zoned-date-ops` skill.
186
-
187
- ### Set one or more fields directly
188
-
189
- ```ts
190
- import { setDate, setDateTime, setTime } from "@northguild/gmt";
191
-
192
- setDate("2024-03-10", { year: 2025 }); // "2025-03-10"
193
- setDate("2024-01-31", { month: 2 }); // "2024-02-29" (constrain clamps to the last valid day)
194
- setDate("2024-01-31", { month: 2 }, { overflow: "reject" }); // "" (Feb 31 doesn't exist)
195
- setDate("2024-03-10", {}); // "2024-03-10" (empty fields object is a no-op)
196
-
197
- setDateTime("2024-03-10T12:00:00", { hour: 9 }); // "2024-03-10T09:00:00"
198
- setTime("12:00:00", { hour: 25 }); // "23:00:00" (constrain clamps; overflow has a real effect here, unlike addTime's clock wraparound)
199
- ```
200
-
201
- `setDate`/`setDateTime`/`setTime` wrap `Temporal.*.prototype.with()`, which resolves every supplied field in a single atomic overflow pass. This is the safe alternative to composing `addDate()`/`addDateTime()`/`addTime()` calls field-by-field: each sequential `.add()` resolves overflow against its own intermediate value, so setting month-then-day vs. day-then-month on the same target can silently diverge — `.with()` has no such order-dependence. Zoned/unix/utc equivalents (`setZoned`/`setUnix`/`setUtc`) live in the `zoned-date-ops` skill, since they also take `disambiguation`/`offset`.
202
-
203
- ### Control out-of-range add/subtract results with overflow
204
-
205
- ```ts
206
- import { addDate } from "@northguild/gmt";
207
-
208
- // default overflow: "constrain" clamps to the nearest valid date
209
- const clamped = addDate("2024-01-31", { months: 1 }); // "2024-02-29"
210
-
211
- // overflow: "reject" returns the sentinel instead of clamping
212
- const rejected = addDate("2024-01-31", { months: 1 }, { overflow: "reject" }); // ""
213
- ```
214
-
215
- `overflow` is available on `addDate`, `addDateTime`, `addTime`, `addUnix`, `addUtc`, `addZoned`, and their `subtract` equivalents. It defaults to `"constrain"` (matches prior behavior) and is accepted-but-inert on `addTime`/`subtractTime`, since `PlainTime` arithmetic always wraps around the clock rather than producing an out-of-range value.
216
-
217
- ### Add/subtract business days (Mon–Fri only)
218
-
219
- ```ts
220
- import { addBusinessDays, subtractBusinessDays } from "@northguild/gmt";
221
-
222
- addBusinessDays("2024-03-15", 1);
223
- // "2024-03-18" (Friday + 1 business day skips Sat/Sun to Monday)
224
-
225
- subtractBusinessDays("2024-03-18", 1);
226
- // "2024-03-15" (Monday - 1 business day skips Sat/Sun to Friday)
227
-
228
- addBusinessDays("2024-03-16", 1);
229
- // "2024-03-18" (Saturday start: skip weekend, Monday is the first business day)
230
-
231
- subtractBusinessDays("2024-03-17", 1);
232
- // "2024-03-15" (Sunday start skips to Saturday, then -1 = Friday)
233
-
234
- addBusinessDays("2024-03-15", 0);
235
- // "2024-03-15" (zero returns the input unchanged)
236
-
237
- addBusinessDays("invalid", 1);
238
- // "" (sentinel on invalid input)
239
- ```
240
-
241
- `addBusinessDays`/`subtractBusinessDays` skip Saturday and Sunday during the count. They use fixed ISO Monday–Friday business days with no locale parameter. Negative `amount` on `addBusinessDays` behaves identically to `subtractBusinessDays(value, Math.abs(amount))`, and vice versa.
242
-
243
- To test whether a given date is itself a business day before doing arithmetic on it, use `isBusinessDay` (see the `compare-dates` skill) — it shares this exact fixed ISO Mon–Fri boundary.
244
-
245
- ### Round a diff result with smallestUnit/roundingIncrement/roundingMode
246
-
247
- ```ts
248
- import { diffDate } from "@northguild/gmt";
249
-
250
- // unrounded (default): exact difference in the requested unit
251
- const exact = diffDate("2023-01-01", "2023-01-10", "day"); // 9
252
-
253
- // round to the nearest week
254
- const rounded = diffDate("2023-01-01", "2023-01-10", "week", {
255
- smallestUnit: "week",
256
- roundingMode: "halfExpand",
257
- }); // 1
258
- ```
259
-
260
- `smallestUnit`, `roundingIncrement`, and `roundingMode` are available on `diffDate`, `diffDateTime`, `diffTime`, `diffUnix`, `diffUtc`, and `diffZoned`. All default to no rounding (the prior, exact behavior) when omitted.
261
-
262
- ### Calendar-unit arithmetic on a non-Gregorian date (E5)
263
-
264
- `addDate`, `subtractDate`, `diffDate`, and `diffDateAsDuration` accept a GMT calendar-annotated `PlainDate` string (as produced by `convertDateToCalendar`, e.g. `"5784-06-15[u-ca=hebrew]"`), not just a bare ISO string. Calendar-unit arithmetic ("add 1 month") resolves in that calendar:
265
-
266
- ```ts
267
- import { addDate, convertDateToCalendar, diffDate } from "@northguild/gmt";
268
-
269
- const adarI = convertDateToCalendar("2024-02-24", "hebrew"); // "5784-06-15[u-ca=hebrew]"
270
- addDate(adarI, { months: 1 }); // "5784-07-15[u-ca=hebrew]" — Adar I (leap-only) -> Adar
271
-
272
- diffDate(adarI, "5784-07-15[u-ca=hebrew]", "months"); // 1 — measured in Hebrew
273
- diffDate(adarI, "2024-11-03", "days"); // falls back to Gregorian — the two arguments carry different (or no) calendar tags
274
- ```
275
-
276
- Only `plain/` `PlainDate` functions accept this — `addDateTime`/`addTime`/`addZoned`/`addUtc`/`addUnix` and their `subtract*`/`diff*` siblings reject a calendar annotation (return the sentinel), since GMT has no calendar-annotated `PlainDateTime`/`ZonedDateTime`/UTC grammar and `PlainTime` has no calendar concept at all. `addBusinessDays`/`subtractBusinessDays` also reject it — weekday is calendar-independent in every supported calendar, so a tag would change nothing about the answer while implying it might. See `packages/gmt/README.md`'s "Calendar-aware interval and duration arithmetic" section and `context/roadmap/issues/E.md`'s E5 outcome for the full audit.
277
-
278
- ### Cycle a single field without changing others (E6)
279
-
280
- ```ts
281
- import { cycleDate, cycleDateTime, cycleTime } from "@northguild/gmt";
282
-
283
- cycleDate("2024-12-15", "month", 1); // "2024-01-15" — wraps, stays in the same year
284
- cycleDate("2024-12-31", "day", 1); // "2024-12-01" — wraps within the same month
285
- cycleDate("2024-01-15", "month", 13); // "2024-02-15" — an amount larger than the range still wraps correctly
286
-
287
- cycleDateTime("2024-06-15T23:30:00", "hour", 1); // "2024-06-15T00:30:00" — wraps, stays on the same day
288
- cycleTime("09:22:00", "minute", 15, { round: true }); // "09:30:00" — steps to the next multiple of 15, not the nearest
289
-
290
- cycleDate("2024-06-15", "week", 1); // "" — "week" isn't a cyclable field (only year/month/day/hour/minute/second/ms/us/ns)
291
- ```
292
-
293
- `cycleDate`/`cycleDateTime`/`cycleTime` are not `addDate`/`addDateTime`/`addTime`: they adjust one field and wrap at that field's own min/max instead of carrying into the next larger field. Cycling `month`/`year` can still clamp (or, with `overflow: "reject"`, reject) `day` — the same `.with()`-based clamping `setDate` documents — since they build on the same J1 field setters. `overflow` is accepted on `cycleTime` for signature consistency but is inert there: a cycled time field's wrapped value is always already in range. The `zoned/` equivalent, `cycleZoned`, lives in the `zoned-date-ops` skill since it also takes `disambiguation`/`offset`.
294
-
295
- ## Common Mistakes
296
-
297
- ### Doing calendar arithmetic and timezone arithmetic in two steps
298
-
299
- `addZoned`/`subtractZoned` accept GMT's calendar-annotated zoned string and resolve the calendar
300
- unit and the DST rules together:
301
-
302
- ```ts
303
- addZoned("5784-06-15T14:30:00-05:00[u-ca=hebrew][America/New_York]", { months: 1 });
304
- // "5784-07-15T14:30:00-04:00[u-ca=hebrew][America/New_York]" — Adar I -> Adar AND EST -> EDT
305
- ```
306
-
307
- Splitting this into a `plain/` calendar step plus a zoned conversion gives the wrong answer in
308
- either order: do the calendar step first and DST is applied to an already-resolved wall time; do
309
- the zoned step first and there is no calendar left to step in. Note that `addZonedBusinessDays` /
310
- `subtractZonedBusinessDays` still reject the annotation — day-of-week is ISO-fixed in every
311
- supported calendar, so a tag would change nothing.
312
-
313
- ### Building a calendar-annotated zoned string by hand
314
-
315
- GMT's zoned calendar grammar puts `[u-ca=...]` **before** `[timeZone]` — the reverse of RFC 9557
316
- and of Temporal's own `toString()`:
317
-
318
- ```
319
- 5784-06-15T14:30:00-05:00[u-ca=hebrew][America/New_York] // correct
320
- 5784-06-15T14:30:00-05:00[America/New_York][u-ca=hebrew] // WRONG — silently misparses in Temporal
321
- ```
322
-
323
- Always produce these with `convertZonedToCalendar`, never by concatenation. See the
324
- `zoned-date-ops` skill's Common Mistakes for the full trap.
325
-
326
- ### HIGH Using manual date arithmetic
327
-
328
- Wrong:
329
-
330
- ```ts
331
- const date = new Date("2024-03-15");
332
- date.setDate(date.getDate() + 5); // mutates original
333
- ```
334
-
335
- Correct:
336
-
337
- ```ts
338
- import { addDays } from "@northguild/gmt";
339
-
340
- const result = addDays("2024-03-15", 5); // new immutable date
341
- ```
342
-
343
- Source: AGENTS.md — Never mutate Date objects
344
-
345
- ### HIGH Not handling month overflow
346
-
347
- Wrong:
348
-
349
- ```ts
350
- const result = addMonths("2024-01-31", 1); // may throw or be incorrect
351
- ```
352
-
353
- Correct:
354
-
355
- ```ts
356
- import { addMonths } from "@northguild/gmt";
357
-
358
- const result = addMonths("2024-01-31", 1); // "2024-03-02" (clamped to end of month)
359
- ```
360
-
361
- Source: Temporal.PlainDate.add() — clamps to valid date
362
-
363
- ### MEDIUM Not handling invalid input
364
-
365
- Wrong:
366
-
367
- ```ts
368
- const result = addDays("invalid", 5);
369
- // Assume result is valid date string
370
- processDate(result);
371
- ```
372
-
373
- Correct:
374
-
375
- ```ts
376
- import { addDays, isValidDate } from "@northguild/gmt";
377
-
378
- const input = "2024-03-15";
379
- const result = addDays(input, 5);
380
- if (!isValidDate(result)) {
381
- throw new Error("Invalid result");
382
- }
383
- ```
384
-
385
- Source: packages/gmt/src/plain/calculate/addDate.ts — Returns "" on invalid input
386
-
387
- ### MEDIUM Not handling leap year
388
-
389
- Wrong:
390
-
391
- ```ts
392
- const result = addDays("2024-02-28", 1);
393
- const day = parseDayFromDate(result); // 28, 29, or 1?
394
- ```
395
-
396
- Correct:
397
-
398
- ```ts
399
- import { addDays } from "@northguild/gmt";
400
-
401
- const result = addDays("2024-02-28", 1); // "2024-02-29" (correct for leap year)
402
- ```
403
-
404
- Source: Temporal handles leap years automatically
405
-
406
- ### HIGH Reaching for cycleDate when calendar arithmetic is wanted (or vice versa)
407
-
408
- Wrong:
409
-
410
- ```ts
411
- // Trying to move a date forward by a month, but landing a year off:
412
- const nextBillingDate = cycleDate("2024-12-15", "month", 1); // "2024-01-15" — same year!
413
- ```
414
-
415
- Correct:
416
-
417
- ```ts
418
- import { addDate, cycleDate } from "@northguild/gmt";
419
-
420
- // Calendar arithmetic (crossing year boundaries is expected): use addDate
421
- const nextBillingDate = addDate("2024-12-15", { months: 1 }); // "2025-01-15"
422
-
423
- // Datepicker segment editing (year must NOT change when the month segment wraps): use cycleDate
424
- const monthSegmentIncremented = cycleDate("2024-12-15", "month", 1); // "2024-01-15"
425
- ```
426
-
427
- Source: packages/gmt/src/plain/calculate/cycleDate.ts — cycling wraps at the field's own boundary; adding overflows into the next field
428
-
429
- ## References
430
-
431
- - [Full calculate API](references/calculate-api.md)
432
- - [Temporal.PlainDate arithmetic](https://tc39.es/proposal-temporal/docs/plaindate.html#arithmetic)