Skip to content

Commit d8def8c

Browse files
feat: add HolidayHelper, RecurrenceHelper for v1.4.0
1 parent e97538a commit d8def8c

6 files changed

Lines changed: 670 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
## 1.4.0
2+
3+
- Added `HolidayHelper` — holiday detection and working day utilities
4+
- Added `isHoliday()`, `isWorkingDay()` static methods
5+
- Added `addWorkingDays()` with holiday skip support
6+
- Added `workingDaysBetween()` with holiday skip support
7+
- Added `nextWorkingDay()`, `previousWorkingDay()`
8+
- Added `indianHolidays()`, `globalHolidays()` preset lists
9+
- Added `HolidayExtension` on DateTime
10+
- Added `RecurrenceHelper` — generate recurring date lists
11+
- Added `RecurrenceFrequency` enum: daily, weekly, monthly, yearly
12+
- Recurrence supports: count, until date, skipWeekends, skipHolidays
13+
- Added `RecurrenceHelper.daily/weekly/monthly/yearly` convenience methods
14+
- Added `RecurrenceHelper.nextOccurrence()`
15+
116
## 1.3.0
217

318
- Added `DateBadge` widget — smart date chip/badge

lib/smart_date_formatter.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,5 @@ export 'src/widgets/countdown_text.dart';
3939
export 'src/widgets/date_badge.dart';
4040
export 'src/widgets/smart_date_text.dart';
4141
export 'src/widgets/relative_date_builder.dart';
42+
export 'src/holiday_helper.dart';
43+
export 'src/recurrence_helper.dart';

