web-dev-qa-db-ja.com

C#で2つの日付の間の平日数を計算します

平日の間の日付を繰り返して数えることなく、2つの指定された日付の間の平日の数を取得するにはどうすればよいですか?

かなり簡単に思えますが、次のことを順守する決定的な正解を見つけることができないようです。

  1. 合計は包括的である必要があるため、GetNumberOfWeekdays(new DateTime(2009,11,30)、new DateTime(2009,12,4))は月曜日から金曜日の5に等しくなります。
  2. うるう日を考慮に入れる必要があります
  3. 平日を数えながら、その間のすべての日付を繰り返すだけではありません。

同様の質問回答 が近づいていますが正しくありません

23
David Glenn

これから リンク

    public static int Weekdays(DateTime dtmStart, DateTime dtmEnd)
    {
        // This function includes the start and end date in the count if they fall on a weekday
        int dowStart = ((int)dtmStart.DayOfWeek == 0 ? 7 : (int)dtmStart.DayOfWeek);
        int dowEnd = ((int)dtmEnd.DayOfWeek == 0 ? 7 : (int)dtmEnd.DayOfWeek);
        TimeSpan tSpan = dtmEnd - dtmStart;
        if (dowStart <= dowEnd)
        {
            return (((tSpan.Days / 7) * 5) + Math.Max((Math.Min((dowEnd + 1), 6) - dowStart), 0));
        }
        return (((tSpan.Days / 7) * 5) + Math.Min((dowEnd + 6) - Math.Min(dowStart, 6), 5));
    }


  [1]: http://www.eggheadcafe.com/community/aspnet/2/44982/how-to-calculate-num-of-w.aspx

テスト(各テストは5を返します):

    int ndays = Weekdays(new DateTime(2009, 11, 30), new DateTime(2009, 12, 4));
    System.Console.WriteLine(ndays);

    // leap year test
    ndays = Weekdays(new DateTime(2000, 2,27), new DateTime(2000, 3, 5));
    System.Console.WriteLine(ndays);

    // non leap year test
    ndays = Weekdays(new DateTime(2007, 2, 25), new DateTime(2007, 3, 4));
    System.Console.WriteLine(ndays);
12
dcp

O(1)ソリューション:

// Count days from d0 to d1 inclusive, excluding weekends
public static int countWeekDays(DateTime d0, DateTime d1)
{
    int ndays = 1 + Convert.ToInt32((d1 - d0).TotalDays);
    int nsaturdays = (ndays + Convert.ToInt32(d0.DayOfWeek)) / 7;
    return ndays - 2 * nsaturdays
           - (d0.DayOfWeek == DayOfWeek.Sunday ? 1 : 0)
           + (d1.DayOfWeek == DayOfWeek.Saturday ? 1 : 0);
}

2014年1月の例:

    January 2014
Su Mo Tu We Th Fr Sa
          1  2  3  4
 5  6  7  8  9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

countWeekDays(new DateTime(2014, 1, 1), new DateTime(2014, 1, 1)); // 1
countWeekDays(new DateTime(2014, 1, 1), new DateTime(2014, 1, 2)); // 2
countWeekDays(new DateTime(2014, 1, 1), new DateTime(2014, 1, 3)); // 3
countWeekDays(new DateTime(2014, 1, 1), new DateTime(2014, 1, 4)); // 3
countWeekDays(new DateTime(2014, 1, 1), new DateTime(2014, 1, 5)); // 3
countWeekDays(new DateTime(2014, 1, 1), new DateTime(2014, 1, 6)); // 4

N.B. DateTime入力は、ほぼ同じ時刻にする必要があります。上記の例のように、年、月、日のみに基づいてDateTimeオブジェクトを作成している場合は、問題ないはずです。反例として、1月1日の午前12:01から1月2日の午後11:59までは2日間しかありませんが、これらの時間を使用すると、上記の関数は3をカウントします。

13
Matt

最終日が土曜日または日曜日の場合、eFlohの回答には1日余分にありました。これで修正されます。

     public static int Weekdays(DateTime dtmStart, DateTime dtmEnd)
    {
        if (dtmStart > dtmEnd)
        {
            DateTime temp = dtmStart;
            dtmStart = dtmEnd;
            dtmEnd = temp;
        }

        /* Move border dates to the monday of the first full week and sunday of the last week */
        DateTime startMonday = dtmStart;
        int startDays = 1;
        while (startMonday.DayOfWeek != DayOfWeek.Monday)
        {
            if (startMonday.DayOfWeek != DayOfWeek.Saturday && startMonday.DayOfWeek != DayOfWeek.Sunday)
            {
                startDays++;
            }
            startMonday = startMonday.AddDays(1);
        }

        DateTime endSunday = dtmEnd;
        int endDays = 0;
        while (endSunday.DayOfWeek != DayOfWeek.Sunday)
        {
            if (endSunday.DayOfWeek != DayOfWeek.Saturday && endSunday.DayOfWeek != DayOfWeek.Sunday)
            {
                endDays++;
            }
            endSunday = endSunday.AddDays(1);
        }

        int weekDays;

        /* calculate weeks between full week border dates and fix the offset created by moving the border dates */
        weekDays = (Math.Max(0, (int)Math.Ceiling((endSunday - startMonday).TotalDays + 1)) / 7 * 5) + startDays - endDays;

        if (dtmEnd.DayOfWeek == DayOfWeek.Saturday || dtmEnd.DayOfWeek == DayOfWeek.Sunday)
        {
            weekDays -= 1;
        }

        return weekDays; 

    }
