Skip to content

Commit 664343a

Browse files
committed
Rewrite NorwegianDateUtil to use java.time
java.util.Date and java.util.Calendar is old now and we don't want to use that any more. Breaking changes: We use ZonedDateTime in stead of Date to represent a date to check for working days. The list of holidays are now a NavigableSet<LocalDate> and not a Date[]. Co-authored-by: tobiasgwaaler Co-authored-by: runeflobakk Co-authored-by: martin-jackson Co-authored-by: hhaga Co-authored-by: fredrbus
1 parent 0f16b47 commit 664343a

File tree

3 files changed

+191
-276
lines changed

3 files changed

+191
-276
lines changed

pom.xml

+6
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,12 @@
125125
<version>2.2</version>
126126
<scope>test</scope>
127127
</dependency>
128+
<dependency>
129+
<groupId>uk.co.probablyfine</groupId>
130+
<artifactId>java-8-matchers</artifactId>
131+
<version>1.9</version>
132+
<scope>test</scope>
133+
</dependency>
128134
<dependency>
129135
<groupId>org.hibernate.validator</groupId>
130136
<artifactId>hibernate-validator</artifactId>

src/main/java/no/bekk/bekkopen/date/NorwegianDateUtil.java

+71-188
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,24 @@
2626
* #L%
2727
*/
2828

29-
import java.util.Arrays;
30-
import java.util.Calendar;
31-
import java.util.Date;
29+
import java.time.DayOfWeek;
30+
import java.time.LocalDate;
31+
import java.time.Month;
32+
import java.time.ZonedDateTime;
33+
import java.util.Comparator;
3234
import java.util.HashMap;
33-
import java.util.HashSet;
3435
import java.util.Map;
36+
import java.util.NavigableSet;
3537
import java.util.Set;
38+
import java.util.TreeSet;
39+
import java.util.stream.Stream;
3640

