AddBusinessDays i GetBusinessDays


93

Muszę znaleźć 2 eleganckie kompletne implementacje

public static DateTime AddBusinessDays(this DateTime date, int days)
{
 // code here
}

and 

public static int GetBusinessDays(this DateTime start, DateTime end)
{
 // code here
}

Preferowane O (1) (bez pętli).

EDYCJA: Przez dni robocze rozumiem dni robocze (poniedziałek, wtorek, środa, czwartek, piątek). Żadnych świąt, tylko weekendy wyłączone.

Mam już brzydkie rozwiązania, które wydają się działać, ale zastanawiam się, czy są na to eleganckie sposoby. Dzięki


To właśnie napisałem do tej pory. Działa we wszystkich przypadkach i działa też negatywnie. Nadal potrzebuję wdrożenia GetBusinessDays

public static DateTime AddBusinessDays(this DateTime startDate,
                                         int businessDays)
{
    int direction = Math.Sign(businessDays);
    if(direction == 1)
    {
        if(startDate.DayOfWeek == DayOfWeek.Saturday)
        {
            startDate = startDate.AddDays(2);
            businessDays = businessDays - 1;
        }
        else if(startDate.DayOfWeek == DayOfWeek.Sunday)
        {
            startDate = startDate.AddDays(1);
            businessDays = businessDays - 1;
        }
    }
    else
    {
        if(startDate.DayOfWeek == DayOfWeek.Saturday)
        {
            startDate = startDate.AddDays(-1);
            businessDays = businessDays + 1;
        }
        else if(startDate.DayOfWeek == DayOfWeek.Sunday)
        {
            startDate = startDate.AddDays(-2);
            businessDays = businessDays + 1;
        }
    }

    int initialDayOfWeek = (int)startDate.DayOfWeek;

    int weeksBase = Math.Abs(businessDays / 5);
    int addDays = Math.Abs(businessDays % 5);

    if((direction == 1 && addDays + initialDayOfWeek > 5) ||
         (direction == -1 && addDays >= initialDayOfWeek))
    {
        addDays += 2;
    }

    int totalDays = (weeksBase * 7) + addDays;
    return startDate.AddDays(totalDays * direction);
}

14
Czy istnieją eleganckie rozwiązania, jeśli chodzi o coś tak nielogicznego jak daty?
Wyatt Barnett

Martwisz się wakacjami? - James Conigliaro. Nie
Adrian Zanescu

9
Głosowanie na ludzi, którzy próbują pomóc, nie jest zwycięską strategią.
Jamie Ide

1
Krótka notatka o AddBusinessDaysimplementacji w powyższym pytaniu (która w rzeczywistości była usuniętą odpowiedzią, którą zaproponowałem cofnąć; mod skopiował tę odpowiedź na pytanie zamiast tego): Moim zdaniem to rozwiązanie jest lepsze niż wszystkie dotychczasowe odpowiedzi, ponieważ jest to jedyne taki, który poprawnie radzi sobie z wartościami ujemnymi, z sobotą i niedzielą jako źródłem i nie potrzebuje biblioteki innej firmy. (Zrobiłem tutaj mały program do testowania różnych rozwiązań). Dodałbym tylko if (businessDays == 0) return startDate;na początku metody, aby uzyskać poprawny wynik również dla tego przypadku skrajnego.
Slauma

1
@AZ .: Pierwsze usunięcie było dość stare. Po mojej prośbie o cofnięcie usunięcia Twojej odpowiedzi mod cofnął usunięcie odpowiedzi (przez 30 sekund), aby skopiować treść pod Twoim pytaniem, a następnie ponownie ją usunął. Dlatego twoja odpowiedź ma ten ostatni znacznik czasu usunięcia. Napisałem powyższy komentarz, ponieważ dla mojego celu AddBusinessDaysbyło to najbardziej ogólne rozwiązanie, które działało we wszystkich przypadkach, których potrzebuję. Skopiowałem go do jednego z moich aktualnych projektów (po drobnych modyfikacjach i przetłumaczeniu na C ++), dzięki za kod :) Bardzo pomogło, ponieważ zaskakująco trudno jest poprawnie wszystkie skrajne przypadki.
Slauma

Odpowiedzi:


135

Ostatnia próba wykonania pierwszej funkcji:

