@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,418 +0,0 @@
1
- ---
2
- name: durations
3
- description: >
4
- Parse, validate, combine, rebalance, and format ISO 8601 duration strings
5
- (e.g. "P1DT2H30M"). Use isValidDuration to check a duration string is
6
- well-formed, parseDuration to normalize one and optionally control its output
7
- precision/rounding via smallestUnit, fractionalSecondDigits, and roundingMode,
8
- addDuration / subtractDuration to combine two duration strings,
9
- normalizeDuration to roll small units into larger ones (e.g. 90 minutes into 1
10
- hour 30 minutes) via largestUnit/smallestUnit/
11
- roundingIncrement/roundingMode/relativeTo, getDurationUnit to read one stored
12
- component, durationAs to total a duration into a single unit, negateDuration /
13
- absDuration to flip or drop its sign, getDurationSign to test whether it is
14
- negative/zero/positive, compareDurations to order two durations by length, and
15
- formatDuration to render a duration as a human-readable, locale-aware string
16
- (e.g. "1 day, 2 hours, and 30 minutes").
17
- sources:
18
- - 'northguild/gmt:packages/gmt/src/duration/index.ts'
19
- - 'northguild/gmt:packages/gmt/src/duration/calculate/getDurationUnit.ts'
20
- - 'northguild/gmt:packages/gmt/src/duration/calculate/durationAs.ts'
21
- - 'northguild/gmt:packages/gmt/src/duration/calculate/negateDuration.ts'
22
- - 'northguild/gmt:packages/gmt/src/duration/calculate/absDuration.ts'
23
- - 'northguild/gmt:packages/gmt/src/duration/calculate/getDurationSign.ts'
24
- - 'northguild/gmt:packages/gmt/src/duration/compare/compareDurations.ts'
25
- - 'northguild/gmt:packages/gmt/src/plain/calculate/diffDateAsDuration.ts'
26
- - 'northguild/gmt:packages/gmt/src/plain/calculate/diffDateTimeAsDuration.ts'
27
- - 'northguild/gmt:packages/gmt/src/zoned/calculate/diffZonedAsDuration.ts'
28
- - 'northguild/gmt:packages/gmt/src/unix/calculate/diffUnixAsDuration.ts'
29
- - 'northguild/gmt:packages/gmt/src/utc/calculate/diffUtcAsDuration.ts'
30
- metadata:
31
- type: core
32
- library: '@northguild/gmt'
33
- library_version: '1.14.2'
34
- ---
35
-
36
- # Durations
37
-
38
- Use this skill when you need to parse, validate, re-normalize, or format ISO 8601 duration strings — distinct from date/time arithmetic, which takes a `{ unit: number }` object rather than a duration string. See the `calculate-dates` skill for `add*`/`subtract*`/`diff*`.
39
-
40
- ## Setup
41
-
42
- ```ts
43
- import {
44
- absDuration,
45
- addDuration,
46
- compareDurations,
47
- durationAs,
48
- formatDuration,
49
- getDurationSign,
50
- getDurationUnit,
51
- isValidDuration,
52
- negateDuration,
53
- normalizeDuration,
54
- parseDuration,
55
- subtractDuration,
56
- } from "@northguild/gmt";
57
- ```
58
-
59
- ## Core Patterns
60
-
61
- ### Validate an ISO 8601 duration string
62
-
63
- ```ts
64
- const valid = isValidDuration("P1DT2H30M"); // true
65
- const invalid = isValidDuration("not a duration"); // false
66
- const empty = isValidDuration(""); // false
67
- ```
68
-
69
- ### Parse and re-normalize a duration string
70
-
71
- ```ts
72
- const result = parseDuration("P1DT2H30M"); // "P1DT2H30M"
73
- const invalid = parseDuration("not a duration"); // ""
74
- ```
75
-
76
- ### Round a duration's output precision
77
-
78
- ```ts
79
- // smallestUnit rounds down to whole units of the given granularity
80
- const rounded = parseDuration("PT1.5S", { smallestUnit: "second" }); // "PT1S"
81
-
82
- // fractionalSecondDigits pads/truncates the fractional-seconds component
83
- const padded = parseDuration("PT1.5S", { fractionalSecondDigits: 3 }); // "PT1.500S"
84
-
85
- // roundingMode controls how rounding is performed (default halfExpand)
86
- const truncated = parseDuration("PT1.9S", {
87
- smallestUnit: "second",
88
- roundingMode: "trunc",
89
- }); // "PT1S"
90
- ```
91
-
92
- ### Combine two duration strings
93
-
94
- ```ts
95
- const combined = addDuration("P1D", "PT2H"); // "P1DT2H"
96
- const remainder = subtractDuration("P1D", "PT2H"); // "PT22H"
97
- const negative = subtractDuration("PT1H", "PT2H"); // "-PT1H"
98
- ```
99
-
100
- ### Roll small units into larger ones
101
-
102
- ```ts
103
- // largestUnit promotes into a bigger unit
104
- const promoted = normalizeDuration("PT90M", { largestUnit: "hour" }); // "PT1H30M"
105
-
106
- // smallestUnit rounds off sub-unit precision, no largestUnit required
107
- const rounded = normalizeDuration("PT90M30S", { smallestUnit: "minute" }); // "PT91M"
108
-
109
- // no options defaults to largestUnit: "auto" — reformats without promoting
110
- const reformatted = normalizeDuration("PT90M"); // "PT90M" (unchanged)
111
-
112
- // calendar units (year/month/week) need relativeTo as a reference point
113
- const noRef = normalizeDuration("P45D", { largestUnit: "month" }); // ""
114
- const withRef = normalizeDuration("P45D", {
115
- largestUnit: "month",
116
- relativeTo: "2024-01-01",
117
- }); // "P1M14D"
118
- ```
119
-
120
- ### Read one component out of a duration
121
-
122
- `getDurationUnit` returns the component **as stored**, not a converted total.
123
-
124
- ```ts
125
- const hours = getDurationUnit("P1DT2H30M", "hours"); // 2
126
- const minutes = getDurationUnit("P1DT2H30M", "minutes"); // 30
127
-
128
- // "PT90M" stores 90 in its minutes field and nothing in its hours field
129
- const noHours = getDurationUnit("PT90M", "hours"); // 0
130
-
131
- // a negative duration stores every field as negative
132
- const negative = getDurationUnit("-P1DT2H", "hours"); // -2
133
-
134
- // reading a field is not a unit conversion, so calendar units need no relativeTo
135
- const months = getDurationUnit("P1M", "months"); // 1
136
-
137
- const invalid = getDurationUnit("not a duration", "hours"); // null
138
- ```
139
-
140
- ### Total a duration into a single unit
141
-
142
- `durationAs` converts the whole duration and returns a fractional number.
143
-
144
- ```ts
145
- const asHours = durationAs("P1DT2H30M", "hours"); // 26.5
146
- const asMinutes = durationAs("P1DT2H30M", "minutes"); // 1590
147
- const asDays = durationAs("PT36H", "days"); // 1.5 — fractional, not rounded
148
-
149
- // calendar units need a relativeTo anchor, in either direction
150
- const noAnchor = durationAs("P1M", "days"); // null
151
- const feb = durationAs("P1M", "days", { relativeTo: "2024-02-01" }); // 29
152
- const jan = durationAs("P1M", "days", { relativeTo: "2024-01-01" }); // 31
153
-
154
- // a zoned anchor resolves real elapsed time across a DST transition
155
- const springForward = durationAs("P1D", "hours", {
156
- relativeTo: "2024-03-10T00:00:00-05:00[America/New_York]",
157
- }); // 23
158
- ```
159
-
160
- ### Flip, drop, or test a duration's sign
161
-
162
- ```ts
163
- const negated = negateDuration("P1DT2H"); // "-P1DT2H"
164
- const backAgain = negateDuration("-P1DT2H"); // "P1DT2H"
165
- const positive = absDuration("-P1DT2H"); // "P1DT2H"
166
-
167
- const sign = getDurationSign("-P1DT2H"); // -1
168
- const zero = getDurationSign("PT0S"); // 0
169
- const invalid = getDurationSign("not a duration"); // null
170
-
171
- // all three are pure sign operations — calendar units work, no relativeTo needed
172
- const negatedMonth = negateDuration("P1Y2M"); // "-P1Y2M"
173
- ```
174
-
175
- ### Compare two durations by length
176
-
177
- ```ts
178
- const longer = compareDurations("PT1H", "PT30M"); // 1
179
- const shorter = compareDurations("PT30M", "PT1H"); // -1
180
-
181
- // equal by length, not by spelling
182
- const equal = compareDurations("PT60M", "PT1H"); // 0
183
-
184
- // unlike addDuration, Temporal.Duration.compare DOES take relativeTo, so
185
- // calendar-unit durations are comparable — the anchor decides the answer
186
- const noAnchor = compareDurations("P1M", "P30D"); // null
187
- const fromJan = compareDurations("P1M", "P30D", { relativeTo: "2024-01-01" }); // 1
188
- const fromFeb = compareDurations("P1M", "P30D", { relativeTo: "2024-02-01" }); // -1
189
- ```
190
-
191
- ### Render a duration as human-readable text
192
-
193
- ```ts
194
- const long = formatDuration("P1DT2H30M", "en-US");
195
- // "1 day, 2 hours, and 30 minutes"
196
-
197
- const short = formatDuration("P1DT2H30M", "en-US", { style: "short" });
198
- // "1 day, 2 hr, & 30 min"
199
-
200
- const narrow = formatDuration("P1DT2H30M", "en-US", { style: "narrow" });
201
- // "1d, 2h, & 30m"
202
-
203
- // zero-valued components are omitted by default
204
- const noZeros = formatDuration("P1DT0H30M", "en-US"); // "1 day and 30 minutes"
205
- const withZeros = formatDuration("P1DT0H30M", "en-US", { zero: true });
206
- // "0 years, 0 months, 0 weeks, 1 day, 0 hours, 30 minutes, and 0 seconds"
207
-
208
- // a zero-length duration always renders "0 seconds"
209
- const zero = formatDuration("PT0S", "en-US"); // "0 seconds"
210
- ```
211
-
212
- ### Bridge a diff into an ISO duration string
213
-
214
- `diffDate`/`diffDateTime`/`diffZoned`/`diffUnix`/`diffUtc` return a single-unit number (or a `Record<Unit, number>` for an array of units). Their `*AsDuration` siblings return an ISO 8601 duration string instead — the same string shape every other function in this skill consumes/produces.
215
-
216
- ```ts
217
- import { diffDateAsDuration, diffZonedAsDuration } from "@northguild/gmt";
218
-
219
- const span = diffDateAsDuration("2024-03-10", "2024-04-05", "days"); // "P26D"
220
-
221
- const negative = diffDateAsDuration("2024-04-05", "2024-03-10", "days"); // "-P26D"
222
-
223
- const zoned = diffZonedAsDuration(
224
- "2024-03-09T12:00:00-05:00[America/New_York]",
225
- "2024-03-11T12:00:00-04:00[America/New_York]",
226
- "days",
227
- ); // "P1DT23H" — real elapsed time across a DST spring-forward transition
228
- ```
229
-
230
- Feed the result straight into a `duration` namespace function, e.g. `formatDuration(diffDateAsDuration(a, b, "days"), "en-US")`.
231
-
232
- ## Common Mistakes
233
-
234
- ### HIGH Confusing duration strings with unit objects
235
-
236
- Wrong:
237
-
238
- ```ts
239
- import { addDate } from "@northguild/gmt";
240
-
241
- // addDate takes a { unit: number } object, not an ISO duration string
242
- const result = addDate("2024-03-15", "P5D");
243
- ```
244
-
245
- Correct:
246
-
247
- ```ts
248
- import { addDate } from "@northguild/gmt";
249
-
250
- const result = addDate("2024-03-15", { days: 5 }); // "2024-03-20"
251
- ```
252
-
253
- Source: packages/gmt/src/plain/calculate/addDate.ts — accepts `Partial<Record<DateDurationUnit, number>>`, not a duration string
254
-
255
- ### MEDIUM Not validating before parsing
256
-
257
- Wrong:
258
-
259
- ```ts
260
- const result = parseDuration(userInput);
261
- // assume result is a valid duration string
262
- useDuration(result);
263
- ```
264
-
265
- Correct:
266
-
267
- ```ts
268
- import { isValidDuration, parseDuration } from "@northguild/gmt";
269
-
270
- if (!isValidDuration(userInput)) {
271
- throw new Error("Invalid duration");
272
- }
273
- const result = parseDuration(userInput);
274
- ```
275
-
276
- Source: packages/gmt/src/duration/parse/parseDuration.ts — returns `""` on invalid input rather than throwing
277
-
278
- ### MEDIUM Expecting a best-effort answer for calendar units without relativeTo
279
-
280
- `"P1M"` has no fixed length, so nothing in this namespace will guess one for you. Every function that has to *measure* a duration returns its sentinel (`""` for strings, `null` for numbers) when a year/month/week is involved and no `relativeTo` anchor was supplied — it does not fall back to a nominal 30-day month.
281
-
282
- Wrong:
283
-
284
- ```ts
285
- import { addDuration, compareDurations, durationAs } from "@northguild/gmt";
286
-
287
- const combined = addDuration("P1Y", "P1M"); // ""
288
- const asDays = durationAs("P1M", "days"); // null — not 30
289
- const ordered = compareDurations("P1M", "P30D"); // null
290
- ```
291
-
292
- Correct: supply `relativeTo` wherever the function accepts one. Note the three tiers, which differ by what Temporal itself allows:
293
-
294
- ```ts
295
- import {
296
- absDuration,
297
- compareDurations,
298
- durationAs,
299
- getDurationSign,
300
- getDurationUnit,
301
- negateDuration,
302
- normalizeDuration,
303
- } from "@northguild/gmt";
304
-
305
- // 1. Takes relativeTo — pass it and calendar units work
306
- durationAs("P1M", "days", { relativeTo: "2024-02-01" }); // 29
307
- compareDurations("P1M", "P30D", { relativeTo: "2024-01-01" }); // 1
308
- normalizeDuration("P45D", { largestUnit: "month", relativeTo: "2024-01-01" }); // "P1M14D"
309
-
310
- // 2. Never needs relativeTo — reads a stored field or flips a sign, no measuring
311
- getDurationUnit("P1M", "months"); // 1
312
- getDurationSign("-P1Y"); // -1
313
- negateDuration("P1Y2M"); // "-P1Y2M"
314
- absDuration("-P1Y2M"); // "P1Y2M"
315
-
316
- // 3. Has no relativeTo option at all — use addDate/subtractDate with a unit object
317
- addDuration("P1Y", "P1M"); // "" — no way to make this work
318
- ```
319
-
320
- `durationAs`'s rule cuts in both directions, and the requested-unit half bites even on day/time-only input:
321
-
322
- ```ts
323
- durationAs("PT36H", "weeks"); // null — a week is a calendar quantity to Temporal
324
- durationAs("P1M", "hours"); // null — the input's month component needs an anchor
325
- ```
326
-
327
- The anchor decides the answer rather than merely unblocking it: `compareDurations("P1M", "P30D", { relativeTo: "2024-01-01" })` is `1` (January is 31 days) while the same call anchored to `"2024-02-01"` is `-1` (February 2024 is 29). It matters for non-calendar units too when the anchor names a zoned instant — `durationAs("P1D", "hours", { relativeTo: "2024-03-10T00:00:00-05:00[America/New_York]" })` is `23`, not `24`, because that day spans a DST spring-forward.
328
-
329
- Source: packages/gmt/src/duration/calculate/addDuration.ts (`Temporal.Duration.prototype.add`/`.subtract` have no `relativeTo` option, so calendar-unit operands throw and result in `""`), packages/gmt/src/duration/calculate/durationAs.ts and packages/gmt/src/duration/compare/compareDurations.ts (`.total()` and `Temporal.Duration.compare` both accept `relativeTo` and require it for calendar units)
330
-
331
- `relativeTo` also accepts a GMT calendar-annotated `PlainDate` string (E5, e.g. `"5784-06-15[u-ca=hebrew]"`, as produced by `convertDateToCalendar`) — not Temporal's own differently-shaped `[u-ca=...]` convention. `durationAs`, `compareDurations`, and `normalizeDuration` all resolve it the same way:
332
-
333
- ```ts
334
- durationAs("P1Y", "days", { relativeTo: "5784-06-15[u-ca=hebrew]" }); // 385, not the 366 a Gregorian P1Y would total — a Hebrew leap year
335
- ```
336
-
337
- ### HIGH Reading a component with getDurationUnit when you wanted a total
338
-
339
- `getDurationUnit` reads the named field **as stored**; `durationAs` converts the whole duration. They disagree whenever the duration isn't already spelled in the unit you're asking for, and `getDurationUnit`'s wrong answer is a plausible-looking `0` rather than an error.
340
-
341
- Wrong:
342
-
343
- ```ts
344
- import { getDurationUnit } from "@northguild/gmt";
345
-
346
- // "PT90M" holds 90 in its minutes field and nothing in its hours field
347
- const hours = getDurationUnit("PT90M", "hours"); // 0, not 1.5
348
- ```
349
-
350
- Correct:
351
-
352
- ```ts
353
- import { durationAs, getDurationUnit } from "@northguild/gmt";
354
-
355
- const total = durationAs("PT90M", "hours"); // 1.5 — the whole duration in hours
356
- const stored = getDurationUnit("P1DT2H30M", "hours"); // 2 — the hours component itself
357
- ```
358
-
359
- `durationAs` returns a fractional total and never rounds — `durationAs("P1DT2H30M", "days")` is `1.1041666666666667`. If you want a rebalanced duration *string* rather than a number, reach for `normalizeDuration("PT90M", { largestUnit: "hour" })` instead.
360
-
361
- Source: packages/gmt/src/duration/calculate/getDurationUnit.ts (reads `Temporal.Duration`'s field directly) vs. packages/gmt/src/duration/calculate/durationAs.ts (wraps `Temporal.Duration.prototype.total`)
362
-
363
- ### MEDIUM Assuming `normalizeDuration`'s default is always `relativeTo`-free
364
-
365
- Wrong:
366
-
367
- ```ts
368
- import { normalizeDuration } from "@northguild/gmt";
369
-
370
- // input already has a month component — the "auto" default does NOT
371
- // exempt calendar-unit inputs, this still throws internally and returns ""
372
- const result = normalizeDuration("P1M"); // ""
373
- ```
374
-
375
- Correct: pass `relativeTo` whenever a calendar unit (year/month/week) is involved — either as an explicit `largestUnit`, or because the input duration itself already has a nonzero year/month/week component, even under the default `{ largestUnit: "auto" }`.
376
-
377
- ```ts
378
- import { normalizeDuration } from "@northguild/gmt";
379
-
380
- const result = normalizeDuration("P1M", {
381
- largestUnit: "day",
382
- relativeTo: "2024-01-01",
383
- }); // "P31D"
384
- ```
385
-
386
- Source: packages/gmt/src/duration/normalize/normalizeDuration.ts — `Temporal.Duration.prototype.round` requires `relativeTo` for any week-or-larger unit, whether requested via `largestUnit` or already present in the duration being rounded
387
-
388
- ### MEDIUM Expecting `formatDuration` to match native `Intl.DurationFormat` output exactly
389
-
390
- `formatDuration` does not use `Intl.DurationFormat` — that constructor is absent entirely on Node 20/22 (only ships natively on Node 24+), and GMT does not carry a polyfill dependency for it. Instead, `formatDuration` builds its output from `Intl.NumberFormat({ style: "unit" })` per component plus `Intl.ListFormat` for joining — both universally available, so behavior is identical across all supported Node versions, but output is not guaranteed byte-for-byte identical to what native `Intl.DurationFormat` would produce (e.g. no `"digital"` `01:30:00`-style output is supported).
391
-
392
- Source: packages/gmt/src/duration/format/formatDuration.ts
393
-
394
- ### MEDIUM Passing an array of units to a `*AsDuration` bridge function
395
-
396
- Wrong:
397
-
398
- ```ts
399
- import { diffDateAsDuration } from "@northguild/gmt";
400
-
401
- // diffDate accepts unit | unit[], but diffDateAsDuration only accepts a
402
- // single unit — an array here fails validation and returns ""
403
- const result = diffDateAsDuration("2024-03-10", "2024-04-05", ["days"]); // ""
404
- ```
405
-
406
- Correct: pass a single unit. It only sets the resulting duration's `largestUnit` — an ISO duration string already expresses a full multi-unit breakdown (e.g. `"P1DT23H"`) without needing an array of requested units the way `diffDate`'s `Record<Unit, number>` return shape does.
407
-
408
- ```ts
409
- import { diffDateAsDuration } from "@northguild/gmt";
410
-
411
- const result = diffDateAsDuration("2024-03-10", "2024-04-05", "days"); // "P26D"
412
- ```
413
-
414
- Source: packages/gmt/src/plain/calculate/diffDateAsDuration.ts (and its `diffDateTimeAsDuration`/`diffZonedAsDuration`/`diffUnixAsDuration`/`diffUtcAsDuration` siblings) — signature takes `unit: DateDurationUnit`, not `unit: DateDurationUnit | DateDurationUnit[]` like their counterparts
415
-
416
- ## References
417
-
418
- - [Temporal.Duration](https://tc39.es/proposal-temporal/docs/duration.html)