【问题标题】:How to get the next working day, excluding weekends and holidays如何获得下一个工作日,不包括周末和节假日
【发布时间】:2016-01-27 01:53:44
【问题描述】:

我有一个需要在日期字段上工作的要求,所以要求是这样的

我将该字段称为最短可能日期

  1. 给日期加上 +1

  2. 如果在增加 1 天后,最短可能日期恰好落在周末(周六或周日),则显示下一个工作日,即周一

  3. 如果可能的最短日期恰好是节假日,则显示下一个工作日。 (节假日 1.1、1.5、3.10、25.12、26.12)

  4. 如果添加 1 天后的最短可能日期恰好落在周末(周六或周日),而后一天是假期,则显示下一个工作日。例如:+1 天后,如果可能的最小值是星期六,我们将不得不显示星期一。但是如果星期一恰好是假期,那么我们必须显示星期二。

我已经尝试通过多个 if 和 else 情况来解决上述问题,但只是想知道是否有任何通用和优雅的方式来解决这个问题?

我试过了

var Holidays = new List<DateTime>();
Holidays.Add(new DateTime(DateTime.Now.Year,1,1));
Holidays.Add(new DateTime(DateTime.Now.Year,1,5));
Holidays.Add(new DateTime(DateTime.Now.Year,3,10));
Holidays.Add(new DateTime(DateTime.Now.Year,12,25));

if(date.DayOfWeek === DayOfWeek.Saturday || date.DayOfWeek === DayOfWeek.Sunday)

{

     //Logic to add +1 and again some logic to check for weekends and weekdays
}


else if(holidays.Contain(date))
{

   //Logic to add +1 and again some logic to check for weekends and weekdays
}