public static DateTime AddBusinessDays(DateTime date, int days)
{
    if (days < 0)
    {
        throw new ArgumentException("days cannot be negative", "days");
    }

    if (days == 0) return date;

    if (date.DayOfWeek == DayOfWeek.Saturday)
    {
        date = date.AddDays(2);
        days -= 1;
    }
    else if (date.DayOfWeek == DayOfWeek.Sunday)
    {
        date = date.AddDays(1);
        days -= 1;
    }

    date = date.AddDays(days / 5 * 7);
    int extraDays = days % 5;

    if ((int)date.DayOfWeek + extraDays > 5)
    {
        extraDays += 2;
    }

    return date.AddDays(extraDays);

}

Drugą funkcję, GetBusinessDays, można zaimplementować w następujący sposób:

public static int GetBusinessDays(DateTime start, DateTime end)
{
    if (start.DayOfWeek == DayOfWeek.Saturday)
    {
        start = start.AddDays(2);
    }
    else if (start.DayOfWeek == DayOfWeek.Sunday)
    {
        start = start.AddDays(1);
    }

    if (end.DayOfWeek == DayOfWeek.Saturday)
    {
        end = end.AddDays(-1);
    }
    else if (end.DayOfWeek == DayOfWeek.Sunday)
    {
        end = end.AddDays(-2);
    }

    int diff = (int)end.Subtract(start).TotalDays;

    int result = diff / 7 * 5 + diff % 7;

    if (end.DayOfWeek < start.DayOfWeek)
    {
        return result - 2;
    }
    else{
        return result;
    }
}

W drugim przypadku jednym rozwiązaniem jest uwzględnienie różnicy między datą a datą + dniami. Jest to fajne, ponieważ gwarantuje prawidłową synchronizację obu funkcji i usuwa nadmiarowość.
Brian,

Podaj bieżącą datę, działaj przez 0 do 10 dni roboczych, zawsze kończy się niepowodzeniem w środę.
Adrian Godong,

1
Tak, w końcu tam dotarliśmy. (Mówię „my” za mój mały wkład!) Głosował za wysiłkiem.
Noldorin

Dzięki za Twój wkład Noldorin, niestety mogę tylko głosować za Twoimi komentarzami!
Patrick McDonald

3
DateTime.AddDays działa z liczbami ujemnymi. Nie jest to zgodne z tym samym wzorcem, co użycie liczb ujemnych z AddBusinessDays, który umożliwia wybranie dni wolnych od pracy.
Ristogod

63

przy użyciu Fluent DateTime :

var now = DateTime.Now;
var dateTime1 = now.AddBusinessDays(3);
var dateTime2 = now.SubtractBusinessDays(5);

kod wewnętrzny jest następujący

    /// <summary>
    /// Adds the given number of business days to the <see cref="DateTime"/>.
    /// </summary>
    /// <param name="current">The date to be changed.</param>
    /// <param name="days">Number of business days to be added.</param>
    /// <returns>A <see cref="DateTime"/> increased by a given number of business days.</returns>
    public static DateTime AddBusinessDays(this DateTime current, int days)
    {
        var sign = Math.Sign(days);
        var unsignedDays = Math.Abs(days);
        for (var i = 0; i < unsignedDays; i++)
        {
            do
            {
                current = current.AddDays(sign);
            }
            while (current.DayOfWeek == DayOfWeek.Saturday ||
                current.DayOfWeek == DayOfWeek.Sunday);
        }
        return current;
    }

    /// <summary>
    /// Subtracts the given number of business days to the <see cref="DateTime"/>.
    /// </summary>
    /// <param name="current">The date to be changed.</param>
    /// <param name="days">Number of business days to be subtracted.</param>
    /// <returns>A <see cref="DateTime"/> increased by a given number of business days.</returns>
    public static DateTime SubtractBusinessDays(this DateTime current, int days)
    {
        return AddBusinessDays(current, -days);
    }

To jedyne rozwiązanie, które faktycznie działało u mnie po konwersji na VB.Net
Nicholas

1
OP nie zażądał żadnych pętli, podczas gdy ten wyraźnie ma pętle. Nie ma nic eleganckiego w robieniu czegoś w najmniej efektywny sposób.
Neolisk

13

