AddBusinessDays y GetBusinessDays

Necesito encontrar 2 implementaciones completas elegantes de

public static DateTime AddBusinessDays(this DateTime date, int days) { // code here } and public static int GetBusinessDays(this DateTime start, DateTime end) { // code here } 

O (1) preferible (sin bucles).

EDITAR: por días hábiles quiero decir días hábiles (lunes, martes, miércoles, jueves, viernes). Sin vacaciones, solo los fines de semana excluidos.

Ya tengo algunas soluciones feas que parecen funcionar, pero me pregunto si hay formas elegantes de hacerlo. Gracias


Esto es lo que he escrito hasta ahora. Funciona en todos los casos y también tiene efectos negativos. Todavía necesita una implementación GetBusinessDays

 public static DateTime AddBusinessDays(this DateTime startDate, int businessDays) { int direction = Math.Sign(businessDays); if(direction == 1) { if(startDate.DayOfWeek == DayOfWeek.Saturday) { startDate = startDate.AddDays(2); businessDays = businessDays - 1; } else if(startDate.DayOfWeek == DayOfWeek.Sunday) { startDate = startDate.AddDays(1); businessDays = businessDays - 1; } } else { if(startDate.DayOfWeek == DayOfWeek.Saturday) { startDate = startDate.AddDays(-1); businessDays = businessDays + 1; } else if(startDate.DayOfWeek == DayOfWeek.Sunday) { startDate = startDate.AddDays(-2); businessDays = businessDays + 1; } } int initialDayOfWeek = (int)startDate.DayOfWeek; int weeksBase = Math.Abs(businessDays / 5); int addDays = Math.Abs(businessDays % 5); if((direction == 1 && addDays + initialDayOfWeek > 5) || (direction == -1 && addDays >= initialDayOfWeek)) { addDays += 2; } int totalDays = (weeksBase * 7) + addDays; return startDate.AddDays(totalDays * direction); } 

Último bash para su primera función:

 public static DateTime AddBusinessDays(DateTime date, int days) { if (days < 0) { throw new ArgumentException("days cannot be negative", "days"); } if (days == 0) return date; if (date.DayOfWeek == DayOfWeek.Saturday) { date = date.AddDays(2); days -= 1; } else if (date.DayOfWeek == DayOfWeek.Sunday) { date = date.AddDays(1); days -= 1; } date = date.AddDays(days / 5 * 7); int extraDays = days % 5; if ((int)date.DayOfWeek + extraDays > 5) { extraDays += 2; } return date.AddDays(extraDays); } 

La segunda función, GetBusinessDays, se puede implementar de la siguiente manera:

 public static int GetBusinessDays(DateTime start, DateTime end) { if (start.DayOfWeek == DayOfWeek.Saturday) { start = start.AddDays(2); } else if (start.DayOfWeek == DayOfWeek.Sunday) { start = start.AddDays(1); } if (end.DayOfWeek == DayOfWeek.Saturday) { end = end.AddDays(-1); } else if (end.DayOfWeek == DayOfWeek.Sunday) { end = end.AddDays(-2); } int diff = (int)end.Subtract(start).TotalDays; int result = diff / 7 * 5 + diff % 7; if (end.DayOfWeek < start.DayOfWeek) { return result - 2; } else{ return result; } } 

usando Fluent DateTime :

 var now = DateTime.Now; var dateTime1 = now.AddBusinessDays(3); var dateTime2 = now.SubtractBusinessDays(5); 

el código interno es el siguiente

  ///  /// Adds the given number of business days to the . ///  /// The date to be changed. /// Number of business days to be added. /// A  increased by a given number of business days. public static DateTime AddBusinessDays(this DateTime current, int days) { var sign = Math.Sign(days); var unsignedDays = Math.Abs(days); for (var i = 0; i < unsignedDays; i++) { do { current = current.AddDays(sign); } while (current.DayOfWeek == DayOfWeek.Saturday || current.DayOfWeek == DayOfWeek.Sunday); } return current; } ///  /// Subtracts the given number of business days to the . ///  /// The date to be changed. /// Number of business days to be subtracted. /// A  increased by a given number of business days. public static DateTime SubtractBusinessDays(this DateTime current, int days) { return AddBusinessDays(current, -days); } 

Creé una extensión que le permite agregar o restar días hábiles. Use un número negativo de días hábiles para restar. Creo que es una solución bastante elegante. Parece que funciona en todos los casos.

 namespace Extensions.DateTime { public static class BusinessDays { public static System.DateTime AddBusinessDays(this System.DateTime source, int businessDays) { var dayOfWeek = businessDays < 0 ? ((int)source.DayOfWeek - 12) % 7 : ((int)source.DayOfWeek + 6) % 7; switch (dayOfWeek) { case 6: businessDays--; break; case -6: businessDays++; break; } return source.AddDays(businessDays + ((businessDays + dayOfWeek) / 5) * 2); } } } 

Ejemplo:

 using System; using System.Windows.Forms; using Extensions.DateTime; namespace AddBusinessDaysTest { public partial class Form1 : Form { public Form1() { InitializeComponent(); label1.Text = DateTime.Now.AddBusinessDays(5).ToString(); label2.Text = DateTime.Now.AddBusinessDays(-36).ToString(); } } } 

Para mí, tenía que tener una solución que saltara los fines de semana y fuera negativa o positiva. Mi criterio era que si avanzaba y aterrizaba en un fin de semana tendría que avanzar al lunes. Si retrocediera y aterrizara en un fin de semana tendría que saltar al viernes.

Por ejemplo:

  • Miércoles – 3 días hábiles = Último viernes
  • Miércoles + 3 días hábiles = lunes
  • Viernes – 7 días hábiles = Último miércoles
  • Martes – 5 días hábiles = Último martes

Bueno, ya captas la idea 😉

Terminé escribiendo esta clase de extensión

 public static partial class MyExtensions { public static DateTime AddBusinessDays(this DateTime date, int addDays) { while (addDays != 0) { date = date.AddDays(Math.Sign(addDays)); if (MyClass.IsBusinessDay(date)) { addDays = addDays - Math.Sign(addDays); } } return date; } } 

Utiliza este método que pensé que sería útil usar en otro lugar …

 public class MyClass { public static bool IsBusinessDay(DateTime date) { switch (date.DayOfWeek) { case DayOfWeek.Monday: case DayOfWeek.Tuesday: case DayOfWeek.Wednesday: case DayOfWeek.Thursday: case DayOfWeek.Friday: return true; default: return false; } } } 

Si no quieres molestarte con eso, puedes reemplazar if (MyClass.IsBusinessDay(date)) con if if ((date.DayOfWeek != DayOfWeek.Saturday) && (date.DayOfWeek != DayOfWeek.Sunday))

Entonces ahora puedes hacer

 var myDate = DateTime.Now.AddBusinessDays(-3); 

o

 var myDate = DateTime.Now.AddBusinessDays(5); 

Aquí están los resultados de algunas pruebas:

 Resultado previsto de la prueba
 Miércoles -4 días hábiles Jueves Jueves
 Miércoles -3 días hábiles Viernes Viernes
 Miércoles +3 días hábiles lunes lunes
 Viernes -7 días hábiles miércoles miércoles
 Martes -5 días hábiles martes martes
 Viernes +1 días hábiles lunes lunes
 Sábado +1 días hábiles lunes lunes
 Domingo -1 días hábiles Viernes Viernes
 Lunes -1 días hábiles Viernes Viernes
 Lunes +1 días hábiles martes martes
 Lunes +0 días hábiles lunes lunes

Con la internacionalización, esto es difícil. Como se menciona en otros hilos aquí en SOF, las vacaciones difieren de un país a otro con certeza e incluso de una provincia a otra. La mayoría de los gobiernos no progtwign sus vacaciones más de cinco años más o menos.

 public static DateTime AddBusinessDays(this DateTime date, int days) { date = date.AddDays((days / 5) * 7); int remainder = days % 5; switch (date.DayOfWeek) { case DayOfWeek.Tuesday: if (remainder > 3) date = date.AddDays(2); break; case DayOfWeek.Wednesday: if (remainder > 2) date = date.AddDays(2); break; case DayOfWeek.Thursday: if (remainder > 1) date = date.AddDays(2); break; case DayOfWeek.Friday: if (remainder > 0) date = date.AddDays(2); break; case DayOfWeek.Saturday: if (days > 0) date = date.AddDays((remainder == 0) ? 2 : 1); break; case DayOfWeek.Sunday: if (days > 0) date = date.AddDays((remainder == 0) ? 1 : 0); break; default: // monday break; } return date.AddDays(remainder); } 

Llego tarde a la respuesta, pero hice una pequeña biblioteca con toda la personalización necesaria para hacer operaciones simples en días laborables … Lo dejo aquí: Working Days Management

  public static DateTime AddBusinessDays(DateTime date, int days) { if (days == 0) return date; int i = 0; while (i < days) { if (!(date.DayOfWeek == DayOfWeek.Saturday || date.DayOfWeek == DayOfWeek.Sunday)) i++; date = date.AddDays(1); } return date; } 

Quería un “AddBusinessDays” que admitiera números negativos de días para agregar, y terminé con esto:

 // 0 == Monday, 6 == Sunday private static int epochDayToDayOfWeek0Based(long epochDay) { return (int)Math.floorMod(epochDay + 3, 7); } public static int daysBetween(long fromEpochDay, long toEpochDay) { // http://stackoverflow.com/questions/1617049/calculate-the-number-of-business-days-between-two-dates final int fromDOW = epochDayToDayOfWeek0Based(fromEpochDay); final int toDOW = epochDayToDayOfWeek0Based(toEpochDay); long calcBusinessDays = ((toEpochDay - fromEpochDay) * 5 + (toDOW - fromDOW) * 2) / 7; if (toDOW == 6) calcBusinessDays -= 1; if (fromDOW == 6) calcBusinessDays += 1; return (int)calcBusinessDays; } public static long addDays(long epochDay, int n) { // https://alecpojidaev.wordpress.com/2009/10/29/work-days-calculation-with-c/ // NB: in .NET, Sunday == 0, but in our code Monday == 0 final int dow = (epochDayToDayOfWeek0Based(epochDay) + 1) % 7; final int wds = n + (dow == 0 ? 1 : dow); // Adjusted number of working days to add, given that we now start from the immediately preceding Sunday final int wends = n < 0 ? ((wds - 5) / 5) * 2 : (wds / 5) * 2 - (wds % 5 == 0 ? 2 : 0); return epochDay - dow + // Find the immediately preceding Sunday wds + // Add computed working days wends; // Add weekends that occur within each complete working week } 

No se requiere bucle, por lo que debe ser razonablemente rápido incluso para adiciones "grandes".

Funciona con días expresados ​​como un número de días calendario desde la época, ya que eso está expuesto por la nueva clase JDK8 LocalDate y yo estaba trabajando en Java. Sin embargo, debería ser simple de adaptar a otras configuraciones.

Las propiedades fundamentales son que addDays siempre devuelve un día de la semana, y que para todo d y n , daysBetween(d, addDays(d, n)) == n

Tenga en cuenta que, teóricamente, agregar 0 días y restar 0 días debería ser operaciones diferentes (si su fecha es un domingo, agregar 0 días debería llevarlo al lunes y restar 0 días debería llevarlo al viernes). Como no existe el 0 negativo (¡fuera del punto flotante!), He elegido interpretar un argumento n = 0 como significado agregar cero días.

Creo que esta podría ser una forma más sencilla de GetBusinessDays:

  public int GetBusinessDays(DateTime start, DateTime end, params DateTime[] bankHolidays) { int tld = (int)((end - start).TotalDays) + 1; //including end day int not_buss_day = 2 * (tld / 7); //Saturday and Sunday int rest = tld % 7; //rest. if (rest > 0) { int tmp = (int)start.DayOfWeek - 1 + rest; if (tmp == 6 || start.DayOfWeek == DayOfWeek.Sunday) not_buss_day++; else if (tmp > 6) not_buss_day += 2; } foreach (DateTime bankHoliday in bankHolidays) { DateTime bh = bankHoliday.Date; if (!(bh.DayOfWeek == DayOfWeek.Saturday || bh.DayOfWeek == DayOfWeek.Sunday) && (start <= bh && bh <= end)) { not_buss_day++; } } return tld - not_buss_day; } 

La única solución real es hacer que esas llamadas accedan a una tabla de base de datos que define el calendario de su negocio. Podría codificarlo para una semana laboral de lunes a viernes sin demasiada dificultad, pero manejar las vacaciones sería un desafío.

Editado para agregar una solución parcial no elegante y no probada:

 public static DateTime AddBusinessDays(this DateTime date, int days) { for (int index = 0; index < days; index++) { switch (date.DayOfWeek) { case DayOfWeek.Friday: date = date.AddDays(3); break; case DayOfWeek.Saturday: date = date.AddDays(2); break; default: date = date.AddDays(1); break; } } return date; } 

También violé el requisito de no bucles.

Aquí está mi código con fecha de salida y fecha de entrega en el cliente.

  // Calculate departure date TimeSpan DeliveryTime = new TimeSpan(14, 30, 0); TimeSpan now = DateTime.Now.TimeOfDay; DateTime dt = DateTime.Now; if (dt.TimeOfDay > DeliveryTime) dt = dt.AddDays(1); if (dt.DayOfWeek == DayOfWeek.Saturday) dt = dt.AddDays(1); if (dt.DayOfWeek == DayOfWeek.Sunday) dt = dt.AddDays(1); dt = dt.Date + DeliveryTime; string DepartureDay = "today at "+dt.ToString("HH:mm"); if (dt.Day!=DateTime.Now.Day) { DepartureDay = dt.ToString("dddd at HH:mm", new CultureInfo(WebContextState.CurrentUICulture)); } Return DepartureDay; // Caclulate delivery date dt = dt.AddDays(1); if (dt.DayOfWeek == DayOfWeek.Saturday) dt = dt.AddDays(1); if (dt.DayOfWeek == DayOfWeek.Sunday) dt = dt.AddDays(1); string DeliveryDay = dt.ToString("dddd", new CultureInfo(WebContextState.CurrentUICulture)); return DeliveryDay; 

Feliz encoding.

 public static DateTime AddWorkingDays(this DateTime date, int daysToAdd) { while (daysToAdd > 0) { date = date.AddDays(1); if (date.DayOfWeek != DayOfWeek.Saturday && date.DayOfWeek != DayOfWeek.Sunday) { daysToAdd -= 1; } } return date; } 

Espero que esto ayude a alguien.

 private DateTime AddWorkingDays(DateTime addToDate, int numberofDays) { addToDate= addToDate.AddDays(numberofDays); while (addToDate.DayOfWeek == DayOfWeek.Saturday || addToDate.DayOfWeek == DayOfWeek.Sunday) { addToDate= addToDate.AddDays(1); } return addToDate; }