Skip to content

Commit 639a299

Browse files
authored
Merge pull request #1 from apiaju/dev
feat: add date range support to getHolidays
2 parents 7b21520 + 3fc8d0c commit 639a299

6 files changed

Lines changed: 126 additions & 9 deletions

File tree

package-lock.json

Lines changed: 6 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@apiaju/calendar",
3-
"version": "1.0.0",
3+
"version": "1.1.0",
44
"description": "Calendário de feriados de Aracaju-SE. Feriados nacionais, estaduais e municipais com suporte a timezone e dias úteis.",
55
"type": "module",
66
"main": "./dist/index.cjs",

src/functions.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Holiday, BusinessDayOptions } from './types'
1+
import type { Holiday, BusinessDayOptions, GetHolidaysOptions } from './types'
22
import { getHolidaysForYear } from './holidays'
33
import { toAracajuDate, isWeekend, makeAracajuDate } from './utils'
44

@@ -31,9 +31,30 @@ export function getHoliday(date: Date = new Date()): Holiday | null {
3131
return findHoliday(date)
3232
}
3333

34-
export function getHolidays(year?: number): Holiday[] {
35-
const y = year ?? toAracajuDate(new Date()).year
36-
return getHolidaysForYear(y)
34+
export function getHolidays(year?: number): Holiday[]
35+
export function getHolidays(options: GetHolidaysOptions): Holiday[]
36+
export function getHolidays(yearOrOptions?: number | GetHolidaysOptions): Holiday[] {
37+
if (yearOrOptions === undefined || typeof yearOrOptions === 'number') {
38+
const y = yearOrOptions ?? toAracajuDate(new Date()).year
39+
return getHolidaysForYear(y)
40+
}
41+
42+
const { start, end } = yearOrOptions
43+
const s = toAracajuDate(start)
44+
const e = toAracajuDate(end)
45+
const startMs = makeAracajuDate(s.year, s.month, s.day).getTime()
46+
const endMs = makeAracajuDate(e.year, e.month, e.day).getTime()
47+
48+
if (startMs > endMs) return []
49+
50+
const result: Holiday[] = []
51+
for (let y = s.year; y <= e.year; y++) {
52+
for (const h of getHolidaysForYear(y)) {
53+
const t = h.date.getTime()
54+
if (t >= startMs && t <= endMs) result.push(h)
55+
}
56+
}
57+
return result
3758
}
3859

3960
export function isBusinessDay(date: Date = new Date(), options?: BusinessDayOptions): boolean {

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export type { Holiday, HolidayType, HolidayLevel, BusinessDayOptions } from './types'
1+
export type { Holiday, HolidayType, HolidayLevel, BusinessDayOptions, GetHolidaysOptions } from './types'
22
export {
33
isHoliday,
44
isOptionalHoliday,

src/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,8 @@ export interface Holiday {
1212
export interface BusinessDayOptions {
1313
saturdayIsBusinessDay?: boolean
1414
}
15+
16+
export interface GetHolidaysOptions {
17+
start: Date
18+
end: Date
19+
}

tests/functions.test.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,94 @@ describe('getHolidays', () => {
7171
})
7272
})
7373

74+
describe('getHolidays with date range', () => {
75+
it('returns holidays within a single-year range', () => {
76+
// 2025: Sexta-feira Santa (4/18), Tiradentes (4/21), Dia do Trabalho (5/1),
77+
// Corpus Christi (6/19), São João (6/24) = 5 holidays
78+
const result = getHolidays({ start: makeAracajuDate(2025, 4, 1), end: makeAracajuDate(2025, 6, 30) })
79+
expect(result).toHaveLength(5)
80+
const names = result.map((h) => h.name)
81+
expect(names).toContain('Sexta-feira Santa')
82+
expect(names).toContain('Tiradentes')
83+
expect(names).toContain('Dia do Trabalho')
84+
expect(names).toContain('Corpus Christi')
85+
expect(names).toContain('São João')
86+
})
87+
88+
it('includes holidays exactly on start and end bounds (inclusive)', () => {
89+
// Dia do Trabalho 2025 = May 1; São João 2025 = June 24
90+
const result = getHolidays({ start: makeAracajuDate(2025, 5, 1), end: makeAracajuDate(2025, 6, 24) })
91+
const names = result.map((h) => h.name)
92+
expect(names).toContain('Dia do Trabalho')
93+
expect(names).toContain('São João')
94+
})
95+
96+
it('returns holidays across a cross-year range', () => {
97+
// 2024: Nossa Sra. da Conceição (12/8), Natal (12/25)
98+
// 2025: Confraternização Universal (1/1)
99+
const result = getHolidays({ start: makeAracajuDate(2024, 12, 1), end: makeAracajuDate(2025, 1, 31) })
100+
expect(result).toHaveLength(3)
101+
const names = result.map((h) => h.name)
102+
expect(names).toContain('Nossa Sra. da Conceição')
103+
expect(names).toContain('Natal')
104+
expect(names).toContain('Confraternização Universal')
105+
})
106+
107+
it('returns result sorted by date for cross-year ranges', () => {
108+
const result = getHolidays({ start: makeAracajuDate(2024, 12, 1), end: makeAracajuDate(2025, 1, 31) })
109+
for (let i = 1; i < result.length; i++) {
110+
expect(result[i].date.getTime()).toBeGreaterThanOrEqual(result[i - 1].date.getTime())
111+
}
112+
})
113+
114+
it('returns empty array when range contains no holidays', () => {
115+
// 2025-03-11 to 2025-03-16 — Carnaval acabou (3/3–3/4); próximo é Aniversário de Aracaju (3/17)
116+
const result = getHolidays({ start: makeAracajuDate(2025, 3, 11), end: makeAracajuDate(2025, 3, 16) })
117+
expect(result).toHaveLength(0)
118+
})
119+
120+
it('returns empty array when start is after end', () => {
121+
const result = getHolidays({ start: makeAracajuDate(2025, 6, 1), end: makeAracajuDate(2025, 3, 1) })
122+
expect(result).toHaveLength(0)
123+
})
124+
125+
it('returns a single holiday when range is one day and it is a holiday', () => {
126+
const result = getHolidays({ start: makeAracajuDate(2025, 12, 25), end: makeAracajuDate(2025, 12, 25) })
127+
expect(result).toHaveLength(1)
128+
expect(result[0].name).toBe('Natal')
129+
})
130+
131+
it('returns empty array when range is one day and it is not a holiday', () => {
132+
const result = getHolidays({ start: makeAracajuDate(2025, 3, 10), end: makeAracajuDate(2025, 3, 10) })
133+
expect(result).toHaveLength(0)
134+
})
135+
136+
it('normalizes UTC date input via toAracajuDate', () => {
137+
// 2025-01-01T03:00:00Z = 2025-01-01 00:00 Aracaju — inclui Confraternização
138+
const startUTC = new Date('2025-01-01T03:00:00Z')
139+
const endUTC = new Date('2025-01-01T03:00:00Z')
140+
const result = getHolidays({ start: startUTC, end: endUTC })
141+
expect(result).toHaveLength(1)
142+
expect(result[0].name).toBe('Confraternização Universal')
143+
})
144+
145+
it('does not include holiday when UTC midnight resolves to previous Aracaju day', () => {
146+
// 2025-01-01T00:00:00Z = 2024-12-31 21:00 Aracaju — Dec 31 não é feriado
147+
const startUTC = new Date('2025-01-01T00:00:00Z')
148+
const endUTC = new Date('2025-01-01T00:00:00Z')
149+
const result = getHolidays({ start: startUTC, end: endUTC })
150+
expect(result).toHaveLength(0)
151+
})
152+
153+
it('backward compat: numeric year still works', () => {
154+
expect(getHolidays(2025)).toHaveLength(17)
155+
})
156+
157+
it('backward compat: no argument still works', () => {
158+
expect(Array.isArray(getHolidays())).toBe(true)
159+
})
160+
})
161+
74162
describe('isBusinessDay', () => {
75163
it('returns true for a regular weekday', () => {
76164
// 2025-03-10 Monday, not a holiday

0 commit comments

Comments
 (0)