【问题讨论】:

    标签: c# .net algorithm date datetime


    【解决方案1】:

    基本上你想得到下一个工作日。因此,您可以在此条件下循环,将当前日期加 1 天

    do {
      date = date.AddDays(1);
    } while(IsHoliday(date) || IsWeekend(date));
    

    在前面的代码中,IsHoliday 是一个判断日期是否为假日的谓词。例如,无耻地重用你的代码:

    class Program
    {
        private static readonly HashSet<DateTime> Holidays = new HashSet<DateTime>();
    
        private static bool IsHoliday(DateTime date)
        {
            return Holidays.Contains(date);
        }
    
        private static bool IsWeekend(DateTime date)
        {
            return date.DayOfWeek == DayOfWeek.Saturday
                || date.DayOfWeek == DayOfWeek.Sunday;
        }
    
    
        private static DateTime GetNextWorkingDay(DateTime date)
        {
            do
            {
                date = date.AddDays(1);
            } while (IsHoliday(date) || IsWeekend(date));
            return date;
        }
    
        static void Main(string[] args)
        {
            Holidays.Add(new DateTime(DateTime.Now.Year, 1, 1));
            Holidays.Add(new DateTime(DateTime.Now.Year, 1, 5));
            Holidays.Add(new DateTime(DateTime.Now.Year, 3, 10));
            Holidays.Add(new DateTime(DateTime.Now.Year, 12, 25));
    
            var dt = GetNextWorkingDay(DateTime.Parse(@"2015-10-31"));
    
            Console.WriteLine(dt);
    
            Console.ReadKey();
        }
    }
    

    【讨论】:

    • +1 并通过使IsHoliday 成为一个函数,您可以避免构建静态的假期列表,而是动态计算传入的日期/年份 - 这对于像复活节这样移动的日期很重要.
    • IsHolliday(date) 是什么?这种方法存在吗?
    • @Fabjan 你必须自己实现这个方法。例如,您可以插入 OP 编写的用于检查日期是否为 hollidays 的代码。
    • 我更喜欢这个实现。比其他活跃的更简单。
    • 如果 GetNextWorkingDay() 返回与过去相同的日期(如果它是工作日)会稍微好一点 - 即删除过去必须已经被计算为工作日的假设。可能使用没有“do”的while循环来完成
    【解决方案2】:

    根据@fjardon 的回答,你可以使用我的项目Nager.Date 它包含100 多个国家的公共假期和周末。

    nuget

    PM> install-package Nager.Date
    

    代码 sn-p

    //usings
    using Nager.Date;
    using Nager.Date.Extensions;
    
    //logic
    var date = DateTime.Today; //Set start date
    var countryCode = CountryCode.US; //Set country
    
    do
    {
        date = date.AddDays(1);
    } while (DateSystem.IsPublicHoliday(date, countryCode) || date.IsWeekend(countryCode));
    

    【讨论】:

      【解决方案3】:

      固定的日期列表是表达假期的一种有限的方式。

      考虑到您的列表仅包含当年的日期,因此如果今天是 2015 年 12 月 30 日,那么下一个假期将是 2016 年 1 月 1 日 - 您不会在列表中找到它。

      还要考虑到很多假期不会在每年的同一天。它们通常与一周中的某一天相关联,有时它们由宗教日历或任意确定。

      一个更强大的系统应该能够处理各种不同类型的假期。这是一种可能的实现方式:

      public abstract class Holiday
      {
          public abstract DateTime? GetDate(int year);
      }
      
      public class MonthDayBasedHoliday : Holiday
      {
          private readonly int _month;
          private readonly int _day;
      
          public MonthDayBasedHoliday(int month, int day)
          {
              _month = month;
              _day = day;
          }
      
          public override DateTime? GetDate(int year)
          {
              return new DateTime(year, _month, _day);
          }
      }
      
      public class DayOfWeekBasedHoliday : Holiday
      {
          private readonly int _occurrence;
          private readonly DayOfWeek _dayOfWeek;
          private readonly int _month;
      
          public DayOfWeekBasedHoliday(int occurrence, DayOfWeek dayOfWeek, int month)
          {
              _occurrence = occurrence;
              _dayOfWeek = dayOfWeek;
              _month = month;
          }
      
          public override DateTime? GetDate(int year)
          {
              if (_occurrence <= 4)
              {
                  DateTime dt = new DateTime(year, _month, 1);
                  int delta = (_dayOfWeek - dt.DayOfWeek + 7) % 7;
                  delta += 7 * (_occurrence - 1);
                  return dt.AddDays(delta);
              }
              else  // last occurrence in month
              {
                  int daysInMonth = DateTime.DaysInMonth(year, _month);
                  DateTime dt = new DateTime(year, _month, daysInMonth);
                  int delta = (dt.DayOfWeek - _dayOfWeek + 7) % 7;
                  return dt.AddDays(-delta);
              }
          }
      }
      
      public class FixedDateBasedHoliday : Holiday
      {
          private readonly IDictionary<int, DateTime> _dates;
      
          public FixedDateBasedHoliday(params DateTime[] dates)
          {
              _dates = dates.ToDictionary(x => x.Year, x => x);
          }
      
          public override DateTime? GetDate(int year)
          {
              if (_dates.ContainsKey(year))
                  return _dates[year];
      
              // fixed date not established for year
              return null;
          }
      }
      

      有了这些定义,我们现在可以更加稳健地定义假期,例如:

      var holidays = new List<Holiday>();
      
      // New Year's Day
      holidays.Add(new MonthDayBasedHoliday(1, 1));
      
      // President's Day (US)
      holidays.Add(new DayOfWeekBasedHoliday(3, DayOfWeek.Monday, 2));
      
      // Easter (Western Observance)
      holidays.Add(new FixedDateBasedHoliday(new DateTime(2015, 4, 5), new DateTime(2016, 3, 27)));
      
      // Memorial Day (US)
      holidays.Add(new DayOfWeekBasedHoliday(5, DayOfWeek.Monday, 5));
      
      // Christmas Day
      holidays.Add(new MonthDayBasedHoliday(12, 25));
      

      现在,我们可以根据您的要求创建一个检查下一个工作日的方法:

      public static DateTime GetNextNonHolidayWeekDay(DateTime date, IList<Holiday> holidays, IList<DayOfWeek> weekendDays)
      {
          // always start with tomorrow, and truncate time to be safe
          date = date.Date.AddDays(1);
      
          // calculate holidays for both this year and next year
          var holidayDates = holidays.Select(x => x.GetDate(date.Year))
              .Union(holidays.Select(x => x.GetDate(date.Year + 1)))
              .Where(x=> x != null)
              .Select(x=> x.Value)
              .OrderBy(x => x).ToArray();
      
          // increment until we get a non-weekend and non-holiday date
          while (true)
          {
              if (weekendDays.Contains(date.DayOfWeek) || holidayDates.Contains(date))
                  date = date.AddDays(1);
              else
                  return date;
          }
      }
      

      该方法可以在抽象的Holiday 类中使用,也可以在任何地方使用。

      示例用法(上面对holidays的定义):

      var weekendDays = new[] { DayOfWeek.Saturday, DayOfWeek.Sunday };
      
      DateTime workDay = GetNextNonHolidayWeekDay(new DateTime(2015, 12, 31), holidays, weekendDays);
      // returns 2016-01-04
      

      尽管如此,这仍然不是一个完整的解决方案。许多假期有更复杂的计算规则。作为留给读者的练习,尝试在美国感恩节假期的第二天实现一个派生自Holiday 的类。第一天总是在 11 月的第 4 个星期四,但第二天总是“11 月的第 4 个星期四之后的星期五”,而不仅仅是“11 月的第 4 个星期五”(参见 2019 年 11 月的示例)很重要)。

      【讨论】:

      • 您模拟假期的方式似乎非常合适。这是一个很好的答案。
      【解决方案4】:
      var Holidays = new List<DateTime>();
      Holidays.Add(new DateTime(DateTime.Now.Year, 1, 1));
      Holidays.Add(new DateTime(DateTime.Now.Year, 1, 5));
      Holidays.Add(new DateTime(DateTime.Now.Year, 3, 10));
      Holidays.Add(new DateTime(DateTime.Now.Year, 12, 25));
      
      var exclude = new List<DayOfWeek> {DayOfWeek.Saturday, DayOfWeek.Sunday};
      
      var targetDate = new DateTime(2015, 12, 24);
      
      
      var myDate = Enumerable.Range(1, 30)
        .Select( i => targetDate.AddDays(i) )
        .First(a =>  
        !( exclude.Contains( a.DayOfWeek ) || 
           Holidays.Contains(a)) );
      

      【讨论】:

      • 您应该从 targetDate 而不是当前日期开始 datesAhead。那么就不需要a &gt; targetDate
      • 好吧,因为它只会迭代直到匹配到传递给Enumerable.Range 的计数只需要超过直到下一个工作日的最大天数。我的猜测是 5 或 6,但增大它不会产生负面影响。
      【解决方案5】:

      我的项目中有类似的需求,我使用 SQl Server 检索。 如果你可以在 SQL server 中做,我们有一个非常简单的查询,它返回下一个工作日。

      DECLARE @dt datetime='20150113' 
      SELECT DATEADD(dd,CASE WHEN DATEDIFF(dd,0,@dt)%7 > 3 THEN 7-DATEDIFF(dd,0,@dt)%7 ELSE 1 END,@dt)
      

      解释:
      根据 DB
      ,基准日期始终为 '1900 jan 1 st' 19000101 --> 星期一 --> -- 所以结果是 THEN VALUE =1 天
      19000102 --> 星期二 --> -- 所以结果是 THEN VALUE =1 天
      19000103 --> 星期三 --> -- 所以结果是 THEN VALUE =1 天
      19000104 --> 星期四 --> -- 所以结果是 THEN VALUE =1 天
      19000105 --> 星期五 --> -- 所以结果是 7-@number = 3 天
      19000106 --> 星期六 --> -- 所以结果是 7-@number =2 天
      19000107 --> 星期日 --> -- 所以结果是 7-@number =1 天

      【讨论】:

      • 我觉得这是最简单的方法,它对我有用。如果有人没有得到正确的输出,请告诉我,我可以提供帮助
      • 完全忽略了问题的“和假期”部分。
      猜你喜欢
      • 1970-01-01
      • 2021-10-02
      • 2012-08-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多