Stworzyłem rozszerzenie, które pozwala dodawać lub odejmować dni robocze. Do odejmowania użyj ujemnej liczby dni roboczych. Myślę, że to dość eleganckie rozwiązanie. Wydaje się, że działa we wszystkich przypadkach.

namespace Extensions.DateTime
{
    public static class BusinessDays
    {
        public static System.DateTime AddBusinessDays(this System.DateTime source, int businessDays)
        {
            var dayOfWeek = businessDays < 0
                                ? ((int)source.DayOfWeek - 12) % 7
                                : ((int)source.DayOfWeek + 6) % 7;

            switch (dayOfWeek)
            {
                case 6:
                    businessDays--;
                    break;
                case -6:
                    businessDays++;
                    break;
            }

            return source.AddDays(businessDays + ((businessDays + dayOfWeek) / 5) * 2);
        }
    }
}

Przykład:

using System;
using System.Windows.Forms;
using Extensions.DateTime;

namespace AddBusinessDaysTest
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            label1.Text = DateTime.Now.AddBusinessDays(5).ToString();
            label2.Text = DateTime.Now.AddBusinessDays(-36).ToString();
        }
    }
}

Wynik jest wątpliwy, jeśli data źródłowa to sobota lub niedziela. Na przykład: Sobota + 1 dzień roboczy to wtorek, gdzie raczej spodziewam się poniedziałku.
Slauma

3
@Slauma: Tak działa większość firm w Kanadzie. +1 dzień roboczy = „następny dzień roboczy”, co w przypadku soboty to wtorek. Poniedziałek to „ten sam dzień roboczy”.
Neolisk

3
@Slauma Program działa zgodnie z przeznaczeniem. Pomyśl o tym logicznie. Jeśli coś związanego z biznesem rozpocznie się w sobotę i musisz dać ludziom 1 dzień roboczy na reakcję w ciągu tego dnia roboczego, czy rozsądnie byłoby powiedzieć im, że należy to zrobić do poniedziałku ?!
Riegardt Steyn

8

Dla mnie musiałem znaleźć rozwiązanie, które pomija weekendy i wychodzi zarówno negatywnie, jak i pozytywnie. Moje kryteria były takie, że jeśli wyląduje w weekend, będzie musiał przejść do poniedziałku. Gdyby cofał się i wylądował w weekend, musiałby przeskoczyć do piątku.

Na przykład:

  • Środa - 3 dni robocze = ostatni piątek
  • Środa + 3 dni robocze = poniedziałek
  • Piątek - 7 dni roboczych = Ostatnia środa
  • Wtorek - 5 dni roboczych = ostatni wtorek

Cóż, masz pomysł;)

Skończyło się na napisaniu tej klasy rozszerzającej

public static partial class MyExtensions
{
    public static DateTime AddBusinessDays(this DateTime date, int addDays)
    {
        while (addDays != 0)
        {
            date = date.AddDays(Math.Sign(addDays));
            if (MyClass.IsBusinessDay(date))
            {
                addDays = addDays - Math.Sign(addDays);
            }
        }
        return date;
    }
}

Korzysta z tej metody, którą moim zdaniem przyda się w innym miejscu ...

public class MyClass
{
    public static bool IsBusinessDay(DateTime date)
    {
        switch (date.DayOfWeek)
        {
            case DayOfWeek.Monday:
            case DayOfWeek.Tuesday:
            case DayOfWeek.Wednesday:
            case DayOfWeek.Thursday:
            case DayOfWeek.Friday:
                return true;
            default:
                return false;
        }
    }
}

Jeśli nie chcesz się tym przejmować, możesz po prostu zastąpić if (MyClass.IsBusinessDay(date))ifif ((date.DayOfWeek != DayOfWeek.Saturday) && (date.DayOfWeek != DayOfWeek.Sunday))

Więc teraz możesz to zrobić

var myDate = DateTime.Now.AddBusinessDays(-3);

lub

var myDate = DateTime.Now.AddBusinessDays(5);

Oto wyniki niektórych testów:

