Trong C #, làm cách nào để tính số ngày làm việc (hoặc các ngày trong tuần) giữa hai ngày?
Câu trả lời:
Tôi đã có một nhiệm vụ như vậy trước đây và tôi đã có giải pháp. Tôi sẽ tránh liệt kê tất cả các ngày trong khoảng thời gian có thể tránh được, đó là trường hợp ở đây. Tôi thậm chí không đề cập đến việc tạo một loạt các phiên bản DateTime, như tôi đã thấy trong một trong các câu trả lời ở trên. Điều này thực sự lãng phí sức mạnh xử lý. Đặc biệt là trong tình huống thế giới thực, khi bạn phải kiểm tra khoảng thời gian vài tháng. Xem mã của tôi, với bình luận, bên dưới.
/// <summary>
/// Calculates number of business days, taking into account:
/// - weekends (Saturdays and Sundays)
/// - bank holidays in the middle of the week
/// </summary>
/// <param name="firstDay">First day in the time interval</param>
/// <param name="lastDay">Last day in the time interval</param>
/// <param name="bankHolidays">List of bank holidays excluding weekends</param>
/// <returns>Number of business days during the 'span'</returns>
public static int BusinessDaysUntil(this DateTime firstDay, DateTime lastDay, params DateTime[] bankHolidays)
{
firstDay = firstDay.Date;
lastDay = lastDay.Date;
if (firstDay > lastDay)
throw new ArgumentException("Incorrect last day " + lastDay);
TimeSpan span = lastDay - firstDay;
int businessDays = span.Days + 1;
int fullWeekCount = businessDays / 7;
// find out if there are weekends during the time exceedng the full weeks
if (businessDays > fullWeekCount*7)
{
// we are here to find out if there is a 1-day or 2-days weekend
// in the time interval remaining after subtracting the complete weeks
int firstDayOfWeek = (int) firstDay.DayOfWeek;
int lastDayOfWeek = (int) lastDay.DayOfWeek;
if (lastDayOfWeek < firstDayOfWeek)
lastDayOfWeek += 7;
if (firstDayOfWeek <= 6)
{
if (lastDayOfWeek >= 7)// Both Saturday and Sunday are in the remaining time interval
businessDays -= 2;
else if (lastDayOfWeek >= 6)// Only Saturday is in the remaining time interval
businessDays -= 1;
}
else if (firstDayOfWeek <= 7 && lastDayOfWeek >= 7)// Only Sunday is in the remaining time interval
businessDays -= 1;
}
// subtract the weekends during the full weeks in the interval
businessDays -= fullWeekCount + fullWeekCount;
// subtract the number of bank holidays during the time interval
foreach (DateTime bankHoliday in bankHolidays)
{
DateTime bh = bankHoliday.Date;
if (firstDay <= bh && bh <= lastDay)
--businessDays;
}
return businessDays;
}
Chỉnh sửa bởi Slauma, tháng 8 năm 2011
Câu trả lời chính xác! Có một chút lỗi. Tôi có quyền tự do chỉnh sửa câu trả lời này vì người trả lời vắng mặt kể từ năm 2009.
Đoạn mã trên giả định rằng DayOfWeek.Sundaycó giá trị 7không phải như vậy. Giá trị thực sự là 0. Nó dẫn đến một phép tính sai nếu ví dụ firstDayvà lastDaycả hai cùng một ngày Chủ nhật. Phương thức trả về 1trong trường hợp này nhưng nó phải như vậy 0.
Cách khắc phục dễ dàng nhất cho lỗi này: Thay thế trong mã phía trên các dòng có firstDayOfWeekvà lastDayOfWeekđược khai báo bằng cách sau:
int firstDayOfWeek = firstDay.DayOfWeek == DayOfWeek.Sunday
? 7 : (int)firstDay.DayOfWeek;
int lastDayOfWeek = lastDay.DayOfWeek == DayOfWeek.Sunday
? 7 : (int)lastDay.DayOfWeek;
Bây giờ kết quả là:
&& !(bh.DayOfWeek == DayOfWeek.Sunday || bh.DayOfWeek == DayOfWeek.Saturday), nó sẽ trừ vào cùng một ngày hai lần, nếu ngày lễ rơi vào cuối tuần.
Đồng ý. Tôi nghĩ đã đến lúc đăng câu trả lời đúng:
public static double GetBusinessDays(DateTime startD, DateTime endD)
{
double calcBusinessDays =
1 + ((endD - startD).TotalDays * 5 -
(startD.DayOfWeek - endD.DayOfWeek) * 2) / 7;
if (endD.DayOfWeek == DayOfWeek.Saturday) calcBusinessDays--;
if (startD.DayOfWeek == DayOfWeek.Sunday) calcBusinessDays--;
return calcBusinessDays;
}
Nguồn chính thức:
http://alecpojidaev.wordpress.com/2009/10/29/work-days-calculation-with-c/
PS Giải pháp được đăng ở trên làm cho tôi sic vì một số lý do.
Tôi biết câu hỏi này đã được giải quyết, nhưng tôi nghĩ tôi có thể đưa ra một câu trả lời dễ hiểu hơn có thể giúp ích cho những khách truy cập khác trong tương lai.
Đây là lý do của tôi:
public int GetWorkingDays(DateTime from, DateTime to)
{
var dayDifference = (int)to.Subtract(from).TotalDays;
return Enumerable
.Range(1, dayDifference)
.Select(x => from.AddDays(x))
.Count(x => x.DayOfWeek != DayOfWeek.Saturday && x.DayOfWeek != DayOfWeek.Sunday);
}
Đây là bài nộp ban đầu của tôi:
public int GetWorkingDays(DateTime from, DateTime to)
{
var totalDays = 0;
for (var date = from; date < to; date = date.AddDays(1))
{
if (date.DayOfWeek != DayOfWeek.Saturday
&& date.DayOfWeek != DayOfWeek.Sunday)
totalDays++;
}
return totalDays;
}
to > from. Có lẽ đó là vấn đề?
Xác định một Phương thức mở rộng trên DateTime như sau:
public static class DateTimeExtensions
{
public static bool IsWorkingDay(this DateTime date)
{
return date.DayOfWeek != DayOfWeek.Saturday
&& date.DayOfWeek != DayOfWeek.Sunday;
}
}
Sau đó, sử dụng trong mệnh đề Where để lọc danh sách ngày tháng rộng hơn:
var allDates = GetDates(); // method which returns a list of dates
// filter dates by working day's
var countOfWorkDays = allDates
.Where(day => day.IsWorkingDay())
.Count() ;
Tôi đã sử dụng mã sau để tính cả những ngày nghỉ của ngân hàng:
public class WorkingDays
{
public List<DateTime> GetHolidays()
{
var client = new WebClient();
var json = client.DownloadString("https://www.gov.uk/bank-holidays.json");
var js = new JavaScriptSerializer();
var holidays = js.Deserialize <Dictionary<string, Holidays>>(json);
return holidays["england-and-wales"].events.Select(d => d.date).ToList();
}
public int GetWorkingDays(DateTime from, DateTime to)
{
var totalDays = 0;
var holidays = GetHolidays();
for (var date = from.AddDays(1); date <= to; date = date.AddDays(1))
{
if (date.DayOfWeek != DayOfWeek.Saturday
&& date.DayOfWeek != DayOfWeek.Sunday
&& !holidays.Contains(date))
totalDays++;
}
return totalDays;
}
}
public class Holidays
{
public string division { get; set; }
public List<Event> events { get; set; }
}
public class Event
{
public DateTime date { get; set; }
public string notes { get; set; }
public string title { get; set; }
}
Và bài kiểm tra đơn vị:
[TestClass]
public class WorkingDays
{
[TestMethod]
public void SameDayIsZero()
{
var service = new WorkingDays();
var from = new DateTime(2013, 8, 12);
Assert.AreEqual(0, service.GetWorkingDays(from, from));
}
[TestMethod]
public void CalculateDaysInWorkingWeek()
{
var service = new WorkingDays();
var from = new DateTime(2013, 8, 12);
var to = new DateTime(2013, 8, 16);
Assert.AreEqual(4, service.GetWorkingDays(from, to), "Mon - Fri = 4");
Assert.AreEqual(1, service.GetWorkingDays(from, new DateTime(2013, 8, 13)), "Mon - Tues = 1");
}
[TestMethod]
public void NotIncludeWeekends()
{
var service = new WorkingDays();
var from = new DateTime(2013, 8, 9);
var to = new DateTime(2013, 8, 16);
Assert.AreEqual(5, service.GetWorkingDays(from, to), "Fri - Fri = 5");
Assert.AreEqual(2, service.GetWorkingDays(from, new DateTime(2013, 8, 13)), "Fri - Tues = 2");
Assert.AreEqual(1, service.GetWorkingDays(from, new DateTime(2013, 8, 12)), "Fri - Mon = 1");
}
[TestMethod]
public void AccountForHolidays()
{
var service = new WorkingDays();
var from = new DateTime(2013, 8, 23);
Assert.AreEqual(0, service.GetWorkingDays(from, new DateTime(2013, 8, 26)), "Fri - Mon = 0");
Assert.AreEqual(1, service.GetWorkingDays(from, new DateTime(2013, 8, 27)), "Fri - Tues = 1");
}
}
Chà này đã bị đánh chết. :) Tuy nhiên tôi vẫn sẽ cung cấp một câu trả lời khác bởi vì tôi cần một cái gì đó khác một chút. Giải pháp này khác ở chỗ nó trả về Khoảng thời gian làm việc từ đầu đến cuối và bạn có thể đặt giờ làm việc trong ngày và thêm ngày nghỉ. Vì vậy, bạn có thể sử dụng nó để tính toán nếu nó xảy ra trong một ngày, trong nhiều ngày, vào cuối tuần và thậm chí cả ngày lễ. Và bạn có thể nhận được ngày làm việc hoặc không chỉ bằng cách lấy những gì bạn cần từ đối tượng TimeSpan trả về. Và cách nó sử dụng danh sách ngày, bạn có thể thấy việc thêm danh sách ngày không làm việc sẽ dễ dàng như thế nào nếu đó không phải là Thứ Bảy và Chủ Nhật. Và tôi đã thử nghiệm trong một năm, và nó có vẻ rất nhanh.
Tôi chỉ hy vọng việc dán mã là chính xác. Nhưng tôi biết nó hoạt động.
public static TimeSpan GetBusinessTimespanBetween(
DateTime start, DateTime end,
TimeSpan workdayStartTime, TimeSpan workdayEndTime,
List<DateTime> holidays = null)
{
if (end < start)
throw new ArgumentException("start datetime must be before end datetime.");
// Just create an empty list for easier coding.
if (holidays == null) holidays = new List<DateTime>();
if (holidays.Where(x => x.TimeOfDay.Ticks > 0).Any())
throw new ArgumentException("holidays can not have a TimeOfDay, only the Date.");
var nonWorkDays = new List<DayOfWeek>() { DayOfWeek.Saturday, DayOfWeek.Sunday };
var startTime = start.TimeOfDay;
// If the start time is before the starting hours, set it to the starting hour.
if (startTime < workdayStartTime) startTime = workdayStartTime;
var timeBeforeEndOfWorkDay = workdayEndTime - startTime;
// If it's after the end of the day, then this time lapse doesn't count.
if (timeBeforeEndOfWorkDay.TotalSeconds < 0) timeBeforeEndOfWorkDay = new TimeSpan();
// If start is during a non work day, it doesn't count.
if (nonWorkDays.Contains(start.DayOfWeek)) timeBeforeEndOfWorkDay = new TimeSpan();
else if (holidays.Contains(start.Date)) timeBeforeEndOfWorkDay = new TimeSpan();
var endTime = end.TimeOfDay;
// If the end time is after the ending hours, set it to the ending hour.
if (endTime > workdayEndTime) endTime = workdayEndTime;
var timeAfterStartOfWorkDay = endTime - workdayStartTime;
// If it's before the start of the day, then this time lapse doesn't count.
if (timeAfterStartOfWorkDay.TotalSeconds < 0) timeAfterStartOfWorkDay = new TimeSpan();
// If end is during a non work day, it doesn't count.
if (nonWorkDays.Contains(end.DayOfWeek)) timeAfterStartOfWorkDay = new TimeSpan();
else if (holidays.Contains(end.Date)) timeAfterStartOfWorkDay = new TimeSpan();
// Easy scenario if the times are during the day day.
if (start.Date.CompareTo(end.Date) == 0)
{
if (nonWorkDays.Contains(start.DayOfWeek)) return new TimeSpan();
else if (holidays.Contains(start.Date)) return new TimeSpan();
return endTime - startTime;
}
else
{
var timeBetween = end - start;
var daysBetween = (int)Math.Floor(timeBetween.TotalDays);
var dailyWorkSeconds = (int)Math.Floor((workdayEndTime - workdayStartTime).TotalSeconds);
var businessDaysBetween = 0;
// Now the fun begins with calculating the actual Business days.
if (daysBetween > 0)
{
var nextStartDay = start.AddDays(1).Date;
var dayBeforeEnd = end.AddDays(-1).Date;
for (DateTime d = nextStartDay; d <= dayBeforeEnd; d = d.AddDays(1))
{
if (nonWorkDays.Contains(d.DayOfWeek)) continue;
else if (holidays.Contains(d.Date)) continue;
businessDaysBetween++;
}
}
var dailyWorkSecondsToAdd = dailyWorkSeconds * businessDaysBetween;
var output = timeBeforeEndOfWorkDay + timeAfterStartOfWorkDay;
output = output + new TimeSpan(0, 0, dailyWorkSecondsToAdd);
return output;
}
}
Và đây là mã kiểm tra: Lưu ý rằng bạn chỉ cần đặt hàm này vào một lớp có tên là DateHelper để mã kiểm tra hoạt động.
[TestMethod]
public void TestGetBusinessTimespanBetween()
{
var workdayStart = new TimeSpan(8, 0, 0);
var workdayEnd = new TimeSpan(17, 0, 0);
var holidays = new List<DateTime>()
{
new DateTime(2018, 1, 15), // a Monday
new DateTime(2018, 2, 15) // a Thursday
};
var testdata = new[]
{
new
{
expectedMinutes = 0,
start = new DateTime(2016, 10, 19, 9, 50, 0),
end = new DateTime(2016, 10, 19, 9, 50, 0)
},
new
{
expectedMinutes = 10,
start = new DateTime(2016, 10, 19, 9, 50, 0),
end = new DateTime(2016, 10, 19, 10, 0, 0)
},
new
{
expectedMinutes = 5,
start = new DateTime(2016, 10, 19, 7, 50, 0),
end = new DateTime(2016, 10, 19, 8, 5, 0)
},
new
{
expectedMinutes = 5,
start = new DateTime(2016, 10, 19, 16, 55, 0),
end = new DateTime(2016, 10, 19, 17, 5, 0)
},
new
{
expectedMinutes = 15,
start = new DateTime(2016, 10, 19, 16, 50, 0),
end = new DateTime(2016, 10, 20, 8, 5, 0)
},
new
{
expectedMinutes = 10,
start = new DateTime(2016, 10, 19, 16, 50, 0),
end = new DateTime(2016, 10, 20, 7, 55, 0)
},
new
{
expectedMinutes = 5,
start = new DateTime(2016, 10, 19, 17, 10, 0),
end = new DateTime(2016, 10, 20, 8, 5, 0)
},
new
{
expectedMinutes = 0,
start = new DateTime(2016, 10, 19, 17, 10, 0),
end = new DateTime(2016, 10, 20, 7, 5, 0)
},
new
{
expectedMinutes = 545,
start = new DateTime(2016, 10, 19, 12, 10, 0),
end = new DateTime(2016, 10, 20, 12, 15, 0)
},
// Spanning multiple weekdays
new
{
expectedMinutes = 835,
start = new DateTime(2016, 10, 19, 12, 10, 0),
end = new DateTime(2016, 10, 21, 8, 5, 0)
},
// Spanning multiple weekdays
new
{
expectedMinutes = 1375,
start = new DateTime(2016, 10, 18, 12, 10, 0),
end = new DateTime(2016, 10, 21, 8, 5, 0)
},
// Spanning from a Thursday to a Tuesday, 5 mins short of complete day.
new
{
expectedMinutes = 1615,
start = new DateTime(2016, 10, 20, 12, 10, 0),
end = new DateTime(2016, 10, 25, 12, 5, 0)
},
// Spanning from a Thursday to a Tuesday, 5 mins beyond complete day.
new
{
expectedMinutes = 1625,
start = new DateTime(2016, 10, 20, 12, 10, 0),
end = new DateTime(2016, 10, 25, 12, 15, 0)
},
// Spanning from a Friday to a Monday, 5 mins beyond complete day.
new
{
expectedMinutes = 545,
start = new DateTime(2016, 10, 21, 12, 10, 0),
end = new DateTime(2016, 10, 24, 12, 15, 0)
},
// Spanning from a Friday to a Monday, 5 mins short complete day.
new
{
expectedMinutes = 535,
start = new DateTime(2016, 10, 21, 12, 10, 0),
end = new DateTime(2016, 10, 24, 12, 5, 0)
},
// Spanning from a Saturday to a Monday, 5 mins short complete day.
new
{
expectedMinutes = 245,
start = new DateTime(2016, 10, 22, 12, 10, 0),
end = new DateTime(2016, 10, 24, 12, 5, 0)
},
// Spanning from a Saturday to a Sunday, 5 mins beyond complete day.
new
{
expectedMinutes = 0,
start = new DateTime(2016, 10, 22, 12, 10, 0),
end = new DateTime(2016, 10, 23, 12, 15, 0)
},
// Times within the same Saturday.
new
{
expectedMinutes = 0,
start = new DateTime(2016, 10, 22, 12, 10, 0),
end = new DateTime(2016, 10, 23, 12, 15, 0)
},
// Spanning from a Saturday to the Sunday next week.
new
{
expectedMinutes = 2700,
start = new DateTime(2016, 10, 22, 12, 10, 0),
end = new DateTime(2016, 10, 30, 12, 15, 0)
},
// Spanning a year.
new
{
expectedMinutes = 143355,
start = new DateTime(2016, 10, 22, 12, 10, 0),
end = new DateTime(2017, 10, 30, 12, 15, 0)
},
// Spanning a year with 2 holidays.
new
{
expectedMinutes = 142815,
start = new DateTime(2017, 10, 22, 12, 10, 0),
end = new DateTime(2018, 10, 30, 12, 15, 0)
},
};
foreach (var item in testdata)
{
Assert.AreEqual(item.expectedMinutes,
DateHelper.GetBusinessTimespanBetween(
item.start, item.end,
workdayStart, workdayEnd,
holidays)
.TotalMinutes);
}
}
Giải pháp này tránh lặp lại, hoạt động cho cả sự khác biệt giữa + ve và -ve ngày trong tuần và bao gồm một bộ thử nghiệm đơn vị để hồi quy so với phương pháp đếm ngày trong tuần chậm hơn. Tôi cũng bao gồm một phương pháp ngắn gọn để thêm các ngày trong tuần cũng hoạt động theo cùng một cách không lặp lại.
Kiểm tra đơn vị bao gồm một vài nghìn kết hợp ngày để kiểm tra toàn diện tất cả các kết hợp ngày bắt đầu / kết thúc trong tuần với cả phạm vi ngày nhỏ và lớn.
Quan trọng : Chúng tôi đưa ra giả định rằng chúng tôi đang đếm ngày bằng cách loại trừ ngày bắt đầu và bao gồm cả ngày kết thúc. Điều này quan trọng khi tính các ngày trong tuần vì ngày bắt đầu / ngày kết thúc cụ thể mà bạn bao gồm / loại trừ sẽ ảnh hưởng đến kết quả. Điều này cũng đảm bảo rằng chênh lệch giữa hai ngày bằng nhau luôn bằng 0 và chúng tôi chỉ bao gồm toàn bộ ngày làm việc vì thông thường bạn muốn câu trả lời là chính xác cho bất kỳ thời điểm nào vào ngày bắt đầu hiện tại (thường là hôm nay) và bao gồm ngày kết thúc đầy đủ (ví dụ: ngày đáo hạn).
LƯU Ý: Mã này cần một điều chỉnh bổ sung cho các ngày lễ nhưng để phù hợp với giả định ở trên, mã này phải loại trừ các ngày nghỉ vào ngày bắt đầu.
Thêm các ngày trong tuần:
private static readonly int[,] _addOffset =
{
// 0 1 2 3 4
{0, 1, 2, 3, 4}, // Su 0
{0, 1, 2, 3, 4}, // M 1
{0, 1, 2, 3, 6}, // Tu 2
{0, 1, 4, 5, 6}, // W 3
{0, 1, 4, 5, 6}, // Th 4
{0, 3, 4, 5, 6}, // F 5
{0, 2, 3, 4, 5}, // Sa 6
};
public static DateTime AddWeekdays(this DateTime date, int weekdays)
{
int extraDays = weekdays % 5;
int addDays = weekdays >= 0
? (weekdays / 5) * 7 + _addOffset[(int)date.DayOfWeek, extraDays]
: (weekdays / 5) * 7 - _addOffset[6 - (int)date.DayOfWeek, -extraDays];
return date.AddDays(addDays);
}
Tính chênh lệch các ngày trong tuần:
static readonly int[,] _diffOffset =
{
// Su M Tu W Th F Sa
{0, 1, 2, 3, 4, 5, 5}, // Su
{4, 0, 1, 2, 3, 4, 4}, // M
{3, 4, 0, 1, 2, 3, 3}, // Tu
{2, 3, 4, 0, 1, 2, 2}, // W
{1, 2, 3, 4, 0, 1, 1}, // Th
{0, 1, 2, 3, 4, 0, 0}, // F
{0, 1, 2, 3, 4, 5, 0}, // Sa
};
public static int GetWeekdaysDiff(this DateTime dtStart, DateTime dtEnd)
{
int daysDiff = (int)(dtEnd - dtStart).TotalDays;
return daysDiff >= 0
? 5 * (daysDiff / 7) + _diffOffset[(int) dtStart.DayOfWeek, (int) dtEnd.DayOfWeek]
: 5 * (daysDiff / 7) - _diffOffset[6 - (int) dtStart.DayOfWeek, 6 - (int) dtEnd.DayOfWeek];
}
Tôi thấy rằng hầu hết các giải pháp khác về tràn ngăn xếp đều chậm (lặp đi lặp lại) hoặc quá phức tạp và nhiều giải pháp hoàn toàn không chính xác. Đạo đức của câu chuyện là ... Đừng tin tưởng nó trừ khi bạn đã kiểm tra kỹ lưỡng nó !!
Các bài kiểm tra đơn vị dựa trên thử nghiệm NUnit Combinatorial và phần mở rộng ShouldBe NUnit.
[TestFixture]
public class DateTimeExtensionsTests
{
/// <summary>
/// Exclude start date, Include end date
/// </summary>
/// <param name="dtStart"></param>
/// <param name="dtEnd"></param>
/// <returns></returns>
private IEnumerable<DateTime> GetDateRange(DateTime dtStart, DateTime dtEnd)
{
Console.WriteLine(@"dtStart={0:yy-MMM-dd ddd}, dtEnd={1:yy-MMM-dd ddd}", dtStart, dtEnd);
TimeSpan diff = dtEnd - dtStart;
Console.WriteLine(diff);
if (dtStart <= dtEnd)
{
for (DateTime dt = dtStart.AddDays(1); dt <= dtEnd; dt = dt.AddDays(1))
{
Console.WriteLine(@"dt={0:yy-MMM-dd ddd}", dt);
yield return dt;
}
}
else
{
for (DateTime dt = dtStart.AddDays(-1); dt >= dtEnd; dt = dt.AddDays(-1))
{
Console.WriteLine(@"dt={0:yy-MMM-dd ddd}", dt);
yield return dt;
}
}
}
[Test, Combinatorial]
public void TestGetWeekdaysDiff(
[Values(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 20, 30)]
int startDay,
[Values(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 20, 30)]
int endDay,
[Values(7)]
int startMonth,
[Values(7)]
int endMonth)
{
// Arrange
DateTime dtStart = new DateTime(2016, startMonth, startDay);
DateTime dtEnd = new DateTime(2016, endMonth, endDay);
int nDays = GetDateRange(dtStart, dtEnd)
.Count(dt => dt.DayOfWeek != DayOfWeek.Saturday && dt.DayOfWeek != DayOfWeek.Sunday);
if (dtEnd < dtStart) nDays = -nDays;
Console.WriteLine(@"countBusDays={0}", nDays);
// Act / Assert
dtStart.GetWeekdaysDiff(dtEnd).ShouldBe(nDays);
}
[Test, Combinatorial]
public void TestAddWeekdays(
[Values(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 20, 30)]
int startDay,
[Values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 20, 30)]
int weekdays)
{
DateTime dtStart = new DateTime(2016, 7, startDay);
DateTime dtEnd1 = dtStart.AddWeekdays(weekdays); // ADD
dtStart.GetWeekdaysDiff(dtEnd1).ShouldBe(weekdays);
DateTime dtEnd2 = dtStart.AddWeekdays(-weekdays); // SUBTRACT
dtStart.GetWeekdaysDiff(dtEnd2).ShouldBe(-weekdays);
}
}
Dưới đây là một số mã cho mục đích đó, với các ngày lễ của Thụy Điển nhưng bạn có thể điều chỉnh những ngày lễ nào cần tính. Lưu ý rằng tôi đã thêm một giới hạn mà bạn có thể muốn xóa, nhưng đó là đối với hệ thống dựa trên web và tôi không muốn bất kỳ ai nhập một ngày lớn nào đó để làm hỏng quá trình
public static int GetWorkdays(DateTime from ,DateTime to)
{
int limit = 9999;
int counter = 0;
DateTime current = from;
int result = 0;
if (from > to)
{
DateTime temp = from;
from = to;
to = temp;
}
if (from >= to)
{
return 0;
}
while (current <= to && counter < limit)
{
if (IsSwedishWorkday(current))
{
result++;
}
current = current.AddDays(1);
counter++;
}
return result;
}
public static bool IsSwedishWorkday(DateTime date)
{
return (!IsSwedishHoliday(date) && date.DayOfWeek != DayOfWeek.Saturday && date.DayOfWeek != DayOfWeek.Sunday);
}
public static bool IsSwedishHoliday(DateTime date)
{
return (
IsSameDay(GetEpiphanyDay(date.Year), date) ||
IsSameDay(GetMayDay(date.Year), date) ||
IsSameDay(GetSwedishNationalDay(date.Year), date) ||
IsSameDay(GetChristmasDay(date.Year), date) ||
IsSameDay(GetBoxingDay(date.Year), date) ||
IsSameDay(GetGoodFriday(date.Year), date) ||
IsSameDay(GetAscensionDay(date.Year), date) ||
IsSameDay(GetAllSaintsDay(date.Year), date) ||
IsSameDay(GetMidsummersDay(date.Year), date) ||
IsSameDay(GetPentecostDay(date.Year), date) ||
IsSameDay(GetEasterMonday(date.Year), date) ||
IsSameDay(GetNewYearsDay(date.Year), date) ||
IsSameDay(GetEasterDay(date.Year), date)
);
}
// Trettondagen
public static DateTime GetEpiphanyDay(int year)
{
return new DateTime(year, 1, 6);
}
// Första maj
public static DateTime GetMayDay(int year)
{
return new DateTime(year,5,1);
}
// Juldagen
public static DateTime GetSwedishNationalDay(int year)
{
return new DateTime(year, 6, 6);
}
// Juldagen
public static DateTime GetNewYearsDay(int year)
{
return new DateTime(year,1,1);
}
// Juldagen
public static DateTime GetChristmasDay(int year)
{
return new DateTime(year,12,25);
}
// Annandag jul
public static DateTime GetBoxingDay(int year)
{
return new DateTime(year, 12, 26);
}
// Långfredagen
public static DateTime GetGoodFriday(int year)
{
return GetEasterDay(year).AddDays(-3);
}
// Kristi himmelsfärdsdag
public static DateTime GetAscensionDay(int year)
{
return GetEasterDay(year).AddDays(5*7+4);
}
// Midsommar
public static DateTime GetAllSaintsDay(int year)
{
DateTime result = new DateTime(year,10,31);
while (result.DayOfWeek != DayOfWeek.Saturday)
{
result = result.AddDays(1);
}
return result;
}
// Midsommar
public static DateTime GetMidsummersDay(int year)
{
DateTime result = new DateTime(year, 6, 20);
while (result.DayOfWeek != DayOfWeek.Saturday)
{
result = result.AddDays(1);
}
return result;
}
// Pingstdagen
public static DateTime GetPentecostDay(int year)
{
return GetEasterDay(year).AddDays(7 * 7);
}
// Annandag påsk
public static DateTime GetEasterMonday(int year)
{
return GetEasterDay(year).AddDays(1);
}
public static DateTime GetEasterDay(int y)
{
double c;
double n;
double k;
double i;
double j;
double l;
double m;
double d;
c = System.Math.Floor(y / 100.0);
n = y - 19 * System.Math.Floor(y / 19.0);
k = System.Math.Floor((c - 17) / 25.0);
i = c - System.Math.Floor(c / 4) - System.Math.Floor((c - k) / 3) + 19 * n + 15;
i = i - 30 * System.Math.Floor(i / 30);
i = i - System.Math.Floor(i / 28) * (1 - System.Math.Floor(i / 28) * System.Math.Floor(29 / (i + 1)) * System.Math.Floor((21 - n) / 11));
j = y + System.Math.Floor(y / 4.0) + i + 2 - c + System.Math.Floor(c / 4);
j = j - 7 * System.Math.Floor(j / 7);
l = i - j;
m = 3 + System.Math.Floor((l + 40) / 44);// month
d = l + 28 - 31 * System.Math.Floor(m / 4);// day
double days = ((m == 3) ? d : d + 31);
DateTime result = new DateTime(y, 3, 1).AddDays(days-1);
return result;
}
Đây là mã mẫu nhanh. Đó là một phương thức lớp, vì vậy sẽ chỉ hoạt động bên trong lớp của bạn. Nếu bạn muốn static, hãy thay đổi chữ ký thành private static(hoặc public static).
private IEnumerable<DateTime> GetWorkingDays(DateTime sd, DateTime ed)
{
for (var d = sd; d <= ed; d = d.AddDays(1))
if (d.DayOfWeek != DayOfWeek.Saturday && d.DayOfWeek != DayOfWeek.Sunday)
yield return d;
}
Phương thức này tạo một biến vòng lặp d, khởi tạo nó vào ngày bắt đầu sd, sau đó tăng lên một ngày mỗi lần lặp ( d = d.AddDays(1)).
Nó trả về các giá trị mong muốn bằng cách sử dụng yield, tạo ra một iterator. Điều thú vị về trình vòng lặp là chúng không giữ tất cả các giá trị của IEnumerablebộ nhớ, chỉ gọi từng giá trị một cách tuần tự. Điều này có nghĩa là bạn có thể gọi phương thức này từ bình minh đến nay mà không phải lo lắng về việc hết bộ nhớ.
Tôi đã tìm kiếm rất nhiều thuật toán, dễ hiểu, để tính toán ngày làm việc giữa 2 ngày và cũng để loại trừ các ngày lễ quốc gia, và cuối cùng tôi quyết định sử dụng phương pháp này:
public static int NumberOfWorkingDaysBetween2Dates(DateTime start,DateTime due,IEnumerable<DateTime> holidays)
{
var dic = new Dictionary<DateTime, DayOfWeek>();
var totalDays = (due - start).Days;
for (int i = 0; i < totalDays + 1; i++)
{
if (!holidays.Any(x => x == start.AddDays(i)))
dic.Add(start.AddDays(i), start.AddDays(i).DayOfWeek);
}
return dic.Where(x => x.Value != DayOfWeek.Saturday && x.Value != DayOfWeek.Sunday).Count();
}
Về cơ bản, tôi muốn xem từng ngày và đánh giá các điều kiện của mình:
nhưng tôi cũng muốn tránh lặp lại ngày tháng.
Bằng cách chạy và đo thời gian cần thiết để đánh giá 1 năm đầy đủ, tôi đưa ra kết quả sau:
static void Main(string[] args)
{
var start = new DateTime(2017, 1, 1);
var due = new DateTime(2017, 12, 31);
var sw = Stopwatch.StartNew();
var days = NumberOfWorkingDaysBetween2Dates(start, due,NationalHolidays());
sw.Stop();
Console.WriteLine($"Total working days = {days} --- time: {sw.Elapsed}");
Console.ReadLine();
// result is:
// Total working days = 249-- - time: 00:00:00.0269087
}
Chỉnh sửa: một phương pháp mới đơn giản hơn:
public static int ToBusinessWorkingDays(this DateTime start, DateTime due, DateTime[] holidays)
{
return Enumerable.Range(0, (due - start).Days)
.Select(a => start.AddDays(a))
.Where(a => a.DayOfWeek != DayOfWeek.Sunday)
.Where(a => a.DayOfWeek != DayOfWeek.Saturday)
.Count(a => !holidays.Any(x => x == a));
}
Tôi nghĩ rằng không có câu trả lời nào ở trên thực sự đúng. Không ai trong số họ giải quyết tất cả các trường hợp đặc biệt như khi ngày bắt đầu và kết thúc vào giữa cuối tuần, khi ngày bắt đầu vào thứ sáu và kết thúc vào thứ hai tiếp theo, v.v. Trên hết, tất cả chúng đều làm tròn các phép tính thành toàn bộ ngày, vì vậy, nếu ngày bắt đầu ở giữa một ngày thứ bảy, chẳng hạn, nó sẽ trừ nguyên một ngày so với ngày làm việc, đưa ra kết quả sai ...
Dù sao, đây là giải pháp của tôi khá hiệu quả và đơn giản và hoạt động cho mọi trường hợp. Bí quyết chỉ là tìm thứ Hai trước đó cho ngày bắt đầu và ngày kết thúc, sau đó thực hiện một khoản bù nhỏ khi bắt đầu và kết thúc xảy ra vào cuối tuần:
public double WorkDays(DateTime startDate, DateTime endDate){
double weekendDays;
double days = endDate.Subtract(startDate).TotalDays;
if(days<0) return 0;
DateTime startMonday = startDate.AddDays(DayOfWeek.Monday - startDate.DayOfWeek).Date;
DateTime endMonday = endDate.AddDays(DayOfWeek.Monday - endDate.DayOfWeek).Date;
weekendDays = ((endMonday.Subtract(startMonday).TotalDays) / 7) * 2;
// compute fractionary part of weekend days
double diffStart = startDate.Subtract(startMonday).TotalDays - 5;
double diffEnd = endDate.Subtract(endMonday).TotalDays - 5;
// compensate weekenddays
if(diffStart>0) weekendDays -= diffStart;
if(diffEnd>0) weekendDays += diffEnd;
return days - weekendDays;
}
using System;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
DateTime start = new DateTime(2014, 1, 1);
DateTime stop = new DateTime(2014, 12, 31);
int totalWorkingDays = GetNumberOfWorkingDays(start, stop);
Console.WriteLine("There are {0} working days.", totalWorkingDays);
}
private static int GetNumberOfWorkingDays(DateTime start, DateTime stop)
{
TimeSpan interval = stop - start;
int totalWeek = interval.Days / 7;
int totalWorkingDays = 5 * totalWeek;
int remainingDays = interval.Days % 7;
for (int i = 0; i <= remainingDays; i++)
{
DayOfWeek test = (DayOfWeek)(((int)start.DayOfWeek + i) % 7);
if (test >= DayOfWeek.Monday && test <= DayOfWeek.Friday)
totalWorkingDays++;
}
return totalWorkingDays;
}
}
}
Phương pháp này không sử dụng bất kỳ vòng lặp nào và thực sự khá đơn giản. Nó mở rộng phạm vi ngày thành cả tuần vì chúng tôi biết rằng mỗi tuần có 5 ngày làm việc. Sau đó, nó sử dụng một bảng tra cứu để tìm số ngày làm việc cần trừ từ đầu và cuối để có kết quả phù hợp. Tôi đã mở rộng phép tính để giúp hiển thị những gì đang xảy ra, nhưng toàn bộ điều có thể được cô đọng thành một dòng nếu cần.
Dù sao, điều này cũng phù hợp với tôi và vì vậy tôi nghĩ tôi sẽ đăng nó ở đây để phòng trường hợp nó có thể giúp ích cho người khác. Chúc bạn viết mã vui vẻ.
Phép tính
Văn hóa
Mã giả định một tuần làm việc từ thứ Hai đến thứ Sáu. Đối với các nền văn hóa khác, chẳng hạn như Chủ Nhật đến Thứ Năm, bạn sẽ cần phải bù các ngày trước khi tính toán.
phương pháp
public int Weekdays(DateTime min, DateTime max)
{
if (min.Date > max.Date) throw new Exception("Invalid date span");
var t = (max.AddDays(1).Date - min.Date).TotalDays;
var a = (int) min.DayOfWeek;
var b = 6 - (int) max.DayOfWeek;
var k = 1.4;
var m = new int[]{0, 0, 1, 2, 3, 4, 5};
var c = m[a] + m[b];
return (int)((t + a + b) / k) - c;
}
Tôi sẽ chỉ chia sẻ giải pháp của tôi. Nó làm việc cho tôi, có lẽ tôi chỉ không nhận thấy / biết rằng có một lỗi. Tôi bắt đầu bằng cách nhận được tuần đầu tiên không hoàn thành nếu có. một tuần hoàn chỉnh là từ chủ nhật cho thứ bảy, vì vậy nếu (int) _now.DayOfWeek không phải là 0 (Chủ nhật), thì tuần đầu tiên không đầy đủ.
Tôi chỉ trừ 1 đến số tuần đầu tiên cho ngày thứ bảy của tuần đầu tiên, sau đó cộng nó vào số mới;
Sau đó, tôi nhận được tuần chưa hoàn thành cuối cùng, sau đó trừ đi 1 cho ngày chủ nhật rồi cộng vào số mới.
Sau đó, cuối cùng, số tuần hoàn chỉnh nhân với 5 (ngày trong tuần) đã được thêm vào số đếm mới.
public int RemoveNonWorkingDays(int numberOfDays){
int workingDays = 0;
int firstWeek = 7 - (int)_now.DayOfWeek;
if(firstWeek < 7){
if(firstWeek > numberOfDays)
return numberOfDays;
workingDays += firstWeek-1;
numberOfDays -= firstWeek;
}
int lastWeek = numberOfDays % 7;
if(lastWeek > 0){
numberOfDays -= lastWeek;
workingDays += lastWeek - 1;
}
workingDays += (numberOfDays/7)*5;
return workingDays;
}
Tôi đã gặp sự cố khi tìm phiên bản TSQL vững chắc của mã này. Dưới đây về cơ bản là chuyển đổi mã C # ở đây với việc bổ sung bảng Ngày lễ sẽ được sử dụng để tính toán trước ngày nghỉ.
CREATE TABLE dbo.Holiday
(
HolidayDt DATE NOT NULL,
Name NVARCHAR(50) NOT NULL,
IsWeekday BIT NOT NULL,
CONSTRAINT PK_Holiday PRIMARY KEY (HolidayDt)
)
GO
CREATE INDEX IDX_Holiday ON Holiday (HolidayDt, IsWeekday)
GO
CREATE function dbo.GetBusinessDays
(
@FirstDay datetime,
@LastDay datetime
)
RETURNS INT
AS
BEGIN
DECLARE @BusinessDays INT, @FullWeekCount INT
SELECT @FirstDay = CONVERT(DATETIME,CONVERT(DATE,@FirstDay))
, @LastDay = CONVERT(DATETIME,CONVERT(DATE,@LastDay))
IF @FirstDay > @LastDay
RETURN NULL;
SELECT @BusinessDays = DATEDIFF(DAY, @FirstDay, @LastDay) + 1
SELECT @FullWeekCount = @BusinessDays / 7;
-- find out if there are weekends during the time exceedng the full weeks
IF @BusinessDays > (@FullWeekCount * 7)
BEGIN
-- we are here to find out if there is a 1-day or 2-days weekend
-- in the time interval remaining after subtracting the complete weeks
DECLARE @firstDayOfWeek INT, @lastDayOfWeek INT;
SELECT @firstDayOfWeek = DATEPART(DW, @FirstDay), @lastDayOfWeek = DATEPART(DW, @LastDay);
IF @lastDayOfWeek < @firstDayOfWeek
SELECT @lastDayOfWeek = @lastDayOfWeek + 7;
IF @firstDayOfWeek <= 6
BEGIN
IF (@lastDayOfWeek >= 7) --Both Saturday and Sunday are in the remaining time interval
BEGIN
SELECT @BusinessDays = @BusinessDays - 2
END
ELSE IF @lastDayOfWeek>=6 --Only Saturday is in the remaining time interval
BEGIN
SELECT @BusinessDays = @BusinessDays - 1
END
END
ELSE IF @firstDayOfWeek <= 7 AND @lastDayOfWeek >=7 -- Only Sunday is in the remaining time interval
BEGIN
SELECT @BusinessDays = @BusinessDays - 1
END
END
-- subtract the weekends during the full weeks in the interval
DECLARE @Holidays INT;
SELECT @Holidays = COUNT(*)
FROM Holiday
WHERE HolidayDt BETWEEN @FirstDay AND @LastDay
AND IsWeekday = CAST(1 AS BIT)
SELECT @BusinessDays = @BusinessDays - (@FullWeekCount + @FullWeekCount) -- - @Holidays
RETURN @BusinessDays
END
int BusinessDayDifference(DateTime Date1, DateTime Date2)
{
int Sign = 1;
if (Date2 > Date1)
{
Sign = -1;
DateTime TempDate = Date1;
Date1 = Date2;
Date2 = TempDate;
}
int BusDayDiff = (int)(Date1.Date - Date2.Date).TotalDays;
if (Date1.DayOfWeek == DayOfWeek.Saturday)
BusDayDiff -= 1;
if (Date2.DayOfWeek == DayOfWeek.Sunday)
BusDayDiff -= 1;
int Week1 = GetWeekNum(Date1);
int Week2 = GetWeekNum(Date2);
int WeekDiff = Week1 - Week2;
BusDayDiff -= WeekDiff * 2;
foreach (DateTime Holiday in Holidays)
if (Date1 >= Holiday && Date2 <= Holiday)
BusDayDiff--;
BusDayDiff *= Sign;
return BusDayDiff;
}
private int GetWeekNum(DateTime Date)
{
return (int)(Date.AddDays(-(int)Date.DayOfWeek).Ticks / TimeSpan.TicksPerDay / 7);
}
Đây là một giải pháp rất đơn giản cho vấn đề này. Chúng tôi có ngày bắt đầu, ngày kết thúc và "vòng lặp for" để ghi ngày tháng và tính toán xem đó là ngày làm việc hay ngày cuối tuần bằng cách chuyển đổi thành chuỗi DayOfWeek.
class Program
{
static void Main(string[] args)
{
DateTime day = new DateTime();
Console.Write("Inser your end date (example: 01/30/2015): ");
DateTime endDate = DateTime.Parse(Console.ReadLine());
int numberOfDays = 0;
for (day = DateTime.Now.Date; day.Date < endDate.Date; day = day.Date.AddDays(1))
{
string dayToString = Convert.ToString(day.DayOfWeek);
if (dayToString != "Saturday" && dayToString != "Sunday") numberOfDays++;
}
Console.WriteLine("Number of working days (not including local holidays) between two dates is "+numberOfDays);
}
}
Dựa trên nhận xét được đánh dấu là câu trả lời và bản vá được khuyến nghị, cũng như -> Phiên bản này muốn chuyển đổi Ngày thành Giờ làm việc ... Cân nhắc Cũng như giờ trong ngày.
/// <summary>
/// Calculates number of business days, taking into account:
/// - weekends (Saturdays and Sundays)
/// - bank holidays in the middle of the week
/// </summary>
/// <param name="firstDay">First day in the time interval</param>
/// <param name="lastDay">Last day in the time interval</param>
/// <param name="bankHolidays">List of bank holidays excluding weekends</param>
/// <returns>Number of business hours during the 'span'</returns>
public static int BusinessHoursUntil(DateTime firstDay, DateTime lastDay, params DateTime[] bankHolidays)
{
var original_firstDay = firstDay;
var original_lastDay = lastDay;
firstDay = firstDay.Date;
lastDay = lastDay.Date;
if (firstDay > lastDay)
return -1; //// throw new ArgumentException("Incorrect last day " + lastDay);
TimeSpan span = lastDay - firstDay;
int businessDays = span.Days + 1;
int fullWeekCount = businessDays / 7;
// find out if there are weekends during the time exceedng the full weeks
if (businessDays > fullWeekCount * 7)
{
// we are here to find out if there is a 1-day or 2-days weekend
// in the time interval remaining after subtracting the complete weeks
int firstDayOfWeek = firstDay.DayOfWeek == DayOfWeek.Sunday ? 7 : (int)firstDay.DayOfWeek;
int lastDayOfWeek = lastDay.DayOfWeek == DayOfWeek.Sunday ? 7 : (int)lastDay.DayOfWeek;
if (lastDayOfWeek < firstDayOfWeek)
lastDayOfWeek += 7;
if (firstDayOfWeek <= 6)
{
if (lastDayOfWeek >= 7)// Both Saturday and Sunday are in the remaining time interval
businessDays -= 2;
else if (lastDayOfWeek >= 6)// Only Saturday is in the remaining time interval
businessDays -= 1;
}
else if (firstDayOfWeek <= 7 && lastDayOfWeek >= 7)// Only Sunday is in the remaining time interval
businessDays -= 1;
}
// subtract the weekends during the full weeks in the interval
businessDays -= fullWeekCount + fullWeekCount;
if (bankHolidays != null && bankHolidays.Any())
{
// subtract the number of bank holidays during the time interval
foreach (DateTime bankHoliday in bankHolidays)
{
DateTime bh = bankHoliday.Date;
if (firstDay <= bh && bh <= lastDay)
--businessDays;
}
}
int total_business_hours = 0;
if (firstDay.Date == lastDay.Date)
{//If on the same day, go granular with Hours from the Orginial_*Day values
total_business_hours = (int)(original_lastDay - original_firstDay).TotalHours;
}
else
{//Convert Business-Days to TotalHours
total_business_hours = (int)(firstDay.AddDays(businessDays).AddHours(firstDay.Hour) - firstDay).TotalHours;
}
return total_business_hours;
}
Tôi vừa cải thiện câu trả lời @Alexander và @Slauma để hỗ trợ một tuần làm việc dưới dạng tham số, đối với các trường hợp thứ bảy là ngày làm việc hoặc thậm chí các trường hợp chỉ có một vài ngày trong tuần được coi là ngày làm việc:
/// <summary>
/// Calculate the number of business days between two dates, considering:
/// - Days of the week that are not considered business days.
/// - Holidays between these two dates.
/// </summary>
/// <param name="fDay">First day of the desired 'span'.</param>
/// <param name="lDay">Last day of the desired 'span'.</param>
/// <param name="BusinessDaysOfWeek">Days of the week that are considered to be business days, if NULL considers monday, tuesday, wednesday, thursday and friday as business days of the week.</param>
/// <param name="Holidays">Holidays, if NULL, considers no holiday.</param>
/// <returns>Number of business days during the 'span'</returns>
public static int BusinessDaysUntil(this DateTime fDay, DateTime lDay, DayOfWeek[] BusinessDaysOfWeek = null, DateTime[] Holidays = null)
{
if (BusinessDaysOfWeek == null)
BusinessDaysOfWeek = new DayOfWeek[] { DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday };
if (Holidays == null)
Holidays = new DateTime[] { };
fDay = fDay.Date;
lDay = lDay.Date;
if (fDay > lDay)
throw new ArgumentException("Incorrect last day " + lDay);
int bDays = (lDay - fDay).Days + 1;
int fullWeekCount = bDays / 7;
int fullWeekCountMult = 7 - WeekDays.Length;
// Find out if there are weekends during the time exceedng the full weeks
if (bDays > (fullWeekCount * 7))
{
int fDayOfWeek = (int)fDay.DayOfWeek;
int lDayOfWeek = (int)lDay.DayOfWeek;
if (fDayOfWeek > lDayOfWeek)
lDayOfWeek += 7;
// If they are the same, we already covered it right before the Holiday subtraction
if (lDayOfWeek != fDayOfWeek)
{
// Here we need to see if any of the days between are considered business days
for (int i = fDayOfWeek; i <= lDayOfWeek; i++)
if (!WeekDays.Contains((DayOfWeek)(i > 6 ? i - 7 : i)))
bDays -= 1;
}
}
// Subtract the days that are not in WeekDays[] during the full weeks in the interval
bDays -= (fullWeekCount * fullWeekCountMult);
// Subtract the number of bank holidays during the time interval
bDays = bDays - Holidays.Select(x => x.Date).Count(x => fDay <= x && x <= lDay);
return bDays;
}
Đây là hàm mà chúng ta có thể sử dụng để tính ngày làm việc giữa hai ngày. Tôi không sử dụng danh sách ngày lễ vì nó có thể khác nhau giữa các quốc gia / khu vực.
Nếu chúng ta vẫn muốn sử dụng nó, chúng ta có thể lấy đối số thứ ba là danh sách ngày nghỉ và trước khi tăng số lượng, chúng ta nên kiểm tra xem danh sách đó có chứa d
public static int GetBussinessDaysBetweenTwoDates(DateTime StartDate, DateTime EndDate)
{
if (StartDate > EndDate)
return -1;
int bd = 0;
for (DateTime d = StartDate; d < EndDate; d = d.AddDays(1))
{
if (d.DayOfWeek != DayOfWeek.Saturday && d.DayOfWeek != DayOfWeek.Sunday)
bd++;
}
return bd;
}
Tôi tin rằng đây có thể là một cách đơn giản hơn:
public int BusinessDaysUntil(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;
}
Đây là một ý tưởng khác - phương pháp này cho phép chỉ định bất kỳ tuần làm việc và ngày nghỉ nào.
Ý tưởng ở đây là chúng tôi tìm ra cốt lõi của phạm vi ngày từ ngày làm việc đầu tiên trong tuần đến ngày cuối tuần cuối cùng trong tuần. Điều này cho phép chúng tôi tính toán cả tuần một cách dễ dàng ( mà không cần lặp lại tất cả các ngày). Tất cả những gì chúng ta cần làm sau đó là thêm ngày làm việc trước ngày bắt đầu và kết thúc của phạm vi cốt lõi này.
public static int CalculateWorkingDays(
DateTime startDate,
DateTime endDate,
IList<DateTime> holidays,
DayOfWeek firstDayOfWeek,
DayOfWeek lastDayOfWeek)
{
// Make sure the defined working days run contiguously
if (lastDayOfWeek < firstDayOfWeek)
{
throw new Exception("Last day of week cannot fall before first day of week!");
}
// Create a list of the days of the week that make-up the weekend by working back
// from the firstDayOfWeek and forward from lastDayOfWeek to get the start and end
// the weekend
var weekendStart = lastDayOfWeek == DayOfWeek.Saturday ? DayOfWeek.Sunday : lastDayOfWeek + 1;
var weekendEnd = firstDayOfWeek == DayOfWeek.Sunday ? DayOfWeek.Saturday : firstDayOfWeek - 1;
var weekendDays = new List<DayOfWeek>();
var w = weekendStart;
do {
weekendDays.Add(w);
if (w == weekendEnd) break;
w = (w == DayOfWeek.Saturday) ? DayOfWeek.Sunday : w + 1;
} while (true);
// Force simple dates - no time
startDate = startDate.Date;
endDate = endDate.Date;
// Ensure a progessive date range
if (endDate < startDate)
{
var t = startDate;
startDate = endDate;
endDate = t;
}
// setup some working variables and constants
const int daysInWeek = 7; // yeah - really!
var actualStartDate = startDate; // this will end up on startOfWeek boundary
var actualEndDate = endDate; // this will end up on weekendEnd boundary
int workingDaysInWeek = daysInWeek - weekendDays.Count;
int workingDays = 0; // the result we are trying to find
int leadingDays = 0; // the number of working days leading up to the firstDayOfWeek boundary
int trailingDays = 0; // the number of working days counting back to the weekendEnd boundary
// Calculate leading working days
// if we aren't on the firstDayOfWeek we need to step forward to the nearest
if (startDate.DayOfWeek != firstDayOfWeek)
{
var d = startDate;
do {
if (d.DayOfWeek == firstDayOfWeek || d >= endDate)
{
actualStartDate = d;
break;
}
if (!weekendDays.Contains(d.DayOfWeek))
{
leadingDays++;
}
d = d.AddDays(1);
} while(true);
}
// Calculate trailing working days
// if we aren't on the weekendEnd we step back to the nearest
if (endDate >= actualStartDate && endDate.DayOfWeek != weekendEnd)
{
var d = endDate;
do {
if (d.DayOfWeek == weekendEnd || d < actualStartDate)
{
actualEndDate = d;
break;
}
if (!weekendDays.Contains(d.DayOfWeek))
{
trailingDays++;
}
d = d.AddDays(-1);
} while(true);
}
// Calculate the inclusive number of days between the actualStartDate and the actualEndDate
var coreDays = (actualEndDate - actualStartDate).Days + 1;
var noWeeks = coreDays / daysInWeek;
// add together leading, core and trailing days
workingDays += noWeeks * workingDaysInWeek;
workingDays += leadingDays;
workingDays += trailingDays;
// Finally remove any holidays that fall within the range.
if (holidays != null)
{
workingDays -= holidays.Count(h => h >= startDate && (h <= endDate));
}
return workingDays;
}
Vì tôi không thể bình luận. Có một vấn đề nữa với giải pháp được chấp nhận là các ngày nghỉ của ngân hàng được trừ đi ngay cả khi chúng diễn ra vào cuối tuần. Xem cách đầu vào khác được kiểm tra, nó chỉ phù hợp rằng điều này là tốt.
Do đó, bước trước nên là:
// subtract the number of bank holidays during the time interval
foreach (DateTime bankHoliday in bankHolidays)
{
DateTime bh = bankHoliday.Date;
// Do not subtract bank holidays when they fall in the weekend to avoid double subtraction
if (bh.DayOfWeek == DayOfWeek.Saturday || bh.DayOfWeek == DayOfWeek.Sunday)
continue;
if (firstDay <= bh && bh <= lastDay)
--businessDays;
}
Đây là một cách tiếp cận nếu bạn đang sử dụng MVC. Tôi cũng đã tính toán các ngày lễ quốc gia hoặc bất kỳ ngày lễ hội nào cần loại trừ bằng cách tìm nạp nó từ các lịch ngày lễ mà bạn sẽ cần tạo một ngày lễ.
foreach (DateTime day in EachDay(model))
{
bool key = false;
foreach (LeaveModel ln in holidaycalendar)
{
if (day.Date == ln.Date && day.DayOfWeek != DayOfWeek.Saturday && day.DayOfWeek != DayOfWeek.Sunday)
{
key = true; break;
}
}
if (day.DayOfWeek == DayOfWeek.Saturday || day.DayOfWeek == DayOfWeek.Sunday)
{
key = true;
}
if (key != true)
{
leavecount++;
}
}
Mô hình để lại là một danh sách ở đây
Đây là một chức năng trợ giúp tôi đã viết cho nhiệm vụ đó.
nó cũng trả về số ngày cuối tuần thông qua outtham số.
nếu muốn, bạn có thể tùy chỉnh thời gian chạy các ngày "cuối tuần" cho các quốc gia sử dụng các ngày cuối tuần khác nhau hoặc bao gồm các ngày lễ thì weekendDays[]thông số tùy chọn:
public static int GetNetworkDays(DateTime startDate, DateTime endDate,out int totalWeekenDays, DayOfWeek[] weekendDays = null)
{
if (startDate >= endDate)
{
throw new Exception("start date can not be greater then or equel to end date");
}
DayOfWeek[] weekends = new DayOfWeek[] { DayOfWeek.Sunday, DayOfWeek.Saturday };
if (weekendDays != null)
{
weekends = weekendDays;
}
var totaldays = (endDate - startDate).TotalDays + 1; // add one to include the first day to
var counter = 0;
var workdaysCounter = 0;
var weekendsCounter = 0;
for (int i = 0; i < totaldays; i++)
{
if (weekends.Contains(startDate.AddDays(counter).DayOfWeek))
{
weekendsCounter++;
}
else
{
workdaysCounter++;
}
counter++;
}
totalWeekenDays = weekendsCounter;
return workdaysCounter;
}
Tôi đã đưa ra giải pháp sau
var dateStart = new DateTime(2019,01,10);
var dateEnd = new DateTime(2019,01,31);
var timeBetween = (dateEnd - dateStart).TotalDays + 1;
int numberOf7DayWeeks = (int)(timeBetween / 7);
int numberOfWeekendDays = numberOf7DayWeeks * 2;
int workingDays =(int)( timeBetween - numberOfWeekendDays);
if(dateStart.DayOfWeek == DayOfWeek.Saturday || dateEnd.DayOfWeek == DayOfWeek.Sunday){
workingDays -=2;
}
if(dateStart.DayOfWeek == DayOfWeek.Sunday || dateEnd.DayOfWeek == DayOfWeek.Saturday){
workingDays -=1;
}
Bạn chỉ cần lặp lại từng ngày trong phạm vi thời gian và trừ đi một ngày từ bộ đếm nếu đó là Thứ Bảy hoặc Chủ Nhật.
private float SubtractWeekend(DateTime start, DateTime end) {
float totaldays = (end.Date - start.Date).Days;
var iterationVal = totalDays;
for (int i = 0; i <= iterationVal; i++) {
int dayVal = (int)start.Date.AddDays(i).DayOfWeek;
if(dayVal == 6 || dayVal == 0) {
// saturday or sunday
totalDays--;
}
}
return totalDays;
}
public static int CalculateBusinessDaysInRange(this DateTime startDate, DateTime endDate, params DateTime[] holidayDates)
{
endDate = endDate.Date;
if(startDate > endDate)
throw new ArgumentException("The end date can not be before the start date!", nameof(endDate));
int accumulator = 0;
DateTime itterator = startDate.Date;
do
{
if(itterator.DayOfWeek != DayOfWeek.Saturday && itterator.DayOfWeek != DayOfWeek.Sunday && !holidayDates.Any(hol => hol.Date == itterator))
{ accumulator++; }
}
while((itterator = itterator.AddDays(1)).Date <= endDate);
return accumulator
}
Tôi chỉ đăng bài này bởi vì mặc dù tất cả các câu trả lời xuất sắc đã được đưa ra, không có phép toán nào có ý nghĩa đối với tôi. Đây chắc chắn là một phương pháp KISS nên hoạt động và khá dễ bảo trì. Được cấp nếu bạn đang tính toán các phạm vi lớn hơn 2-3 tháng, đây sẽ không phải là cách hiệu quả nhất. Chúng tôi chỉ đơn giản xác định xem đó là Thứ Bảy hay Chủ Nhật hay ngày đó là một ngày lễ nhất định. Nếu không, chúng tôi thêm một ngày làm việc. Nếu đúng như vậy thì mọi thứ đều ổn.
Tôi chắc rằng điều này thậm chí có thể được đơn giản hóa hơn với LINQ, nhưng cách này dễ hiểu hơn nhiều.
Tuy nhiên, một cách tiếp cận khác để tính toán ngày làm việc, không tính đến ngày nghỉ, nhưng tính đến thời gian trong ngày trả về một lượng nhỏ số ngày:
public static double GetBusinessDays(DateTime startD, DateTime endD)
{
while (IsWeekend(startD))
startD = startD.Date.AddDays(1);
while (IsWeekend(endD))
endD = endD.Date.AddDays(-1);
var bussDays = (endD - startD).TotalDays -
(2 * ((int)(endD - startD).TotalDays / 7)) -
(startD.DayOfWeek > endD.DayOfWeek ? 2 : 0);
return bussDays;
}
public static bool IsWeekend(DateTime d)
{
return d.DayOfWeek == DayOfWeek.Saturday || d.DayOfWeek == DayOfWeek.Sunday;
}
Bạn có thể thử với nó tại đây: https://rextester.com/ASHRS53997
Đây là một giải pháp chung.
startdayvalue là số ngày của ngày bắt đầu.
ngày cuối tuần_1 là số ngày của cuối tuần.
số ngày - MON - 1, TUE - 2, ... SAT - 6, SUN -7.
sự khác biệt là sự khác biệt giữa hai ngày ..
Ví dụ: Ngày bắt đầu: 4 tháng 4 năm 2013, Ngày kết thúc: 14 tháng 4 năm 2013
Chênh lệch: 10, giá trị ngày bắt đầu: 4, ngày cuối tuần 1: 7 (nếu CHỦ NHẬT là ngày cuối tuần đối với bạn.)
Điều này sẽ cung cấp cho bạn số ngày nghỉ.
Không ngày làm việc = (Chênh lệch + 1) - ngày lễ1
if (startdayvalue > weekendday_1)
{
if (difference > ((7 - startdayvalue) + weekendday_1))
{
holiday1 = (difference - ((7 - startdayvalue) + weekendday_1)) / 7;
holiday1 = holiday1 + 1;
}
else
{
holiday1 = 0;
}
}
else if (startdayvalue < weekendday_1)
{
if (difference > (weekendday_1 - startdayvalue))
{
holiday1 = (difference - (weekendday_1 - startdayvalue)) / 7;
holiday1 = holiday1 + 1;
}
else if (difference == (weekendday_1 - startdayvalue))
{
holiday1 = 1;
}
else
{
holiday1 = 0;
}
}
else
{
holiday1 = difference / 7;
holiday1 = holiday1 + 1;
}