ใน C #, วิธีการที่ฉันสามารถคำนวณจำนวนของธุรกิจ (หรือวันธรรมดา) วันระหว่างวันที่สองวัน?
ใน C #, วิธีการที่ฉันสามารถคำนวณจำนวนของธุรกิจ (หรือวันธรรมดา) วันระหว่างวันที่สองวัน?
คำตอบ:
ฉันเคยทำงานแบบนี้มาก่อนและฉันมีวิธีแก้ปัญหา ฉันจะหลีกเลี่ยงการแจกแจงทุกวันในช่วงเวลาที่สามารถหลีกเลี่ยงได้ซึ่งเป็นกรณีนี้ ฉันไม่ได้พูดถึงการสร้างอินสแตนซ์ DateTime จำนวนมากอย่างที่ฉันเห็นในหนึ่งในคำตอบด้านบน นี่มันเปลืองพลังประมวลผลจริงๆ โดยเฉพาะอย่างยิ่งในสถานการณ์จริงเมื่อคุณต้องตรวจสอบช่วงเวลาหลายเดือน ดูรหัสของฉันพร้อมความคิดเห็นด้านล่าง
/// <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;
}
แก้ไขโดย Slauma สิงหาคม 2554
ตอบโจทย์มาก! มีข้อผิดพลาดเล็กน้อยแม้ว่า ฉันมีอิสระในการแก้ไขคำตอบนี้เนื่องจากไม่มีผู้ตอบตั้งแต่ปี 2009
โค้ดด้านบนถือว่าDayOfWeek.Sunday
มีค่า7
ซึ่งไม่เป็นเช่นนั้น 0
ค่าที่เป็นจริง ซึ่งนำไปสู่การคำนวณที่ไม่ถูกต้องหากเป็นเช่นนั้นfirstDay
และlastDay
เป็นวันอาทิตย์เดียวกัน วิธีการส่งกลับ1
ในกรณีนี้ 0
แต่มันควรจะเป็น
แก้ไขข้อบกพร่องนี้ได้ง่ายที่สุด: แทนที่โค้ดเหนือบรรทัดที่firstDayOfWeek
และlastDayOfWeek
ประกาศตามรายการต่อไปนี้:
int firstDayOfWeek = firstDay.DayOfWeek == DayOfWeek.Sunday
? 7 : (int)firstDay.DayOfWeek;
int lastDayOfWeek = lastDay.DayOfWeek == DayOfWeek.Sunday
? 7 : (int)lastDay.DayOfWeek;
ตอนนี้ผลลัพธ์คือ:
&& !(bh.DayOfWeek == DayOfWeek.Sunday || bh.DayOfWeek == DayOfWeek.Saturday)
มิฉะนั้นจะแทนที่ในวันเดียวกันสองครั้งหากวันหยุดตรงกับวันหยุดสุดสัปดาห์
ตกลง. ฉันคิดว่าถึงเวลาโพสต์คำตอบที่ถูกต้อง:
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;
}
แหล่งที่มาเดิม:
http://alecpojidaev.wordpress.com/2009/10/29/work-days-calculation-with-c/
โซลูชัน PS ที่โพสต์ไว้ด้านบนทำให้ฉันเป็นคนแปลกหน้าด้วยเหตุผลบางประการ
ฉันรู้ว่าคำถามนี้ได้รับการแก้ไขแล้ว แต่ฉันคิดว่าฉันสามารถให้คำตอบที่ตรงไปตรงมามากขึ้นซึ่งอาจช่วยผู้เยี่ยมชมคนอื่น ๆ ในอนาคต
นี่คือสิ่งที่ฉันทำ:
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);
}
นี่เป็นการส่งต้นฉบับของฉัน:
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
. บางทีนั่นอาจเป็นปัญหา?
กำหนดวิธีการขยายใน DateTime ดังนี้:
public static class DateTimeExtensions
{
public static bool IsWorkingDay(this DateTime date)
{
return date.DayOfWeek != DayOfWeek.Saturday
&& date.DayOfWeek != DayOfWeek.Sunday;
}
}
จากนั้นใช้ภายในประโยค Where เพื่อกรองรายการวันที่ที่กว้างขึ้น:
var allDates = GetDates(); // method which returns a list of dates
// filter dates by working day's
var countOfWorkDays = allDates
.Where(day => day.IsWorkingDay())
.Count() ;
ฉันใช้รหัสต่อไปนี้เพื่อเข้าบัญชีวันหยุดธนาคาร:
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; }
}
และการทดสอบหน่วย:
[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");
}
}
นี่มันถูกทำร้ายจนตาย :) อย่างไรก็ตามฉันจะให้คำตอบอื่นเพราะฉันต้องการบางอย่างที่แตกต่างออกไปเล็กน้อย โซลูชันนี้แตกต่างกันตรงที่ส่งคืน Business TimeSpanระหว่างเริ่มต้นและสิ้นสุดและคุณสามารถตั้งค่าเวลาทำการของวันและเพิ่มวันหยุด คุณจึงใช้คำนวณได้ว่าจะเกิดขึ้นภายใน 1 วันข้ามวันช่วงสุดสัปดาห์หรือแม้แต่วันหยุด และคุณจะได้รับเพียงแค่วันทำการหรือไม่เพียงแค่ได้รับสิ่งที่คุณต้องการจากวัตถุ TimeSpan ที่ส่งคืน และวิธีที่ใช้รายการวันคุณจะเห็นได้ว่าการเพิ่มรายการวันที่ไม่ทำงานนั้นง่ายมากแค่ไหนหากไม่ใช่วันเสาร์และอาทิตย์ทั่วไป และฉันทดสอบเป็นเวลาหนึ่งปีและดูเหมือนว่าจะเร็วมาก
ฉันหวังว่าการวางโค้ดจะถูกต้อง แต่ฉันรู้ว่ามันได้ผล
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;
}
}
และนี่คือรหัสทดสอบ: โปรดทราบว่าคุณต้องใส่ฟังก์ชันนี้ในคลาสที่เรียกว่าDateHelperเพื่อให้รหัสทดสอบทำงาน
[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);
}
}
โซลูชันนี้จะหลีกเลี่ยงการวนซ้ำใช้ได้กับทั้ง + ve และ -ve ความแตกต่างของวันทำงานและรวมถึงชุดทดสอบหน่วยเพื่อถดถอยเทียบกับวิธีการนับวันธรรมดาที่ช้าลง ฉันยังได้รวมวิธีการที่รัดกุมในการเพิ่มวันธรรมดาซึ่งก็ใช้ได้ผลเช่นเดียวกันโดยไม่ต้องทำซ้ำ
การทดสอบหน่วยครอบคลุมชุดวันที่ไม่กี่พันชุดเพื่อทดสอบชุดค่าผสมของวันทำงานเริ่มต้น / สิ้นสุดทั้งหมดอย่างละเอียดถี่ถ้วนโดยมีทั้งช่วงวันที่ขนาดเล็กและขนาดใหญ่
สำคัญ : เราตั้งสมมติฐานว่าเรากำลังนับวันโดยไม่รวมวันที่เริ่มต้นและวันที่สิ้นสุด นี่เป็นสิ่งสำคัญเมื่อนับวันธรรมดาเนื่องจากวันเริ่มต้น / วันสิ้นสุดที่คุณรวม / ไม่รวมมีผลต่อผลลัพธ์ นอกจากนี้ยังช่วยให้มั่นใจได้ว่าความแตกต่างระหว่างสองวันที่เท่ากันจะเป็นศูนย์เสมอและเราจะรวมเฉพาะวันทำงานเต็มตามที่คุณต้องการให้คำตอบถูกต้องตลอดเวลาในวันที่เริ่มต้นปัจจุบัน (มักจะเป็นวันนี้) และรวมวันที่สิ้นสุดแบบเต็ม (เช่น วันที่ครบกำหนด)
หมายเหตุ: รหัสนี้ต้องการการปรับปรุงเพิ่มเติมสำหรับวันหยุด แต่เพื่อให้สอดคล้องกับสมมติฐานข้างต้นรหัสนี้จะต้องไม่รวมวันหยุดในวันที่เริ่มต้น
เพิ่มวันธรรมดา:
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);
}
คำนวณความแตกต่างในวันธรรมดา:
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];
}
ฉันพบว่าโซลูชันอื่น ๆ ส่วนใหญ่ในสแต็กล้นนั้นช้า (ซ้ำแล้วซ้ำอีก) หรือซับซ้อนเกินไปและหลายวิธีก็ไม่ถูกต้อง คติธรรมของเรื่องคือ ... อย่าวางใจเว้นแต่จะได้ทดสอบอย่างละเอียดถี่ถ้วน !!
การทดสอบหน่วยตามการทดสอบNUnit Combinatorialและส่วนขยายของ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);
}
}
นี่คือรหัสบางส่วนสำหรับจุดประสงค์นั้นพร้อมวันหยุดของสวีเดน แต่คุณสามารถปรับเปลี่ยนวันหยุดที่จะนับ โปรดทราบว่าฉันได้เพิ่มขีด จำกัด ที่คุณอาจต้องการลบ แต่มันเป็นสำหรับระบบบนเว็บและฉันไม่ต้องการให้ใครป้อนวันที่ใหญ่โตเพื่อหมูกระบวนการ
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;
}
นี่คือโค้ดตัวอย่างสั้น ๆ เป็นวิธีการของชั้นเรียนจึงใช้ได้เฉพาะในชั้นเรียนของคุณเท่านั้น ถ้าคุณต้องการให้static
เปลี่ยนลายเซ็นเป็นprivate static
(หรือ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;
}
วิธีนี้จะสร้างตัวแปรลูปd
เริ่มต้นเป็นวันเริ่มต้นsd
จากนั้นเพิ่มขึ้นทีละวันในแต่ละการวนซ้ำ ( d = d.AddDays(1)
)
ส่งคืนค่าที่ต้องการโดยใช้yield
ซึ่งสร้างiterator
ไฟล์. สิ่งที่ยอดเยี่ยมเกี่ยวกับตัวทำซ้ำคือพวกเขาไม่ได้เก็บค่าทั้งหมดของIEnumerable
หน่วยความจำไว้เพียง แต่เรียกแต่ละค่าตามลำดับ ซึ่งหมายความว่าคุณสามารถเรียกใช้วิธีนี้ได้ตั้งแต่เช้ามืดจนถึงปัจจุบันโดยไม่ต้องกังวลว่าหน่วยความจำจะหมด
ฉันค้นหาอัลกอริทึมเพื่อคำนวณวันทำการระหว่างวันที่ 2 วันที่และไม่รวมวันหยุดประจำชาติเป็นจำนวนมากและในที่สุดฉันก็ตัดสินใจใช้แนวทางนี้:
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();
}
โดยทั่วไปฉันต้องการไปกับแต่ละวันและประเมินเงื่อนไขของฉัน:
แต่ฉันก็ต้องการหลีกเลี่ยงการย้ำวันที่
ด้วยการวิ่งและวัดเวลาที่ต้องใช้ในการประเมิน 1 ปีเต็มฉันได้ผลลัพธ์ต่อไปนี้:
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
}
แก้ไข: วิธีการใหม่ที่ง่ายขึ้น:
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));
}
ฉันคิดว่าไม่มีคำตอบใดที่ถูกต้อง ไม่มีใครแก้ปัญหากรณีพิเศษทั้งหมดเช่นเมื่อวันที่เริ่มต้นและสิ้นสุดในช่วงกลางของวันหยุดสุดสัปดาห์เมื่อวันที่เริ่มต้นในวันศุกร์และสิ้นสุดในวันจันทร์ถัดไปเป็นต้นนอกจากนี้พวกเขาจะคำนวณทั้งหมด วันดังนั้นหากวันที่เริ่มต้นอยู่ตรงกลางของวันเสาร์เช่นวันที่เริ่มต้นจะถูกแทนที่ทั้งวันจากวันทำการทำให้ผลลัพธ์ผิด ...
อย่างไรก็ตามนี่คือโซลูชันของฉันที่ค่อนข้างมีประสิทธิภาพและเรียบง่ายและใช้ได้กับทุกกรณี เคล็ดลับคือเพียงค้นหาวันจันทร์ก่อนหน้าสำหรับวันที่เริ่มต้นและวันที่สิ้นสุดจากนั้นทำการชดเชยเล็กน้อยเมื่อเริ่มต้นและสิ้นสุดเกิดขึ้นในช่วงสุดสัปดาห์:
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;
}
}
}
วิธีนี้ไม่ใช้ลูปใด ๆ และค่อนข้างง่าย ขยายช่วงวันที่เป็นสัปดาห์เต็มเนื่องจากเราทราบว่าแต่ละสัปดาห์มี 5 วันทำการ จากนั้นใช้ตารางค้นหาเพื่อหาจำนวนวันทำการเพื่อลบออกจากจุดเริ่มต้นและจุดสิ้นสุดเพื่อให้ได้ผลลัพธ์ที่ถูกต้อง ฉันได้ขยายการคำนวณเพื่อช่วยแสดงสิ่งที่เกิดขึ้น แต่สิ่งทั้งหมดสามารถรวมเป็นบรรทัดเดียวได้หากจำเป็น
อย่างไรก็ตามสิ่งนี้ใช้ได้กับฉันดังนั้นฉันจึงคิดว่าจะโพสต์ไว้ที่นี่เผื่อว่ามันจะช่วยคนอื่นได้ มีความสุขในการเขียนโค้ด
การคำนวณ
วัฒนธรรม
รหัสจะถือว่าเป็นสัปดาห์การทำงานในวันจันทร์ถึงวันศุกร์ สำหรับวัฒนธรรมอื่น ๆ เช่นวันอาทิตย์ถึงวันพฤหัสบดีคุณจะต้องหักล้างวันที่ก่อนการคำนวณ
วิธี
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;
}
ฉันจะแบ่งปันวิธีแก้ปัญหาของฉัน มันใช้ได้ผลสำหรับฉันบางทีฉันอาจจะไม่สังเกตเห็น / รู้ว่ามีจุดบกพร่อง ฉันเริ่มต้นด้วยการรับสัปดาห์แรกที่ไม่สมบูรณ์หากมี สัปดาห์ที่สมบูรณ์คือตั้งแต่วันอาทิตย์สำหรับวันเสาร์ดังนั้นหาก (int) _now.DayOfWeek ไม่ใช่ 0 (วันอาทิตย์) สัปดาห์แรกก็ไม่สมบูรณ์
ฉันเพิ่งลบ 1 ถึงสัปดาห์แรกนับเป็นวันเสาร์ของสัปดาห์แรกแล้วบวกเข้าไปในการนับใหม่
จากนั้นฉันได้รับสัปดาห์สุดท้ายที่ไม่สมบูรณ์จากนั้นลบ 1 สำหรับวันอาทิตย์แล้วบวกกับการนับใหม่
จากนั้นในที่สุดจำนวนสัปดาห์ที่สมบูรณ์คูณด้วย 5 (วันธรรมดา) ก็ถูกเพิ่มเข้าไปในการนับใหม่
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;
}
ฉันมีปัญหาในการค้นหารหัสรุ่น TSQL ที่มั่นคง ด้านล่างนี้เป็นการแปลงรหัส C # ที่นี่โดยมีการเพิ่มตารางวันหยุดซึ่งควรใช้ในการคำนวณวันหยุดล่วงหน้า
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);
}
นี่เป็นวิธีแก้ปัญหาที่ง่ายมากสำหรับปัญหานี้ เรามีวันที่เริ่มต้นวันที่สิ้นสุดและ "สำหรับการวนซ้ำ" สำหรับการใส่วันและคำนวณเพื่อดูว่าเป็นวันทำงานหรือวันหยุดสุดสัปดาห์โดยการแปลงเป็นสตริง 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);
}
}
ตามความคิดเห็นที่ทำเครื่องหมายว่าเป็นคำตอบและแพทช์แนะนำเช่นเดียวกับ -> เวอร์ชันนี้ต้องการแปลงวันเป็นเวลาทำการ ... พิจารณาชั่วโมงของวันเดียวกันด้วย
/// <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;
}
ฉันเพิ่งปรับปรุงคำตอบของ @Alexander และ @Slauma เพื่อรองรับสัปดาห์ธุรกิจเป็นพารามิเตอร์สำหรับกรณีที่วันเสาร์เป็นวันทำการหรือแม้แต่กรณีที่มีเพียงสองสามวันของสัปดาห์ที่ถือเป็นวันทำการ:
/// <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;
}
นี่คือฟังก์ชันที่เราสามารถใช้คำนวณวันทำการระหว่างวันที่สองวัน ฉันไม่ได้ใช้รายการวันหยุดเนื่องจากอาจแตกต่างกันไปในแต่ละประเทศ / ภูมิภาค
หากเราต้องการใช้มันต่อไปเราสามารถใช้อาร์กิวเมนต์ที่สามเป็นรายการวันหยุดและก่อนที่จะเพิ่มจำนวนเราควรตรวจสอบว่ารายการนั้นไม่มี 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;
}
ฉันเชื่อว่านี่อาจเป็นวิธีที่ง่ายกว่านี้:
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;
}
นี่เป็นอีกแนวคิดหนึ่ง - วิธีนี้ช่วยให้ระบุสัปดาห์ทำงานและวันหยุดได้
แนวคิดในที่นี้คือเราจะค้นหาแกนหลักของช่วงวันที่ตั้งแต่วันทำการแรกของสัปดาห์จนถึงวันหยุดสุดสัปดาห์สุดท้ายของสัปดาห์ สิ่งนี้ทำให้เราสามารถคำนวณทั้งสัปดาห์ได้อย่างง่ายดาย ( โดยไม่ต้องวนซ้ำวันที่ทั้งหมด) สิ่งที่เราต้องทำคือเพิ่มวันทำการที่อยู่ก่อนเริ่มต้นและสิ้นสุดของช่วงหลักนี้
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;
}
เนื่องจากฉันไม่สามารถแสดงความคิดเห็นได้ ยังมีอีกปัญหาหนึ่งเกี่ยวกับวิธีแก้ปัญหาที่ได้รับการยอมรับซึ่งวันหยุดธนาคารจะถูกหักออกแม้ว่าจะเป็นวันหยุดสุดสัปดาห์ก็ตาม เมื่อเห็นว่ามีการตรวจสอบอินพุตอื่นอย่างไรจึงเหมาะสมเท่านั้นที่เป็นเช่นกัน
foreach ควรเป็น:
// 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;
}
นี่คือแนวทางหากคุณใช้ MVC ฉันได้คำนวณวันหยุดประจำชาติหรือวันเทศกาลใด ๆ ที่จะยกเว้นโดยดึงข้อมูลจากปฏิทินวันหยุดซึ่งคุณจะต้องกำหนดวันใหม่
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++;
}
}
Leavemodel เป็นรายการที่นี่
นี่คือฟังก์ชันตัวช่วยที่ฉันเขียนสำหรับงานนั้น
นอกจากนี้ยังส่งกลับการนับของวันหยุดสุดสัปดาห์ผ่านทางout
พารามิเตอร์
หากคุณต้องการให้คุณปรับแต่งวันทำงานใน "วันหยุดสุดสัปดาห์" สำหรับประเทศที่ใช้วันหยุดสุดสัปดาห์ที่แตกต่างกันหรือรวมถึงช่วงวันหยุดผ่านweekendDays[]
พารามิเตอร์ตัวเลือก:
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;
}
ฉันคิดวิธีแก้ปัญหาต่อไปนี้
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;
}
คุณต้องทำซ้ำในแต่ละวันในช่วงเวลาและลบวันจากตัวนับหากเป็นวันเสาร์หรือวันอาทิตย์
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
}
ฉันโพสต์สิ่งนี้เพียงเพราะแม้จะมีคำตอบที่ยอดเยี่ยมทั้งหมด แต่ก็ไม่มีคณิตศาสตร์ใดที่เหมาะกับฉัน นี่เป็นวิธีการ KISS ที่ควรใช้งานได้จริงและดูแลรักษาได้ดี จริงอยู่หากคุณกำลังคำนวณช่วงที่มากกว่า 2-3 เดือนนี่จะไม่ใช่วิธีที่มีประสิทธิภาพสูงสุด เราเพียงระบุว่าเป็นวันเสาร์หรือวันอาทิตย์หรือวันที่เป็นวันหยุดที่กำหนด ถ้าไม่ใช่เราเพิ่มวันทำการ ถ้าเป็นเช่นนั้นทุกอย่างก็ดี
ฉันแน่ใจว่านี่อาจจะง่ายกว่านี้ด้วย LINQ แต่วิธีนี้เข้าใจง่ายกว่ามาก
อีกวิธีหนึ่งในการคำนวณวันทำการโดยไม่พิจารณาวันหยุด แต่คำนึงถึงเวลาของวันที่ส่งคืนจำนวนวันที่เป็นเศษส่วน:
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;
}
คุณสามารถเล่นซอได้ที่นี่: https://rextester.com/ASHRS53997
นี่เป็นวิธีแก้ปัญหาทั่วไป
startdayvalue คือวันที่ของวันที่เริ่มต้น
weekendday_1 คือจำนวนวันของวันสิ้นสัปดาห์
วันที่ - จันทร์ - 1, อังคาร - 2, ... ส. - 6, อาทิตย์ -7
ความแตกต่างคือความแตกต่างระหว่างวันที่สองวัน ..
ตัวอย่าง: วันที่เริ่มต้น: 4 เมษายน 2556 วันที่สิ้นสุด: 14 เมษายน 2556
ความแตกต่าง: 10, ค่าเริ่มต้น: 4, วันหยุดสุดสัปดาห์day_1: 7 (ถ้าวันอาทิตย์เป็นวันหยุดสุดสัปดาห์สำหรับคุณ)
ซึ่งจะให้จำนวนวันหยุด
ไม่มีวันทำการ = (ความแตกต่าง + 1) - วันหยุด 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;
}