3741
/**
3842
* Utility class for Norwegian dates.
3943
*/
4044
public class NorwegianDateUtil {
41-
private static Map<Integer, Set<Date>> holidays;
45+
46+
private final static Map<Integer, NavigableSet<LocalDate>> holidays = new HashMap<>();
4247

4348
/**
4449
* Adds the given number of working days to the given date. A working day is
@@ -60,19 +65,14 @@ public class NorwegianDateUtil {
6065
* The number of working days to add.
6166
* @return The new date.
6267
*/
63-
public static Date addWorkingDaysToDate(Date date, int days) {
64-
Calendar cal = dateToCalendar(date);
65-
66-
for (int i = 0; i < days; i++) {
67-
cal.add(Calendar.DATE, 1);
68-
while (!isWorkingDay(cal)) {
69-
cal.add(Calendar.DATE, 1);
70-
}
71-
}
72-
73-
return cal.getTime();
68+
public static ZonedDateTime addWorkingDaysToDate(ZonedDateTime date, int days) {
69+
return Stream.iterate(date, (d) -> d.plusDays(1))
70+
.filter(NorwegianDateUtil::isWorkingDay)
71+
.limit(days + 1)
72+
.max(Comparator.naturalOrder())
73+
.orElse(date);
7474
}
75-
75+
7676
/**
7777
* Will check if the given date is a working day. That is check if the given
7878
* date is a weekend day or a national holiday.
@@ -81,8 +81,8 @@ public static Date addWorkingDaysToDate(Date date, int days) {
8181
* The date to check.
8282
* @return true if the given date is a working day, false otherwise.
8383
*/
84-
public static boolean isWorkingDay(Date date) {
85-
return isWorkingDay(dateToCalendar(date));
84+
public static boolean isWorkingDay(ZonedDateTime date) {
85+
return date.getDayOfWeek() != DayOfWeek.SATURDAY && date.getDayOfWeek() != DayOfWeek.SUNDAY && !isHoliday(date);
8686
}
8787

8888
/**
@@ -92,214 +92,97 @@ public static boolean isWorkingDay(Date date) {
9292
* The Date to check.
9393
* @return true if holiday, false otherwise.
9494
*/
95-
public static boolean isHoliday(Date date) {
96-
return isHoliday(dateToCalendar(date));
95+
public static boolean isHoliday(ZonedDateTime date) {
96+
final Set<LocalDate> holidaySet = getHolidaySet(date.getYear());
97+
return holidaySet.contains(date.toLocalDate());
9798
}
98-
99+
99100
/**
100-
* Return a sorted array of holidays for a given year.
101+
* Return a sorted set of holidays for a given year.
101102
*
102103
* @param year
103104
* The year to get holidays for.
104-
* @return The array of holidays, sorted by date.
105+
* @return The set of holidays, naturally sorted by date.
105106
*/
106-
public static Date[] getHolidays(int year) {
107-
Set<Date> days = getHolidaySet(year);
108-
Date[] dates = days.toArray(new Date[days.size()]);
109-
Arrays.sort(dates);
110-
return dates;
107+
public static NavigableSet<LocalDate> getHolidays(int year) {
108+
return getHolidaySet(year);
111109
}
112110

111+
/**
112+
* Calculates easter day (sunday) by using Spencer Jones formula found here:
113+
* <a href="http://no.wikipedia.org/wiki/P%C3%A5skeformelen">Wikipedia -
114+
* Påskeformelen</a>
115+
*
116+
* @param year
117+
* The year to calculate from.
118+
* @return The LocalDate representing easter day for the given year.
119+
*/
120+
private static LocalDate getEasterDay(int year) {
121+
int a = year % 19;
122+
int b = year / 100;
123+
int c = year % 100;
124+
int d = b / 4;
125+
int e = b % 4;
126+
int f = (b + 8) / 25;
127+
int g = (b - f + 1) / 3;
128+
int h = ((19 * a) + b - d - g + 15) % 30;
129+
int i = c / 4;
130+
int k = c % 4;
131+
int l = (32 + (2 * e) + (2 * i) - h - k) % 7;
132+
int m = (a + (11 * h) + (22 * l)) / 451;
133+
int n = (h + l - (7 * m) + 114) / 31; // This is the month number.
134+
int p = (h + l - (7 * m) + 114) % 31; // This is the date minus one.
135+
136+
return LocalDate.of(year, n, p + 1);
137+
}
138+
113139
/**
114140
* Get a set of holidays for a given year.
115141
*
116142
* @param year
117143
* The year to get holidays for.
118144
* @return The set of dates.
119145
*/
120-
private static Set<Date> getHolidaySet(int year) {
121-
if (holidays == null) {
122-
holidays = new HashMap<>();
123-
}
146+
private static NavigableSet<LocalDate> getHolidaySet(int year) {
124147
if (!holidays.containsKey(year)) {
125-
Set<Date> yearSet = new HashSet<>();
148+
NavigableSet<LocalDate> yearSet = new TreeSet<>();
126149

127150
// Add set holidays.
128-
yearSet.add(getDate(1, Calendar.JANUARY, year));
129-
yearSet.add(getDate(1, Calendar.MAY, year));
130-
yearSet.add(getDate(17, Calendar.MAY, year));
131-
yearSet.add(getDate(25, Calendar.DECEMBER, year));
132-
yearSet.add(getDate(26, Calendar.DECEMBER, year));
151+
yearSet.add(LocalDate.of(year, Month.JANUARY, 1));
152+
yearSet.add(LocalDate.of(year, Month.MAY, 1));
153+
yearSet.add(LocalDate.of(year, Month.MAY, 17));
154+
yearSet.add(LocalDate.of(year, Month.DECEMBER, 25));
155+
yearSet.add(LocalDate.of(year, Month.DECEMBER, 26));
133156

134157
// Add movable holidays - based on easter day.
135-
Calendar easterDay = dateToCalendar(getEasterDay(year));
158+
final LocalDate easterDay = getEasterDay(year);
136159

137160
// Sunday before easter.
138-
yearSet.add(rollGetDate(easterDay, -7));
161+
yearSet.add(easterDay.minusDays(7));
139162

140163
// Thursday before easter.
141-
yearSet.add(rollGetDate(easterDay, -3));
164+
yearSet.add(easterDay.minusDays(3));
142165

143166
// Friday before easter.
144-
yearSet.add(rollGetDate(easterDay, -2));
167+
yearSet.add(easterDay.minusDays(2));
145168

146169
// Easter day.
147-
yearSet.add(easterDay.getTime());
170+
yearSet.add(easterDay);
148171

149172
// Second easter day.
150-
yearSet.add(rollGetDate(easterDay, 1));
173+
yearSet.add(easterDay.plusDays(1));
151174

152175
// "Kristi himmelfart" day.
153-
yearSet.add(rollGetDate(easterDay, 39));
176+
yearSet.add(easterDay.plusDays(39));
154177

155178
// "Pinse" day.
156-
yearSet.add(rollGetDate(easterDay, 49));
179+
yearSet.add(easterDay.plusDays(49));
157180

158181
// Second "Pinse" day.
159-
yearSet.add(rollGetDate(easterDay, 50));
182+
yearSet.add(easterDay.plusDays(50));
160183

161184
holidays.put(year, yearSet);
162185
}
163186
return holidays.get(year);
164187
}
165-
166-
/**
167-
* Will check if the given date is a working day. That is check if the given
168-
* date is a weekend day or a national holiday.
169-
*
170-
* @param cal
171-
* The Calendar object representing the date.
172-
* @return true if the given date is a working day, false otherwise.
173-
*/
174-
private static boolean isWorkingDay(Calendar cal) {
175-
return cal.get(Calendar.DAY_OF_WEEK) != Calendar.SATURDAY && cal.get(Calendar.DAY_OF_WEEK) != Calendar.SUNDAY
176-
&& !isHoliday(cal);
177-
}
178-
179-
/**
180-
* Check if given Calendar object represents a holiday.
181-
*
182-
* @param cal
183-
* The Calendar to check.
184-
* @return true if holiday, false otherwise.
185-
*/
186-
private static boolean isHoliday(Calendar cal) {
187-
int year = cal.get(Calendar.YEAR);
188-
Set<?> yearSet = getHolidaySet(year);
189-
for (Object aYearSet : yearSet) {
190-
Date date = (Date) aYearSet;
191-
if (checkDate(cal, dateToCalendar(date))) {
192-
return true;
193-
}
194-
}
195-
return false;
196-
}
197-
198-
/**
199-
* Calculates easter day (sunday) by using Spencer Jones formula found here:
200-
* <a href="http://no.wikipedia.org/wiki/P%C3%A5skeformelen">Wikipedia -
201-
* Påskeformelen</a>
202-
*
203-
* @param year
204-
* The year to calculate from.
205-
* @return The Calendar object representing easter day for the given year.
206-
*/
207-
private static Date getEasterDay(int year) {
208-
int a = year % 19;
209-
int b = year / 100;
210-
int c = year % 100;
211-
int d = b / 4;
212-
int e = b % 4;
213-
int f = (b + 8) / 25;
214-
int g = (b - f + 1) / 3;
215-
int h = ((19 * a) + b - d - g + 15) % 30;
216-
int i = c / 4;
217-
int k = c % 4;
218-
int l = (32 + (2 * e) + (2 * i) - h - k) % 7;
219-
int m = (a + (11 * h) + (22 * l)) / 451;
220-
int n = (h + l - (7 * m) + 114) / 31; // This is the month number.
221-
int p = (h + l - (7 * m) + 114) % 31; // This is the date minus one.
222-
223-
Calendar cal = Calendar.getInstance();
224-
cal.set(Calendar.YEAR, year);
225-
cal.set(Calendar.MONTH, n - 1);
226-
cal.set(Calendar.DATE, p + 1);
227-
228-
return cal.getTime();
229-
}
230-
231-
/**
232-
* Check if the given dates match on day and month.
233-
*
234-
* @param cal
235-
* The Calendar representing the first date.
236-
* @param other
237-
* The Calendar representing the second date.
238-
* @return true if they match, false otherwise.
239-
*/
240-
private static boolean checkDate(Calendar cal, Calendar other) {
241-
return checkDate(cal, other.get(Calendar.DATE), other.get(Calendar.MONTH));
242-
}
243-
244-
/**
245-
* Check if the given date represents the given date and month.
246-
*
247-
* @param cal
248-
* The Calendar object representing date to check.
249-
* @param date
250-
* The date.
251-
* @param month
252-
* The month.
253-
* @return true if they match, false otherwise.
254-
*/
255-
private static boolean checkDate(Calendar cal, int date, int month) {
256-
return cal.get(Calendar.DATE) == date && cal.get(Calendar.MONTH) == month;
257-
}
258-
259-
/**
260-
* Convert the given Date object to a Calendar instance.
261-
*
262-
* @param date
263-
* The Date object.
264-
* @return The Calendar instance.
265-
*/
266-
private static Calendar dateToCalendar(Date date) {
267-
Calendar cal = Calendar.getInstance();
268-
cal.setTime(date);
269-
return cal;
270-
}
271-
272-
/**
273-
* Add the given number of days to the calendar and convert to Date.
274-
*
275-
* @param calendar
276-
* The calendar to add to.
277-
* @param days
278-
* The number of days to add.
279-
* @return The date object given by the modified calendar.
280-
*/
281-
private static Date rollGetDate(Calendar calendar, int days) {
282-
Calendar easterSunday = (Calendar) calendar.clone();
283-
easterSunday.add(Calendar.DATE, days);
284-
return easterSunday.getTime();
285-
}
286-
287-
/**
288-
* Get the date for the given values.
289-
*
290-
* @param day
291-
* The day.
292-
* @param month
293-
* The month.
294-
* @param year
295-
* The year.
296-
* @return The date represented by the given values.
297-
*/
298-
private static Date getDate(int day, int month, int year) {
299-
Calendar cal = Calendar.getInstance();
300-
cal.set(Calendar.YEAR, year);
301-
cal.set(Calendar.MONTH, month);
302-
cal.set(Calendar.DATE, day);
303-
return cal.getTime();
304-
}
305188
}

0 commit comments

Comments
 (0)