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;
}
}
@SalcedoEsteban
Copy link

Hola amigo, no logro entender que es lo que se retorna en este código, es el numero de días hábiles y festivos?

@marlonramirez
Copy link
Author

marlonramirez commented Jul 15, 2020

Hola Esteban, la clase tiene dos funcionalidades:

  • Conocer si un día en especifico es festivo isHoliday se piden como parámetros el mes (de 0 a 11) y el día (iniciando en 1). Ejemplo new DateUtil(2020).isHoliday(4, 1), para preguntar si el primero de mayo es festivo.

  • Cual es el siguiente día hábil getNextDayEnabled se piden como parámetros la fecha inicial en la que se deben contra los días hábiles y cuantos días se deben contar. Ejemplo new DateUtil(2020).getNextDayEnabled(new Date(), 5), para contar cinco dias hábiles a partir de hoy.

Ambas funcionalidades se basan en el año con el que se instancia la clase. Espero que te haya aclarado tus dudas.

@marlonramirez
Copy link
Author

En la linea 47 en Asención del señor de pascua no es 36 sino 43. Ya que con 36 siempre toma el día festivo una semana antes de lo que debería dar, para que lo tengas en cuenta. Muchas gracias por la clase me sirvió mucho

Gracias por la corrección Sergio, según wikipedia el día se celebra 40 días despúes de pascua y por ley emiliani se pasa al siguiente lunes, se realízo el cambio de 36 a 40 días.

@SalcedoEsteban
Copy link

Entiendo marlon, muchas gracias. Tú sabes de casualidad como podría yo saber cuales son los días hábiles entre dos fechas? sin contar festivos, sábados y domingos? he buscado varias formas pero no logro dar con la que necesito :( me puedes guiar?

@marlonramirez
Copy link
Author

Hace algún tiempo modifique la clase para hacer justo lo que indicas, actualizó con el método getCountEnabedDays para que te puedas guiar.

@SalcedoEsteban
Copy link

Muchas gracias, marlon.

@paulamariin
Copy link

Excelente Marlon, muchas gracias por tu aporte, aunque me genera la duda de los años bisiestos, ya que probé tu código con el mes de febrero de este año y toma el 29 como día hábil.

@marlonramirez
Copy link
Author

Excelente Marlon, muchas gracias por tu aporte, aunque me genera la duda de los años bisiestos, ya que probé tu código con el mes de febrero de este año y toma el 29 como día hábil.

Hola Paula gracias por reportar el bug, revisando el algoritmo habia un error al contar los días tanto para el método getNextDayEnabled como para countEnabedDays esto estaba haciendo que tomara como días no habiles el domingo y el lunes (en el caso de los métodos mencionados).

Apovecho la revisión para renombrar algunos métodos y corregir algunos bugs generados al mezclar clases.

@GTRONICK
Copy link

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!

@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