@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.
- package/README.md +43 -13
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/plain/calculate/diffDateAsDuration.js +1 -1
- package/dist/plain/calculate/index.d.ts +2 -1
- package/dist/plain/calculate/index.js +2 -1
- package/dist/plain/calculate/{weekOfYear.d.ts → weekOfYearForDate.d.ts} +0 -14
- package/dist/plain/calculate/weekOfYearForDate.js +28 -0
- package/dist/plain/calculate/weekOfYearForDateTime.d.ts +14 -0
- package/dist/plain/calculate/{weekOfYear.js → weekOfYearForDateTime.js} +1 -27
- package/dist/plain/format/formatCalendar.d.ts +2 -2
- package/dist/plain/format/formatDate.d.ts +3 -2
- package/dist/plain/format/formatDate.js +1 -1
- package/dist/plain/format/formatDateRange.d.ts +3 -2
- package/dist/plain/format/formatDateRange.js +1 -1
- package/dist/plain/format/formatDateTime.d.ts +3 -2
- package/dist/plain/format/formatDateTime.js +1 -1
- package/dist/plain/format/formatDateTimeRange.d.ts +3 -2
- package/dist/plain/format/formatDateTimeRange.js +1 -1
- package/dist/plain/format/formatDateTimeToParts.d.ts +3 -2
- package/dist/plain/format/formatDateTimeToParts.js +1 -1
- package/dist/plain/format/formatDateToParts.d.ts +3 -2
- package/dist/plain/format/formatDateToParts.js +1 -1
- package/dist/plain/format/formatRelativeDate.d.ts +3 -14
- package/dist/plain/format/formatRelativeDateTime.d.ts +3 -14
- package/dist/plain/format/formatRelativeTime.d.ts +3 -14
- package/dist/plain/format/formatTime.d.ts +2 -1
- package/dist/plain/get/getNowUnit.d.ts +2 -3
- package/dist/plain/interval/splitIntervalByUnitDate.js +1 -1
- package/dist/regex/calendar-date.d.ts +19 -0
- package/dist/regex/calendar-date.js +19 -10
- package/dist/regex/calendar-zoned-date-time.d.ts +45 -0
- package/dist/regex/calendar-zoned-date-time.js +45 -40
- package/dist/regex/date-time.d.ts +18 -0
- package/dist/regex/date-time.js +18 -1
- package/dist/regex/date.d.ts +16 -0
- package/dist/regex/date.js +16 -1
- package/dist/regex/http-date.d.ts +15 -0
- package/dist/regex/http-date.js +15 -6
- package/dist/regex/leap-second.d.ts +12 -0
- package/dist/regex/leap-second.js +12 -1
- package/dist/regex/rfc-2822.d.ts +18 -0
- package/dist/regex/rfc-2822.js +18 -8
- package/dist/regex/rfc-3339.d.ts +19 -0
- package/dist/regex/rfc-3339.js +19 -6
- package/dist/regex/sql-date-time.d.ts +14 -0
- package/dist/regex/sql-date-time.js +14 -6
- package/dist/regex/time-zone-like.d.ts +21 -0
- package/dist/regex/time-zone-like.js +21 -0
- package/dist/regex/time.d.ts +59 -0
- package/dist/regex/time.js +59 -3
- package/dist/regex/unix.d.ts +21 -0
- package/dist/regex/unix.js +21 -0
- package/dist/regex/utc-date-time.d.ts +20 -0
- package/dist/regex/utc-date-time.js +20 -1
- package/dist/types/calendar-options.d.ts +11 -0
- package/dist/types/calendar-options.js +0 -0
- package/dist/types/datetime-format-options.d.ts +6 -0
- package/dist/types/datetime-format-options.js +0 -0
- package/dist/types/duration-unit.d.ts +5 -0
- package/dist/types/duration-unit.js +0 -0
- package/dist/types/index.d.ts +11 -0
- package/dist/types/index.js +11 -0
- package/dist/types/now-unit.d.ts +5 -0
- package/dist/types/now-unit.js +0 -0
- package/dist/types/relative-date-time-unit.d.ts +6 -0
- package/dist/types/relative-date-time-unit.js +0 -0
- package/dist/types/relative-date-unit.d.ts +5 -0
- package/dist/types/relative-date-unit.js +0 -0
- package/dist/types/relative-time-options.d.ts +18 -0
- package/dist/types/relative-time-options.js +0 -0
- package/dist/types/relative-time-unit.d.ts +5 -0
- package/dist/types/relative-time-unit.js +0 -0
- package/dist/types/relative-unit.d.ts +6 -0
- package/dist/types/relative-unit.js +0 -0
- package/dist/types/unix-now-unit.d.ts +5 -0
- package/dist/types/unix-now-unit.js +0 -0
- package/dist/types/utc-now-unit.d.ts +5 -0
- package/dist/types/utc-now-unit.js +0 -0
- package/dist/unix/calculate/startOrEndOfUnix.d.ts +18 -0
- package/dist/unix/calculate/startOrEndOfUnix.js +18 -0
- package/dist/unix/format/formatCalendarUnix.d.ts +27 -1
- package/dist/unix/format/formatRelativeUnix.d.ts +1 -8
- package/dist/unix/format/formatUnix.d.ts +19 -0
- package/dist/unix/format/formatUnix.js +19 -0
- package/dist/unix/get/getUnixNowUnit.d.ts +2 -3
- package/dist/unix/parse/parseUnitFromUnix.d.ts +24 -0
- package/dist/unix/validate/isValidUnixUnit.d.ts +15 -0
- package/dist/utc/calculate/startOrEndOfUtc.d.ts +17 -0
- package/dist/utc/calculate/startOrEndOfUtc.js +17 -0
- package/dist/utc/format/formatCalendarUtc.d.ts +2 -2
- package/dist/utc/format/formatRelativeUtc.d.ts +2 -12
- package/dist/utc/format/formatUtc.d.ts +33 -0
- package/dist/utc/format/formatUtc.js +17 -0
- package/dist/utc/get/getUtcNowUnit.d.ts +2 -3
- package/dist/utc/parse/parseUnitFromUtc.d.ts +23 -0
- package/dist/zoned/format/formatRelativeZoned.d.ts +1 -8
- package/dist/zoned/format/formatTimeZoneName.d.ts +15 -0
- package/dist/zoned/format/formatZonedDateTime.d.ts +2 -1
- package/dist/zoned/format/formatZonedRange.d.ts +2 -1
- package/dist/zoned/format/formatZonedToParts.d.ts +2 -1
- package/dist/zoned/get/getDstTransitions.d.ts +18 -2
- package/dist/zoned/get/getZonedNowUnit.d.ts +24 -0
- package/dist/zoned/interval/intervalDifferenceZoned.d.ts +3 -3
- package/dist/zoned/interval/intervalDifferenceZoned.js +3 -3
- package/dist/zoned/interval/intervalXorZoned.d.ts +6 -4
- package/dist/zoned/interval/intervalXorZoned.js +6 -4
- package/dist/zoned/parse/getZonedOffsetAs.d.ts +11 -0
- package/dist/zoned/parse/parseUnitFromZoned.d.ts +24 -0
- package/package.json +25 -7
- package/skills/gmt-arithmetic/SKILL.md +83 -0
- package/skills/gmt-basics/SKILL.md +80 -0
- package/skills/gmt-integration/SKILL.md +73 -0
- package/skills/gmt-timezone/SKILL.md +80 -0
- package/skills/_artifacts/domain_map.yaml +0 -547
- package/skills/_artifacts/skill_spec.md +0 -67
- package/skills/_artifacts/skill_tree.yaml +0 -278
- package/skills/api-expansion-workflow/SKILL.md +0 -52
- package/skills/app-integration/SKILL.md +0 -38
- package/skills/calculate-dates/SKILL.md +0 -432
- package/skills/compare-dates/SKILL.md +0 -412
- package/skills/convert-types/SKILL.md +0 -305
- package/skills/durations/SKILL.md +0 -418
- package/skills/format-date-time/SKILL.md +0 -486
- package/skills/format-date-time/references/format-api.md +0 -34
- package/skills/format-relative-time/SKILL.md +0 -223
- package/skills/get-current/SKILL.md +0 -229
- package/skills/interval-ops/SKILL.md +0 -432
- package/skills/issue-creation/SKILL.md +0 -118
- package/skills/lint-package-suggestion/SKILL.md +0 -37
- package/skills/new-method-implementation/SKILL.md +0 -71
- package/skills/parse-date-time/SKILL.md +0 -349
- package/skills/pr-contribution/SKILL.md +0 -180
- package/skills/unit-test-generation/SKILL.md +0 -56
- package/skills/validate-dates/SKILL.md +0 -438
- package/skills/zoned-date-ops/SKILL.md +0 -422
- package/skills/zoned-date-ops/references/timezones.md +0 -13
|
@@ -1,432 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: interval-ops
|
|
3
|
-
description: >
|
|
4
|
-
Interval and range math over ISO datetime/zoned/unix strings. Use
|
|
5
|
-
isValid*Interval to validate start/end pairs, intervalContains* for
|
|
6
|
-
point-or-interval containment, intervalsOverlap* for overlap booleans,
|
|
7
|
-
intervalIntersection*/intervalUnion* for set-theoretic combine/difference
|
|
8
|
-
returning { start, end } | null, intervalDifference*/intervalXor*/
|
|
9
|
-
intervalAbuts*/intervalEngulfs* for set operations, intervalFromDuration* to
|
|
10
|
-
build an interval from a point plus an ISO 8601 duration, splitIntervalByUnit*
|
|
11
|
-
to tile an interval by duration unit, intervalCount* for calendar-unit
|
|
12
|
-
boundaries crossed, intervalLength* for exact fractional duration in a unit,
|
|
13
|
-
intervalDivideEqually* to split into n equal parts, intervalSplitAt* to split
|
|
14
|
-
at arbitrary points, mergeIntervals*/intervalXorAll* as list-form
|
|
15
|
-
generalizations of intervalUnion*/intervalXor*, and intervalOverlappingDays*
|
|
16
|
-
for shared calendar dates between two intervals. Covers plain, zoned, unix,
|
|
17
|
-
utc. Returns false/null/[] on invalid input — never throws.
|
|
18
|
-
sources:
|
|
19
|
-
- 'northguild/gmt:packages/gmt/src/plain/interval/index.ts'
|
|
20
|
-
- 'northguild/gmt:packages/gmt/src/zoned/interval/index.ts'
|
|
21
|
-
- 'northguild/gmt:packages/gmt/src/unix/interval/index.ts'
|
|
22
|
-
- 'northguild/gmt:packages/gmt/src/utc/interval/index.ts'
|
|
23
|
-
- 'northguild/gmt:packages/gmt/src/duration/validate/isValidDuration.ts'
|
|
24
|
-
metadata:
|
|
25
|
-
type: core
|
|
26
|
-
library: '@northguild/gmt'
|
|
27
|
-
library_version: '1.14.2'
|
|
28
|
-
---
|
|
29
|
-
|
|
30
|
-
# Interval Operations
|
|
31
|
-
|
|
32
|
-
Use this skill when working with two-point ranges (start/end pairs) across plain, zoned, unix, or UTC values.
|
|
33
|
-
|
|
34
|
-
## Setup
|
|
35
|
-
|
|
36
|
-
```ts
|
|
37
|
-
import {
|
|
38
|
-
isValidDateInterval,
|
|
39
|
-
intervalContainsDate,
|
|
40
|
-
intervalsOverlapDate,
|
|
41
|
-
intervalIntersectionDate,
|
|
42
|
-
intervalUnionDate,
|
|
43
|
-
intervalDifferenceDate,
|
|
44
|
-
intervalXorDate,
|
|
45
|
-
intervalAbutsDate,
|
|
46
|
-
intervalEngulfsDate,
|
|
47
|
-
intervalFromDurationDate,
|
|
48
|
-
splitIntervalByUnitDate,
|
|
49
|
-
intervalCountDate,
|
|
50
|
-
intervalLengthDate,
|
|
51
|
-
intervalDivideEquallyDate,
|
|
52
|
-
intervalSplitAtDate,
|
|
53
|
-
mergeIntervalsDate,
|
|
54
|
-
intervalXorAllDate,
|
|
55
|
-
intervalOverlappingDaysDate,
|
|
56
|
-
} from "@northguild/gmt";
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
Every function has `Time`, `DateTime`, `Zoned`, `Unix`, and `Utc` siblings. Replace the suffix to match your value type.
|
|
60
|
-
|
|
61
|
-
## Function Reference
|
|
62
|
-
|
|
63
|
-
| Family | Signature | Returns | Invalid |
|
|
64
|
-
| --- | --- | --- | --- |
|
|
65
|
-
| `isValid*Interval` | `(start, end)` | `boolean` | `false` |
|
|
66
|
-
| `intervalContains*` | `(start, end, pointOrInnerStart, innerEnd?)` | `boolean` | `false` |
|
|
67
|
-
| `intervalsOverlap*` | `(aStart, aEnd, bStart, bEnd)` | `boolean` | `false` |
|
|
68
|
-
| `intervalIntersection*` | `(aStart, aEnd, bStart, bEnd)` | `{ start, end } \| null` | `null` |
|
|
69
|
-
| `intervalUnion*` | `(aStart, aEnd, bStart, bEnd)` | `{ start, end } \| null` | `null` |
|
|
70
|
-
| `intervalDifference*` | `(aStart, aEnd, bStart, bEnd)` | `Array<{ start, end }>` | `[]` |
|
|
71
|
-
| `intervalXor*` | `(aStart, aEnd, bStart, bEnd)` | `Array<{ start, end }>` | `[]` |
|
|
72
|
-
| `intervalAbuts*` | `(aStart, aEnd, bStart, bEnd)` | `boolean` | `false` |
|
|
73
|
-
| `intervalEngulfs*` | `(aStart, aEnd, bStart, bEnd)` | `boolean` | `false` |
|
|
74
|
-
| `intervalFromDuration*` | `(value, duration, anchor, options?)` | `{ start, end } \| null` | `null` |
|
|
75
|
-
| `splitIntervalByUnit*` | `(start, end, unit, amount)` | `Array<{ start, end }>` | `[]` |
|
|
76
|
-
| `intervalCount*` | `(start, end, unit)` | `number \| null` | `null` |
|
|
77
|
-
| `intervalLength*` | `(start, end, unit)` | `number \| null` (fractional) | `null` |
|
|
78
|
-
| `intervalDivideEqually*` | `(start, end, n)` | `Array<{ start, end }>` | `[]` |
|
|
79
|
-
| `intervalSplitAt*` | `(start, end, points)` | `Array<{ start, end }>` | `[]` |
|
|
80
|
-
| `mergeIntervals*` | `(intervals: Array<{ start, end }>)` | `Array<{ start, end }>` | `[]` |
|
|
81
|
-
| `intervalXorAll*` | `(intervals: Array<{ start, end }>)` | `Array<{ start, end }>` | `[]` |
|
|
82
|
-
| `intervalOverlappingDays*` | `(aStart, aEnd, bStart, bEnd)` — `Unix` variant takes a 5th `options?: { epochUnit?, timeZone? }` | `number \| null` | `null` (`0` when disjoint) |
|
|
83
|
-
|
|
84
|
-
`intervalOverlappingDays*` has no `Time` sibling — `PlainTime` has no calendar, so a day count is undefined for it.
|
|
85
|
-
|
|
86
|
-
`intervalContains*` supports two modes:
|
|
87
|
-
- 3-arg: `intervalContains(start, end, point)` — point-in-interval
|
|
88
|
-
- 4-arg: `intervalContains(start, end, innerStart, innerEnd)` — interval-in-interval
|
|
89
|
-
|
|
90
|
-
`intervalEngulfs*` is equivalent to `intervalContains*`'s 4-argument mode.
|
|
91
|
-
|
|
92
|
-
## Core Patterns
|
|
93
|
-
|
|
94
|
-
### Validate an interval
|
|
95
|
-
|
|
96
|
-
```ts
|
|
97
|
-
isValidDateInterval("2024-01-01", "2024-12-31"); // true
|
|
98
|
-
isValidDateInterval("2024-12-31", "2024-01-01"); // false (inverted)
|
|
99
|
-
isValidDateInterval("invalid", "2024-12-31"); // false
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
### Point inside interval
|
|
103
|
-
|
|
104
|
-
```ts
|
|
105
|
-
intervalContainsDate("2024-01-01", "2024-12-31", "2024-06-15"); // true
|
|
106
|
-
intervalContainsTime("09:00:00", "17:00:00", "12:00:00"); // true
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
### Interval inside interval
|
|
110
|
-
|
|
111
|
-
```ts
|
|
112
|
-
intervalContainsDate("2024-01-01", "2024-12-31", "2024-03-01", "2024-09-01"); // true
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
### Overlap vs adjacency
|
|
116
|
-
|
|
117
|
-
`intervalsOverlap*` returns `true` only when intervals share actual time. Adjacent intervals (one's end equals the other's start) do **not** overlap:
|
|
118
|
-
|
|
119
|
-
```ts
|
|
120
|
-
intervalsOverlapDate("2024-01-01", "2024-06-30", "2024-04-01", "2024-12-31"); // true
|
|
121
|
-
intervalsOverlapDate("2024-01-01", "2024-06-30", "2024-06-30", "2024-12-31"); // false
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
`intervalAbuts*` is the complementary check — `true` only when intervals touch at exactly one boundary with zero gap and zero overlap:
|
|
125
|
-
|
|
126
|
-
```ts
|
|
127
|
-
intervalAbutsDate("2024-01-01", "2024-06-30", "2024-06-30", "2024-12-31"); // true
|
|
128
|
-
intervalAbutsDate("2024-01-01", "2024-06-29", "2024-06-30", "2024-12-31"); // false (gap)
|
|
129
|
-
intervalAbutsDate("2024-01-01", "2024-06-30", "2024-04-01", "2024-12-31"); // false (overlap)
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
### Intersection and union
|
|
133
|
-
|
|
134
|
-
```ts
|
|
135
|
-
intervalIntersectionDate("2024-01-01", "2024-06-30", "2024-04-01", "2024-12-31");
|
|
136
|
-
// { start: "2024-04-01", end: "2024-06-30" }
|
|
137
|
-
|
|
138
|
-
intervalIntersectionDate("2024-01-01", "2024-06-30", "2024-07-01", "2024-12-31");
|
|
139
|
-
// null (disjoint)
|
|
140
|
-
|
|
141
|
-
intervalUnionDate("2024-01-01", "2024-06-30", "2024-04-01", "2024-12-31");
|
|
142
|
-
// { start: "2024-01-01", end: "2024-12-31" }
|
|
143
|
-
|
|
144
|
-
intervalUnionDate("2024-01-01", "2024-06-30", "2024-07-01", "2024-12-31");
|
|
145
|
-
// null (disjoint with gap)
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
Adjacent intervals count as overlapping for intersection and as mergeable for union.
|
|
149
|
-
|
|
150
|
-
### Set difference and symmetric difference
|
|
151
|
-
|
|
152
|
-
```ts
|
|
153
|
-
intervalDifferenceDate("2024-01-01", "2024-12-31", "2024-06-01", "2024-07-01");
|
|
154
|
-
// [{ start: "2024-01-01", end: "2024-05-31" }, { start: "2024-07-02", end: "2024-12-31" }]
|
|
155
|
-
|
|
156
|
-
intervalXorDate("2024-01-01", "2024-06-30", "2024-04-01", "2024-12-31");
|
|
157
|
-
// [{ start: "2024-01-01", end: "2024-03-31" }, { start: "2024-07-01", end: "2024-12-31" }]
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
Both return `[]` when B fully covers A or on invalid input.
|
|
161
|
-
|
|
162
|
-
### Construct an interval from a point + duration
|
|
163
|
-
|
|
164
|
-
`intervalFromDuration*` builds an interval from a single point and an ISO 8601 duration string, anchored at either end — Luxon's `Interval.after`/`Interval.before` as one function with an `anchor` param:
|
|
165
|
-
|
|
166
|
-
```ts
|
|
167
|
-
intervalFromDurationDate("2024-01-01", "P1M", "start");
|
|
168
|
-
// { start: "2024-01-01", end: "2024-02-01" }
|
|
169
|
-
|
|
170
|
-
intervalFromDurationDate("2024-02-01", "P1M", "end");
|
|
171
|
-
// { start: "2024-01-01", end: "2024-02-01" }
|
|
172
|
-
|
|
173
|
-
intervalFromDurationZoned(
|
|
174
|
-
"2024-03-09T02:30:00-05:00[America/New_York]",
|
|
175
|
-
"P1D",
|
|
176
|
-
"start",
|
|
177
|
-
);
|
|
178
|
-
// { start: "2024-03-09T02:30:00-05:00[America/New_York]", end: "2024-03-10T03:30:00-04:00[America/New_York]" }
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
Calendar units (years/months/weeks) resolve against `value` itself via Temporal's `add()`/`subtract()` — no separate `relativeTo` is needed. `intervalFromDurationZoned` accepts the same `disambiguation`/`offset`/`overflow` options as `addZoned`; `intervalFromDurationUnix` accepts `addUnix`'s `epochUnit`/`timeZone`/`overflow` options. A negative `duration` that inverts the computed span returns `null`, same as any other invalid input.
|
|
182
|
-
|
|
183
|
-
`intervalFromDurationTime` is the one exception: `PlainTime` has no calendar, so a `duration` with a nonzero years/months/weeks/days component returns `null` rather than silently ignoring those fields:
|
|
184
|
-
|
|
185
|
-
```ts
|
|
186
|
-
intervalFromDurationTime("12:00:00", "PT1H", "start"); // { start: "12:00:00", end: "13:00:00" }
|
|
187
|
-
intervalFromDurationTime("12:00:00", "P1D", "start"); // null (date units need relativeTo, unsupported)
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
### Split by duration unit
|
|
191
|
-
|
|
192
|
-
```ts
|
|
193
|
-
splitIntervalByUnitDate("2024-01-01", "2024-01-10", "day", 2);
|
|
194
|
-
// [{ start: "2024-01-01", end: "2024-01-03" }, ..., { start: "2024-01-09", end: "2024-01-10" }]
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
The final slice is trimmed so its `end` never exceeds the original `end`.
|
|
198
|
-
|
|
199
|
-
### Count unit boundaries crossed
|
|
200
|
-
|
|
201
|
-
```ts
|
|
202
|
-
intervalCountDateTime("2024-01-01T23:59:00", "2024-01-02T00:01:00", "day");
|
|
203
|
-
// 2 — two minutes long, but it crosses a day boundary
|
|
204
|
-
|
|
205
|
-
intervalCountDate("2024-01-01", "2024-01-03", "day"); // 2 (end boundary excluded)
|
|
206
|
-
intervalCountDate("2024-01-15", "2024-03-10", "month"); // 3
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
This is calendar-boundary counting, not duration. Use `diff*` when you want exact elapsed time.
|
|
210
|
-
|
|
211
|
-
### Exact interval length vs boundary count
|
|
212
|
-
|
|
213
|
-
`intervalLength*` answers "how long is this interval" as a real, possibly fractional number — `intervalCount*` answers "how many `unit` boundaries does it touch". They read as synonyms but are not:
|
|
214
|
-
|
|
215
|
-
```ts
|
|
216
|
-
intervalCountDateTime("2024-01-01T23:59:00", "2024-01-02T00:01:00", "day");
|
|
217
|
-
// 2 — touches 2 day boundaries
|
|
218
|
-
|
|
219
|
-
intervalLengthDateTime("2024-01-01T23:59:00", "2024-01-02T00:01:00", "day");
|
|
220
|
-
// 0.001388888888888889 — the interval is only ~2 minutes long
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
`intervalLength*` uses `Duration.prototype.total`, so calendar units (month, year) resolve against the interval's own `start` rather than truncating: `intervalLengthDate("2024-01-01", "2024-01-16", "month")` is `15/31`, not `0`. `intervalLengthZoned`/`intervalLengthUnix`/`intervalLengthUtc` are DST-aware the same way `intervalCountZoned` is — a spring-forward day is `23` hours via `intervalLengthZoned(..., "hour")` but exactly `1` via `intervalLengthZoned(..., "day")`.
|
|
224
|
-
|
|
225
|
-
### Split an interval into n equal parts
|
|
226
|
-
|
|
227
|
-
```ts
|
|
228
|
-
intervalDivideEquallyDate("2024-01-01", "2024-01-05", 4);
|
|
229
|
-
// [{ start: "2024-01-01", end: "2024-01-02" }, ..., { start: "2024-01-04", end: "2024-01-05" }]
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
`n` must be a positive integer (`n <= 0` or non-integer returns `[]`). `n === 1` returns the original interval as a single-element array. A zero-length interval returns `n` identical zero-length sub-intervals. `PlainDate` has no fractional-day representation, so `intervalDivideEquallyDate` rounds internal boundaries to the nearest whole day when the total doesn't divide evenly by `n`; every other variant computes boundaries from total elapsed nanoseconds and is exact (`intervalDivideEquallyZoned` splits by real elapsed time, so a DST-crossing interval's midpoint lands on the real midpoint, not the local-clock midpoint).
|
|
233
|
-
|
|
234
|
-
### Split an interval at arbitrary points
|
|
235
|
-
|
|
236
|
-
```ts
|
|
237
|
-
intervalSplitAtDate("2024-01-01", "2024-01-10", ["2024-01-07", "2024-01-03"]);
|
|
238
|
-
// [{ start: "2024-01-01", end: "2024-01-03" }, { start: "2024-01-03", end: "2024-01-07" }, { start: "2024-01-07", end: "2024-01-10" }]
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
Points need not be sorted — `intervalSplitAt*` sorts them internally. Points outside `[start, end]` or exactly on a boundary are dropped (they cannot introduce a new sub-interval). An empty `points` array, or one containing only out-of-range/duplicate points, returns `[{ start, end }]` — the whole interval, unsplit.
|
|
242
|
-
|
|
243
|
-
### Merge and XOR a list of intervals
|
|
244
|
-
|
|
245
|
-
`mergeIntervals*` and `intervalXorAll*` are the list-form generalizations of `intervalUnion*` and `intervalXor*`, which are pairwise only:
|
|
246
|
-
|
|
247
|
-
```ts
|
|
248
|
-
mergeIntervalsDate([
|
|
249
|
-
{ start: "2024-01-01", end: "2024-01-10" },
|
|
250
|
-
{ start: "2024-01-05", end: "2024-01-15" },
|
|
251
|
-
{ start: "2024-02-01", end: "2024-02-05" },
|
|
252
|
-
]);
|
|
253
|
-
// [{ start: "2024-01-01", end: "2024-01-15" }, { start: "2024-02-01", end: "2024-02-05" }]
|
|
254
|
-
|
|
255
|
-
intervalXorAllDate([
|
|
256
|
-
{ start: "2024-01-01", end: "2024-01-10" },
|
|
257
|
-
{ start: "2024-01-05", end: "2024-01-15" },
|
|
258
|
-
{ start: "2024-01-08", end: "2024-01-20" },
|
|
259
|
-
]);
|
|
260
|
-
// [{ start: "2024-01-01", end: "2024-01-04" }, { start: "2024-01-08", end: "2024-01-10" }, { start: "2024-01-16", end: "2024-01-20" }]
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
Both take a single array of `{ start, end }` records (order doesn't matter) instead of two flat 4-arg intervals. `mergeIntervals*` collapses overlapping/adjacent intervals into the minimum non-overlapping set. `intervalXorAll*` returns the set covered by an odd number of the input intervals — two identical intervals cancel out to `[]`, and for exactly two intervals the result is identical to the pairwise `intervalXor*`.
|
|
264
|
-
|
|
265
|
-
### Count shared days between two intervals
|
|
266
|
-
|
|
267
|
-
`intervalOverlappingDays*` is the numeric counterpart to `intervalIntersection*` — how many distinct calendar dates the two intervals' closed intersection touches, inclusive of both endpoints:
|
|
268
|
-
|
|
269
|
-
```ts
|
|
270
|
-
intervalOverlappingDaysDate("2024-01-01", "2024-06-30", "2024-04-01", "2024-12-31");
|
|
271
|
-
// 91
|
|
272
|
-
|
|
273
|
-
intervalOverlappingDaysDate("2024-01-01", "2024-06-30", "2024-06-30", "2024-12-31");
|
|
274
|
-
// 1 (adjacent, shares one date)
|
|
275
|
-
|
|
276
|
-
intervalOverlappingDaysDate("2024-01-01", "2024-06-30", "2024-07-01", "2024-12-31");
|
|
277
|
-
// 0 (disjoint — a well-defined answer, not invalid input)
|
|
278
|
-
|
|
279
|
-
intervalOverlappingDaysUnix(0, 172800000, 86400000, 259200000, { timeZone: "UTC" });
|
|
280
|
-
// 2
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
`intervalOverlappingDaysZoned` and `intervalOverlappingDaysUnix` count days in `aStart`'s zone (`intervalOverlappingDaysUnix` defaults to the system zone, overridable via `{ timeZone }`), same rule as `intervalCountZoned`/`intervalCountUnix` — so `intervalOverlappingDaysZoned` is not commutative when the two intervals carry different zones.
|
|
284
|
-
|
|
285
|
-
## Return Conventions
|
|
286
|
-
|
|
287
|
-
- `boolean` families (`isValid*Interval`, `intervalContains*`, `intervalsOverlap*`, `intervalAbuts*`, `intervalEngulfs*`): `false` on invalid input.
|
|
288
|
-
- Object families (`intervalIntersection*`, `intervalUnion*`, `intervalFromDuration*`): `null` on no-result or invalid input.
|
|
289
|
-
- Array families (`intervalDifference*`, `intervalXor*`, `splitIntervalByUnit*`, `intervalDivideEqually*`, `intervalSplitAt*`, `mergeIntervals*`, `intervalXorAll*`): `[]` on no-result or invalid input.
|
|
290
|
-
- Number families (`intervalCount*`, `intervalLength*`, `intervalOverlappingDays*`): `null` on invalid input; `intervalOverlappingDays*` additionally returns `0` for a disjoint pair (not invalid — a well-defined "zero shared days").
|
|
291
|
-
|
|
292
|
-
Never throws. Wrap in try-catch only if you need to distinguish "invalid input" from other errors.
|
|
293
|
-
|
|
294
|
-
## Namespace selection
|
|
295
|
-
|
|
296
|
-
- `plain/*` — timezone-free dates, times, or datetimes.
|
|
297
|
-
- `zoned/*` — IANA-timezone-aware strings (`"2024-01-01T00:00:00+00:00[UTC]"`). Rejects any `[u-ca=...]` calendar annotation (E5) — calendar-system awareness is `plain/`-only.
|
|
298
|
-
- `unix/*` — epoch-second numbers (returns arrays of numbers, not strings).
|
|
299
|
-
- `utc/*` — UTC instants (`"2024-01-01T00:00:00Z"`).
|
|
300
|
-
|
|
301
|
-
## Calendar-aware `Date` intervals (E5)
|
|
302
|
-
|
|
303
|
-
Every `Date`-suffixed function in this namespace (`intervalContainsDate`, `intervalUnionDate`, `intervalCountDate`, `splitIntervalByUnitDate`, and the rest) accepts a GMT calendar-annotated `PlainDate` string (as produced by `convertDateToCalendar`), not just bare ISO strings. The policy differs by return family:
|
|
304
|
-
|
|
305
|
-
- **Ordering/count families accept mixed calendars** — `intervalContains*`, `intervalsOverlap*`, `intervalAbuts*`, `intervalEngulfs*`, `isValidDateInterval`, `intervalOverlappingDaysDate` compare or count regardless of which calendar each argument is tagged with, since ordering and day-counting are calendar-independent.
|
|
306
|
-
- **Value-returning families require all arguments to share one calendar** — `intervalUnion*`, `intervalIntersection*`, `intervalDifference*`, `intervalXor*`, `intervalXorAll*`, `mergeIntervals*`, `intervalDivideEqually*`, `intervalSplitAt*` return their sentinel (`null`/`[]`) on a calendar mismatch, since there's no principled output calendar to pick for a returned date value.
|
|
307
|
-
- **Calendar-unit arithmetic families measure in the shared calendar, falling back to Gregorian on a mismatch** — `intervalCountDate`, `intervalLengthDate`, `splitIntervalByUnitDate`, `intervalFromDurationDate`. A Hebrew leap year crosses **13** month boundaries, not 12:
|
|
308
|
-
|
|
309
|
-
```ts
|
|
310
|
-
import { intervalCountDate, splitIntervalByUnitDate } from "@northguild/gmt";
|
|
311
|
-
|
|
312
|
-
intervalCountDate(
|
|
313
|
-
"5784-01-01[u-ca=hebrew]",
|
|
314
|
-
"5785-01-01[u-ca=hebrew]",
|
|
315
|
-
"month",
|
|
316
|
-
); // 13, not 12 — Hebrew leap years insert Adar I
|
|
317
|
-
|
|
318
|
-
splitIntervalByUnitDate(
|
|
319
|
-
"5784-01-01[u-ca=hebrew]",
|
|
320
|
-
"5785-01-01[u-ca=hebrew]",
|
|
321
|
-
"month",
|
|
322
|
-
1,
|
|
323
|
-
).length; // 13 slices, each a real Hebrew month
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
`*DateTime`/`*Time`/`unix/*`/`utc/*` intervals are unaffected — no calendar-annotated `PlainDateTime` grammar exists, and `PlainTime` has no calendar. See `packages/gmt/README.md`'s "Calendar-aware interval and duration arithmetic" and `context/roadmap/issues/E.md`'s E5 outcome for the full per-function audit.
|
|
327
|
-
|
|
328
|
-
## Common Mistakes
|
|
329
|
-
|
|
330
|
-
### Mixing calendar systems across interval endpoints
|
|
331
|
-
|
|
332
|
-
Both `plain/` and `zoned/` interval families split by return type:
|
|
333
|
-
|
|
334
|
-
- **Ordering / counting functions accept mixed calendars** — `intervalContains*`,
|
|
335
|
-
`intervalsOverlap*`, `intervalAbuts*`, `intervalEngulfs*`, `intervalOverlappingDays*`,
|
|
336
|
-
`isValidDateInterval` / `isValidCalendarZonedInterval`. Ordering does not depend on calendar.
|
|
337
|
-
- **Value-returning set operations reject a mismatch** — `intervalUnion*`, `intervalIntersection*`,
|
|
338
|
-
`intervalDifference*`, `intervalXor*`, `intervalXorAll*`, `mergeIntervals*`,
|
|
339
|
-
`intervalDivideEqually*`, `intervalSplitAt*` return their sentinel (`null`/`[]`) unless every
|
|
340
|
-
endpoint names the same calendar. There is no principled output calendar to pick, and four of
|
|
341
|
-
them return arrays whose elements would otherwise disagree.
|
|
342
|
-
- **Measurement functions fall back to Gregorian** — `intervalCount*`, `intervalLength*`,
|
|
343
|
-
`splitIntervalByUnit*` measure in the shared calendar when both tags match, else in
|
|
344
|
-
Gregorian/ISO. They do **not** return the sentinel on a mismatch.
|
|
345
|
-
|
|
346
|
-
For `zoned/`, use `isValidCalendarZonedInterval` (not `isValidZonedInterval`, which still rejects
|
|
347
|
-
every `[u-ca=...]` annotation).
|
|
348
|
-
|
|
349
|
-
### Building a calendar-annotated zoned string by hand
|
|
350
|
-
|
|
351
|
-
GMT's zoned calendar grammar puts `[u-ca=...]` **before** `[timeZone]` — the reverse of RFC 9557
|
|
352
|
-
and of Temporal's own `toString()`:
|
|
353
|
-
|
|
354
|
-
```
|
|
355
|
-
5784-06-15T14:30:00-05:00[u-ca=hebrew][America/New_York] // correct
|
|
356
|
-
5784-06-15T14:30:00-05:00[America/New_York][u-ca=hebrew] // WRONG — silently misparses in Temporal
|
|
357
|
-
```
|
|
358
|
-
|
|
359
|
-
Always produce these with `convertZonedToCalendar`, never by concatenation. See the
|
|
360
|
-
`zoned-date-ops` skill's Common Mistakes for the full trap.
|
|
361
|
-
|
|
362
|
-
### HIGH: Confusing `intervalsOverlap` with `intervalAbuts`
|
|
363
|
-
|
|
364
|
-
Adjacent intervals (A ends where B starts) do **not** overlap. `intervalsOverlap` returns `false`; `intervalAbuts` returns `true`. They are complementary, not interchangeable.
|
|
365
|
-
|
|
366
|
-
### HIGH: Assuming `intervalEngulfs` is new logic
|
|
367
|
-
|
|
368
|
-
`intervalEngulfs(start, end, innerStart, innerEnd)` is identical to `intervalContains(start, end, innerStart, innerEnd)` with 4 arguments. Use whichever name reads better at the call site — do not implement duplicate logic.
|
|
369
|
-
|
|
370
|
-
### MEDIUM: Mutating intersection/union results
|
|
371
|
-
|
|
372
|
-
`intervalIntersection*` / `intervalUnion*` return plain `{ start, end }` records. Mutating them does not affect the original intervals (which are string inputs, not objects). Safe to treat as immutable.
|
|
373
|
-
|
|
374
|
-
### MEDIUM: Off-by-one on `splitIntervalByUnit` final slice
|
|
375
|
-
|
|
376
|
-
The last sub-interval's `end` is **trimmed** to the original `end`, not advanced by `amount × unit`. Do not assume all slices have equal length.
|
|
377
|
-
|
|
378
|
-
### HIGH: Using `intervalCount` where you meant a duration
|
|
379
|
-
|
|
380
|
-
`intervalCount*` counts calendar-unit boundaries the half-open interval `[start, end)` touches, not
|
|
381
|
-
elapsed time. `intervalCountDateTime("2024-01-01T23:59:00", "2024-01-02T00:01:00", "day")` is `2`
|
|
382
|
-
for a two-minute interval. Use `diffDateTime`/`diffUnix`/etc. for exact duration.
|
|
383
|
-
|
|
384
|
-
### MEDIUM: Expecting a zero-length interval to count 1
|
|
385
|
-
|
|
386
|
-
A zero-length interval counts `1` only when it sits mid-unit. Exactly on a boundary it counts `0` —
|
|
387
|
-
`intervalCountDate("2024-01-01", "2024-01-01", "month")` is `0`, while
|
|
388
|
-
`intervalCountDate("2024-01-15", "2024-01-15", "month")` is `1`.
|
|
389
|
-
|
|
390
|
-
### LOW: Using `intervalContains` 3-arg for intervals
|
|
391
|
-
|
|
392
|
-
`intervalContains(start, end, innerStart)` treats the third arg as a single point. To test interval-in-interval, pass all four arguments.
|
|
393
|
-
|
|
394
|
-
### HIGH: Assuming `intervalFromDurationTime` applies a calendar-unit duration
|
|
395
|
-
|
|
396
|
-
`Temporal.PlainTime.prototype.add`/`.subtract` silently discard `years`/`months`/`weeks`/`days` fields rather than throwing — `intervalFromDurationTime` guards this explicitly and returns `null` instead of producing a misleadingly zero-length interval. Only `PlainTime` has this restriction; `Date`/`DateTime`/`Utc`/`Unix`/`Zoned` all resolve calendar units against the point itself with no `relativeTo` needed.
|
|
397
|
-
|
|
398
|
-
### MEDIUM: Forgetting a negative `duration` can invert the span
|
|
399
|
-
|
|
400
|
-
`intervalFromDuration*("2024-01-05", "-P10D", "start")` computes an end before the start and returns `null`, same as any other invalid input — it does not swap the endpoints for you.
|
|
401
|
-
|
|
402
|
-
### HIGH: Expecting date-fns's `getOverlappingDaysInIntervals` number from `intervalOverlappingDays*`
|
|
403
|
-
|
|
404
|
-
date-fns's `getOverlappingDaysInIntervals` rounds up elapsed 24-hour periods; GMT's `intervalOverlappingDays*` counts inclusive calendar dates instead — they disagree at every exact-day boundary. date-fns's own doc example:
|
|
405
|
-
|
|
406
|
-
```ts
|
|
407
|
-
// date-fns: getOverlappingDaysInIntervals(Jan 10–20, Jan 17–21) → 3
|
|
408
|
-
intervalOverlappingDaysDate("2014-01-10", "2014-01-20", "2014-01-17", "2014-01-21");
|
|
409
|
-
// 4 — not 3
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
To reproduce date-fns's number exactly, compose `intervalIntersection*` with `intervalCount*` instead:
|
|
413
|
-
|
|
414
|
-
```ts
|
|
415
|
-
const span = intervalIntersectionDate(aStart, aEnd, bStart, bEnd);
|
|
416
|
-
span ? intervalCountDate(span.start, span.end, "day") : 0; // date-fns semantics
|
|
417
|
-
```
|
|
418
|
-
|
|
419
|
-
### HIGH: Confusing `intervalLength` with `intervalCount`
|
|
420
|
-
|
|
421
|
-
Same trap as above, one level deeper: `intervalLength*` and `intervalCount*` read as synonyms but answer different questions. The same interval returns different numbers from each:
|
|
422
|
-
|
|
423
|
-
```ts
|
|
424
|
-
intervalCountDateTime("2024-01-01T23:59:00", "2024-01-02T00:01:00", "day"); // 2 — boundaries crossed
|
|
425
|
-
intervalLengthDateTime("2024-01-01T23:59:00", "2024-01-02T00:01:00", "day"); // 0.0014 — exact elapsed duration
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
Use `intervalCount*` for "how many `unit`s does this span touch" (calendar-boundary counting) and `intervalLength*` for "how long is this, expressed in `unit`" (exact, possibly fractional duration).
|
|
429
|
-
|
|
430
|
-
### MEDIUM: Expecting `intervalXorAll`/`mergeIntervals` to take flat arguments like their pairwise siblings
|
|
431
|
-
|
|
432
|
-
`intervalXor*`/`intervalUnion*` take four flat string/number arguments (`aStart, aEnd, bStart, bEnd`). Their list-form generalizations `intervalXorAll*`/`mergeIntervals*` take one array of `{ start, end }` records instead, since the list can be any length. Passing flat arguments to the list form is a type error, not a runtime sentinel.
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: issue-creation
|
|
3
|
-
description: >
|
|
4
|
-
Create feature requests for missing @northguild/gmt functionality with proper
|
|
5
|
-
context, examples, and details that help maintainers understand and implement
|
|
6
|
-
the requested feature.
|
|
7
|
-
metadata:
|
|
8
|
-
library_version: 1.14.2
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
# Issue Creation
|
|
12
|
-
|
|
13
|
-
Use this skill when a user needs functionality that gmt does not currently provide.
|
|
14
|
-
|
|
15
|
-
## When to Create an Issue
|
|
16
|
-
|
|
17
|
-
- A needed date/time operation is not available in gmt
|
|
18
|
-
- Existing gmt methods don't handle a specific edge case
|
|
19
|
-
- Documentation is unclear and causes confusion
|
|
20
|
-
- A bug is found in existing functionality
|
|
21
|
-
|
|
22
|
-
## Required Issue Details
|
|
23
|
-
|
|
24
|
-
### 1. Problem Statement
|
|
25
|
-
|
|
26
|
-
Describe what you're trying to accomplish:
|
|
27
|
-
|
|
28
|
-
```
|
|
29
|
-
I need to [specific task] but gmt doesn't support this.
|
|
30
|
-
Current workaround: [what you currently do]
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
### 2. Expected Behavior
|
|
34
|
-
|
|
35
|
-
Show what you expect:
|
|
36
|
-
|
|
37
|
-
```js
|
|
38
|
-
// Input
|
|
39
|
-
"2024-03-15"
|
|
40
|
-
|
|
41
|
-
// Expected output
|
|
42
|
-
"Q1 2024"
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
### 3. Plain vs Zoned
|
|
46
|
-
|
|
47
|
-
Clarify timezone requirements:
|
|
48
|
-
|
|
49
|
-
- **Plain** (timezone-free): Operations that work regardless of timezone
|
|
50
|
-
- **Zoned** (timezone-aware): Operations in a specific IANA timezone
|
|
51
|
-
|
|
52
|
-
### 4. Invalid Input Handling
|
|
53
|
-
|
|
54
|
-
Specify what should happen with invalid inputs:
|
|
55
|
-
|
|
56
|
-
- String return: `""` (empty string)
|
|
57
|
-
- Number return: `null`
|
|
58
|
-
- Boolean return: `false`
|
|
59
|
-
|
|
60
|
-
### 5. Locale Requirements
|
|
61
|
-
|
|
62
|
-
If locale affects output, specify which locales need support.
|
|
63
|
-
|
|
64
|
-
## Issue Template
|
|
65
|
-
|
|
66
|
-
```markdown
|
|
67
|
-
## Problem
|
|
68
|
-
[What you need but gmt doesn't provide]
|
|
69
|
-
|
|
70
|
-
## Expected Behavior
|
|
71
|
-
[Code example showing input and expected output]
|
|
72
|
-
|
|
73
|
-
## Plain or Zoned?
|
|
74
|
-
[Does this need timezone awareness?]
|
|
75
|
-
|
|
76
|
-
## Invalid Input
|
|
77
|
-
[What should happen with invalid inputs?]
|
|
78
|
-
|
|
79
|
-
## Use Case
|
|
80
|
-
[Why do you need this? What's the business context?]
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
## Example Issues
|
|
84
|
-
|
|
85
|
-
### Good Issue
|
|
86
|
-
|
|
87
|
-
```markdown
|
|
88
|
-
## Problem
|
|
89
|
-
I need to get the ISO week number from a date string.
|
|
90
|
-
|
|
91
|
-
## Expected Behavior
|
|
92
|
-
getWeekOfYear("2024-01-01") // returns 1
|
|
93
|
-
getWeekOfYear("2024-12-31") // returns 1 (ISO weeks start Monday)
|
|
94
|
-
|
|
95
|
-
## Plain or Zoned?
|
|
96
|
-
Plain (no timezone needed)
|
|
97
|
-
|
|
98
|
-
## Invalid Input
|
|
99
|
-
Return null on invalid input
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
### What to Avoid
|
|
103
|
-
|
|
104
|
-
- "Add date formatting" (too vague)
|
|
105
|
-
- "Add support for [framework]" (gmt is framework-agnostic)
|
|
106
|
-
- "It's broken" (no reproduction steps)
|
|
107
|
-
|
|
108
|
-
## After Creating
|
|
109
|
-
|
|
110
|
-
1. Check for duplicate issues first
|
|
111
|
-
2. Add to existing issue if duplicates found
|
|
112
|
-
3. Monitor the issue for maintainer responses
|
|
113
|
-
4. Be ready to provide additional context
|
|
114
|
-
|
|
115
|
-
## Related Skills
|
|
116
|
-
|
|
117
|
-
- **pr-contribution**: If you want to implement the feature yourself
|
|
118
|
-
- **new-method-implementation**: For maintainer guidance on implementation
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: lint-package-suggestion
|
|
3
|
-
description: >
|
|
4
|
-
Recommend the right @northguild lint package(s) from the repository toolchain
|
|
5
|
-
(ESLint, Biome, Oxlint), and suggest adding @northguild/gmt when lint-only
|
|
6
|
-
setups need safe Date-to-Temporal refactor helpers.
|
|
7
|
-
metadata:
|
|
8
|
-
library_version: 1.14.2
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
# Lint Package Suggestion
|
|
12
|
-
|
|
13
|
-
Use this skill when working in a project that already uses `@northguild/gmt` or is adopting it.
|
|
14
|
-
|
|
15
|
-
## Decision policy
|
|
16
|
-
|
|
17
|
-
1. Detect current lint stack
|
|
18
|
-
- If project uses ESLint, suggest `@northguild/gmt-eslint`.
|
|
19
|
-
- If project uses Biome, suggest `@northguild/gmt-biome` (or suggest selecting individual plugins via `@northguild/gmt-biome/plugins/<name>` when only a subset of rules is desired). Both `.../<name>` and `.../<name>.grit` forms are supported; prefer the extensionless form.
|
|
20
|
-
- If project uses Oxlint, suggest `@northguild/gmt-oxlint`.
|
|
21
|
-
|
|
22
|
-
2. Respect developer choice
|
|
23
|
-
- These packages can be used individually or together.
|
|
24
|
-
- Do not force all tools; recommend what matches current tooling.
|
|
25
|
-
|
|
26
|
-
3. Runtime + lint bridge
|
|
27
|
-
- If a project has a gmt lint package but not `@northguild/gmt`, suggest installing `@northguild/gmt` to support safe Date-to-Temporal refactors.
|
|
28
|
-
|
|
29
|
-
4. Migration framing
|
|
30
|
-
- Position lint findings as guidance toward existing gmt methods first.
|
|
31
|
-
- If no gmt helper exists for a violation pattern, explain Temporal custom method path and suggest opening a GitHub issue.
|
|
32
|
-
|
|
33
|
-
## Recommendation examples
|
|
34
|
-
|
|
35
|
-
- ESLint repo + gmt runtime: suggest `@northguild/gmt-eslint`.
|
|
36
|
-
- Biome repo + no gmt runtime: suggest both `@northguild/gmt-biome` and `@northguild/gmt`.
|
|
37
|
-
- Multi-linter repo: recommend keeping policy consistent across configured tools.
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: new-method-implementation
|
|
3
|
-
description: >
|
|
4
|
-
Implement or extend a gmt API in packages/gmt/src by reusing existing gmt
|
|
5
|
-
helpers first, then Temporal only when needed, while preserving string-first
|
|
6
|
-
I/O, plain/zoned separation, and fallback-return rules.
|
|
7
|
-
metadata:
|
|
8
|
-
library_version: 1.14.2
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
# New Method Implementation
|
|
12
|
-
|
|
13
|
-
Use this skill when adding a new method under `packages/gmt/src`.
|
|
14
|
-
|
|
15
|
-
## Required behavior
|
|
16
|
-
|
|
17
|
-
1. Use existing gmt methods first.
|
|
18
|
-
2. If no existing gmt method covers the need, use Temporal to implement a new custom method.
|
|
19
|
-
3. Keep public I/O string-first unless the established module pattern is different.
|
|
20
|
-
4. Enforce strict input/output shapes: prefer ISO 8601 strings, IANA timezone identifiers, and explicit Unix epoch numbers for inputs. Do not accept fuzzy or ambiguous formats — require callers to canonicalize input or use parse/validate helpers.
|
|
21
|
-
5. Keep plain and zoned logic separate.
|
|
22
|
-
6. Do not use `Date`, `Date.now`, or manual date parsing.
|
|
23
|
-
|
|
24
|
-
## Naming conventions
|
|
25
|
-
|
|
26
|
-
Follow existing patterns for method names:
|
|
27
|
-
|
|
28
|
-
- ForDate/ForDateTime/ForZoned suffixes for type-specific variants (e.g., `getQuarterForDate`, `getQuarterForZoned`)
|
|
29
|
-
- startOf* / endOf* for boundaries (e.g., `startOfQuarterForDate`)
|
|
30
|
-
- isBetween* for range checks (e.g., `isBetweenDate`, `isBetweenZoned`)
|
|
31
|
-
- get* for getters (e.g., `getQuarterForDate`)
|
|
32
|
-
|
|
33
|
-
## Implementation flow
|
|
34
|
-
|
|
35
|
-
1. Discover existing method
|
|
36
|
-
- Search `packages/gmt/src/plain` and `packages/gmt/src/zoned` for an existing helper.
|
|
37
|
-
- If one exists, use it and do not add duplicate API surface.
|
|
38
|
-
|
|
39
|
-
2. Decide module boundary
|
|
40
|
-
- Put timezone-free logic in `plain/*`.
|
|
41
|
-
- Put timezone-aware logic in `zoned/*`.
|
|
42
|
-
|
|
43
|
-
3. Build with Temporal
|
|
44
|
-
- Parse using Temporal constructors.
|
|
45
|
-
- Perform immutable Temporal operations.
|
|
46
|
-
- Return normalized output in the module's expected shape.
|
|
47
|
-
|
|
48
|
-
4. Apply fallback policy
|
|
49
|
-
- String return type: return `""` on invalid input.
|
|
50
|
-
- Number return type: return `null` on invalid input.
|
|
51
|
-
- Boolean return type: return `false` on invalid input.
|
|
52
|
-
|
|
53
|
-
5. Add tests
|
|
54
|
-
- Add strong unit tests for valid, invalid, and boundary scenarios.
|
|
55
|
-
- Include DST and timezone boundaries for zoned methods.
|
|
56
|
-
- Use `it.each` template tables for matrixed test cases.
|
|
57
|
-
|
|
58
|
-
6. Update exports and docs
|
|
59
|
-
- Add to relevant `index.ts` file in the module.
|
|
60
|
-
- Add to package.json exports if new subpath needed.
|
|
61
|
-
- Update README.md API Surface section.
|
|
62
|
-
|
|
63
|
-
## Prompter guidance when method does not exist
|
|
64
|
-
|
|
65
|
-
If requested behavior is not currently covered by gmt:
|
|
66
|
-
|
|
67
|
-
1. Tell the prompter it is possible via Temporal because gmt ships Temporal.
|
|
68
|
-
2. State this requires a new custom gmt method.
|
|
69
|
-
3. Ask them to open a GitHub issue requesting the functionality.
|
|
70
|
-
4. If they proceed locally, implement the method plus solid unit tests.
|
|
71
|
-
5. Recommend they open a PR or include exact proposal details in the issue.
|