3
bhg

これは、dcpによるソリューションよりもうまくいくはずです。

    /// <summary>
    /// Count Weekdays between two dates
    /// </summary>
    /// <param name="dtmStart">first date</param>
    /// <param name="dtmEnd">second date</param>
    /// <returns>weekdays between the two dates, including the start and end day</returns>
    internal static int getWeekdaysBetween(DateTime dtmStart, DateTime dtmEnd)
    {
        if (dtmStart > dtmEnd)
        {
            DateTime temp = dtmStart;
            dtmStart = dtmEnd;
            dtmEnd = temp;
        }

        /* Move border dates to the monday of the first full week and sunday of the last week */
        DateTime startMonday = dtmStart;
        int startDays = 1;
        while (startMonday.DayOfWeek != DayOfWeek.Monday)
        {
            if (startMonday.DayOfWeek != DayOfWeek.Saturday && startMonday.DayOfWeek != DayOfWeek.Sunday)
            {
                startDays++;
            }
            startMonday = startMonday.AddDays(1);
        }

        DateTime endSunday = dtmEnd;
        int endDays = 0;
        while (endSunday.DayOfWeek != DayOfWeek.Sunday)
        {
            if (endSunday.DayOfWeek != DayOfWeek.Saturday && endSunday.DayOfWeek != DayOfWeek.Sunday)
            {
                endDays++;
            }
            endSunday = endSunday.AddDays(1);
        }

        int weekDays;

        /* calculate weeks between full week border dates and fix the offset created by moving the border dates */
        weekDays = (Math.Max(0, (int)Math.Ceiling((endSunday - startMonday).TotalDays + 1)) / 7 * 5) + startDays - endDays;

        return weekDays;
    }
2
eFloh

正/負(絶対値ではない)が必要だったので、次のように解決しました。

    public static int WeekdayDifference(DateTime StartDate, DateTime EndDate)
    {
        DateTime thisDate = StartDate;
        int weekDays = 0;
        while (thisDate != EndDate)
        {
            if (thisDate.DayOfWeek != DayOfWeek.Saturday && thisDate.DayOfWeek != DayOfWeek.Sunday) { weekDays++; }
            if (EndDate > StartDate) { thisDate = thisDate.AddDays(1); } else { thisDate = thisDate.AddDays(-1); }
        }

        /* Determine if value is positive or negative */
        if (EndDate > StartDate) {
            return weekDays;
        }
        else
        {
            return weekDays * -1;
        }
    }
2
Scott R. Frost

日付の範囲を取得するユーティリティ関数:

public IEnumerable<DateTime> GetDates(DateTime begin, int count)
{
    var first = new DateTime(begin.Year, begin.Month, begin.Day);
    var maxYield = Math.Abs(count);
    for (int i = 0; i < maxYield; i++)
    {
        if(count < 0)
            yield return first - TimeSpan.FromDays(i);
        else
            yield return first + TimeSpan.FromDays(i);      
    }
    yield break;
}

public IEnumerable<DateTime> GetDates(DateTime begin, DateTime end)
{
    var days = (int)Math.Ceiling((end - begin).TotalDays);
    return GetDates(begin, days);
}

LINQPadデモコード:

var begin = DateTime.Now;
var end = begin + TimeSpan.FromDays(14);

var dates = GetDates(begin, end);
var weekdays = dates.Count(x => x.DayOfWeek != DayOfWeek.Saturday && x.DayOfWeek != DayOfWeek.Sunday);
var mondays = dates.Count(x => x.DayOfWeek == DayOfWeek.Monday);
var firstThursday = dates
    .OrderBy(d => d)
    .FirstOrDefault(d => d.DayOfWeek == DayOfWeek.Thursday);

dates.Dump("Dates in Range");
weekdays.Dump("Count of Weekdays");
mondays.Dump("Count of Mondays");
firstThursday.Dump("First Thursday");
1
Casey Chester
  public static List<DateTime> Weekdays(DateTime startDate, DateTime endDate)
  {
      if (startDate > endDate)
      {
          Swap(ref startDate, ref endDate);
      }
      List<DateTime> days = new List<DateTime>();

      var ts = endDate - startDate;
      for (int i = 0; i < ts.TotalDays; i++)
      {
          var cur = startDate.AddDays(i);
          if (cur.DayOfWeek != DayOfWeek.Saturday && cur.DayOfWeek != DayOfWeek.Sunday)
              days.Add(cur);
          //if (startingDate.AddDays(i).DayOfWeek != DayOfWeek.Saturday || startingDate.AddDays(i).DayOfWeek != DayOfWeek.Sunday)
          //yield return startingDate.AddDays(i);
      }
      return days;
  }

