如何计算假期?

问题描述 投票:0回答:5

我有一个应用程序,可以在人们生病时进行登记,并在此事件发生后的一定工作日内向经理发送通知。

我可以使用 DateTime 的属性来跳过周末,但如何跳过假期?

我在某处读到,教堂假期是从复活节开始计算的。但如何呢?我首先如何找到复活节?

c# .net datetime
5个回答
1
投票

跳过日期之间的假期和计算复活节是两种不同的野兽,你可能想要前者。

您的申请应该维护一个假期列表,正如 @GeneMyers 关于

Holidays in United States

的评论所建议的那样

通过此列表,您可以使用如下代码:

private static void Main(string[] args)
{
    var start  = new DateTime(2015, 01, 01); // Thusday
    var finish = new DateTime(2015, 01, 31); // Saturday

    var totalDays = finish.Subtract(start).TotalDays;  // 30 days
    var test1 = GetWorkingDays(start, finish, false, new List<DateTime>()); // should be 30
    var test2 = GetWorkingDays(start, finish, true , new List<DateTime>()); // should be 21
    var test3 = GetWorkingDays(start, finish, true , new List<DateTime>     // should be 19
    {
        new DateTime(2015, 01, 01), // New Year's Day
        new DateTime(2015, 01, 09), // Random holiday
        new DateTime(2015, 01, 25), // Sao Paulo city foundation, sunday
    });
}

public static int GetWorkingDays(DateTime from, DateTime to, 
                                 bool skipWeekends, IEnumerable<DateTime> holidays)
{
    var totalDays = (int)to.Subtract(from).TotalDays;

    var isHoliday = new Func<DateTime, bool>(date =>
        holidays.Contains(date));
    var isWeekend = new Func<DateTime, bool>(date => 
        date.DayOfWeek == DayOfWeek.Saturday || 
        date.DayOfWeek == DayOfWeek.Sunday);

    return Enumerable
        .Range (0, totalDays + 1)
        .Select(days => from.AddDays(days))
        .Count (date => (!skipWeekends || !isWeekend(date)) && !isHoliday(date)) - 1;
}

1
投票

我会使用您所在区域的已知假期表,而不是尝试计算它们 - 例如,Outlook 允许您导入假期列表。还要记住,东正教复活节和“西方”复活节是有区别的。

如果您特别希望计算公历复活节,请查看此 BASIC 代码,如果您仍然愿意自己执行此操作,则可以轻松将其移植到 C#。

https://www.assa.org.au/edm#Computer


0
投票

我创建了以下类来添加/减去/计算工作日:它包括一个计算复活节的方法(也可用于检索相关的复活节星期一和/或耶稣受难日)和一个可自定义的 DateTimes IEnumerable 来视为“节假日”(例如,城镇圣守护神节或公司假期)。

以下是相关源代码:值得注意的是,主要方法(AddBusinessDays、SubtractBusinessDays 和 GetBusinessDays)既可以用作静态辅助方法,也可以用作扩展方法。

/// <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
投票

也许有点太晚了,但你可以使用我的图书馆Nager.Date。通过我的库,您可以计算 100 多个不同国家/地区的假期。还有一个 nuget 包可用。


0
投票

我知道这已经很老了,但它引导我走向正确的方向,我想发布我的代码。我需要为我正在开发的员工假期跟踪器获取假期,并在这个过程中发现复活节要求您计算春分和月亮周期。我为我的特定地区设置了假期,因为对于我的申请来说,这就是我所需要的。我发现的卫星计算结果足够近似,可以得到精确到小时的月相,您需要用三角学来计算月亮和太阳的位置,我认为这对于此目的来说是不必要的。

我在 GitHub 上找到的月相源代码。我尝试根据网上找到的一些公式示例创建自己的方法,但它会缩短一天。

工作示例

public class Holiday
{
    private DateTime? _date;
    public DateTime? Date
    {
        get { return _date; }
        set { _date = value; }
    }
    private  DateTime? _inLieuDate;
    public  DateTime? InLieuDate
    {
        get { return _inLieuDate; }
        set { _inLieuDate = value; }
    }
    private string _name;
    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }
    public Holiday(string name, DateTime? date)
    {
        Date = date;
        Name = name;
    }
    public Holiday(string name, DateTime? date, DateTime? inLieuDate)
    {
        Date = date;
        Name = name;
        InLieuDate = inLieuDate;
    }
}
public static class LocalHolidays
{

