Skip to content

Commit fce2967

Browse files
committed
feat(query): add weekday query methods 📆
- Add lastWeekday helper function to get last weekday occurrence in month - Add nearestWeekday helper function to move weekends to nearest weekday - Add nextWeekday helper function to get next weekday occurrence - Add nthWeekday helper function to get nth weekday occurrence in month - Add prevWeekday helper function to get previous weekday occurrence - Add weekday query methods to Daytime class - Add weekday query method type definitions to IDaytime interface - Add comprehensive tests for all weekday query methods - Fix arrow function formatting in Main test file
1 parent 0891175 commit fce2967

6 files changed

Lines changed: 468 additions & 1 deletion

File tree

src/Daytime.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,25 @@ export class Daytime implements Types.IDaytime {
469469
return this.get('month')
470470
}
471471

472+
/**
473+
* Gets the last occurrence of a weekday in the month.
474+
* @param weekday - The weekday (0-6, Sunday is 0)
475+
* @returns A new Daytime instance representing the last occurrence of the weekday
476+
*/
477+
lastWeekday(weekday: number): Types.IDaytime {
478+
const newDate = Helpers.lastWeekday(this.date, weekday)
479+
return new Daytime(newDate)
480+
}
481+
482+
/**
483+
* Gets the nearest weekday to this date (moves weekends to nearest weekday).
484+
* @returns A new Daytime instance representing the nearest weekday
485+
*/
486+
nearestWeekday(): Types.IDaytime {
487+
const newDate = Helpers.nearestWeekday(this.date)
488+
return new Daytime(newDate)
489+
}
490+
472491
/**
473492
* Gets the next business day.
474493
* @returns A new Daytime instance representing the next business day
@@ -478,6 +497,27 @@ export class Daytime implements Types.IDaytime {
478497
return new Daytime(newDate)
479498
}
480499

500+
/**
501+
* Gets the next occurrence of a weekday.
502+
* @param weekday - The weekday (0-6, Sunday is 0)
503+
* @returns A new Daytime instance representing the next occurrence of the weekday
504+
*/
505+
nextWeekday(weekday: number): Types.IDaytime {
506+
const newDate = Helpers.nextWeekday(this.date, weekday)
507+
return new Daytime(newDate)
508+
}
509+
510+
/**
511+
* Gets the nth occurrence of a weekday in the month.
512+
* @param n - The occurrence number (1-based)
513+
* @param weekday - The weekday (0-6, Sunday is 0)
514+
* @returns A new Daytime instance representing the nth occurrence of the weekday
515+
*/
516+
nthWeekday(n: number, weekday: number): Types.IDaytime {
517+
const newDate = Helpers.nthWeekday(this.date, n, weekday)
518+
return new Daytime(newDate)
519+
}
520+
481521
/**
482522
* Gets the previous business day.
483523
* @returns A new Daytime instance representing the previous business day
@@ -487,6 +527,16 @@ export class Daytime implements Types.IDaytime {
487527
return new Daytime(newDate)
488528
}
489529

530+
/**
531+
* Gets the previous occurrence of a weekday.
532+
* @param weekday - The weekday (0-6, Sunday is 0)
533+
* @returns A new Daytime instance representing the previous occurrence of the weekday
534+
*/
535+
prevWeekday(weekday: number): Types.IDaytime {
536+
const newDate = Helpers.prevWeekday(this.date, weekday)
537+
return new Daytime(newDate)
538+
}
539+
490540
/**
491541
* Gets the previous business day.
492542
* @returns A new Daytime instance representing the previous business day

src/Types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,20 @@ export interface IDaytime {
134134
minute(): number
135135
/** Gets the month */
136136
month(): number
137+
/** Gets the last occurrence of a weekday in the month */
138+
lastWeekday(weekday: number): IDaytime
139+
/** Gets the nearest weekday to this date */
140+
nearestWeekday(): IDaytime
137141
/** Gets the next business day */
138142
nextBusinessDay(): IDaytime
143+
/** Gets the next occurrence of a weekday */
144+
nextWeekday(weekday: number): IDaytime
145+
/** Gets the nth occurrence of a weekday in the month */
146+
nthWeekday(n: number, weekday: number): IDaytime
139147
/** Gets the previous business day */
140148
prevBusinessDay(): IDaytime
149+
/** Gets the previous occurrence of a weekday */
150+
prevWeekday(weekday: number): IDaytime
141151
/** Gets the previous business day */
142152
previousBusinessDay(): IDaytime
143153
/** Gets the quarter */

