Skip to content

Commit b19c47d

Browse files
feat: isLeapYear, isWeekend and isWeekday utils
1 parent f3a70ee commit b19c47d

13 files changed

Lines changed: 939 additions & 166 deletions

File tree

UTILITY_RESEARCH.md

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
# Utility Research: d3-time & date-fns
2+
3+
## Already Implemented ✅
4+
5+
- `startOf` / `endOf` - Start/end of units
6+
- `add` / `subtract` - Date arithmetic
7+
- `round` - Round to nearest unit
8+
- `isBefore` / `isAfter` - Comparisons
9+
- `equals` - Equality with unit
10+
- `isSameOrBefore` / `isSameOrAfter` - Comparison with unit
11+
- `isBetween` / `intersects` - Range operations
12+
- `since` / `until` - Duration calculations
13+
- `format` - Date formatting
14+
15+
## Recommended Additions
16+
17+
### 1. Rounding & Boundaries (from d3-time)
18+
19+
#### `ceil(date, unit)` - Round up to nearest unit ✅
20+
21+
- **Use case**: "Next Monday", "Next hour", "Next month"
22+
- **Example**: `ceil('2024-03-15T14:30:00Z', { unit: 'hour' })``'2024-03-15T15:00:00Z'`
23+
- **Status**: High priority - complements `round` and `startOf`
24+
25+
#### `floor(date, unit)` - Round down to nearest unit ✅
26+
27+
- **Use case**: Similar to `startOf` but more explicit naming
28+
- **Note**: Could be alias for `startOf` or separate implementation
29+
- **Status**: Medium priority - `startOf` already covers this
30+
31+
### 2. Range Generation (from d3-time & date-fns)
32+
33+
#### `range(start, end, unit, step?)` - Generate array of dates
34+
35+
- **Use case**: Generate dates for calendars, charts, time series
36+
- **Example**: `range('2024-03-01', '2024-03-31', { unit: 'day' })` → array of all days in March
37+
- **Example**: `range('2024-03-01T00:00:00Z', '2024-03-01T23:00:00Z', { unit: 'hour', step: 2 })` → every 2 hours
38+
- **Status**: High priority - very useful for UI components
39+
40+
#### `eachDayOfInterval(range)` - Generate days in range
41+
42+
- **Use case**: Calendar views, date pickers
43+
- **Status**: Medium priority - can use `range` with `unit: 'day'`
44+
45+
#### `eachWeekOfInterval(range)` - Generate weeks in range
46+
47+
- **Use case**: Week-based views
48+
- **Status**: Medium priority - can use `range` with `unit: 'week'`
49+
50+
### 3. Interval Counting (from d3-time)
51+
52+
#### `count(start, end, unit)` - Count intervals between dates
53+
54+
- **Use case**: "How many days/months/weeks between two dates?"
55+
- **Example**: `count('2024-03-01', '2024-03-31', { unit: 'day' })``30`
56+
- **Note**: Different from `until` which returns duration - this counts discrete intervals
57+
- **Status**: High priority - common use case
58+
59+
### 4. Date Clamping (from date-fns)
60+
61+
#### `clamp(date, range)` - Ensure date is within range
62+
63+
- **Use case**: Date picker min/max constraints, validation
64+
- **Example**: `clamp('2024-05-01', { start: '2024-03-01', end: '2024-04-30' })``'2024-04-30'`
65+
- **Status**: High priority - essential for date pickers
66+
67+
### 5. Relative Time Checks (from date-fns)
68+
69+
#### `isToday(date)` - Check if date is today
70+
71+
- **Use case**: UI highlighting, relative formatting
72+
- **Status**: Medium priority - convenience function
73+
74+
#### `isYesterday(date)` / `isTomorrow(date)` - Relative day checks
75+
76+
- **Use case**: Relative date formatting ("yesterday", "tomorrow")
77+
- **Status**: Low priority - can be built with `isSameDay` + date arithmetic
78+
79+
#### `isPast(date)` / `isFuture(date)` - Relative to now
80+
81+
- **Use case**: Validation, conditional rendering
82+
- **Status**: Medium priority - common use case
83+
84+
### 6. Day/Week Helpers (from date-fns)
85+
86+
#### `isWeekend(date)` / `isWeekday(date)` - Day type checks
87+
88+
- **Use case**: Business logic, calendar highlighting
89+
- **Status**: Medium priority - useful utility
90+
91+
#### `isSameDay(date1, date2)` / `isSameMonth(date1, date2)` / `isSameYear(date1, date2)`
92+
93+
- **Use case**: Convenience wrappers around `equals` with specific units
94+
- **Status**: Low priority - `equals` with unit already covers this
95+
96+
### 7. Getters (from date-fns)
97+
98+
#### `getDayOfYear(date)` - Get day of year (1-365/366)
99+
100+
- **Use case**: Analytics, progress tracking
101+
- **Status**: Medium priority - niche but useful
102+
103+
#### `getWeek(date)` - Get ISO week number
104+
105+
- **Use case**: Week-based reporting, ISO 8601 compliance
106+
- **Status**: Medium priority - depends on week calculation needs
107+
108+
#### `getWeeksInMonth(date)` - Count weeks in month
109+
110+
- **Use case**: Calendar layout calculations
111+
- **Status**: Low priority - can be calculated
112+
113+
### 8. Setters (from date-fns)
114+
115+
#### `set(date, { year?, month?, day?, hour?, ... })` - Set date components
116+
117+
- **Use case**: Date manipulation, form inputs
118+
- **Example**: `set('2024-03-15T14:00:00Z', { month: 5, day: 1 })``'2024-06-01T14:00:00Z'`
119+
- **Status**: High priority - very useful for date manipulation
120+
121+
#### `setDate(date, day)` / `setMonth(date, month)` / `setYear(date, year)` - Individual setters
122+
123+
- **Use case**: Convenience setters
124+
- **Status**: Medium priority - can use `set` with single property
125+
126+
### 9. Min/Max Operations (from date-fns)
127+
128+
#### `min(dates[])` - Find earliest date
129+
130+
- **Use case**: Finding earliest date in array
131+
- **Status**: Medium priority - useful utility
132+
133+
#### `max(dates[])` - Find latest date
134+
135+
- **Use case**: Finding latest date in array
136+
- **Status**: Medium priority - useful utility
137+
138+
### 10. Calendar Differences (from date-fns)
139+
140+
#### `differenceInCalendarDays(date1, date2)` - Calendar day difference
141+
142+
- **Use case**: "How many calendar days apart?" vs duration
143+
- **Note**: Different from `until` - counts calendar boundaries, not duration
144+
- **Status**: Medium priority - useful distinction from duration
145+
146+
#### `differenceInCalendarMonths(date1, date2)` / `differenceInCalendarYears(date1, date2)`
147+
148+
- **Use case**: Calendar-based differences
149+
- **Status**: Medium priority - similar to calendar days
150+
151+
### 11. Offset/Step Operations (from d3-time)
152+
153+
#### `offset(date, unit, step)` - Advance by step intervals
154+
155+
- **Use case**: "Next 3 Mondays", "Every 15 minutes"
156+
- **Example**: `offset('2024-03-15T14:00:00Z', { unit: 'day', step: 7 })` → next week same time
157+
- **Status**: Medium priority - can use `add` with duration
158+
159+
#### `every(unit, step)` - Create filtered interval
160+
161+
- **Use case**: "Every 15 minutes", "Every 2 weeks"
162+
- **Status**: Low priority - advanced use case
163+
164+
### 12. Conversion Helpers (from date-fns)
165+
166+
#### `toDate(dateInput)` - Convert to Date object
167+
168+
- **Use case**: Interop with Date-based libraries
169+
- **Status**: Low priority - `asDate()` already exists on results
170+
171+
#### `fromUnixTime(timestamp)` - Create date from Unix timestamp
172+
173+
- **Use case**: API responses, database timestamps
174+
- **Status**: Medium priority - common conversion
175+
176+
#### `getUnixTime(date)` - Get Unix timestamp
177+
178+
- **Use case**: API requests, database storage
179+
- **Status**: Medium priority - common conversion
180+
181+
## Priority Recommendations
182+
183+
### High Priority (Implement Soon)
184+
185+
1. **`ceil`** - Round up to nearest unit (complements `round`)
186+
2. **`range`** - Generate date arrays (essential for calendars/charts)
187+
3. **`count`** - Count intervals between dates (common use case)
188+
4. **`clamp`** - Date clamping (essential for date pickers)
189+
5. **`set`** - Set date components (very useful for manipulation)
190+
191+
### Medium Priority (Consider Adding)
192+
193+
1. **`isPast` / `isFuture`** - Relative time checks
194+
2. **`isWeekend` / `isWeekday`** - Day type checks
195+
3. **`min` / `max`** - Array operations
196+
4. **`differenceInCalendarDays`** - Calendar differences
197+
5. **`fromUnixTime` / `getUnixTime`** - Unix timestamp conversion
198+
6. **`getDayOfYear`** - Day of year getter
199+
7. **`getWeek`** - ISO week number
200+
201+
### Low Priority (Nice to Have)
202+
203+
1. **`isToday` / `isYesterday` / `isTomorrow`** - Can be built from existing functions
204+
2. **`isSameDay` / `isSameMonth`** - Convenience wrappers around `equals`
205+
3. **`eachDayOfInterval`** - Can use `range` with `unit: 'day'`
206+
4. **`setDate` / `setMonth`** - Can use `set` with single property
207+
5. **`every`** - Advanced interval filtering
208+
209+
## Implementation Notes
210+
211+
- All functions should follow existing patterns:
212+
- Accept `DateInput` (string | number | Date | ZonedDateTime)
213+
- Support `DateOptions` (timeZone, calendar)
214+
- Use Temporal API internally
215+
- Return appropriate types (boolean, number, array, etc.)
216+
217+
- Consider creating a `range` function that's flexible enough to replace multiple "each\*" functions
218+
219+
- `ceil` should mirror `round` but always round up instead of nearest
220+
221+
- `clamp` should work with the existing `Range` type

packages/time/src/date/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,6 @@ export * from './isSameOrAfter'
1414
export * from './isBetween'
1515
export * from './intersects'
1616
export * from './format'
17+
export * from './isLeapYear'
18+
export * from './isWeekend'
19+
export * from './isWeekday'
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './isLeapYear'
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { getDateDefaults } from '../dateDefaults'
2+
import { toZonedDateTime } from '../helpers'
3+
import type { DateInput, DateOptions } from '../types'
4+
5+
export interface IsLeapYearOptions extends DateOptions {}
6+
7+
/**
8+
* isLeapYear
9+
* Returns true if the year of the given date is a leap year
10+
*/
11+
export function isLeapYear(date: DateInput, options?: IsLeapYearOptions): boolean {
12+
const defaults = getDateDefaults()
13+
const { timeZone = defaults.timeZone, calendar = defaults.calendar } = options ?? {}
14+
15+
return toZonedDateTime(date, timeZone, calendar).inLeapYear
16+
}

0 commit comments

Comments
 (0)