Tính số ngày làm việc giữa hai ngày?


Câu trả lời:


120

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ụ firstDaylastDaycả 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ó firstDayOfWeeklastDayOfWeekđượ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à:

  • Thứ sáu đến thứ sáu -> 1
  • Thứ Bảy đến Thứ Bảy -> 0
  • Chủ nhật đến Chủ nhật -> 0
  • Thứ sáu đến thứ bảy -> 1
  • Thứ Sáu đến Chủ Nhật -> 1
  • Thứ sáu đến thứ hai -> 2
  • Thứ Bảy đến Thứ Hai -> 1
  • Chủ nhật đến thứ hai -> 1
  • Thứ Hai đến Thứ Hai -> 1

1
+1 Đó có lẽ là cách dễ nhất và hiệu quả nhất để thực hiện (giải pháp của tôi đến từ C ++ không sử dụng sự hỗ trợ của TimeSpan, C # làm cho một số tác vụ dễ dàng hơn nhiều). BankHolidays cũng là một liên lạc tuyệt vời!
RedGlyph

2
Ngoài ra, hãy đảm bảo rằng ngày nghỉ của ngân hàng như sau: if (firstDay <= bh && bh <= lastDay && bh.IsWorkingDay ())
Tawani

5
Cảm ơn vì phương pháp. Mặc dù vậy, tôi đã phải thêm phần sau vào câu lệnh if-statement / lặp lại các ngày lễ ngân hàng:, nếu không && !(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.
KristianB

Tôi đã thay đổi vòng lặp cuối cùng cho một câu lệnh Linq: businessDays - = bankHolidays.Select (bankHoliday => bankHoliday.Date) .Count (bh => firstDay <= bh && bh <= lastDay);
JoanComasFdz

1
Ngoài ra, họ là những quốc gia không có ngày cuối tuần vào thứ bảy, chủ nhật. Xem liên kết này để biết thêm thông tin: en.wikipedia.org/wiki/Workweek_and_weekend
Gatej Alexandru

104

Đồ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.


10
Làm việc tốt, nhưng có lẽ sử dụng DayOfWeek tự đóng gói thay vì chuyển chúng thành int?
Neo

3
Nghiêm túc, giải pháp tốt nhất hiện có. Cheers Alec
Mizmor

6
Lưu ý rằng mặc dù hàm này trả về giá trị gấp đôi, nó chỉ nên được tin cậy để cung cấp cho cả ngày làm việc. Nó không trả về câu trả lời chính xác cho các ngày phân số khi thời gian có liên quan.
Pakman

4
Chỉ cần để nhận xét, với '1+' nó giả bắt đầu trong ngày đầu tiên cho đến cuối của ngày cuối cùng, nếu không có sự '1+' nó giả cuối của ngày đầu tiên cho đến khi cuối cùng của ngày cuối cùng. Tôi đã mất một thời gian để tìm ra điều đó, vì tôi đã giả định bắt đầu ngày đầu tiên cho đến đầu ngày cuối cùng, điều này có ý nghĩa hơn đối với tôi.
Jeffry van de Vuurst

11
Đây KHÔNG phải là câu trả lời chính xác. Số ngày có thể kéo dài đến 4. Gần như đúng, không tính đến thời điểm bắt đầu và kết thúc của ngày cuối tuần, đó là một chút khó khăn nhất. Phần bắt đầu - kết thúc cũng không được nằm trong dấu ngoặc đơn. Nó không liên quan gì đến vấn đề. 60% thời gian giải pháp này là SAI .
Owl

47

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;
}

"ở đâu" có thể là "đếm" để rút ngắn nó
bygrace

1
Rõ ràng hơn nhiều, và các giải pháp được liệt kê sẽ giúp loại bỏ các ngày nghỉ ngân hàng. Tuy nhiên, số lượng lớn chúng chậm hơn nhiều; Trong LINQPad, việc tính toán số ngày làm việc cho khoảng cách 90 ngày trong vòng lặp 1 triệu lần mất 10 giây bằng cách sử dụng giải pháp này và chỉ khoảng 0,2 giây khi sử dụng câu trả lời được chấp nhận hoặc câu trả lời đẹp hơn nhiều của Alec Pojidaev.
Whelkaholism

Để được bao gồm, mã phải là: return Enumerable .Range (0, dayDifference + 1) ...
Edza

không trở lại những ngày trong quá khứ. Như -18 ngày làm việc.
iwtu

@iwtu Điều này giả định rằng to > from. Có lẽ đó là vấn đề?
Alpha

22

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() ;

Bạn sẽ không chỉ tiếp tục và kéo dài khoảng thời gian để có thể sử dụng nó - vì anh ấy đã nói rằng anh ấy muốn sử dụng khoảng cách giữa hai ngày chứ không phải danh sách ngày?
WesleyJohnson

Khoảng cách giữa hai ngày là số ngày giữa, vì vậy Count () là đủ.
Công ty Carles

3
Tôi không chắc tại sao đây là một câu trả lời phù hợp ... anh ấy không có danh sách các ngày riêng lẻ, anh ấy có hai ngày và anh ấy muốn tìm số ngày làm việc giữa chúng. Để sử dụng giải pháp này, bạn phải cung cấp một chức năng khác tạo ra một danh sách mọi ngày giữa hai năm.
Adam Robinson

1
adam, đây là một ví dụ đơn giản với số lượng mã tối thiểu cần thiết để thể hiện một khái niệm. Trong câu trả lời ban đầu của tôi, tôi cũng bao gồm một vòng lặp đưa danh sách allDates mà từ đó tôi đã trừu tượng hóa thành hàm "GetDates". Có thể dễ dàng chuyển bài kiểm tra IsWorkingDay ra khỏi câu lệnh LINQ và vào vòng lặp đó. Cá nhân tôi thích cách nó bây giờ mặc dù vì nó rất dễ đọc về những gì đang xảy ra.
Qwerty

10
Thể thiếu bằng cách thay đổi đâu Count, và loại bỏ Đếm
recursive

12

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");
    }
}