    public static List<Holiday> GetHolidays(int year)
    {
        List<Holiday> holidays = new List<Holiday> ();

        // New Years
        DateTime newYearsDate = new DateTime(year, 1, 1);
        DateTime inLieunewYearsDate = AdjustForWeekendHoliday(newYearsDate);
        holidays.Add(new Holiday("New Years Day",newYearsDate,inLieunewYearsDate));

        // Family Day -- 3rd Monday in February
        var _familyDay = (from day in Enumerable.Range(1, DateTime.DaysInMonth(year, 2))
                        where new DateTime(year, 2, day).DayOfWeek == DayOfWeek.Monday
                        select day).ElementAt(2);
        DateTime familyDay = new DateTime(year, 2, _familyDay);
        holidays.Add(new Holiday("Family Day", familyDay));

        // Good Friday -- The friday before the first Sunday after the full Moon that occurs on or after the spring equinox
        DateTime goodFriday = GetEasterSunday(year);
        DayOfWeek dayOfWeek = goodFriday.DayOfWeek;
        while (dayOfWeek != DayOfWeek.Friday)
        {
            goodFriday = goodFriday.AddDays(-1);
            dayOfWeek = goodFriday.DayOfWeek;
        }
        holidays.Add(new Holiday("Good Friday", goodFriday));

        // Victoria Day -- Monday before May 25th 
        DateTime victoriaDay = new DateTime(year, 5, 25);
        dayOfWeek = victoriaDay.DayOfWeek;
        while (dayOfWeek != DayOfWeek.Monday)
        {
            victoriaDay = victoriaDay.AddDays(-1);
            dayOfWeek = victoriaDay.DayOfWeek;
        }
        holidays.Add(new Holiday("Victoria Day", victoriaDay));
                    
        // Canada Day
        DateTime canadaDay = new DateTime(year, 7, 1);
        DateTime inLieucanadaDay = AdjustForWeekendHoliday(canadaDay);
        holidays.Add(new Holiday("Canada Day", canadaDay,inLieucanadaDay));

        // Heritage Day -- 1st Monday in August 
        DateTime heritageDay = new DateTime(year, 8, 1);
        dayOfWeek = heritageDay.DayOfWeek;
        while (dayOfWeek != DayOfWeek.Monday)
        {
            heritageDay = heritageDay.AddDays(1);
            dayOfWeek = heritageDay.DayOfWeek;
        }
        holidays.Add(new Holiday("Heritage Day", heritageDay));

        // Labour Day -- 1st Monday in September 
        DateTime labourDay = new DateTime(year, 9, 1);
        dayOfWeek = labourDay.DayOfWeek;
        while (dayOfWeek != DayOfWeek.Monday)
        {
            labourDay = labourDay.AddDays(1);
            dayOfWeek = labourDay.DayOfWeek;
        }
        holidays.Add(new Holiday("Labour Day", labourDay));

        // Thanksgiving Day -- 2nd Monday in October
        var thanksgiving = (from day in Enumerable.Range(1, DateTime.DaysInMonth(year, 10))
                        where new DateTime(year, 10, day).DayOfWeek == DayOfWeek.Monday
                        select day).ElementAt(1);
        DateTime thanksgivingDay = new DateTime(year, 10, thanksgiving);
        holidays.Add(new Holiday("Thanksgiving Day", thanksgivingDay));

        // Remembrance Day 
        DateTime remembranceDay = new DateTime(year, 11, 11);
        DateTime inLieuremembranceDay = AdjustForWeekendHoliday(remembranceDay);
        holidays.Add(new Holiday("Remembrance Day", remembranceDay, inLieuremembranceDay));

        // Christmas Day 
        DateTime christmasDay = new DateTime(year, 12, 25);
        DateTime inLieuchristmasDay = AdjustForWeekendHoliday(christmasDay);
        holidays.Add(new Holiday("Christmas Day", christmasDay, inLieuchristmasDay));

        // Boxing Day 
        DateTime boxingDay = new DateTime(year, 12, 26);
        DateTime inLieuBoxingDay = AdjustForWeekendHoliday(boxingDay);
        holidays.Add(new Holiday("Boxing Day", boxingDay, inLieuBoxingDay));



        return holidays;
    }

    public static DateTime AdjustForWeekendHoliday(DateTime holiday)
    {
        if (holiday.DayOfWeek == DayOfWeek.Saturday)
        {
            return holiday.AddDays(-1);
        }
        else if (holiday.DayOfWeek == DayOfWeek.Sunday)
        {
            return holiday.AddDays(1);
        }
        else
        {
            return holiday;
        }
    }

    internal static DateTime GetEasterSunday(int year)
    { 
        DateTime springEquinox = GetSpringEquinox(year);
        Moon moon = new Moon();
        DateTime foundDate = moon.GetNextFullMoon(springEquinox);
        DayOfWeek dayOfWeek = foundDate.DayOfWeek;
        while (dayOfWeek != DayOfWeek.Sunday)
        {
            foundDate = foundDate.AddDays(1);
            dayOfWeek = foundDate.DayOfWeek;
        }
        return foundDate;
    }
    internal static DateTime GetSpringEquinox(int year)
    {
        int lastTwoDigits = year % 100;
        int calculatedDate = (int)(((lastTwoDigits * 0.2422) + 20.646) - GetCurrentCententuryLeapYears(year));
        DateTime foundDate = new DateTime(year, 3,calculatedDate);
        return foundDate;
    }