Oczekiwany wynik testu
Środa -4 dni robocze Czwartek Czwartek
Środa -3 dni robocze piątek piątek
Środa +3 dni robocze Poniedziałek Poniedziałek
Piątek -7 dni roboczych Środa Środa
Wtorek -5 dni roboczych wtorek wtorek
Piątek +1 dni robocze poniedziałek poniedziałek
Sobota +1 dni robocze poniedziałek poniedziałek
Niedziela -1 dni robocze Piątek Piątek
Poniedziałek -1 dni robocze piątek piątek
Poniedziałek +1 dni robocze wtorek wtorek
Poniedziałek + 0 dni roboczych Poniedziałek Poniedziałek

Zrobiłem również drugą metodę jako metodę rozszerzającą: public static bool IsBusinessDay (ta data DateTime)
Andy B

2
public static DateTime AddBusinessDays(this DateTime date, int days)
{
    date = date.AddDays((days / 5) * 7);

    int remainder = days % 5;

    switch (date.DayOfWeek)
    {
        case DayOfWeek.Tuesday:
            if (remainder > 3) date = date.AddDays(2);
            break;
        case DayOfWeek.Wednesday:
            if (remainder > 2) date = date.AddDays(2);
            break;
        case DayOfWeek.Thursday:
            if (remainder > 1) date = date.AddDays(2);
            break;
        case DayOfWeek.Friday:
            if (remainder > 0) date = date.AddDays(2);
            break;
        case DayOfWeek.Saturday:
            if (days > 0) date = date.AddDays((remainder == 0) ? 2 : 1);
            break;
        case DayOfWeek.Sunday:
            if (days > 0) date = date.AddDays((remainder == 0) ? 1 : 0);
            break;
        default:  // monday
            break;
    }

    return date.AddDays(remainder);
}

1

Spóźniłem się na odpowiedź, ale stworzyłem małą bibliotekę ze wszystkimi dostosowaniami potrzebnymi do wykonywania prostych operacji w dni robocze ... Zostawiam to tutaj: Zarządzanie dniami roboczymi


2
Niestety jest to licencja GNU, więc jest to „trucizna prawna” dla każdej aplikacji komercyjnej. Czy jest szansa, że ​​rozluźnisz to na „MIT” lub „Apache”?
Tony O'Hagan

Niektóre listy statyczne powinny prawdopodobnie być tablicami (a nie listami połączonymi).
Tony O'Hagan

1
Właśnie zmieniłem licencję na MIT (nie chcę niczego blokować na czymś tak prostym). Przyjrzę się twojej drugiej propozycji.
Bez kości

Fajnie, byłoby interesujące zobaczyć zarządzanie dniami roboczymi według kraju, ponieważ niektóre kraje mogą mieć inne dni robocze niż od poniedziałku do piątku.
serializator

1

Jedynym prawdziwym rozwiązaniem jest udostępnienie tych wywołań tabeli bazy danych, która definiuje kalendarz dla Twojej firmy. Możesz zaprogramować go na tydzień pracy od poniedziałku do piątku bez zbytniego utrudniania, ale obsługa świąt byłaby wyzwaniem.

Edytowano, aby dodać nieeleganckie i nietestowane rozwiązanie częściowe:

public static DateTime AddBusinessDays(this DateTime date, int days)
{
    for (int index = 0; index < days; index++)
    {
        switch (date.DayOfWeek)
        {
            case DayOfWeek.Friday:
                date = date.AddDays(3);
                break;
            case DayOfWeek.Saturday:
                date = date.AddDays(2);
                break;
            default:
                date = date.AddDays(1);
                break;
         }
    }
    return date;
}

Naruszyłem również wymóg braku pętli.


Nie sądzę, żeby przypadek soboty kiedykolwiek został trafiony.
CoderDennis

@Dennis - tak by było, gdyby minioną datą była sobota.
Jamie Ide

Pozwoliłem sobie edytować twój kod, aby działał. Przetestuj kod przed wysłaniem go następnym razem, dziękuję.
bytecode77

I pomyślałem, że zero głosów pozytywnych mówi samo za siebie. Dzięki!
Jamie Ide,

1