tại sao bạn bắt đầu đếm bằng cách thêm 1 ngày vào "from" @ for (var date = from.AddDays (1); date <= to; date = date.AddDays (1))?
Oncel Umut TURER

6

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);
    }
}

5

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);
    }
}

Ý tưởng cho việc này đến từ một giải pháp SQL mà tôi đã tìm thấy khi bị tràn ngăn xếp. Ý tưởng của họ rất chắc chắn nhưng thật đáng buồn là nó cũng có một lỗi. Nó hoạt động cho các giá trị + ve nhưng ánh xạ bảng tra cứu của chúng không chính xác cho các giá trị -ve.
Tony O'Hagan

4

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;
    }

hàm Issamedate bị thiếu nhưng chỉ là public static bool IsSameDay (DateTime date1, DateTime date2) {return date1.Date == date2.Date; }
Choco Smith

Bạn có thể sử dụng bảng tra cứu mảng int thay vì khởi tạo các đối tượng Ngày mới.
TheRealChx101

3

Đâ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ớ.


1
Phương thức này không trả về số ngày làm việc giữa hai ngày, nó trả về ngày làm việc giữa hai ngày. Mã bạn đang đề xuất rất rõ ràng và tôi thích cách sử dụng lợi nhuận, nhưng nó không trả lời câu hỏi.
Martin

3

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:

  1. Không phải thứ bảy
  2. Không phải là chủ nhật
  3. Không phải là ngày lễ quốc gia

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));

        }

1

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;
    }

2
Điều này trả về -1 nếu được gọi cùng với Thứ Bảy và Chủ Nhật.
Whelkaholism

1
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;
        }
    }
}

1

Hoạt động và không có vòng lặp

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

  • t: Tổng số ngày giữa các ngày (1 nếu min = max)
  • a + b: Cần thêm ngày để mở rộng tổng số tuần lên đầy đủ
  • k: 1,4 là số ngày trong tuần mỗi tuần, tức là, (t / 7) * 5
  • c: Số ngày trong tuần cần trừ vào tổng số
  • m: Bảng tra cứu được sử dụng để tìm giá trị của "c" cho mỗi ngày trong tuần

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;
}

1
làm thế nào bạn có thể có K với giá trị 1,4?
toha

0

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;
        }

0

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

0
    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);
    }

0

Đâ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);
    }
}

0

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;
    }

0

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;
}

0

Đâ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;
    }

0

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;
    }

0

Đâ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;
}

0

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;
    }

0

Đâ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


0

Đâ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;
}

0

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;
}

0

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;
    }

0
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.


0

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


-1

Đâ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;
    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.