src/helpers/Query.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,95 @@ export function daysInYear(date: Date): number {
6161
return Helpers.isLeapYear(date) ? 366 : 365
6262
}
6363

64+
/**
65+
* Gets the last occurrence of a weekday in the month for the given date.
66+
* @param date - The date to get last weekday for
67+
* @param weekday - The weekday (0-6, Sunday is 0)
68+
* @returns A new Date representing the last occurrence of the weekday in the month
69+
*/
70+
export function lastWeekday(date: Date, weekday: number): Date {
71+
const lastDay = Helpers.endOfMonth(date)
72+
const lastDayOfWeek = lastDay.getDay()
73+
let daysToSubtract = (lastDayOfWeek - weekday + 7) % 7
74+
if (daysToSubtract === 0 && lastDayOfWeek !== weekday) {
75+
daysToSubtract = 7
76+
}
77+
const result = Helpers.cloneDate(lastDay)
78+
result.setDate(result.getDate() - daysToSubtract)
79+
return result
80+
}
81+
82+
/**
83+
* Gets the nearest weekday to the given date (moves weekends to nearest weekday).
84+
* @param date - The date to get nearest weekday for
85+
* @returns A new Date representing the nearest weekday
86+
*/
87+
export function nearestWeekday(date: Date): Date {
88+
const dayOfWeek = date.getDay()
89+
const result = Helpers.cloneDate(date)
90+
if (dayOfWeek === 0) {
91+
result.setDate(result.getDate() + 1)
92+
} else if (dayOfWeek === 6) {
93+
result.setDate(result.getDate() - 1)
94+
}
95+
return result
96+
}
97+
98+
/**
99+
* Gets the next occurrence of a weekday from the given date.
100+
* @param date - The date to get next weekday for
101+
* @param weekday - The weekday (0-6, Sunday is 0)
102+
* @returns A new Date representing the next occurrence of the weekday
103+
*/
104+
export function nextWeekday(date: Date, weekday: number): Date {
105+
const currentDayOfWeek = date.getDay()
106+
let daysToAdd = (weekday - currentDayOfWeek + 7) % 7
107+
if (daysToAdd === 0) {
108+
daysToAdd = 7
109+
}
110+
const result = Helpers.cloneDate(date)
111+
result.setDate(result.getDate() + daysToAdd)
112+
return result
113+
}
114+
115+
/**
116+
* Gets the nth occurrence of a weekday in the month for the given date.
117+
* @param date - The date to get nth weekday for
118+
* @param n - The occurrence number (1-based)
119+
* @param weekday - The weekday (0-6, Sunday is 0)
120+
* @returns A new Date representing the nth occurrence of the weekday in the month
121+
*/
122+
export function nthWeekday(date: Date, n: number, weekday: number): Date {
123+
const firstDayOfMonth = Helpers.startOfMonth(date)
124+
const lastDayOfMonth = Helpers.endOfMonth(date)
125+
const firstDayOfWeek = firstDayOfMonth.getDay()
126+
const daysInMonth = lastDayOfMonth.getDate()
127+
const daysToFirst = (weekday - firstDayOfWeek + 7) % 7
128+
const maxOccurrences = Math.floor((daysInMonth - daysToFirst - 1) / 7) + 1
129+
const targetN = Math.min(n, maxOccurrences)
130+
const daysToAdd = daysToFirst + (targetN - 1) * 7
131+
const result = Helpers.cloneDate(firstDayOfMonth)
132+
result.setDate(result.getDate() + daysToAdd)
133+
return result
134+
}
135+
136+
/**
137+
* Gets the previous occurrence of a weekday from the given date.
138+
* @param date - The date to get previous weekday for
139+
* @param weekday - The weekday (0-6, Sunday is 0)
140+
* @returns A new Date representing the previous occurrence of the weekday
141+
*/
142+
export function prevWeekday(date: Date, weekday: number): Date {
143+
const currentDayOfWeek = date.getDay()
144+
let daysToSubtract = (currentDayOfWeek - weekday + 7) % 7
145+
if (daysToSubtract === 0) {
146+
daysToSubtract = 7
147+
}
148+
const result = Helpers.cloneDate(date)
149+
result.setDate(result.getDate() - daysToSubtract)
150+
return result
151+
}
152+
64153
/**
65154
* Gets the week number within the month for the given date.
66155
* @param date - The date to get week of month for

tests/Daytime.test.ts

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,3 +592,117 @@ Deno.test('Daytime: all query convenience methods should return booleans', () =>
592592
expect(typeof daytime(now).isSameYear(future)).toBe('boolean')
593593
expect(typeof daytime(now).isSameQuarter(future)).toBe('boolean')
594594
})
595+
596+
Deno.test('Daytime: lastWeekday - should return last weekday of month', () => {
597+
const date = daytime('2026-01-15')
598+
const lastMonday = date.lastWeekday(1)
599+
expect(lastMonday.dayOfWeek()).toEqual(1)
600+
expect(lastMonday.month()).toEqual(1)
601+
expect(lastMonday.toDate().getTime()).toBeGreaterThanOrEqual(date.toDate().getTime())
602+
})
603+
604+
Deno.test('Daytime: lastWeekday - should work with chaining', () => {
605+
const date = daytime('2026-01-15')
606+
const result = date.lastWeekday(5).add(1, 'day')
607+
expect(typeof result.toDate()).toEqual('object')
608+
})
609+
610+
Deno.test('Daytime: nearestWeekday - should move weekends to nearest weekday', () => {
611+
const saturday = daytime('2026-01-03')
612+
const sunday = daytime('2026-01-04')
613+
expect(saturday.nearestWeekday().dayOfWeek()).toBeLessThan(6)
614+
expect(sunday.nearestWeekday().dayOfWeek()).toBeGreaterThan(0)
615+
})
616+
617+
Deno.test('Daytime: nearestWeekday - should keep weekdays unchanged', () => {
618+
const monday = daytime('2026-01-05')
619+
const tuesday = daytime('2026-01-06')
620+
expect(monday.nearestWeekday().dayOfWeek()).toEqual(1)
621+
expect(tuesday.nearestWeekday().dayOfWeek()).toEqual(2)
622+
})
623+
624+
Deno.test('Daytime: nearestWeekday - should work with chaining', () => {
625+
const date = daytime('2026-01-03')
626+
const nearest = date.nearestWeekday()
627+
expect(nearest.dayOfWeek()).toBeLessThan(6)
628+
const result = nearest.add(1, 'day')
629+
expect(typeof result.toDate()).toEqual('object')
630+
})
631+
632+
Deno.test('Daytime: nextWeekday - should find next occurrence of weekday', () => {
633+
const date = daytime('2026-01-05')
634+
const nextMonday = date.nextWeekday(1)
635+
expect(nextMonday.dayOfWeek()).toEqual(1)
636+
expect(nextMonday.toDate().getTime()).toBeGreaterThan(date.toDate().getTime())
637+
})
638+
639+
Deno.test('Daytime: nextWeekday - should work for all weekdays', () => {
640+
const date = daytime('2026-01-15')
641+
for (let weekday = 0; weekday <= 6; weekday++) {
642+
const result = date.nextWeekday(weekday)
643+
expect(result.dayOfWeek()).toEqual(weekday)
644+
expect(result.toDate().getTime()).toBeGreaterThan(date.toDate().getTime())
645+
}
646+
})
647+
648+
Deno.test('Daytime: nextWeekday - should work with chaining', () => {
649+
const date = daytime('2026-01-15')
650+
const result = date.nextWeekday(1).add(1, 'day')
651+
expect(typeof result.toDate()).toEqual('object')
652+
})
653+
654+
Deno.test('Daytime: nthWeekday - should find nth occurrence in month', () => {
655+
const date = daytime('2026-01-15')
656+
const firstMonday = date.nthWeekday(1, 1)
657+
expect(firstMonday.dayOfWeek()).toEqual(1)
658+
expect(firstMonday.month()).toEqual(1)
659+
expect(firstMonday.day()).toEqual(5)
660+
})
661+
662+
Deno.test('Daytime: nthWeekday - should handle different occurrences', () => {
663+
const date = daytime('2026-01-15')
664+
const secondFriday = date.nthWeekday(2, 5)
665+
const thirdWednesday = date.nthWeekday(3, 3)
666+
expect(secondFriday.dayOfWeek()).toEqual(5)
667+
expect(thirdWednesday.dayOfWeek()).toEqual(3)
668+
expect(thirdWednesday.toDate().getTime()).toBeGreaterThan(secondFriday.toDate().getTime())
669+
})
670+
671+
Deno.test('Daytime: nthWeekday - should work with chaining', () => {
672+
const date = daytime('2026-01-15')
673+
const result = date.nthWeekday(1, 1).add(1, 'week')
674+
expect(typeof result.toDate()).toEqual('object')
675+
})
676+
677+
Deno.test('Daytime: prevWeekday - should find previous occurrence of weekday', () => {
678+
const date = daytime('2026-01-15')
679+
const prevMonday = date.prevWeekday(1)
680+
expect(prevMonday.dayOfWeek()).toEqual(1)
681+
expect(prevMonday.toDate().getTime()).toBeLessThan(date.toDate().getTime())
682+
})
683+
684+
Deno.test('Daytime: prevWeekday - should work for all weekdays', () => {
685+
const date = daytime('2026-01-15')
686+
for (let weekday = 0; weekday <= 6; weekday++) {
687+
const result = date.prevWeekday(weekday)
688+
expect(result.dayOfWeek()).toEqual(weekday)
689+
expect(result.toDate().getTime()).toBeLessThan(date.toDate().getTime())
690+
}
691+
})
692+
693+
Deno.test('Daytime: prevWeekday - should work with chaining', () => {
694+
const date = daytime('2026-01-15')
695+
const result = date.prevWeekday(5).subtract(1, 'day')
696+
expect(typeof result.toDate()).toEqual('object')
697+
})
698+
699+
Deno.test('Daytime: weekday methods should maintain immutability', () => {
700+
const date = daytime('2026-01-15')
701+
const originalTime = date.toDate().getTime()
702+
date.lastWeekday(1)
703+
date.nearestWeekday()
704+
date.nextWeekday(1)
705+
date.nthWeekday(1, 1)
706+
date.prevWeekday(5)
707+
expect(date.toDate().getTime()).toEqual(originalTime)
708+
})

tests/Main.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ Deno.test('Utility: valueOf - should support sorting', () => {
134134
new Date('2026-01-15T12:00:00Z'),
135135
new Date('2026-01-16T12:00:00Z')
136136
]
137-
const sorted = dates.map(d => daytime(d)).sort((a, b) => a.valueOf() - b.valueOf())
137+
const sorted = dates.map((d) => daytime(d)).sort((a, b) => a.valueOf() - b.valueOf())
138138
expect(sorted[0]?.valueOf()).toEqual(dates[1]?.getTime())
139139
expect(sorted[1]?.valueOf()).toEqual(dates[2]?.getTime())
140140
expect(sorted[2]?.valueOf()).toEqual(dates[0]?.getTime())

0 commit comments

Comments
 (0)