Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite NorwegianDateUtil to use java.time #72

Merged
merged 2 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@
<version>2.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>uk.co.probablyfine</groupId>
<artifactId>java-8-matchers</artifactId>
<version>1.9</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
Expand Down
263 changes: 75 additions & 188 deletions src/main/java/no/bekk/bekkopen/date/NorwegianDateUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,28 @@
* #L%
*/

import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.Month;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Stream;

/**
* Utility class for Norwegian dates.
*/
public class NorwegianDateUtil {
private static Map<Integer, Set<Date>> holidays;

public static final String ZONEID_EUROPE_OSLO = "Europe/Oslo";
public static final ZoneId ZONE_NORWAY = ZoneId.of(ZONEID_EUROPE_OSLO);

private final static Map<Integer, NavigableSet<LocalDate>> holidays = new HashMap<>();

/**
* Adds the given number of working days to the given date. A working day is
Expand All @@ -60,19 +69,14 @@ public class NorwegianDateUtil {
* The number of working days to add.
* @return The new date.
*/
public static Date addWorkingDaysToDate(Date date, int days) {
Calendar cal = dateToCalendar(date);

for (int i = 0; i < days; i++) {
cal.add(Calendar.DATE, 1);
while (!isWorkingDay(cal)) {
cal.add(Calendar.DATE, 1);
}
}

return cal.getTime();
public static ZonedDateTime addWorkingDaysToDate(ZonedDateTime date, int days) {
return Stream.iterate(date, (d) -> d.plusDays(1))
.filter(NorwegianDateUtil::isWorkingDay)
.limit(days + 1)
.max(Comparator.naturalOrder())
.orElse(date);
}

/**
* Will check if the given date is a working day. That is check if the given
* date is a weekend day or a national holiday.
Expand All @@ -81,8 +85,8 @@ public static Date addWorkingDaysToDate(Date date, int days) {
* The date to check.
* @return true if the given date is a working day, false otherwise.
*/
public static boolean isWorkingDay(Date date) {
return isWorkingDay(dateToCalendar(date));
public static boolean isWorkingDay(ZonedDateTime date) {
return date.getDayOfWeek() != DayOfWeek.SATURDAY && date.getDayOfWeek() != DayOfWeek.SUNDAY && !isHoliday(date);
}

/**
Expand All @@ -92,214 +96,97 @@ public static boolean isWorkingDay(Date date) {
* The Date to check.
* @return true if holiday, false otherwise.
*/
public static boolean isHoliday(Date date) {
return isHoliday(dateToCalendar(date));
public static boolean isHoliday(ZonedDateTime date) {
final Set<LocalDate> holidaySet = getHolidaySet(date.getYear());
return holidaySet.contains(date.toLocalDate());
}

/**
* Return a sorted array of holidays for a given year.
* Return a sorted set of holidays for a given year.
*
* @param year
* The year to get holidays for.
* @return The array of holidays, sorted by date.
* @return The set of holidays, naturally sorted by date.
*/
public static Date[] getHolidays(int year) {
Set<Date> days = getHolidaySet(year);
Date[] dates = days.toArray(new Date[days.size()]);
Arrays.sort(dates);
return dates;
public static NavigableSet<LocalDate> getHolidays(int year) {
return getHolidaySet(year);
}

/**
* Calculates easter day (sunday) by using Spencer Jones formula found here:
* <a href="http://no.wikipedia.org/wiki/P%C3%A5skeformelen">Wikipedia -
* Påskeformelen</a>
*
* @param year
* The year to calculate from.
* @return The LocalDate representing easter day for the given year.
*/
private static LocalDate getEasterDay(int year) {
int a = year % 19;
int b = year / 100;
int c = year % 100;
int d = b / 4;
int e = b % 4;
int f = (b + 8) / 25;
int g = (b - f + 1) / 3;
int h = ((19 * a) + b - d - g + 15) % 30;
int i = c / 4;
int k = c % 4;
int l = (32 + (2 * e) + (2 * i) - h - k) % 7;
int m = (a + (11 * h) + (22 * l)) / 451;
int n = (h + l - (7 * m) + 114) / 31; // This is the month number.
int p = (h + l - (7 * m) + 114) % 31; // This is the date minus one.

return LocalDate.of(year, n, p + 1);
}

/**
* Get a set of holidays for a given year.
*
* @param year
* The year to get holidays for.
* @return The set of dates.
*/
private static Set<Date> getHolidaySet(int year) {
if (holidays == null) {
holidays = new HashMap<>();
}
private static NavigableSet<LocalDate> getHolidaySet(int year) {
if (!holidays.containsKey(year)) {
Set<Date> yearSet = new HashSet<>();
NavigableSet<LocalDate> yearSet = new TreeSet<>();

// Add set holidays.
yearSet.add(getDate(1, Calendar.JANUARY, year));
yearSet.add(getDate(1, Calendar.MAY, year));
yearSet.add(getDate(17, Calendar.MAY, year));
yearSet.add(getDate(25, Calendar.DECEMBER, year));
yearSet.add(getDate(26, Calendar.DECEMBER, year));
yearSet.add(LocalDate.of(year, Month.JANUARY, 1));
yearSet.add(LocalDate.of(year, Month.MAY, 1));
yearSet.add(LocalDate.of(year, Month.MAY, 17));
yearSet.add(LocalDate.of(year, Month.DECEMBER, 25));
yearSet.add(LocalDate.of(year, Month.DECEMBER, 26));

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

// Sunday before easter.
yearSet.add(rollGetDate(easterDay, -7));
yearSet.add(easterDay.minusDays(7));

// Thursday before easter.
yearSet.add(rollGetDate(easterDay, -3));
yearSet.add(easterDay.minusDays(3));

// Friday before easter.
yearSet.add(rollGetDate(easterDay, -2));
yearSet.add(easterDay.minusDays(2));

// Easter day.
yearSet.add(easterDay.getTime());
yearSet.add(easterDay);

// Second easter day.
yearSet.add(rollGetDate(easterDay, 1));
yearSet.add(easterDay.plusDays(1));

// "Kristi himmelfart" day.
yearSet.add(rollGetDate(easterDay, 39));
yearSet.add(easterDay.plusDays(39));

// "Pinse" day.
yearSet.add(rollGetDate(easterDay, 49));
yearSet.add(easterDay.plusDays(49));

// Second "Pinse" day.
yearSet.add(rollGetDate(easterDay, 50));
yearSet.add(easterDay.plusDays(50));

holidays.put(year, yearSet);
}
return holidays.get(year);
}

/**
* Will check if the given date is a working day. That is check if the given
* date is a weekend day or a national holiday.
*
* @param cal
* The Calendar object representing the date.
* @return true if the given date is a working day, false otherwise.
*/
private static boolean isWorkingDay(Calendar cal) {
return cal.get(Calendar.DAY_OF_WEEK) != Calendar.SATURDAY && cal.get(Calendar.DAY_OF_WEEK) != Calendar.SUNDAY
&& !isHoliday(cal);
}

/**
* Check if given Calendar object represents a holiday.
*
* @param cal
* The Calendar to check.
* @return true if holiday, false otherwise.
*/
private static boolean isHoliday(Calendar cal) {
int year = cal.get(Calendar.YEAR);
Set<?> yearSet = getHolidaySet(year);
for (Object aYearSet : yearSet) {
Date date = (Date) aYearSet;
if (checkDate(cal, dateToCalendar(date))) {
return true;
}
}
return false;
}

/**
* Calculates easter day (sunday) by using Spencer Jones formula found here:
* <a href="http://no.wikipedia.org/wiki/P%C3%A5skeformelen">Wikipedia -
* Påskeformelen</a>
*
* @param year
* The year to calculate from.
* @return The Calendar object representing easter day for the given year.
*/
private static Date getEasterDay(int year) {
int a = year % 19;
int b = year / 100;
int c = year % 100;
int d = b / 4;
int e = b % 4;
int f = (b + 8) / 25;
int g = (b - f + 1) / 3;
int h = ((19 * a) + b - d - g + 15) % 30;
int i = c / 4;
int k = c % 4;
int l = (32 + (2 * e) + (2 * i) - h - k) % 7;
int m = (a + (11 * h) + (22 * l)) / 451;
int n = (h + l - (7 * m) + 114) / 31; // This is the month number.
int p = (h + l - (7 * m) + 114) % 31; // This is the date minus one.

Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, year);
cal.set(Calendar.MONTH, n - 1);
cal.set(Calendar.DATE, p + 1);

return cal.getTime();
}

/**
* Check if the given dates match on day and month.
*
* @param cal
* The Calendar representing the first date.
* @param other
* The Calendar representing the second date.
* @return true if they match, false otherwise.
*/
private static boolean checkDate(Calendar cal, Calendar other) {
return checkDate(cal, other.get(Calendar.DATE), other.get(Calendar.MONTH));
}

/**
* Check if the given date represents the given date and month.
*
* @param cal
* The Calendar object representing date to check.
* @param date
* The date.
* @param month
* The month.
* @return true if they match, false otherwise.
*/
private static boolean checkDate(Calendar cal, int date, int month) {
return cal.get(Calendar.DATE) == date && cal.get(Calendar.MONTH) == month;
}

/**
* Convert the given Date object to a Calendar instance.
*
* @param date
* The Date object.
* @return The Calendar instance.
*/
private static Calendar dateToCalendar(Date date) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
return cal;
}

/**
* Add the given number of days to the calendar and convert to Date.
*
* @param calendar
* The calendar to add to.
* @param days
* The number of days to add.
* @return The date object given by the modified calendar.
*/
private static Date rollGetDate(Calendar calendar, int days) {
Calendar easterSunday = (Calendar) calendar.clone();
easterSunday.add(Calendar.DATE, days);
return easterSunday.getTime();
}

/**
* Get the date for the given values.
*
* @param day
* The day.
* @param month
* The month.
* @param year
* The year.
* @return The date represented by the given values.
*/
private static Date getDate(int day, int month, int year) {
Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, year);
cal.set(Calendar.MONTH, month);
cal.set(Calendar.DATE, day);
return cal.getTime();
}
}
Loading
Loading