Skip to content

Instantly share code, notes, and snippets.

@marlonramirez
Last active April 18, 2024 16:33
Show Gist options
  • Save marlonramirez/425dc84848cb480b183d68b743c5ee36 to your computer and use it in GitHub Desktop.
Save marlonramirez/425dc84848cb480b183d68b743c5ee36 to your computer and use it in GitHub Desktop.
Gestión de fechas en colombia, días habiles, festivos, etc.
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
public class HolidayUtil {
private int year;
private int easterMonth;
private int easterDay;
private ArrayList<String> holidays;
/**
* Class constructor. Initializes this class with the given year.
* @param year Year to be used as reference.
*/
public HolidayUtil(int year) {
this.year = year;
this.holidays = new ArrayList<>();
int a = year % 19;
int b = year / 100;
int c = year % 100;
int d = b / 4;
int e = b % 4;
int g = (8 * b + 13) / 25;
int h = (19 * a + b - d - g + 15) % 30;
int j = c / 4;
int k = c % 4;
int m = (a + 11 * h) / 319;
int r = (2 * e + 2 * j - k - h + m + 32) % 7;
this.easterMonth = (h - m + r + 90) / 25;
this.easterDay = (h - m + r + this.easterMonth + 19) % 32;
this.easterMonth--;
this.holidays.add("0:1"); // Primero de Enero
this.holidays.add("4:1"); // Dia del trabajo 1 de mayo
this.holidays.add("6:20"); //Independencia 20 de Julio
this.holidays.add("7:7"); //Batalla de boyaca 7 de agosto
this.holidays.add("11:8"); //Maria inmaculada 8 de diciembre
this.holidays.add("11:25"); //Navidad 25 de diciembre
this.calculateEmiliani(0, 6); // Reyes magos 6 de enero
this.calculateEmiliani(2, 19); //San jose 19 de marzo
this.calculateEmiliani(5, 29); //San pedro y san pablo 29 de junio
this.calculateEmiliani(7, 15); //Asuncion 15 de agosto
this.calculateEmiliani(9, 12); //Descubrimiento de america 12 de octubre
this.calculateEmiliani(10, 1); //Todos los santos 1 de noviembre
this.calculateEmiliani(10, 11); //Independencia de cartagena 11 de noviembre
this.calculateOtherHoliday(-3, false); //jueves santos
this.calculateOtherHoliday(-2, false); //viernes santo
this.calculateOtherHoliday(40, true); //Asención del señor de pascua
this.calculateOtherHoliday(60, true); //Corpus cristi
this.calculateOtherHoliday(68, true); //Sagrado corazon
}
/**
* Move a holiday to monday by emiliani law
* @param month Original month of the holiday
* @param day Original day of the holiday
*/
private void calculateEmiliani(int month, int day) {
Calendar date = Calendar.getInstance();
date.set(this.year, month, day);
int dayOfWeek = date.get(Calendar.DAY_OF_WEEK);
switch (dayOfWeek) {
case 1:
date.add(Calendar.DATE, 1);
break;
case 3:
date.add(Calendar.DATE, 6);
break;
case 4:
date.add(Calendar.DATE, 5);
break;
case 5:
date.add(Calendar.DATE, 4);
break;
case 6:
date.add(Calendar.DATE, 3);
break;
case 7:
date.add(Calendar.DATE, 2);
break;
default:
break;
}
this.holidays.add(date.get(Calendar.MONTH) + ":" + date.get(Calendar.DATE));
}
/**
* Calculate holidays according to easter day
* @param days Number of days after (+) or before (-) of easter day
* @param emiliani Indicates whether the emiliani law affects
*/
private void calculateOtherHoliday(int days, boolean emiliani) {
Calendar date = Calendar.getInstance();
date.set(this.year, this.easterMonth, this.easterDay);
date.add(Calendar.DATE, days);
if (emiliani) {
this.calculateEmiliani(date.get(Calendar.MONTH), date.get(Calendar.DATE));
} else {
this.holidays.add(date.get(Calendar.MONTH) + ":" + date.get(Calendar.DATE));
}
}
/**
* Indicates if a day is a holiday
* @param month Month in which the day is contained
* @param day Day to evaluate
* @return true if the day is a holiday, false otherwise
*/
public boolean isHoliday(int month, int day) {
return this.holidays.contains(month + ":" +day);
}
/**
* Get the year
* @return int representation of the year
*/
public int getYear() {
return year;
}
/**
* Get the next available business day from a given date and days amount
* @param date Start date to count from
* @param days Number of days to count
* @return Date representation of the next business day
*/
public static Date getNextBusinessDay(Date date, int days) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
HolidayUtil lobHolidayUtil = new HolidayUtil(calendar.get(Calendar.YEAR));
while(days > 0) {
calendar.add(Calendar.DATE, 1);
if (calendar.get(Calendar.YEAR) != lobHolidayUtil.getYear()) {
lobHolidayUtil = new HolidayUtil(calendar.get(Calendar.YEAR));
}
int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
if (dayOfWeek != 1 && dayOfWeek != 7 && !lobHolidayUtil.isHoliday(calendar.get(Calendar.MONTH), calendar.get(Calendar.DATE))) {
days--;
}
}
return calendar.getTime();
}
/**
* Get the amount of business days between two dates
* @param dateInit Start date
* @param dateEnd End date
* @return Amount for business days
*/
public static int countBusinessDays(Date dateInit, Date dateEnd){
Calendar limitDay = Calendar.getInstance();
int days = 0;
if (dateEnd != null) {
limitDay.setTime(dateEnd);
}
Calendar startDay = Calendar.getInstance();
startDay.setTime(dateInit);
HolidayUtil lobHolidayUtil = new HolidayUtil(startDay.get(Calendar.YEAR));
while(startDay.getTime().before(limitDay.getTime())) {
startDay.add(Calendar.DATE, 1);
if (startDay.get(Calendar.YEAR) != lobHolidayUtil.getYear()) {
lobHolidayUtil = new HolidayUtil(startDay.get(Calendar.YEAR));
}
int dayOfWeek = startDay.get(Calendar.DAY_OF_WEEK);
if (dayOfWeek != 1 && dayOfWeek != 7 && !lobHolidayUtil.isHoliday(startDay.get(Calendar.MONTH), startDay.get(Calendar.DATE))) {
days++;
}
}
return days;
}
}
@marlonramirez
Copy link
Author

