我正在搜索stackoverflow,以找到完全匹配的问题以及对类似问题的响应,以在C#中解决。
尽管我在可用的问题上发现了一些相似之处,但在公共假期,周末和非工作时间中,如何在c#中以天,小时和分钟为单位计算sla并没有发现任何特定的问题和答案。
例如,我将票证的日期时间提高为21/02/2019 10:00:00 pm,如果我只想添加n(在本示例中为21)工作时间(不包括非工作时间),周末和公共假期在C#中查找该票证的sla日期时间。
尽管我在仅计算工作时间,周末时实现了一些逻辑,但很难排除公众假期。还要欣赏比长行函数更好,更简单且易于理解的方法(可能使用linq)。感谢社区中的任何示例代码。
我从下面的其他stackoverflow链接中得到了一个改进的工作解决方案,但是如果我们连续获得2天的假期,则需要进一步完善以简化和解决这种情况下无法解决的所有错误,然后计算sla从第三天开始,等等。
到目前为止,我得到的解决方案是:
public virtual DateTime AddWithinWorkingHours(DateTime start, TimeSpan offset)
{
//Get publicholidaysList from holiday table to not to include in working hour calculation
var holidaysList = _holidayManager.GetHolidays().Result;
// Don't start counting hours until start time is during working hours
if (start.TimeOfDay.TotalHours > StartHour + HoursPerDay)
start = start.Date.AddDays(1).AddHours(StartHour);
if (start.TimeOfDay.TotalHours < StartHour)
start = start.Date.AddHours(StartHour);
if (start.DayOfWeek == DayOfWeek.Saturday)
start.AddDays(2);
//if it is a Sunday or holiday date, skip that date in workinghour calc
else if (start.DayOfWeek == DayOfWeek.Sunday || holidaysList.Exists(hd=>hd.Date == start.Date))
start.AddDays(1);
// Calculate how much working time already passed on the first day
TimeSpan firstDayOffset = start.TimeOfDay.Subtract(TimeSpan.FromHours(StartHour));
// Calculate number of whole days to add
int wholeDays = (int)(offset.Add(firstDayOffset).TotalHours / HoursPerDay);
// How many hours off the specified offset does this many whole days consume?
TimeSpan wholeDaysHours = TimeSpan.FromHours(wholeDays * HoursPerDay);
// Calculate the final time of day based on the number of whole days spanned and the specified offset
TimeSpan remainder = offset - wholeDaysHours;
// How far into the week is the starting date?
int weekOffset = ((int)(start.DayOfWeek + 7) - (int)DayOfWeek.Monday) % 7;
// How many weekends are spanned?
int weekends = (int)((wholeDays + weekOffset) / 5);
// Calculate the final result using all the above calculated values
return start.AddDays(wholeDays + weekends * 2).Add(remainder);
}
答案 0 :(得分:0)
我实际上已经花费了最后一个小时来实施此解决方案,该解决方案结合了另一个stackoverflow问题(Add hours to datetime but exclude weekends and should be between working hours)的计算方法,该问题计算了一个日期的工作时间+一个块金,该块验证日期是否为假期,具体取决于哪个指定的国家/地区。 首先安装金块
PM> install-package Nager.Date
然后,我创建了3种方法来实现您的功能,但它很简单,您可以对其进行优化,以使用CountryCode以及工作日的工作时间以及何时开始,但出于示例目的,我对其进行了硬编码:
private static DateTime AddWithinWorkingHours(DateTime start, TimeSpan offset)
{
const int hoursPerDay = 8;
const int startHour = 9;
// Don't start counting hours until start time is during working hours
if (start.TimeOfDay.TotalHours > startHour + hoursPerDay)
start = start.Date.AddDays(1).AddHours(startHour);
if (start.TimeOfDay.TotalHours < startHour)
start = start.Date.AddHours(startHour);
start = CheckTillNoLongerHoliday(start);
if (start.DayOfWeek == DayOfWeek.Saturday)
start = start.AddDays(2);
else if (start.DayOfWeek == DayOfWeek.Sunday)
start = start.AddDays(1);
//Saving this proccessed date to check later if there are more holidays
var dateAfterArranges = start;
// Calculate how much working time already passed on the first day
TimeSpan firstDayOffset = start.TimeOfDay.Subtract(TimeSpan.FromHours(startHour));
// Calculate number of whole days to add
int wholeDays = (int)(offset.Add(firstDayOffset).TotalHours / hoursPerDay);
// How many hours off the specified offset does this many whole days consume?
TimeSpan wholeDaysHours = TimeSpan.FromHours(wholeDays * hoursPerDay);
// Calculate the final time of day based on the number of whole days spanned and the specified offset
TimeSpan remainder = offset - wholeDaysHours;
// How far into the week is the starting date?
int weekOffset = ((int)(start.DayOfWeek + 7) - (int)DayOfWeek.Monday) % 7;
// How many weekends are spanned?
int weekends = (int)((wholeDays + weekOffset) / 5);
//Get the final date without the holidays
start = start.AddDays(wholeDays + weekends * 2).Add(remainder);
//Check again if in this timeSpan there were any more holidays
return InPeriodCheckHolidaysOnWorkingDays(dateAfterArranges, start);
}
private static DateTime CheckTillNoLongerHoliday(DateTime date)
{
if (DateSystem.IsPublicHoliday(date, CountryCode.PT) && !DateSystem.IsWeekend(date, CountryCode.PT))
{
date = date.AddDays(1);
date = CheckTillNoLongerHoliday(date);
}
return date;
}
private static DateTime InPeriodCheckHolidaysOnWorkingDays(DateTime start, DateTime end)
{
var publicHolidays = DateSystem.GetPublicHoliday(2019, CountryCode.PT);
var holidaysSpent = publicHolidays.Where(x => x.Date.Date >= start.Date && x.Date.Date < end.Date);
foreach (var holiday in holidaysSpent)
{
if (!DateSystem.IsWeekend(holiday.Date, CountryCode.PT))
{
end = end.AddDays(1);
if (DateSystem.IsWeekend(end, CountryCode.PT))
{
end = end.AddDays(2);
}
}
}
return end;
}
我实现的是3种方法:AddWithinWorkingHours是所有方法的主要方法,它是由用户在我提到的那个链接上完成的(也要给他功劳)(基本上也要给他功劳),它需要一个DateTime开始日期(在您的示例中是工单增加的时间)和一个TimeSpan,您可以传递工作时间数。接下来的2种方法是考虑“国家假日”的方法,在该示例中您注意到了,我使用了葡萄牙语假日,但是您可以使用Nager.Date nuget程序包支持的任何其他国家/地区代码。
希望它对您有帮助!这对我来说是一个有趣的挑战,但对将来的实现很有用:)
答案 1 :(得分:0)
根据我的经验,这是一个非常容易出错的任务。如果您整小时或全天工作,我建议您逐一列举并保持合格的时间。
但是,如果需要精确度,最好使用库。
Tiago链接到的答案之一中引用的库似乎完全符合您的要求:
https://www.codeproject.com/Articles/168662/Time-Period-Library-for-NET
public void CalendarDateAddSample()
{
CalendarDateAdd calendarDateAdd = new CalendarDateAdd();
// weekdays
calendarDateAdd.AddWorkingWeekDays();
// holidays
calendarDateAdd.ExcludePeriods.Add( new Day( 2011, 4, 5, calendarDateAdd.Calendar ) );
// working hours
calendarDateAdd.WorkingHours.Add( new HourRange( new Time( 08, 30 ), new Time( 12 ) ) );
calendarDateAdd.WorkingHours.Add( new HourRange( new Time( 13, 30 ), new Time( 18 ) ) );
DateTime start = new DateTime( 2011, 4, 1, 9, 0, 0 );
TimeSpan offset = new TimeSpan( 22, 0, 0 ); // 22 hours
DateTime? end = calendarDateAdd.Add( start, offset );
Console.WriteLine( "start: {0}", start );
// > start: 01.04.2011 09:00:00
Console.WriteLine( "offset: {0}", offset );
// > offset: 22:00:00
Console.WriteLine( "end: {0}", end );
// > end: 06.04.2011 16:30:00
}
答案 2 :(得分:0)
这是我计算SLA的解决方案。比我所见的一些过于复杂的文章更容易阅读。代码应始终易于他人维护。
它仅计算工作时间内的时间(工作日的开始和结束值存储在db中,因此可以配置)。它考虑了周六和周日以及任何公共假日(来自内存中的缓存列表)。
public DateTime? CalculateSLADueDate(DateTime slaStartDateUTC, double slaDays, TimeSpan workdayStartUTC, TimeSpan workdayEndUTC)
{
if ((slaDays < 0)
|| (workdayStartUTC > workdayEndUTC))
{
return null;
}
var dueDate = slaStartDateUTC;
var tsWorkdayHours = (workdayEndUTC - workdayStartUTC);
var tsSlaCount = TimeSpan.FromHours(slaDays * ((workdayEndUTC - workdayStartUTC)).TotalHours);
//get list of public holidays from in-memory cache
var blPublicHoliday = new PublicHoliday();
IList<BusObj.PublicHoliday> publicHolidays = blPublicHoliday.SelectAll();
do
{
if ((dueDate.DayOfWeek == DayOfWeek.Saturday)
|| (dueDate.DayOfWeek == DayOfWeek.Sunday)
|| publicHolidays.Any(x => x.HolidayDate == dueDate.Date)
|| ((dueDate.TimeOfDay >= workdayEndUTC) && (dueDate.TimeOfDay < workdayStartUTC)))
{
//jump to start of next day
dueDate = dueDate.AddDays(1);
dueDate = new DateTime(dueDate.Year, dueDate.Month, dueDate.Day, workdayStartUTC.Hours, workdayStartUTC.Minutes, workdayStartUTC.Seconds);
}
else if ((dueDate.TimeOfDay == workdayStartUTC) && (tsSlaCount >= tsWorkdayHours))
{
//add a whole working day
dueDate = dueDate.AddDays(1);
tsSlaCount = tsSlaCount.Subtract(tsWorkdayHours);
}
else if (dueDate.TimeOfDay == workdayStartUTC)
{
//end day - add remainder of time for final work day
dueDate = dueDate.Add(tsSlaCount);
tsSlaCount = tsSlaCount.Subtract(tsSlaCount);
}
else
{
if(workdayEndUTC > dueDate.TimeOfDay)
{
//start day and still in business hours - add rest of today
tsSlaCount = tsSlaCount.Subtract(workdayEndUTC - dueDate.TimeOfDay);
dueDate = dueDate.Add(workdayEndUTC - dueDate.TimeOfDay);
}
if (tsSlaCount.Ticks > 0)
{
//if theres more to process - jump to start of next day
dueDate = dueDate.AddDays(1);
dueDate = new DateTime(dueDate.Year, dueDate.Month, dueDate.Day, workdayStartUTC.Hours, workdayStartUTC.Minutes, workdayStartUTC.Seconds);
}
}
}
while (tsSlaCount.Ticks > 0);
return dueDate;
}
我总是将日期以UTC格式存储在db中,因此请注意,您需要将TimeSpan参数转换为UTC。感谢PeterJ的TimeSpan扩展(我在其中添加了“ this”):
public static class DatetimeExtensionMethod
{
public static TimeSpan LocalTimeSpanToUTC(this TimeSpan ts)
{
DateTime dt = DateTime.Now.Date.Add(ts);
DateTime dtUtc = dt.ToUniversalTime();
TimeSpan tsUtc = dtUtc.TimeOfDay;
return tsUtc;
}
public static TimeSpan UTCTimeSpanToLocal(this TimeSpan tsUtc)
{
DateTime dtUtc = DateTime.UtcNow.Date.Add(tsUtc);
DateTime dt = dtUtc.ToLocalTime();
TimeSpan ts = dt.TimeOfDay;
return ts;
}
}