    internal static int GetCurrentCententuryLeapYears(int year)
    {
        int leapCopunter = 0;
        int century = Convert.ToInt32(Math.Floor(year / 100d))*100;
        for(int i = century;i <= year;i++) 
        {
            if ((i % 4 == 0 && i % 100 != 0) || (i % 400 == 0 && i % 100 == 0))
                {
                leapCopunter++;
            }
        }
        return leapCopunter;
    }
}

public class Moon
{
    static double MoonCycleLength = 29.53058770576;
    private List<MoonPhase> _moonPhases = new List<MoonPhase>();
    public List<MoonPhase> GetMoonPhases
    {
        get { return _moonPhases; }
    }
    public class MoonPhase
    {
        public double FromAge;
        public double ToAge;
        public Phase PhaseOfMoon;

        public MoonPhase(double fromAge,double toAge, Phase phase)
        {
            FromAge = fromAge;
            ToAge = toAge;
            PhaseOfMoon = phase;
        }

        public enum Phase
        {
            NewMoon,
            FirstQuarter,
            FullMoon,
            LastQuater
        }
    }

    public Moon()
    {
        _moonPhases.Add(new MoonPhase(0,1,MoonPhase.Phase.NewMoon));
        _moonPhases.Add(new MoonPhase((MoonCycleLength / 4)-1, (MoonCycleLength / 4) + 1, MoonPhase.Phase.FirstQuarter));
        _moonPhases.Add(new MoonPhase((MoonCycleLength / 2)-1, (MoonCycleLength / 2) + 1, MoonPhase.Phase.FullMoon));
        _moonPhases.Add(new MoonPhase((MoonCycleLength * .75)-1, (MoonCycleLength * .75) + 1, MoonPhase.Phase.LastQuater));
        _moonPhases.Add(new MoonPhase(MoonCycleLength-1,MoonCycleLength, MoonPhase.Phase.NewMoon));
    }



    public int GetJulianDate(int day, int month, int year)
    {
        year = year - (12 - month) / 10;

        month = month + 9;

        if (month >= 12)
            month = month - 12;

        var k1 = (int)(365.25 * (year + 4712));
        var k2 = (int)(30.6001 * month + 0.5);

        // 'j' for dates in Julian calendar:
        var julianDate = k1 + k2 + day + 59;

        //Gregorian calendar
        if (julianDate > 2299160)
        {
            var k3 = (int)((year / 100 + 49) * 0.75) - 38;
            julianDate = julianDate - k3; //at 12h UT (Universal Time)
        }

        return julianDate;
    }

    public double GetMoonAge(DateTime fromDate)
    {
        int day = fromDate.Day;
        int month = fromDate.Month;
        int year = fromDate.Year;
        double ip, age;

        int julianDate = GetJulianDate(day, month, year);

        ip = (julianDate + 4.867) / MoonCycleLength;
        ip = ip - Math.Floor(ip);

        age = ip * MoonCycleLength + MoonCycleLength / 2;

        if (age > MoonCycleLength)
            age -= MoonCycleLength;

        return age;
    }
    public DateTime GetNextFullMoon(DateTime fromDate)
    {
    
        DateTime foundDate = fromDate;
        while (!GetMoonAge(foundDate).IsBetween( GetMoonPhases.Where(x => x.PhaseOfMoon == MoonPhase.Phase.FullMoon).FirstOrDefault().FromAge, GetMoonPhases.Where(x => x.PhaseOfMoon == MoonPhase.Phase.FullMoon).FirstOrDefault().ToAge))
        {
            foundDate = foundDate.AddDays(1);
        }
        return foundDate;
    }
}

public static class CompareValues
{
    public static bool IsBetween(this double item, double start, double end)
    {
        return Comparer<double>.Default.Compare(item, start) >= 0
            && Comparer<double>.Default.Compare(item, end) <= 0;
    }
}

我尝试的月龄计算

    public double GetMoonAge(DateTime fromDate)
    {
        int day = fromDate.Day;
        int month = fromDate.Month;
        int year = fromDate.Year;
        if (month == 1 || month == 2)
        {
            year = year - 1;
            month = month + 12;
        }
        double A = Math.Floor(year / 100d);
        double B = Math.Floor(A / 4d);
        double C = 2 - A + B;
        double E = Math.Floor(365.25d * (year + 4716));
        double F = Math.Floor(30.6001d * (month + 1));
        double JD = C + day + E + F - 1524.5;
        double DaysSinceNew = JD - 2451549.5;
        double newMoons = DaysSinceNew / MoonCycleLength;
        double age = (newMoons - Math.Truncate(newMoons)) * MoonCycleLength;

        return age;
    }

使用示例

var publicHolidays = LocalHolidays.GetHolidays(2024);
foreach (Holiday publicHoliday in publicHolidays)
{
    Console.WriteLine(publicHoliday.Name + "   " + publicHoliday.Date.ToString() + "   " + publicHoliday.InLieuDate.ToString());
}

资源

Github月相

月相计算pdf

如何计算春分

© www.soinside.com 2019 - 2024. All rights reserved.