AddBusinessDays и GetBusinessDays
мне нужно найти 2 элегантные полные реализации
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) предпочтительно (без петель).
изменить: Под рабочими днями я подразумеваю рабочие дни (Понедельник, Вторник, Среда, Четверг, Пятница). Никаких праздников, только выходные исключены.
у меня уже есть некоторые уродливые решения, которые, кажется, работают, но мне интересно, есть ли элегантные способы сделать это. Спасибо
это то, что я написал до сих пор. Он работает во всех случаях и делает негативы тоже. Все еще нужна реализация 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);
}
14 ответов:
последняя попытка для вашей первой функции:
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); }
вторая функция, GetBusinessDays, может быть реализована следующим образом:
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; } }
используя Fluent DateTime:
var now = DateTime.Now; var dateTime1 = now.AddBusinessDays(3); var dateTime2 = now.SubtractBusinessDays(5);
внутренний код выглядит следующим образом
/// <summary> /// Adds the given number of business days to the <see cref="DateTime"/>. /// </summary> /// <param name="current">The date to be changed.</param> /// <param name="days">Number of business days to be added.</param> /// <returns>A <see cref="DateTime"/> increased by a given number of business days.</returns> 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; } /// <summary> /// Subtracts the given number of business days to the <see cref="DateTime"/>. /// </summary> /// <param name="current">The date to be changed.</param> /// <param name="days">Number of business days to be subtracted.</param> /// <returns>A <see cref="DateTime"/> increased by a given number of business days.</returns> public static DateTime SubtractBusinessDays(this DateTime current, int days) { return AddBusinessDays(current, -days); }
Я создал расширение, которое позволяет добавлять или вычитать рабочие дни. Используйте отрицательное число рабочих дней для вычитания. Я думаю, что это довольно элегантное решение. Кажется, это работает во всех случаях.
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); } } }
пример:
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(); } } }
для меня я должен был иметь решение, которое будет пропускать выходные и идти либо отрицательно, либо положительно. Мои критерии были, если бы он пошел вперед и приземлился в выходные, ему нужно было бы перейти к понедельнику. Если бы он возвращался и приземлялся в выходные, ему пришлось бы прыгать в пятницу.
например:
- среда-3 рабочих дня = последняя пятница
- среда + 3 рабочих дня = понедельник
- пятница - 7 рабочих дней = В Прошлую Среду
- вторник-5 рабочих дней = последний вторник
Ну вы поняли ;)
Я закончил писать этот класс расширения
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; } }
он использует этот метод, который я думал, было бы полезно использовать в другом месте...
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; } } }
если вы не хотите беспокоиться об этом, вы можете просто заменить
if (MyClass.IsBusinessDay(date))
С ifif ((date.DayOfWeek != DayOfWeek.Saturday) && (date.DayOfWeek != DayOfWeek.Sunday))
теперь вы можете сделать
var myDate = DateTime.Now.AddBusinessDays(-3);
или
var myDate = DateTime.Now.AddBusinessDays(5);
вот результаты некоторых тестов:
Test Expected Result Wednesday -4 business days Thursday Thursday Wednesday -3 business days Friday Friday Wednesday +3 business days Monday Monday Friday -7 business days Wednesday Wednesday Tuesday -5 business days Tuesday Tuesday Friday +1 business days Monday Monday Saturday +1 business days Monday Monday Sunday -1 business days Friday Friday Monday -1 business days Friday Friday Monday +1 business days Tuesday Tuesday Monday +0 business days Monday Monday
с интернационализацией, это сложно. Как упоминалось в других темах здесь, на SOF, праздники отличаются от страны к стране, конечно, и даже от провинции к провинции. Большинство правительств не планируют свои отпуска более чем на пять лет или около того.
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); }
Я опаздываю на ответ, но я сделал небольшую библиотеку со всеми настройками, необходимыми для выполнения простых операций в рабочие дни... Я оставляю его здесь : Управление Рабочими Днями
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; }
Я хотел "AddBusinessDays", который поддерживал отрицательные числа дней для добавления, и я закончил с этим:
// 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 }
не требуется цикл, поэтому он должен быть достаточно быстрым даже для "больших" дополнений.
Он работает с днями, выраженными как количество календарных дней с эпохи, так как это выставлено новым классом JDK8 LocalDate, и я работал на Java. Должно быть просто адаптироваться к другим настройкам, хотя.
фундаментальные свойства заключаются в том, что
addDays
всегда возвращает будний день, и это для всехd
иn
,daysBetween(d, addDays(d, n)) == n
обратите внимание, что теоретически добавление 0 дней и вычитание 0 дней должны быть разными операциями (если ваша дата-воскресенье, добавление 0 дней должно привести вас к понедельнику, а вычитание 0 дней должно привести вас к пятнице). Поскольку нет такой вещи как отрицательный 0 (за пределами плавающей точкой!), Я решил интерпретировать аргумент n=0 как значение добавить ноль дней.
Я считаю, что это может быть более простой способ получить Businessdays:
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; }
единственное реальное решение состоит в том, чтобы эти вызовы имели доступ к таблице базы данных, которая определяет календарь для вашего бизнеса. Вы можете закодировать его для рабочей недели с понедельника по пятницу без особых трудностей, но обработка праздников будет проблемой.
отредактировано, чтобы добавить неэлегантное и не проверенное частичное решение:
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; }
также я нарушил требование no loops.
вот мой код с датой отправления и датой доставки у клиента.
// 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;
удачи в кодировании.