lib/src/holiday_helper.dart

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
/// Holiday detection and working day utilities with holiday support.
2+
///
3+
/// ```dart
4+
/// final holidays = [
5+
/// DateTime(2024, 12, 25), // Christmas
6+
/// DateTime(2024, 1, 26), // Republic Day
7+
/// ];
8+
///
9+
/// DateTime.now().isHoliday(holidays: holidays) // false
10+
/// DateTime(2024,12,25).isHoliday(holidays: holidays) // true
11+
/// DateTime.now().addWorkingDaysWithHolidays(5, holidays: holidays)
12+
/// ```
13+
class HolidayHelper {
14+
HolidayHelper._();
15+
16+
/// Returns true if [date] is in [holidays] list.
17+
///
18+
/// Comparison is done by date only — time is ignored.
19+
///
20+
/// ```dart
21+
/// HolidayHelper.isHoliday(
22+
/// DateTime(2024, 12, 25),
23+
/// holidays: [DateTime(2024, 12, 25)],
24+
/// ) // true
25+
/// ```
26+
static bool isHoliday(
27+
DateTime date, {
28+
required List<DateTime> holidays,
29+
}) {
30+
final target = DateTime(date.year, date.month, date.day);
31+
return holidays
32+
.any((h) => DateTime(h.year, h.month, h.day).isAtSameMomentAs(target));
33+
}
34+
35+
/// Returns true if [date] is a working day —
36+
/// not a weekend AND not in [holidays].
37+
///
38+
/// ```dart
39+
/// HolidayHelper.isWorkingDay(
40+
/// DateTime(2024, 6, 17), // Monday
41+
/// holidays: [],
42+
/// ) // true
43+
/// ```
44+
static bool isWorkingDay(
45+
DateTime date, {
46+
List<DateTime> holidays = const [],
47+
}) {
48+
if (date.weekday == DateTime.saturday || date.weekday == DateTime.sunday) {
49+
return false;
50+
}
51+
return !isHoliday(date, holidays: holidays);
52+
}
53+
54+
/// Adds [days] working days to [date], skipping weekends and [holidays].
55+
///
56+
/// ```dart
57+
/// HolidayHelper.addWorkingDays(
58+
/// DateTime(2024, 12, 23), // Monday
59+
/// 3,
60+
/// holidays: [DateTime(2024, 12, 25)], // Christmas
61+
/// ) // Dec 27 (skips Sat, Sun, Christmas)
62+
/// ```
63+
static DateTime addWorkingDays(
64+
DateTime date,
65+
int days, {
66+
List<DateTime> holidays = const [],
67+
}) {
68+
DateTime result = date;
69+
int added = 0;
70+
final direction = days >= 0 ? 1 : -1;
71+
final target = days.abs();
72+
73+
while (added < target) {
74+
result = result.add(Duration(days: direction));
75+
if (isWorkingDay(result, holidays: holidays)) {
76+
added++;
77+
}
78+
}
79+
return result;
80+
}
81+
82+
/// Returns number of working days between [start] and [end],
83+
/// excluding weekends and [holidays].
84+
///
85+
/// ```dart
86+
/// HolidayHelper.workingDaysBetween(
87+
/// DateTime(2024, 12, 23),
88+
/// DateTime(2024, 12, 27),
89+
/// holidays: [DateTime(2024, 12, 25)],
90+
/// ) // 2 (Mon + Fri, skipping Wed Christmas)
91+
/// ```
92+
static int workingDaysBetween(
93+
DateTime start,
94+
DateTime end, {
95+
List<DateTime> holidays = const [],
96+
}) {
97+
int count = 0;
98+
DateTime current = start;
99+
while (current.isBefore(end)) {
100+
if (isWorkingDay(current, holidays: holidays)) {
101+
count++;
102+
}
103+
current = current.add(const Duration(days: 1));
104+
}
105+
return count;
106+
}
107+
108+
/// Returns all holidays in [year] from [holidays] list.
109+
///
110+
/// ```dart
111+
/// HolidayHelper.holidaysInYear(
112+
/// 2024,
113+
/// holidays: [DateTime(2024,1,26), DateTime(2024,12,25)],
114+
/// )
115+
/// ```
116+
static List<DateTime> holidaysInYear(
117+
int year, {
118+
required List<DateTime> holidays,
119+
}) =>
120+
holidays.where((h) => h.year == year).toList();
121+
122+
/// Returns next working day from [date] skipping weekends and [holidays].
123+
///
124+
/// ```dart
125+
/// HolidayHelper.nextWorkingDay(DateTime(2024, 12, 25))
126+
/// // Dec 26 (if not holiday) or Dec 27
127+
/// ```
128+
static DateTime nextWorkingDay(
129+
DateTime date, {
130+
List<DateTime> holidays = const [],
131+
}) =>
132+
addWorkingDays(date, 1, holidays: holidays);
133+
134+
/// Returns previous working day from [date].
135+
static DateTime previousWorkingDay(
136+
DateTime date, {
137+
List<DateTime> holidays = const [],
138+
}) =>
139+
addWorkingDays(date, -1, holidays: holidays);
140+
141+
/// Common Indian public holidays for [year].
142+
///
143+
/// ```dart
144+
/// final holidays = HolidayHelper.indianHolidays(2024);
145+
/// date.isHoliday(holidays: holidays)
146+
/// ```
147+
static List<DateTime> indianHolidays(int year) => [
148+
DateTime(year, 1, 26), // Republic Day
149+
DateTime(year, 8, 15), // Independence Day
150+
DateTime(year, 10, 2), // Gandhi Jayanti
151+
DateTime(year, 10, 24), // Dussehra (approximate)
152+
DateTime(year, 11, 1), // Diwali (approximate)
153+
DateTime(year, 11, 15), // Guru Nanak Jayanti (approximate)
154+
DateTime(year, 12, 25), // Christmas
155+
DateTime(year, 1, 1), // New Year
156+
];
157+
158+
/// Common global public holidays for [year].
159+
static List<DateTime> globalHolidays(int year) => [
160+
DateTime(year, 1, 1), // New Year's Day
161+
DateTime(year, 12, 25), // Christmas
162+
DateTime(year, 12, 26), // Boxing Day
163+
];
164+
}
165+
166+
/// Extension on [DateTime] for holiday support.
167+
extension HolidayExtension on DateTime {
168+
/// Returns true if this date is in [holidays].
169+
///
170+
/// ```dart
171+
/// DateTime(2024, 12, 25).isHoliday(
172+
/// holidays: [DateTime(2024, 12, 25)],
173+
/// ) // true
174+
/// ```
175+
bool isHoliday({required List<DateTime> holidays}) =>
176+
HolidayHelper.isHoliday(this, holidays: holidays);
177+
178+
/// Returns true if this date is a working day.
179+
///
180+
/// ```dart
181+
/// DateTime(2024, 6, 17).isWorkingDay() // true — Monday
182+
/// ```
183+
bool isWorkingDay({List<DateTime> holidays = const []}) =>
184+
HolidayHelper.isWorkingDay(this, holidays: holidays);
185+
186+
/// Adds [days] working days skipping weekends and [holidays].
187+
///
188+
/// ```dart
189+
/// DateTime(2024, 12, 23).addWorkingDaysWithHolidays(
190+
/// 3,
191+
/// holidays: [DateTime(2024, 12, 25)],
192+
/// )
193+
/// ```
194+
DateTime addWorkingDaysWithHolidays(
195+
int days, {
196+
List<DateTime> holidays = const [],
197+
}) =>
198+
HolidayHelper.addWorkingDays(this, days, holidays: holidays);
199+
200+
/// Working days until [other] skipping weekends and [holidays].
201+
int workingDaysUntilWithHolidays(
202+
DateTime other, {
203+
List<DateTime> holidays = const [],
204+
}) =>
205+
HolidayHelper.workingDaysBetween(this, other, holidays: holidays);
206+
}

0 commit comments

Comments
 (0)