そしてスワップ日付

  private static void Swap(ref DateTime startDate, ref DateTime endDate)
  {
      object a = startDate;
      startDate = endDate;
      endDate = (DateTime)a;
  }
1
Yaseer Arafat

これは古い質問ですが、曜日を削除できる、より柔軟な回答を共有すると思いました。

最大6日間ループするだけで効率を維持しながら、コードをクリーンで読みやすく保つようにしました。

したがって、OPの場合、次のように使用できます。

    myDate.DaysUntill(DateTime.UtcNow, new List<DayOfWeek> { DayOfWeek.Saturday, DayOfWeek.Sunday });


    /// <summary>
    /// For better accuracy make sure <paramref name="startDate"/> and <paramref name="endDate"/> have the same time zone.
    /// This is only really useful if we use <paramref name="daysOfWeekToExclude"/> - otherwise the computation is trivial.
    /// </summary>
    /// <param name="startDate"></param>
    /// <param name="endDate"></param>
    /// <param name="daysOfWeekToExclude"></param>
    /// <returns></returns>
    public static int DaysUntill(this DateTime startDate, DateTime endDate, IEnumerable<DayOfWeek> daysOfWeekToExclude = null)
    {
        if (startDate >= endDate) return 0;
        daysOfWeekToExclude = daysOfWeekToExclude?.Distinct() ?? new List<DayOfWeek>();

        int nbOfWeeks = (endDate - startDate).Days / 7;
        int nbOfExtraDays = (endDate - startDate).Days % 7;

        int result = nbOfWeeks * (7 - daysOfWeekToExclude.Count());

        for (int i = 0; i < nbOfExtraDays; i++)
        {
            if (!daysOfWeekToExclude.Contains(startDate.AddDays(i + 1).DayOfWeek)) result++;
        }
        return result;
    }
0
Frederic

ここでは、2つの日付の間のDayOfWeekのカウントを計算する関数です。包括的に計算するように注意してください(計算の開始日と終了日を含みます)。

private int GetWeekdayCount(DayOfWeek dayOfWeek, DateTime begin, DateTime end)
    {
        if (begin < end)
        {
            var timeSpan = end.Subtract(begin);
            var fullDays = timeSpan.Days;
            var count = fullDays / 7; // количество дней равно как минимум количеству полных недель попавших в диапазон
            var remain = fullDays % 7; // остаток от деления

            // и вычислим попал ли еще один день в те куски недели, что остаются от полной
            if (remain > 0)
            {
                var dowBegin = (int)begin.DayOfWeek;
                var dowEnd = (int)end.DayOfWeek;
                var dowDay = (int)dayOfWeek;
                if (dowBegin < dowEnd)
                {
                    // когда день недели начала меньше дня недели конца, например:
                    //  начало       конец
                    //    \/          \/
                    //    -- -- -- -- --
                    // Вс Пн Вт Ср Чт Пт Сб
                    if (dowDay >= dowBegin && dowDay <= dowEnd)
                        count++;
                }
                else
                {
                    // когда день недели начала больше дня недели конца, например:
                    //   конец      начало
                    //    \/          \/
                    // -- --          -- --
                    // Вс Пн Вт Ср Чт Пт Сб
                    if (dowDay <= dowEnd || dowDay >= dowBegin)
                        count++;
                }
            }
            else if (begin.DayOfWeek == dayOfWeek)
                count++;

            return count;
        }
        return 0;
    }

これは、前の関数のもう1つの単純なアナログです。

private int GetWeekdayCountStupid(DayOfWeek dayOfWeek, DateTime begin, DateTime end)
    {
        if (begin < end)
        {
            var count = 0;
            var day = begin;
            while (day <= end)
            {
                if (day.DayOfWeek == dayOfWeek)
                    count++;
                day = day.AddDays(1);
            }
            return count;
        }
        return 0;
    }

そして、両方の機能をテストします。

    [TestMethod()]
    public void TestWeekdayCount()
    {
        var init = new DateTime(2000, 01, 01);
        for (int day = 0; day < 7; day++)
        {
            var dayOfWeek = (DayOfWeek)day;
            for (int shift = 0; shift < 8; shift++)
            {
                for (int i = 0; i < 365; i++)
                {
                    var begin = init.AddDays(shift);
                    var end = init.AddDays(shift + i);
                    var count1 = GetWeekdayCount(dayOfWeek, begin, end);
                    var count2 = GetWeekdayCountStupid(dayOfWeek, begin, end);
                    Assert.AreEqual(count1, count2);
                }
            }
        }
    }
0
Fomin Dmitry
var dates = new List<DateTime>();

        for (var dt = YourStartDate; dt <= YourEndDate; dt = dt.AddDays(1))
        {

            if (dt.DayOfWeek != DayOfWeek.Sunday && dt.DayOfWeek != DayOfWeek.Saturday)
            { dates.Add(dt); }

        }

このコードでは、2つの日付の間のすべての営業日をリストすることができます。

これらの日付のカウントが必要な場合は、dates.Countを整数として取得できます。または、毎日取得したい場合は、リストを文字列に結合できます。

0
Memik