Muchas gracias por tu aporte @marlonramirez, he actualizado tu clase aplicando algunos ajustes indicados por SonarLint. He adicionado documentación a los métodos y removido los system.out. No sé como poner un pull request en tu clase, así que hice un fork y lo actualicé en el mío, pero sería bueno que integraras los cambios en el tuyo. Saludos!

Igualmente muchas gracias por tu trabajo @GTRONICK, hice la mezcla de lo que realizaste en el fork y complemente la documentación de los métodos que no estaban claros (NOT CLEAR DEFINITION), aparte de esto también ajuste los métodos de getNextBusinessDay y countBusinessDays para tener en cuenta el cambio de año.

@jhorber95
Copy link

Parce, excelente codigo, buen trabajo. Solo una petición, ¿podrias explicar las operaciones que estan en el constructor?

@rodanmuro
Copy link

Felicitaciones y muchas gracias por esta clase. Está muy interesante

@marlonramirez
Copy link
Author

Parce, excelente codigo, buen trabajo. Solo una petición, ¿podrias explicar las operaciones que estan en el constructor?

Hola @jhorber95 en el constructor se calculan como tal los feriados, existen ciertos días que se basan en el día de pascua, por lo cual se realiza una implementación de la formula de gaus para obtener el día base y calcular por ejemplo la semana santa y el Corpus Christi, espero que la explicación te haya aclarado la duda.

@cardenasdonny
Copy link

Hola Marlon, necesito contactarme contigo para un requerimiento, gracias.

@miguelsan12
Copy link

Hola buenas tardes, tengo una duda como podria testearlo, me podrias compartir un ejemplo de como se mandan a llavar los metodods y sus respuestas gracias