Wskrzeszam ten post, bo dziś musiałem znaleźć sposób na wykluczenie nie tylko sobót i niedziel, ale także świąt. Mówiąc dokładniej, musiałem poradzić sobie z różnymi zestawami możliwych świąt, w tym:

  • święta niezmienne dla krajów (przynajmniej dla krajów zachodnich - np. 1 stycznia).
  • obliczone święta (takie jak Wielkanoc i Poniedziałek Wielkanocny).
  • święta specyficzne dla danego kraju (takie jak włoski dzień wyzwolenia lub amerykański identyfikator 4).
  • święta miejskie (takie jak Dzień Świętego Patrona w Rzymie).
  • wszelkie inne święta na zamówienie (np. „jutro nasze biuro będzie zamknięte”).

Ostatecznie wyszedłem z następującym zestawem klas pomocniczych / rozszerzeń: chociaż nie są one rażąco eleganckie, ponieważ masowo wykorzystują niewydajne pętle, są wystarczająco przyzwoite, aby rozwiązać moje problemy na dobre. Upuszczam cały kod źródłowy tutaj w tym poście, mając nadzieję, że przyda się on również komuś innemu.

Kod źródłowy

/// <summary>
/// Helper/extension class for manipulating date and time values.
/// </summary>
public static class DateTimeExtensions
{
    /// <summary>
    /// Calculates the absolute year difference between two dates.
    /// </summary>
    /// <param name="dt1"></param>
    /// <param name="dt2"></param>
    /// <returns>A whole number representing the number of full years between the specified dates.</returns>
    public static int Years(DateTime dt1,DateTime dt2)
    {
        return Months(dt1,dt2)/12;
        //if (dt2<dt1)
        //{
        //    DateTime dt0=dt1;
        //    dt1=dt2;
        //    dt2=dt0;
        //}

        //int diff=dt2.Year-dt1.Year;
        //int m1=dt1.Month;
        //int m2=dt2.Month;
        //if (m2>m1) return diff;
        //if (m2==m1 && dt2.Day>=dt1.Day) return diff;
        //return (diff-1);
    }

    /// <summary>
    /// Calculates the absolute year difference between two dates.
    /// Alternative, stand-alone version (without other DateTimeUtil dependency nesting required)
    /// </summary>
    /// <param name="start"></param>
    /// <param name="end"></param>
    /// <returns></returns>
    public static int Years2(DateTime start, DateTime end)
    {
        return (end.Year - start.Year - 1) +
            (((end.Month > start.Month) ||
            ((end.Month == start.Month) && (end.Day >= start.Day))) ? 1 : 0);
    }

    /// <summary>
    /// Calculates the absolute month difference between two dates.
    /// </summary>
    /// <param name="dt1"></param>
    /// <param name="dt2"></param>
    /// <returns>A whole number representing the number of full months between the specified dates.</returns>
    public static int Months(DateTime dt1,DateTime dt2)
    {
        if (dt2<dt1)
        {
            DateTime dt0=dt1;
            dt1=dt2;
            dt2=dt0;
        }

        dt2=dt2.AddDays(-(dt1.Day-1));
        return (dt2.Year-dt1.Year)*12+(dt2.Month-dt1.Month);
    }

    /// <summary>
    /// Returns the higher of the two date time values.
    /// </summary>
    /// <param name="dt1">The first of the two <c>DateTime</c> values to compare.</param>
    /// <param name="dt2">The second of the two <c>DateTime</c> values to compare.</param>
    /// <returns><c>dt1</c> or <c>dt2</c>, whichever is higher.</returns>
    public static DateTime Max(DateTime dt1,DateTime dt2)
    {
        return (dt2>dt1?dt2:dt1);
    }

    /// <summary>
    /// Returns the lower of the two date time values.
    /// </summary>
    /// <param name="dt1">The first of the two <c>DateTime</c> values to compare.</param>
    /// <param name="dt2">The second of the two <c>DateTime</c> values to compare.</param>
    /// <returns><c>dt1</c> or <c>dt2</c>, whichever is lower.</returns>
    public static DateTime Min(DateTime dt1,DateTime dt2)
    {
        return (dt2<dt1?dt2:dt1);
    }

    /// <summary>
    /// Adds the given number of business days to the <see cref="DateTime"/>.
    /// </summary>
    /// <param name="current">The date to be changed.</param>
    /// <param name="days">Number of business days to be added.</param>
    /// <param name="holidays">An optional list of holiday (non-business) days to consider.</param>
    /// <returns>A <see cref="DateTime"/> increased by a given number of business days.</returns>
    public static DateTime AddBusinessDays(
        this DateTime current, 
        int days, 
        IEnumerable<DateTime> holidays = null)
    {
        var sign = Math.Sign(days);
        var unsignedDays = Math.Abs(days);
        for (var i = 0; i < unsignedDays; i++)
        {
            do
            {
                current = current.AddDays(sign);
            }
            while (current.DayOfWeek == DayOfWeek.Saturday
                || current.DayOfWeek == DayOfWeek.Sunday
                || (holidays != null && holidays.Contains(current.Date))
                );
        }
        return current;
    }

    /// <summary>
    /// Subtracts the given number of business days to the <see cref="DateTime"/>.
    /// </summary>
    /// <param name="current">The date to be changed.</param>
    /// <param name="days">Number of business days to be subtracted.</param>
    /// <param name="holidays">An optional list of holiday (non-business) days to consider.</param>
    /// <returns>A <see cref="DateTime"/> increased by a given number of business days.</returns>
    public static DateTime SubtractBusinessDays(
        this DateTime current, 
        int days,
        IEnumerable<DateTime> holidays)
    {
        return AddBusinessDays(current, -days, holidays);
    }

    /// <summary>
    /// Retrieves the number of business days from two dates
    /// </summary>
    /// <param name="startDate">The inclusive start date</param>
    /// <param name="endDate">The inclusive end date</param>
    /// <param name="holidays">An optional list of holiday (non-business) days to consider.</param>
    /// <returns></returns>
    public static int GetBusinessDays(
        this DateTime startDate, 
        DateTime endDate,
        IEnumerable<DateTime> holidays)
    {
        if (startDate > endDate)
            throw new NotSupportedException("ERROR: [startDate] cannot be greater than [endDate].");

        int cnt = 0;
        for (var current = startDate; current < endDate; current = current.AddDays(1))
        {
            if (current.DayOfWeek == DayOfWeek.Saturday
                || current.DayOfWeek == DayOfWeek.Sunday
                || (holidays != null && holidays.Contains(current.Date))
                )
            {
                // skip holiday
            }
            else cnt++;
        }
        return cnt;
    }

    /// <summary>
    /// Calculate Easter Sunday for any given year.
    /// src.: https://stackoverflow.com/a/2510411/1233379
    /// </summary>
    /// <param name="year">The year to calcolate Easter against.</param>
    /// <returns>a DateTime object containing the Easter month and day for the given year</returns>
    public static DateTime GetEasterSunday(int year)
    {
        int day = 0;
        int month = 0;

        int g = year % 19;
        int c = year / 100;
        int h = (c - (int)(c / 4) - (int)((8 * c + 13) / 25) + 19 * g + 15) % 30;
        int i = h - (int)(h / 28) * (1 - (int)(h / 28) * (int)(29 / (h + 1)) * (int)((21 - g) / 11));

        day = i - ((year + (int)(year / 4) + i + 2 - c + (int)(c / 4)) % 7) + 28;
        month = 3;

        if (day > 31)
        {
            month++;
            day -= 31;
        }

        return new DateTime(year, month, day);
    }

    /// <summary>
    /// Retrieve holidays for given years
    /// </summary>
    /// <param name="years">an array of years to retrieve the holidays</param>
    /// <param name="countryCode">a country two letter ISO (ex.: "IT") to add the holidays specific for that country</param>
    /// <param name="cityName">a city name to add the holidays specific for that city</param>
    /// <returns></returns>
    public static IEnumerable<DateTime> GetHolidays(IEnumerable<int> years, string countryCode = null, string cityName = null)
    {
        var lst = new List<DateTime>();

        foreach (var year in years.Distinct())
        {
            lst.AddRange(new[] {
                new DateTime(year, 1, 1),       // 1 gennaio (capodanno)
                new DateTime(year, 1, 6),       // 6 gennaio (epifania)
                new DateTime(year, 5, 1),       // 1 maggio (lavoro)
                new DateTime(year, 8, 15),      // 15 agosto (ferragosto)
                new DateTime(year, 11, 1),      // 1 novembre (ognissanti)
                new DateTime(year, 12, 8),      // 8 dicembre (immacolata concezione)
                new DateTime(year, 12, 25),     // 25 dicembre (natale)
                new DateTime(year, 12, 26)      // 26 dicembre (s. stefano)
            });

            // add easter sunday (pasqua) and monday (pasquetta)
            var easterDate = GetEasterSunday(year);
            lst.Add(easterDate);
            lst.Add(easterDate.AddDays(1));

            // country-specific holidays
            if (!String.IsNullOrEmpty(countryCode))
            {
                switch (countryCode.ToUpper())
                {
                    case "IT":
                        lst.Add(new DateTime(year, 4, 25));     // 25 aprile (liberazione)
                        break;
                    case "US":
                        lst.Add(new DateTime(year, 7, 4));     // 4 luglio (Independence Day)
                        break;

                    // todo: add other countries

                    case default:
                        // unsupported country: do nothing
                        break;
                }
            }

            // city-specific holidays
            if (!String.IsNullOrEmpty(cityName))
            {
                switch (cityName)
                {
                    case "Rome":
                    case "Roma":
                        lst.Add(new DateTime(year, 6, 29));  // 29 giugno (s. pietro e paolo)
                        break;
                    case "Milano":
                    case "Milan":
                        lst.Add(new DateTime(year, 12, 7));  // 7 dicembre (s. ambrogio)
                        break;

                    // todo: add other cities

                    default:
                        // unsupported city: do nothing
                        break;

                }
            }
        }
        return lst;
    }
}

Informacje o użytkowaniu

Kod jest dość oczywisty, jednak oto kilka przykładów wyjaśniających, jak możesz go używać.

Dodaj 10 dni roboczych (pomijając tylko sobotę i niedzielę dni tygodnia)

var dtResult = DateTimeUtil.AddBusinessDays(srcDate, 10);

Dodaj 10 dni roboczych (pomijając sobotę, niedzielę i wszystkie święta niezmienione w kraju w 2019 r.)

var dtResult = DateTimeUtil.AddBusinessDays(srcDate, 10, GetHolidays(2019));

Dodaj 10 dni roboczych (pomijając sobotę, niedzielę i wszystkie włoskie święta w 2019 r.)

var dtResult = DateTimeUtil.AddBusinessDays(srcDate, 10, GetHolidays(2019, "IT"));

Dodaj 10 dni roboczych (pomijając sobotę, niedzielę, wszystkie włoskie święta i święta specyficzne dla Rzymu w 2019 r.)

var dtResult = DateTimeUtil.AddBusinessDays(srcDate, 10, GetHolidays(2019, "IT", "Rome"));

Powyższe funkcje i przykłady kodu są dokładniej wyjaśnione w tym poście na moim blogu.


0
    public static DateTime AddBusinessDays(DateTime date, int days)
    {
        if (days == 0) return date;
        int i = 0;
        while (i < days)
        {
            if (!(date.DayOfWeek == DayOfWeek.Saturday ||  date.DayOfWeek == DayOfWeek.Sunday)) i++;  
            date = date.AddDays(1);
        }
        return date;
    }

w przyszłości dodaj trochę więcej kontekstu do odpowiedzi i może dlaczego umieściłeś to, co masz :)
dax

0

Chciałem dodać „AddBusinessDays”, który obsługiwałby ujemne liczby dni, i skończyło się na tym:

// 0 == Monday, 6 == Sunday
private static int epochDayToDayOfWeek0Based(long epochDay) {
    return (int)Math.floorMod(epochDay + 3, 7);
}

public static int daysBetween(long fromEpochDay, long toEpochDay) {
    // http://stackoverflow.com/questions/1617049/calculate-the-number-of-business-days-between-two-dates
    final int fromDOW = epochDayToDayOfWeek0Based(fromEpochDay);
    final int toDOW = epochDayToDayOfWeek0Based(toEpochDay);
    long calcBusinessDays = ((toEpochDay - fromEpochDay) * 5 + (toDOW - fromDOW) * 2) / 7;

    if (toDOW   == 6) calcBusinessDays -= 1;
    if (fromDOW == 6) calcBusinessDays += 1;
    return (int)calcBusinessDays;
}

public static long addDays(long epochDay, int n) {
    // https://alecpojidaev.wordpress.com/2009/10/29/work-days-calculation-with-c/
    // NB: in .NET, Sunday == 0, but in our code Monday == 0
    final int dow = (epochDayToDayOfWeek0Based(epochDay) + 1) % 7;
    final int wds = n + (dow == 0 ? 1 : dow); // Adjusted number of working days to add, given that we now start from the immediately preceding Sunday
    final int wends = n < 0 ? ((wds - 5) / 5) * 2
                            : (wds / 5) * 2 - (wds % 5 == 0 ? 2 : 0);
    return epochDay - dow + // Find the immediately preceding Sunday
           wds +            // Add computed working days
           wends;           // Add weekends that occur within each complete working week
}

Nie jest wymagana żadna pętla, więc powinna być dość szybka nawet dla „dużych” dodatków.

Działa z dniami wyrażonymi jako liczba dni kalendarzowych od epoki, ponieważ jest to ujawnione przez nową klasę JDK8 LocalDate, a ja pracowałem w Javie. Powinien być jednak łatwy do dostosowania do innych ustawień.

Podstawowe właściwości są takie, że addDayszawsze zwraca dzień powszedni i że dla wszystkich di n,daysBetween(d, addDays(d, n)) == n

Zauważ, że teoretycznie dodanie 0 dni i odjęcie 0 dni powinno być różnymi operacjami (jeśli Twoja data to niedziela, dodanie 0 dni powinno przenieść cię do poniedziałku, a odjęcie 0 dni powinno przenieść cię do piątku). Ponieważ nie ma czegoś takiego jak ujemne 0 (poza zmiennoprzecinkowym!), Zdecydowałem się zinterpretować argument n = 0 jako oznaczający dodanie zero dni.


0

Uważam, że może to być prostszy sposób na GetBusinessDays:

    public int GetBusinessDays(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

Oto mój kod z datą wyjazdu i datą dostawy u klienta.

            // Calculate departure date
            TimeSpan DeliveryTime = new TimeSpan(14, 30, 0); 
            TimeSpan now = DateTime.Now.TimeOfDay;
            DateTime dt = DateTime.Now;
            if (dt.TimeOfDay > DeliveryTime) dt = dt.AddDays(1);
            if (dt.DayOfWeek == DayOfWeek.Saturday) dt = dt.AddDays(1);
            if (dt.DayOfWeek == DayOfWeek.Sunday) dt = dt.AddDays(1);
            dt = dt.Date + DeliveryTime;
            string DepartureDay = "today at "+dt.ToString("HH:mm");
            if (dt.Day!=DateTime.Now.Day)
            {
                DepartureDay = dt.ToString("dddd at HH:mm", new CultureInfo(WebContextState.CurrentUICulture));
            }
            Return DepartureDay;

            // Caclulate delivery date
            dt = dt.AddDays(1);
            if (dt.DayOfWeek == DayOfWeek.Saturday) dt = dt.AddDays(1);
            if (dt.DayOfWeek == DayOfWeek.Sunday) dt = dt.AddDays(1);
            string DeliveryDay = dt.ToString("dddd", new CultureInfo(WebContextState.CurrentUICulture));
            return DeliveryDay;

Miłego kodowania.


0
public static DateTime AddWorkingDays(this DateTime date, int daysToAdd)
{
    while (daysToAdd > 0)
    {
        date = date.AddDays(1);

        if (date.DayOfWeek != DayOfWeek.Saturday && date.DayOfWeek != DayOfWeek.Sunday)
        {
            daysToAdd -= 1;
        }
    }

    return date;
}

0
public static int GetBusinessDays(this DateTime start, DateTime end)
            {
                return Enumerable.Range(0, (end- start).Days)
                                .Select(a => start.AddDays(a))
                                .Where(a => a.DayOfWeek != DayOfWeek.Sunday)
                                .Where(a => a.DayOfWeek != DayOfWeek.Saturday)
                                .Count();
    
            }

-1

Mam nadzieję, że to komuś pomoże.

private DateTime AddWorkingDays(DateTime addToDate, int numberofDays)
    {
        addToDate= addToDate.AddDays(numberofDays);
        while (addToDate.DayOfWeek == DayOfWeek.Saturday || addToDate.DayOfWeek == DayOfWeek.Sunday)
        {
            addToDate= addToDate.AddDays(1);
        }
        return addToDate;
    }

2
To jest niepoprawne. W większości przypadków to nie zadziała. Raczej nikomu nie pomogę.
Neolisk
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.