@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,349 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: parse-date-time
|
|
3
|
-
description: >
|
|
4
|
-
Parse individual components (year, month, day, hour, etc.) from date/time
|
|
5
|
-
strings. Use parse* functions for extraction. Returns null on invalid input.
|
|
6
|
-
Also parseRfc2822, parseHttp, parseSql, parseRfc3339 for decoding fixed
|
|
7
|
-
interchange grammars (email Date headers, HTTP headers, SQL datetime literals,
|
|
8
|
-
RFC 3339) into GMT's own ISO strings — these return "" on invalid input, not
|
|
9
|
-
null.
|
|
10
|
-
sources:
|
|
11
|
-
- 'northguild/gmt:packages/gmt/src/plain/parse/index.ts'
|
|
12
|
-
- 'northguild/gmt:packages/gmt/src/zoned/parse/parseRfc2822.ts'
|
|
13
|
-
- 'northguild/gmt:packages/gmt/src/utc/parse/parseHttp.ts'
|
|
14
|
-
- 'northguild/gmt:packages/gmt/src/plain/parse/parseSql.ts'
|
|
15
|
-
- 'northguild/gmt:packages/gmt/src/zoned/parse/parseRfc3339.ts'
|
|
16
|
-
metadata:
|
|
17
|
-
type: core
|
|
18
|
-
library: '@northguild/gmt'
|
|
19
|
-
library_version: '1.14.2'
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
# Parse Date/Time Components
|
|
23
|
-
|
|
24
|
-
Use this skill when you need to extract a specific component (year, month, day, etc.) from a date or time string.
|
|
25
|
-
|
|
26
|
-
## Setup
|
|
27
|
-
|
|
28
|
-
```ts
|
|
29
|
-
import { parseYearFromDate, parseMonthFromDate, parseDayFromDate } from "@northguild/gmt";
|
|
30
|
-
import { parseHourFromTime, parseMinuteFromTime, parseSecondFromTime } from "@northguild/gmt";
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## Core Patterns
|
|
34
|
-
|
|
35
|
-
### Parse year from date
|
|
36
|
-
|
|
37
|
-
```ts
|
|
38
|
-
const year = parseYearFromDate("2024-03-15"); // 2024
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
### Parse month from date
|
|
42
|
-
|
|
43
|
-
```ts
|
|
44
|
-
const month = parseMonthFromDate("2024-03-15"); // 3
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
### Parse day from date
|
|
48
|
-
|
|
49
|
-
```ts
|
|
50
|
-
const day = parseDayFromDate("2024-03-15"); // 15
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
### Parse hour from time
|
|
54
|
-
|
|
55
|
-
```ts
|
|
56
|
-
const hour = parseHourFromTime("14:30:45"); // 14
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
### Parse minute from time
|
|
60
|
-
|
|
61
|
-
```ts
|
|
62
|
-
const minute = parseMinuteFromTime("14:30:45"); // 30
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
### Parse second from time
|
|
66
|
-
|
|
67
|
-
```ts
|
|
68
|
-
const second = parseSecondFromTime("14:30:45"); // 45
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
### Parse from datetime string
|
|
72
|
-
|
|
73
|
-
```ts
|
|
74
|
-
import { parseYearFromDateTime, parseMonthFromDateTime, parseDayFromDateTime } from "@northguild/gmt";
|
|
75
|
-
|
|
76
|
-
const year = parseYearFromDateTime("2024-03-15T14:30:45"); // 2024
|
|
77
|
-
const month = parseMonthFromDateTime("2024-03-15T14:30:45"); // 3
|
|
78
|
-
const day = parseDayFromDateTime("2024-03-15T14:30:45"); // 15
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
### Parse day of week
|
|
82
|
-
|
|
83
|
-
```ts
|
|
84
|
-
import { parseDayOfWeekFromDate } from "@northguild/gmt";
|
|
85
|
-
|
|
86
|
-
const dayOfWeek = parseDayOfWeekFromDate("2024-03-15"); // 5 (Friday)
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
### Parse week of year
|
|
90
|
-
|
|
91
|
-
```ts
|
|
92
|
-
import { parseWeekFromDate } from "@northguild/gmt";
|
|
93
|
-
|
|
94
|
-
const week = parseWeekFromDate("2024-03-15"); // 11
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
### Get calendar quantities (days in month/year, day of year, week counts)
|
|
98
|
-
|
|
99
|
-
These aren't `parse*` functions — they don't extract a stored field, they compute
|
|
100
|
-
a derived quantity — so they live in `calculate/`, not `parse/`. Grouped here
|
|
101
|
-
because they answer the same "read a calendar fact off a date" need.
|
|
102
|
-
|
|
103
|
-
```ts
|
|
104
|
-
import {
|
|
105
|
-
getDaysInMonth,
|
|
106
|
-
getDaysInYear,
|
|
107
|
-
getDayOfYear,
|
|
108
|
-
getWeeksInYear,
|
|
109
|
-
getWeeksInMonth,
|
|
110
|
-
getWeekOfMonth,
|
|
111
|
-
getWeekYear,
|
|
112
|
-
getLocaleWeekYear,
|
|
113
|
-
getWeeksInLocaleWeekYear,
|
|
114
|
-
} from "@northguild/gmt";
|
|
115
|
-
|
|
116
|
-
getDaysInMonth("2024-02-15"); // 29 (leap year)
|
|
117
|
-
getDaysInYear("2024-06-15"); // 366 (leap year)
|
|
118
|
-
getDayOfYear("2024-03-01"); // 61
|
|
119
|
-
getWeeksInYear("2024-06-15"); // 52 (ISO week-numbering year)
|
|
120
|
-
getWeeksInMonth("2024-02-15", "en-US"); // 5 (calendar-grid rows, locale week start)
|
|
121
|
-
getWeekOfMonth("2024-02-29", "en-US"); // 5 (which grid row this date falls on)
|
|
122
|
-
getWeekYear("2024-12-30"); // 2025 (ISO week-numbering year — a Monday in week 1 of 2025)
|
|
123
|
-
getLocaleWeekYear("2022-01-01", "en-US"); // 2022 (locale-relative: en-US always counts Jan 1 as week 1)
|
|
124
|
-
getWeeksInLocaleWeekYear("2020-06-15", "de-DE"); // 53
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
### Parse a fixed producer format against a token pattern
|
|
128
|
-
|
|
129
|
-
Unlike the `parse*From*` functions above, these decode a whole string against a
|
|
130
|
-
caller-supplied token pattern instead of pulling one field out of an ISO
|
|
131
|
-
string — use them for a known, fixed non-ISO producer format (a CSV column, a
|
|
132
|
-
legacy API field, a partially-typed form value).
|
|
133
|
-
|
|
134
|
-
```ts
|
|
135
|
-
import {
|
|
136
|
-
parseDateWithPattern,
|
|
137
|
-
parseDateTimeWithPattern,
|
|
138
|
-
parseTimeWithPattern,
|
|
139
|
-
} from "@northguild/gmt";
|
|
140
|
-
|
|
141
|
-
parseDateWithPattern("03/15/2024", "MM/dd/yyyy"); // "2024-03-15"
|
|
142
|
-
parseDateWithPattern("15-Mar-2024", "dd-MMM-yyyy"); // "2024-03-15"
|
|
143
|
-
parseTimeWithPattern("02:30:45 PM", "hh:mm:ss a"); // "14:30:45"
|
|
144
|
-
parseDateTimeWithPattern("15-Mar-2024 2:30 PM", "dd-MMM-yyyy h:mm a"); // "2024-03-15T14:30:00"
|
|
145
|
-
parseDateWithPattern("02/31/2024", "MM/dd/yyyy"); // "" — shape-valid, not a real date
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
Token table (subset — see JSDoc on each function for the full table):
|
|
149
|
-
|
|
150
|
-
| Token | Field | Notes |
|
|
151
|
-
| ----------------- | --------------------- | --------------------------------------------------- |
|
|
152
|
-
| `yyyy` / `yy` | year | `yy` pivots 00–68 → 2000s, 69–99 → 1900s |
|
|
153
|
-
| `MM` / `M` | month (numeric) | |
|
|
154
|
-
| `MMMM` / `MMM` | month name | locale-aware, via `getLocaleMonthNames` |
|
|
155
|
-
| `dd` / `d` | day | |
|
|
156
|
-
| `EEEE` / `EEE` | weekday name | locale-aware; consumed but not cross-validated |
|
|
157
|
-
| `HH` / `H` | hour (24h) | |
|
|
158
|
-
| `hh` / `h` | hour (12h) | needs `a` in the pattern to resolve to 24h |
|
|
159
|
-
| `mm` / `m` | minute | |
|
|
160
|
-
| `ss` / `s` | second | |
|
|
161
|
-
| `SSS` | millisecond | |
|
|
162
|
-
| `a` | meridiem | locale-aware, via `getLocaleMeridiems` |
|
|
163
|
-
| `GGGG` / `GG` | era name | locale-aware; BCE label ⇒ final year = 1 − parsed year |
|
|
164
|
-
|
|
165
|
-
`parseDateWithPattern` rejects time-only tokens and `parseTimeWithPattern`
|
|
166
|
-
rejects date-only tokens (both return `""`); `parseDateTimeWithPattern`
|
|
167
|
-
accepts the combined set. All three return `""` — never `null` — on no
|
|
168
|
-
match, a malformed pattern, or a shape-valid-but-unreal date/time (the regex
|
|
169
|
-
only proves shape; `Temporal.*.from(..., { overflow: "reject" })` proves the
|
|
170
|
-
value is real).
|
|
171
|
-
|
|
172
|
-
Full docs: `packages/gmt/src/plain/parse/parseDateWithPattern.ts`,
|
|
173
|
-
`parseDateTimeWithPattern.ts`, `parseTimeWithPattern.ts`.
|
|
174
|
-
|
|
175
|
-
### Parse a named machine format (email, HTTP, SQL, RFC 3339)
|
|
176
|
-
|
|
177
|
-
Unlike every other function on this page, these four return `""` on invalid
|
|
178
|
-
input (they produce a whole datetime string, not a numeric/component value),
|
|
179
|
-
matching the sentinel convention of their `format*` counterparts in the
|
|
180
|
-
`format-date-time` skill rather than `parse*From*`'s `null`.
|
|
181
|
-
|
|
182
|
-
```ts
|
|
183
|
-
import { parseRfc2822, parseHttp, parseSql, parseRfc3339 } from "@northguild/gmt";
|
|
184
|
-
|
|
185
|
-
// Email `Date:` headers (RFC 5322 / RFC 2822) → zoned ISO string.
|
|
186
|
-
parseRfc2822("Fri, 15 Mar 2024 14:30:00 -0400");
|
|
187
|
-
// "2024-03-15T14:30:00-04:00[-04:00]"
|
|
188
|
-
|
|
189
|
-
// HTTP headers (RFC 7231 IMF-fixdate only — not RFC 850/asctime) → utc ISO string.
|
|
190
|
-
parseHttp("Fri, 15 Mar 2024 14:30:00 GMT");
|
|
191
|
-
// "2024-03-15T14:30:00Z"
|
|
192
|
-
|
|
193
|
-
// ANSI SQL / ODBC datetime literal (no time zone) → plain ISO string.
|
|
194
|
-
parseSql("2024-03-15 14:30:00");
|
|
195
|
-
// "2024-03-15T14:30:00"
|
|
196
|
-
|
|
197
|
-
// Strict RFC 3339 → zoned ISO string (offset doubles as the zone identifier,
|
|
198
|
-
// since RFC 3339 carries no IANA zone name).
|
|
199
|
-
parseRfc3339("2024-03-15T14:30:00-04:00");
|
|
200
|
-
// "2024-03-15T14:30:00-04:00[-04:00]"
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
`parseRfc2822` accepts a 1- or 2-digit day and RFC 5322's obsolete named
|
|
204
|
-
zones (`GMT`, `UT`, and the eight North American zones); `parseHttp` requires
|
|
205
|
-
strict 2-digit fields and a literal `GMT` only. Each has a `format*`
|
|
206
|
-
counterpart in the `format-date-time` skill.
|
|
207
|
-
|
|
208
|
-
## Common Mistakes
|
|
209
|
-
|
|
210
|
-
### HIGH Not handling null on invalid input
|
|
211
|
-
|
|
212
|
-
Wrong:
|
|
213
|
-
|
|
214
|
-
```ts
|
|
215
|
-
const year = parseYearFromDate("invalid");
|
|
216
|
-
// Assume year is always a number
|
|
217
|
-
console.log(year + 1); // NaN
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
Correct:
|
|
221
|
-
|
|
222
|
-
```ts
|
|
223
|
-
const year = parseYearFromDate("invalid");
|
|
224
|
-
if (year === null) {
|
|
225
|
-
throw new Error("Invalid date");
|
|
226
|
-
}
|
|
227
|
-
console.log(year + 1);
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
Source: packages/gmt/src/plain/parse/parseYearFromDate.ts — Returns null on invalid input
|
|
231
|
-
|
|
232
|
-
### MEDIUM Using string manipulation instead of parse functions
|
|
233
|
-
|
|
234
|
-
Wrong:
|
|
235
|
-
|
|
236
|
-
```ts
|
|
237
|
-
const year = "2024-03-15".split("-")[0]; // "2024" as string
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
Correct:
|
|
241
|
-
|
|
242
|
-
```ts
|
|
243
|
-
import { parseYearFromDate } from "@northguild/gmt";
|
|
244
|
-
|
|
245
|
-
const year = parseYearFromDate("2024-03-15"); // 2024 as number
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
Source: AGENTS.md — Use Temporal for date manipulation
|
|
249
|
-
|
|
250
|
-
### MEDIUM Assuming month is 1-indexed in arithmetic
|
|
251
|
-
|
|
252
|
-
Wrong:
|
|
253
|
-
|
|
254
|
-
```ts
|
|
255
|
-
const month = parseMonthFromDate("2024-01-15");
|
|
256
|
-
// Use directly in array indexing
|
|
257
|
-
const monthName = ["Jan", "Feb", "Mar"][month]; // undefined
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
Correct:
|
|
261
|
-
|
|
262
|
-
```ts
|
|
263
|
-
const month = parseMonthFromDate("2024-01-15");
|
|
264
|
-
const monthName = ["Jan", "Feb", "Mar"][month - 1]; // "Jan"
|
|
265
|
-
```
|
|
266
|
-
|
|
267
|
-
Source: Temporal spec — months are 1-indexed
|
|
268
|
-
|
|
269
|
-
### MEDIUM Confusing getWeekOfMonth with parseWeekFromDate
|
|
270
|
-
|
|
271
|
-
`parseWeekFromDate` returns the ISO week-of-**year** (1-53, resets every
|
|
272
|
-
January). `getWeekOfMonth` returns the calendar-grid row within the
|
|
273
|
-
**month** (1-4/5/6, resets every month, and depends on `locale`'s first
|
|
274
|
-
day of week). They answer different questions and are not interchangeable.
|
|
275
|
-
|
|
276
|
-
Wrong:
|
|
277
|
-
|
|
278
|
-
```ts
|
|
279
|
-
// Trying to size a datepicker's month grid
|
|
280
|
-
const row = parseWeekFromDate("2024-02-29"); // 9 — ISO week-of-year, not useful here
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
Correct:
|
|
284
|
-
|
|
285
|
-
```ts
|
|
286
|
-
import { getWeekOfMonth } from "@northguild/gmt";
|
|
287
|
-
|
|
288
|
-
const row = getWeekOfMonth("2024-02-29", "en-US"); // 5 — grid row within February
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
Source: packages/gmt/src/plain/calculate/getWeekOfMonth.ts
|
|
292
|
-
|
|
293
|
-
### MEDIUM Week number without week-year is a bug in waiting
|
|
294
|
-
|
|
295
|
-
`parseWeekFromDate`/`getWeekNumber` return the week number alone, which
|
|
296
|
-
is ambiguous across a year boundary — 2024-12-30 is ISO week 1, but of
|
|
297
|
-
2025, not 2024. Always pair it with `getWeekYear` (ISO) or
|
|
298
|
-
`getLocaleWeekYear` (locale-relative) when bucketing by week, or
|
|
299
|
-
December dates land in the wrong bucket.
|
|
300
|
-
|
|
301
|
-
Wrong:
|
|
302
|
-
|
|
303
|
-
```ts
|
|
304
|
-
const key = parseWeekFromDate("2024-12-30"); // "1" — 1 of which year?
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
Correct:
|
|
308
|
-
|
|
309
|
-
```ts
|
|
310
|
-
import { getWeekYear, parseWeekFromDate } from "@northguild/gmt";
|
|
311
|
-
|
|
312
|
-
const key = `${getWeekYear("2024-12-30")}-W${parseWeekFromDate("2024-12-30")}`; // "2025-W1"
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
Source: packages/gmt/src/plain/calculate/getWeekYear.ts, getLocaleWeekYear.ts
|
|
316
|
-
|
|
317
|
-
### MEDIUM Checking for `null` on parseRfc2822/parseHttp/parseSql/parseRfc3339
|
|
318
|
-
|
|
319
|
-
Unlike every `parse*From*` component extractor on this page, these four
|
|
320
|
-
return a **string** (a whole datetime), so their sentinel is `""`, not
|
|
321
|
-
`null` — checking for `null` silently lets an empty string through.
|
|
322
|
-
|
|
323
|
-
Wrong:
|
|
324
|
-
|
|
325
|
-
```ts
|
|
326
|
-
const value = parseHttp(headerValue);
|
|
327
|
-
if (value === null) {
|
|
328
|
-
throw new Error("bad Last-Modified header");
|
|
329
|
-
}
|
|
330
|
-
// "" passes this check and flows downstream as if valid.
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
Correct:
|
|
334
|
-
|
|
335
|
-
```ts
|
|
336
|
-
import { parseHttp } from "@northguild/gmt";
|
|
337
|
-
|
|
338
|
-
const value = parseHttp(headerValue);
|
|
339
|
-
if (value === "") {
|
|
340
|
-
throw new Error("bad Last-Modified header");
|
|
341
|
-
}
|
|
342
|
-
```
|
|
343
|
-
|
|
344
|
-
Source: packages/gmt/src/utc/parse/parseHttp.ts — Returns "" on invalid input, matching GMT's string-return sentinel convention
|
|
345
|
-
|
|
346
|
-
## References
|
|
347
|
-
|
|
348
|
-
- [Full parse API](references/parse-api.md)
|
|
349
|
-
- [Temporal.PlainDate documentation](https://tc39.es/proposal-temporal/docs/plaindate.html)
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: pr-contribution
|
|
3
|
-
description: >
|
|
4
|
-
Contribute improvements to @northguild/gmt via pull requests, including new
|
|
5
|
-
methods, bug fixes, and documentation updates.
|
|
6
|
-
metadata:
|
|
7
|
-
library_version: 1.14.2
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
# PR Contribution
|
|
11
|
-
|
|
12
|
-
Use this skill when you want to contribute code directly to gmt.
|
|
13
|
-
|
|
14
|
-
## Contribution Types
|
|
15
|
-
|
|
16
|
-
- **New method**: Add a missing date/time operation
|
|
17
|
-
- **Bug fix**: Correct existing behavior
|
|
18
|
-
- **Documentation**: Improve docs or addexamples
|
|
19
|
-
- **Tests**: Add test coverage for missing cases
|
|
20
|
-
|
|
21
|
-
## Prerequisites
|
|
22
|
-
|
|
23
|
-
1. Install the development environment:
|
|
24
|
-
```bash
|
|
25
|
-
pnpm install
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
2. Understand the project structure:
|
|
29
|
-
```
|
|
30
|
-
packages/gmt/src/
|
|
31
|
-
├── plain/ # Timezone-free operations
|
|
32
|
-
├── zoned/ # Timezone-aware operations
|
|
33
|
-
├── utc/ # UTC operations
|
|
34
|
-
├── unix/ # Unix epoch operations
|
|
35
|
-
└── regex/ # Regex patterns
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
## Creating a PR
|
|
39
|
-
|
|
40
|
-
### 1. Fork and Clone
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
git clone https://github.com/YOUR_FORK/gmt.git
|
|
44
|
-
cd gmt
|
|
45
|
-
pnpm install
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
### 2. Create a Feature Branch
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
git checkout -b feature/my-new-method
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
### 3. Implement the Method
|
|
55
|
-
|
|
56
|
-
Follow the `new-method-implementation` skill guidelines:
|
|
57
|
-
|
|
58
|
-
- Use Temporal under the hood
|
|
59
|
-
- String-in/string-out APIs
|
|
60
|
-
- Plain/zoned separation
|
|
61
|
-
- Proper error handling
|
|
62
|
-
|
|
63
|
-
**Example structure:**
|
|
64
|
-
|
|
65
|
-
```ts
|
|
66
|
-
// packages/gmt/src/plain/get/getQuarter.ts
|
|
67
|
-
import { Temporal } from "@js-temporal/polyfill";
|
|
68
|
-
import { PlainDateSchema } from "../types";
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Get the ISO quarter (1-4) for a PlainDate.
|
|
72
|
-
*
|
|
73
|
-
* @param dateStr ISO 8601 date string (e.g. "2024-03-15")
|
|
74
|
-
* @returns Quarter 1-4, or null on invalid input
|
|
75
|
-
*
|
|
76
|
-
* @example getQuarter("2024-03-15") // 1
|
|
77
|
-
* @example getQuarter("2024-05-20") // 2
|
|
78
|
-
*/
|
|
79
|
-
export function getQuarter(dateStr: string): number | null {
|
|
80
|
-
if (!PlainDateSchema.safeParse(dateStr).success) {
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
try {
|
|
84
|
-
const date = Temporal.PlainDate.from(dateStr);
|
|
85
|
-
return Math.ceil(date.month / 3);
|
|
86
|
-
} catch {
|
|
87
|
-
return null;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
### 4. Add Tests
|
|
93
|
-
|
|
94
|
-
Add comprehensive tests:
|
|
95
|
-
|
|
96
|
-
```ts
|
|
97
|
-
// packages/gmt/src/plain/get/getQuarter.test.ts
|
|
98
|
-
import { describe, it, expect } from "vitest";
|
|
99
|
-
import { getQuarter } from "./getQuarter";
|
|
100
|
-
|
|
101
|
-
describe("getQuarter", () => {
|
|
102
|
-
it.each`
|
|
103
|
-
input | expected
|
|
104
|
-
${"2024-01-01"} | ${1}
|
|
105
|
-
${"2024-03-31"} | ${1}
|
|
106
|
-
${"2024-04-01"} | ${2}
|
|
107
|
-
${"2024-06-30"} | ${2}
|
|
108
|
-
${"2024-07-01"} | ${3}
|
|
109
|
-
${"2024-09-30"} | ${3}
|
|
110
|
-
${"2024-10-01"} | ${4}
|
|
111
|
-
${"2024-12-31"} | ${4}
|
|
112
|
-
`("returns $expected for $input", ({ input, expected }) => {
|
|
113
|
-
expect(getQuarter(input)).toBe(expected);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it("returns null for invalid input", () => {
|
|
117
|
-
expect(getQuarter("invalid")).toBe(null);
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
### 5. Run Tests
|
|
123
|
-
|
|
124
|
-
```bash
|
|
125
|
-
pnpm run test:gmt
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
### 6. Run Linting
|
|
129
|
-
|
|
130
|
-
```bash
|
|
131
|
-
pnpm run lint
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
### 7. Commit and Push
|
|
135
|
-
|
|
136
|
-
```bash
|
|
137
|
-
git add .
|
|
138
|
-
git commit -m "feat(plain): add getQuarter function"
|
|
139
|
-
git push origin feature/my-new-method
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
### 8. Open PR
|
|
143
|
-
|
|
144
|
-
Use the PR template:
|
|
145
|
-
|
|
146
|
-
```markdown
|
|
147
|
-
## Summary
|
|
148
|
-
Brief description of what this adds/fixes
|
|
149
|
-
|
|
150
|
-
## Changes
|
|
151
|
-
- Added `getQuarter` to plain/get
|
|
152
|
-
|
|
153
|
-
## Testing
|
|
154
|
-
- Added unit tests for happy, invalid, and boundary paths
|
|
155
|
-
- All tests pass
|
|
156
|
-
|
|
157
|
-
## Checklist
|
|
158
|
-
- [ ] Tests added
|
|
159
|
-
- [ ] Tests pass
|
|
160
|
-
- [ ] No Date APIs used
|
|
161
|
-
- [ ] String-in/string-out followed
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
## PR Requirements
|
|
165
|
-
|
|
166
|
-
1. **No Date APIs**: Use Temporal only
|
|
167
|
-
2. **Tests required**: Happy path, invalid path, boundary cases
|
|
168
|
-
3. **Documentation**: JSDoc with @example tags
|
|
169
|
-
4. **Lint passes**: No Biome errors
|
|
170
|
-
|
|
171
|
-
## Before Opening
|
|
172
|
-
|
|
173
|
-
- [ ] Tests pass: `pnpm run test:gmt`
|
|
174
|
-
- [ ] Lint passes: `pnpm run lint`
|
|
175
|
-
- [ ] Typecheck passes: `pnpm run typecheck`
|
|
176
|
-
|
|
177
|
-
## Related Skills
|
|
178
|
-
|
|
179
|
-
- **issue-creation**: If you want to discuss before implementing
|
|
180
|
-
- **new-method-implementation**: Implementation guidelines
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: unit-test-generation
|
|
3
|
-
description: >
|
|
4
|
-
Write or update gmt unit tests with deterministic Temporal-safe patterns,
|
|
5
|
-
invalid-input fallback assertions, locale-matrix coverage, and DST/timezone
|
|
6
|
-
boundary cases required by project policy.
|
|
7
|
-
metadata:
|
|
8
|
-
library_version: 1.14.2
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
# Unit Test Generation
|
|
12
|
-
|
|
13
|
-
Use this skill when writing or updating tests for `packages/gmt/src/*` methods.
|
|
14
|
-
|
|
15
|
-
## Required patterns
|
|
16
|
-
|
|
17
|
-
1. Use `it.each` template literals for iterative tests.
|
|
18
|
-
2. Use fake timers for now-dependent behavior.
|
|
19
|
-
3. Never override real runtime functions directly.
|
|
20
|
-
4. Validate type-specific fallback behavior on invalid input.
|
|
21
|
-
5. For locale-aware methods, include the required locale matrix explicitly.
|
|
22
|
-
|
|
23
|
-
## Test checklist
|
|
24
|
-
|
|
25
|
-
1. Happy path
|
|
26
|
-
- Verify expected output for representative valid inputs.
|
|
27
|
-
|
|
28
|
-
2. Invalid input path
|
|
29
|
-
- String-returning method returns `""`.
|
|
30
|
-
- Number-returning method returns `null`.
|
|
31
|
-
- Boolean-returning method returns `false`.
|
|
32
|
-
|
|
33
|
-
- Tests should assert the strict input contract: only ISO 8601 strings, IANA timezone identifiers, or explicit unix epoch numbers are accepted by public APIs. When inputs are outside these shapes, assert the library's typed fallbacks are returned rather than relying on permissive parsing.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
3. Boundary path
|
|
37
|
-
- Include leap day and month rollover scenarios.
|
|
38
|
-
- For zoned behavior, include DST or offset-sensitive scenarios.
|
|
39
|
-
|
|
40
|
-
4. Locale path
|
|
41
|
-
- For locale-aware APIs, include explicit `MustTestLocales.*` rows.
|
|
42
|
-
|
|
43
|
-
## Timer and mocking policy
|
|
44
|
-
|
|
45
|
-
1. Use `vi.useFakeTimers()` and `vi.setSystemTime(...)`.
|
|
46
|
-
2. Restore with `vi.useRealTimers()`.
|
|
47
|
-
3. Use `vi.spyOn(...).mockReturnValue(...)` style APIs when stubbing.
|
|
48
|
-
4. Do not monkey-patch globals or Temporal internals.
|
|
49
|
-
|
|
50
|
-
## Quality bar for new custom methods
|
|
51
|
-
|
|
52
|
-
When a new method is added because no gmt method exists:
|
|
53
|
-
|
|
54
|
-
1. Add strong tests in the same PR.
|
|
55
|
-
2. Cover happy path, invalid path, and boundary path.
|
|
56
|
-
3. Prefer explicit table-driven tests over ad hoc single-case tests.
|