@marlonramirez
Copy link
Author

Hola buenas tardes, tengo una duda como podria testearlo, me podrias compartir un ejemplo de como se mandan a llavar los metodods y sus respuestas gracias

Buenas tardes @miguelsan12, la clase lo que hace es calcular los días festivos en Colombia en un año específico, por lo cual crear una instancia nos da acceso a los métodos públicos isHoliday y getYear, de ese año pasado por constructor; un ejemplo de uso para eso sería:

HolidayUtil holidayUtil = new HolidayUtil(2023);
System.out.println(holidayUtil.isHoliday(9, 16));

Lo anterior verificaría si el 16 de octubre de 2024 es festivo o no (por definición los meses en una java Date inician en 0 y terminan en 11), como solo averiguar si una fecha en específico es día feriado, no es lo suficientemente útil por sí sola, decidí implementar dos de los que a mi parecer son los casos de uso más comunes cuando se trabaja con días hábiles obtener el siguiente día hábil getNextBusinessDay y contar cuantos días hábiles hay entre un rango de fechas countBusinessDays.

Para obtener el siguiente día hábil basta con enviar como primer parámetro el día desde el cual se deben empezar a contar los días y la cantidad de días que se deben contar; por ejemplo, de hoy en 7 días hábiles:

Date today = new Date();
Date nextBusinessDay = HolidayUtil.getNextBusinessDay(today, 7);
System.out.println(nextBusinessDay);

Para obtener cuantos días hábiles hay entre dos fechas, simplemente se le envía el día inicial como primer parámetro y el final como segundo, por ejemplo de hoy al 17 de octubre debería haber 6 días hábiles, ya que el algoritmo no cuenta el día actual y excluye sábados y domingos, así como festivos.

Date today = new Date();
DateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date targetDate = simpleDateFormat.parse("2023-10-17");
int businessDays = HolidayUtil.countBusinessDays(today, targetDate);
System.out.println(businessDays);

Trate de ser lo más explicativo posible, cualquier otro caso de uso diferente sería bueno que sea compartido para ir nutriendo la funcionalidad de la clase, gracias.

@JoanTamayo
Copy link

@marlonramirez, gracias por compartir la clase, me ha sido de utilidad, sin embargo, experimentando un poco, encontré que no me indica que el 16 de Octubre es festivo (día de la raza). ¿podrías por favor explicarme como solucionar este impresvisto? Gracias de antemano.

@marlonramirez
Copy link
Author

@JoanTamayo si revisaste mi última respuesta, justo en ella coloco como ejemplo la fecha que mencionas;

HolidayUtil holidayUtil = new HolidayUtil(2023);
System.out.println(holidayUtil.isHoliday(9, 16));

Lo anterior verificaría si el 16 de octubre de 2023 es festivo o no (por definición los meses en una java Date inician en 0 y terminan en 11), como solo averiguar si una fecha en específico es día feriado, no es lo suficientemente útil por sí sola, decidí implementar dos de los que a mi parecer son los casos de uso más comunes cuando se trabaja con días hábiles obtener el siguiente día hábil getNextBusinessDay y contar cuantos días hábiles hay entre un rango de fechas countBusinessDays.

En los test que realice me da true para isHoliday.

@JoanTamayo
Copy link

Gracias @marlonramirez, efectivamente, fue mi error en el uso de la clase.

@miguelsan12
Copy link

miguelsan12 commented Oct 10, 2023

Para obtener cuantos días hábiles hay entre dos fechas, simplemente se le envía el día inicial como primer parámetro y el final como segundo, por ejemplo de hoy al 17 de octubre debería haber 6 días hábiles, ya que el algoritmo no cuenta el día actual y excluye sábados y domingos, así como festivos.

Graciasl @marlonramirez por escribir y aclarar la duda, muchas gracias

@miguelsan12
Copy link

hola buen dia una pregunta, para poner viernes, sabado y domingo como dia inhabil y mover la fecha al dia habil lunes siguiente como podria hacerle?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment