AddBusinessDays และ GetBusinessDays


93

ฉันต้องการค้นหา 2 การใช้งานที่สมบูรณ์แบบที่สวยงามของ

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

and 

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

O (1) ดีกว่า (ไม่มีลูป)

แก้ไข: ตามวันทำการฉันหมายถึงวันทำการ (จันทร์อังคารพุธพฤหัสบดีศุกร์) ไม่มีวันหยุดยกเว้นวันหยุดสุดสัปดาห์

ฉันมีวิธีแก้ปัญหาที่น่าเกลียดอยู่แล้วซึ่งดูเหมือนจะใช้งานได้ แต่ฉันสงสัยว่ามีวิธีที่ดีในการทำเช่นนี้หรือไม่ ขอบคุณ


นี่คือสิ่งที่ฉันเขียนไว้จนถึงตอนนี้ ใช้งานได้ในทุกกรณีและทำเชิงลบด้วย ยังคงต้องมีการใช้งาน 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
มีวิธีแก้ปัญหาที่สวยงามเมื่อพูดถึงบางสิ่งที่ไร้เหตุผลเช่นวันที่?
Wyatt Barnett

คุณกังวลกับวันหยุดหรือไม่? - เจมส์ Conigliaro ไม่
Adrian Zanescu

9
การโหวตให้คนที่พยายามช่วยไม่ใช่กลยุทธ์ในการชนะ
Jamie Ide

1
หมายเหตุสั้น ๆ เกี่ยวกับการAddBusinessDaysใช้งานในคำถามด้านบน (ซึ่งจริงๆแล้วเป็นคำตอบที่ถูกลบซึ่งฉันได้เสนอให้ยกเลิกการลบ mod คัดลอกคำตอบสำหรับคำถามนั้นแทน): ในความคิดของฉันวิธีนี้ดีกว่าคำตอบทั้งหมดเพราะเป็นเพียงคำตอบเดียว สิ่งที่เกี่ยวข้องกับค่าลบอย่างถูกต้องวันเสาร์และวันอาทิตย์เป็นแหล่งที่มาและไม่ต้องการ lib ของบุคคลที่สาม (ฉันได้สร้างโปรแกรมเล็กน้อยเพื่อทดสอบวิธีแก้ปัญหาต่างๆที่นี่) ฉันจะเพิ่มif (businessDays == 0) return startDate;ที่จุดเริ่มต้นของวิธีการเท่านั้นเพื่อให้ได้ผลลัพธ์ที่ถูกต้องสำหรับกรณีขอบนี้เช่นกัน
Slauma

1
@AZ: การลบครั้งแรกค่อนข้างเก่า หลังจากที่ฉันขอให้ยกเลิกการลบคำตอบของคุณ mod ได้ยกเลิกการลบคำตอบ (เป็นเวลา 30 วินาที) เพื่อคัดลอกเนื้อหาด้านล่างคำถามของคุณจากนั้นเขาก็ลบอีกครั้ง นั่นเป็นเหตุผลที่คำตอบของคุณมีการประทับเวลาลบล่าสุดนี้ ฉันเขียนความคิดเห็นข้างต้นเพราะจุดประสงค์ของฉันคุณAddBusinessDaysเป็นวิธีแก้ปัญหาทั่วไปที่สุดที่นี่ซึ่งใช้ได้ผลในทุกกรณีที่ฉันต้องการ ฉันคัดลอกลงในโปรเจ็กต์ปัจจุบันของฉัน (หลังจากปรับเปลี่ยนเล็กน้อยและแปลเป็น C ++) ขอบคุณสำหรับโค้ด :) มันช่วยได้มากเพราะมันยากอย่างน่าประหลาดใจที่จะทำให้ทุกกรณีขอบถูกต้อง
Slauma

คำตอบ:


135

ความพยายามล่าสุดสำหรับฟังก์ชันแรกของคุณ:

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

}

ฟังก์ชันที่สอง GetBusinessDays สามารถใช้งานได้ดังนี้:

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

สำหรับวิธีที่สองวิธีแก้ปัญหาหนึ่งคือการใช้ความแตกต่างระหว่างวันที่และวันที่ + วัน นี่เป็นเรื่องดีที่รับประกันได้ว่าทั้งสองฟังก์ชั่นจะซิงค์อย่างถูกต้องและช่วยขจัดความซ้ำซ้อน
Brian

ฟีดวันที่ปัจจุบันทำงานเป็นเวลา 0 ถึง 10 วันทำการมักจะล้มเหลวในวันพุธ
Adrian Godong

1
ใช่เราไปถึงจุดนั้นแล้ว (ฉันพูดว่า 'เรา' สำหรับผลงานเล็ก ๆ ของฉัน!) ได้รับการโหวตจากความพยายาม
ริน

ขอบคุณสำหรับข้อมูลของคุณ Noldorin ฉันสามารถเพิ่มคะแนนความคิดเห็นของคุณได้เท่านั้น!
Patrick McDonald

3
DateTime.AddDays ทำงานกับตัวเลขติดลบ ไม่ถูกต้องตามรูปแบบเดียวกันกับการใช้ตัวเลขติดลบกับ AddBusinessDays อนุญาตให้เลือกวันที่ไม่ใช่วันทำการได้
Ristogod

63

ใช้Fluent DateTime :

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

รหัสภายในมีดังนี้

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

นี่เป็นทางออกเดียวที่ใช้งานได้จริงสำหรับฉันเมื่อแปลงเป็น VB.Net
Nicholas

1
OP ไม่ขอลูปในขณะที่อันนี้มีลูปอย่างชัดเจน ไม่มีอะไรสวยหรูเกี่ยวกับการทำบางสิ่งด้วยวิธีที่มีประสิทธิภาพน้อยที่สุด
Neolisk

13

ฉันสร้างส่วนขยายที่ช่วยให้คุณสามารถเพิ่มหรือลบวันทำการได้ ใช้จำนวนวันที่เป็นลบเพื่อลบ ฉันคิดว่ามันเป็นทางออกที่ดีทีเดียว ดูเหมือนว่าจะได้ผลในทุกกรณี

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

ตัวอย่าง:

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

ผลลัพธ์เป็นที่น่าสงสัยว่าวันที่ต้นทางเป็นวันเสาร์หรือวันอาทิตย์ ตัวอย่างเช่นวันเสาร์ + 1 วันทำการส่งผลในวันอังคารซึ่งฉันควรคาดหวังในวันจันทร์
Slauma

3
@Slauma: นี่คือวิธีการดำเนินธุรกิจส่วนใหญ่ในแคนาดา +1 วันทำการ = "วันทำการถัดไป" ซึ่งในกรณีของวันเสาร์คือวันอังคาร วันจันทร์จะเป็น "วันทำการเดียวกัน"
Neolisk

3
@Slauma Program ทำงานตามที่ตั้งใจไว้ คิดอย่างมีเหตุผล หากสิ่งที่เกี่ยวข้องกับธุรกิจเริ่มต้นในวันเสาร์และคุณต้องเผื่อเวลา 1 วันทำการเพื่อให้ผู้คนตอบสนองในช่วงของวันทำการดังกล่าวจะสมเหตุสมผลหรือไม่ที่จะบอกพวกเขาว่าต้องดำเนินการภายในวันจันทร์!
Riegardt Steyn

8

สำหรับฉันฉันต้องมีวิธีแก้ปัญหาที่จะข้ามวันหยุดสุดสัปดาห์และไปในทางลบหรือเชิงบวก เกณฑ์ของฉันคือถ้ามันเดินไปข้างหน้าและลงจอดในวันหยุดสุดสัปดาห์ก็ต้องเลื่อนไปถึงวันจันทร์ ถ้ามันจะกลับมาและลงจอดในวันหยุดสุดสัปดาห์มันจะต้องข้ามไปที่วันศุกร์

ตัวอย่างเช่น:

  • วันพุธ - 3 วันทำการ = ศุกร์สุดท้าย
  • วันพุธ + 3 วันทำการ = วันจันทร์
  • วันศุกร์ - 7 วันทำการ = วันพุธที่ผ่านมา
  • วันอังคาร - 5 วันทำการ = อังคารสุดท้าย

คุณเข้าใจดี;)

ฉันลงเอยด้วยการเขียนคลาสส่วนขยายนี้

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

มันใช้วิธีนี้ฉันคิดว่าจะมีประโยชน์ในการใช้ที่อื่น ...

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

หากคุณไม่ต้องการรบกวนคุณสามารถแทนที่ได้ if (MyClass.IsBusinessDay(date))ด้วย ifif ((date.DayOfWeek != DayOfWeek.Saturday) && (date.DayOfWeek != DayOfWeek.Sunday))

ตอนนี้คุณสามารถทำได้

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

หรือ

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

นี่คือผลลัพธ์จากการทดสอบบางส่วน:

ทดสอบผลลัพธ์ที่คาดหวัง
วันพุธ -4 วันทำการพฤหัสบดีพฤหัสบดี
พุธ -3 วันทำการศุกร์ศุกร์
วันพุธ +3 วันทำการจันทร์จันทร์
วันศุกร์ -7 วันทำการพุธพุธ
วันอังคาร -5 วันทำการอังคารอังคาร
วันศุกร์ +1 วันทำการจันทร์จันทร์
วันเสาร์ +1 วันทำการจันทร์จันทร์
อาทิตย์ -1 วันทำการศุกร์ศุกร์
จันทร์ -1 วันทำการศุกร์ศุกร์
วันจันทร์ +1 วันทำการอังคารอังคาร
จันทร์ +0 วันทำการจันทร์จันทร์

ฉันทำวิธีที่ 2 เป็นวิธีการขยายเช่นกัน: IsBusinessDay บูลแบบคงที่สาธารณะ (วันที่ 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

ฉันมาช้าสำหรับคำตอบ แต่ฉันสร้างห้องสมุดเล็ก ๆ ขึ้นมาพร้อมกับการปรับแต่งทั้งหมดที่จำเป็นในการใช้งานง่าย ๆ ในวันทำงาน ... ฉันปล่อยไว้ที่นี่: Working Days Management


2
น่าเสียดายที่นี่เป็น GNU ที่ได้รับอนุญาตดังนั้นจึงเป็น "พิษทางกฎหมาย" สำหรับแอปเชิงพาณิชย์ใด ๆ มีโอกาสที่คุณจะผ่อนคลายไปกับ "MIT" หรือ "Apache" ไหม
Tony O'Hagan

รายการคงที่บางรายการควรเป็นอาร์เรย์ (แทนที่จะเป็นรายการที่เชื่อมโยง)
Tony O'Hagan

1
ฉันเพิ่งเปลี่ยนใบอนุญาตเป็น MIT (ฉันไม่ต้องการปิดกั้นอะไรในสิ่งที่ง่ายขนาดนั้น) ฉันจะพิจารณาเรื่องอื่น ๆ ของคุณ
ไม่มีกระดูก

ดีมันน่าสนใจที่จะเห็นการจัดการวันทำงานตามประเทศเนื่องจากบางประเทศอาจมีวันทำการอื่นนอกเหนือจากวันจันทร์ถึงวันศุกร์
serializer

1

ทางออกเดียวที่แท้จริงคือให้สายเหล่านั้นเข้าถึงตารางฐานข้อมูลที่กำหนดปฏิทินสำหรับธุรกิจของคุณ คุณสามารถเขียนโค้ดสำหรับวันทำงานวันจันทร์ถึงวันศุกร์ได้โดยไม่ยากเกินไป แต่การจัดการวันหยุดจะเป็นเรื่องที่ท้าทาย

แก้ไขเพื่อเพิ่มโซลูชันบางส่วนที่ไม่หรูหราและไม่ผ่านการทดสอบ:

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

นอกจากนี้ฉันยังละเมิดข้อกำหนดการไม่มีลูป


ฉันไม่คิดว่ากรณีวันเสาร์จะถูกตี
CoderDennis

@ เดนนิส - ถ้าวันที่ผ่านไปเป็นวันเสาร์
Jamie Ide

ฉันใช้เสรีภาพในการแก้ไขรหัสของคุณเพื่อให้ใช้งานได้ กรุณาทดสอบรหัสก่อนโพสต์ในครั้งต่อไปขอบคุณ
bytecode77

และฉันคิดว่าการโหวตเพิ่มเป็นศูนย์พูดเพื่อตัวมันเอง ขอบคุณ!
Jamie Ide

1

ฉันกำลังรื้อฟื้นโพสต์นี้เพราะวันนี้ฉันต้องหาวิธียกเว้นไม่เพียง แต่วันเสาร์และอาทิตย์ในวันธรรมดาเท่านั้น โดยเฉพาะอย่างยิ่งฉันต้องจัดการกับวันหยุดต่างๆที่เป็นไปได้ ได้แก่ :

  • วันหยุดไม่ตรงตามประเทศ (อย่างน้อยสำหรับประเทศตะวันตกเช่นมกราคม 01)
  • วันหยุดที่คำนวณได้ (เช่นวันจันทร์อีสเตอร์และอีสเตอร์)
  • วันหยุดเฉพาะประเทศ (เช่นวันปลดปล่อยอิตาลีหรือ ID4 ของสหรัฐอเมริกา)
  • วันหยุดเฉพาะเมือง (เช่นวันนักบุญอุปถัมภ์ของกรุงโรม)
  • วันหยุดอื่น ๆ ที่กำหนดเอง (เช่น "พรุ่งนี้สำนักงานของเราจะปิด")

ในที่สุดฉันก็ออกมาพร้อมกับชุดของคลาสตัวช่วย / ส่วนขยายต่อไปนี้แม้ว่าพวกเขาจะไม่สวยหรูอย่างโจ่งแจ้งเพราะพวกเขาใช้ลูปที่ไม่มีประสิทธิภาพเป็นจำนวนมาก แต่ก็เหมาะสมพอที่จะแก้ปัญหาของฉันได้ดี ฉันวางซอร์สโค้ดทั้งหมดไว้ที่นี่ในโพสต์นี้หวังว่าจะเป็นประโยชน์กับคนอื่นเช่นกัน

รหัสแหล่งที่มา

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

ข้อมูลการใช้งาน

โค้ดนี้ค่อนข้างอธิบายตัวเองได้ แต่นี่คือตัวอย่างสองสามตัวอย่างเพื่ออธิบายวิธีใช้งาน

เพิ่ม 10 วันทำการ (ข้ามเฉพาะวันเสาร์และวันอาทิตย์ของสัปดาห์)

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

เพิ่ม 10 วันทำการ (ข้ามวันเสาร์วันอาทิตย์และวันหยุดประจำประเทศทั้งหมดสำหรับปี 2019)

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

เพิ่ม 10 วันทำการ (ข้ามวันเสาร์วันอาทิตย์และวันหยุดอิตาลีทั้งหมดสำหรับปี 2019)

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

เพิ่ม 10 วันทำการ (ข้ามวันเสาร์วันอาทิตย์วันหยุดอิตาลีทั้งหมดและวันหยุดเฉพาะกรุงโรมสำหรับปี 2019)

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

ตัวอย่างฟังก์ชันและโค้ดข้างต้นมีการอธิบายเพิ่มเติมในโพสต์ของบล็อกของฉัน


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

ในอนาคตเพิ่มบริบทสำหรับคำตอบอีกเล็กน้อยและอาจเป็นเหตุผลว่าทำไมคุณถึงใส่สิ่งที่คุณมี :)
dax

0

ฉันต้องการ "AddBusinessDays" ที่รองรับจำนวนวันติดลบในการเพิ่มและฉันลงเอยด้วยสิ่งนี้:

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

ไม่จำเป็นต้องมีการวนซ้ำดังนั้นจึงควรเร็วพอสมควรแม้จะมีการเพิ่ม "ใหญ่" ก็ตาม

ใช้งานได้กับวันที่แสดงเป็นจำนวนวันตามปฏิทินนับตั้งแต่ยุคเนื่องจากมีการเปิดเผยโดยคลาส JDK8 LocalDate ใหม่และฉันทำงานใน Java ควรปรับให้เข้ากับการตั้งค่าอื่น ๆ ได้ง่าย

คุณสมบัติพื้นฐานที่addDaysเสมอกลับในวันทำงานและที่ทั้งหมดdและn,daysBetween(d, addDays(d, n)) == n

โปรดทราบว่าในทางทฤษฎีการเพิ่ม 0 วันและการลบ 0 วันควรเป็นการดำเนินการที่แตกต่างกัน (หากวันที่ของคุณเป็นวันอาทิตย์การเพิ่ม 0 วันจะนำคุณไปสู่วันจันทร์และการลบ 0 วันจะนำคุณไปสู่วันศุกร์) เนื่องจากไม่มีสิ่งที่เรียกว่าลบ 0 (นอกจุดลอยตัว!) ฉันจึงเลือกที่จะตีความอาร์กิวเมนต์ n = 0 เป็นความหมายเพิ่มศูนย์วัน


0

ฉันเชื่อว่านี่อาจเป็นวิธีที่ง่ายกว่าใน 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

นี่คือรหัสของฉันพร้อมทั้งวันที่ออกเดินทางและวันที่จัดส่งที่ลูกค้า

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

มีความสุขในการเขียนโค้ด


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

หวังว่านี่จะช่วยใครบางคนได้

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
สิ่งนี้ไม่ถูกต้อง ในกรณีส่วนใหญ่จะไม่ทำงาน ไม่น่าช่วยใคร
Neolisk
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.