zoukankan      html  css  js  c++  java
  • C#编程实践–帮老婆计算产假方案

    摘要

    今天中午午休时,和老婆聊天,老婆还过几天就要请产假了,她在网上问我让我帮她数一下该怎么请假最划算,老婆是个会过日子的人,面对此种要求我当然义不容辞,不过想到这个问题我的第一反应是:这个怎么可以用数的呢?于是,我开始去了解2014年上海市最新的产假政策规定,大致概况如下:“产假加上晚育假一共128天,其中前面98天是正常产假,其中已经包括国家法定节日和双休日,后面30天是晚育假,只包含双休日,不包含国家法定节日,也就是说遇到国家法定节日则假期往后顺延”,注意黑体粗字描述,可以知道这里面的精打细算就体现在前面98天的正常产假。我们要做的就是尽量避免正常产假包含太多的国家法定节假日,否则用老婆的话说那就是“亏”了,注意我把“亏”字打引号,我的意思是在生活中我们不必太过于精打细算斤斤计较,如果过度了那么就容易失去生活情趣和心灵的自由,有句话说吃亏是福。但是,在不妨碍这种前提条件下,我们还是要努力争取,扯远了,这个问题又不复杂,所以,我何乐而不为呢?况且,最近已经准备开始写博了,经常看书看文章看博客,毕竟,纸上得来终觉浅,绝知此事要躬行,还是要经常实践实践,况且作为干这一行的,更要有开放和分享的心态,好了,废话已经很多了,开始代码的构思和实现。

    更新

    此文已更新,请看这里:C#编程实践--产假方案优化版

    领域

    初步定位,这是一个关于时间查询的应用,模型围绕时间建立,假期根据月份和日号来表示(公历和农历),另需要提供假期规则的定义,如下(DateModels.cs):

    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace HelloBaby
    {
        // 这里提供默认的放假集合定义
        public static class DefaultHoliday
        {
            // 元旦1天假
            public static readonly Holiday NewYearsHoliday
                = new Holiday(MonthDayDate.NewYearsDay, 1);
            // 春节3天假,从前一天(除夕)开始放
            public static readonly Holiday SpringFestivalHoliday
                = new Holiday(MonthDayDate.SpringFestival, -1, 3);
            // 清明1天假
            public static readonly Holiday QingMingHoliday
                = new Holiday(MonthDayDate.QingMingDay, 1);
            // 劳动节1天假
            public static readonly Holiday LabourHoliday
                = new Holiday(MonthDayDate.LabourDay, 1);
            // 端午节1天假
            public static readonly Holiday DragonBoatHoliday
                = new Holiday(MonthDayDate.DragonBoatFestival, 1);
            // 中秋节1天假
            public static readonly Holiday MidAutumnHoliday
                = new Holiday(MonthDayDate.MidAutumnDay, 1);
            // 国庆节3天假
            public static readonly Holiday NationalHoliday
                = new Holiday(MonthDayDate.NationalDay, 3);
    
            public static List<Holiday> Holidays = new List<Holiday>{
                DefaultHoliday.NewYearsHoliday,
                DefaultHoliday.SpringFestivalHoliday,
                DefaultHoliday.QingMingHoliday,
                DefaultHoliday.LabourHoliday,
                DefaultHoliday.DragonBoatHoliday,
                DefaultHoliday.MidAutumnHoliday,
                DefaultHoliday.NationalHoliday
            };
        }
    
        // 假期,包含3个成员(农历或公历几月几日?提前几天放?共放几天)
        public struct Holiday
        {
            public Holiday(MonthDayDate monthDay, int startOffset, int days)
                : this()
            {
                MonthDay = monthDay;
                StartOffset = startOffset;
                Days = days;
            }
    
            public Holiday(MonthDayDate monthDay, int days)
                : this(monthDay, 0, days)
            {
            }
    
            public MonthDayDate MonthDay { get; private set; }
            public int StartOffset { get; set; }
            public int Days { get; set; }
    
            // 根据年份获取假期具体日期枚举
            public IEnumerable<DateTime> ToDateTimeRange(int year)
            {
                DateTime gregorian = DateTime.Now;
    
                if (MonthDay.Calendar == CalendarKind.LunarCalendar)
                    gregorian = DateUtility.ConvertLunarYearDate(year, MonthDay.Month, MonthDay.Day);
                else
                    gregorian = new DateTime(year, MonthDay.Month, MonthDay.Day);
    
                DateTime begin = gregorian.AddDays(StartOffset);
                for (int i = 0; i < Days; i++)
                {
                    yield return begin.AddDays((double)i);
                }
            }
        }
    
        // 此处使用Calendar属性来区分历法
        // 也可以将struct改为class使用继承的设计方式,方便扩展
        public struct MonthDayDate
        {
            // 元旦节
            public static readonly MonthDayDate NewYearsDay = new MonthDayDate(1, 1);
            // 中国春节
            public static readonly MonthDayDate SpringFestival =
                new MonthDayDate(1, 1, CalendarKind.LunarCalendar);
            // 清明节
            public static readonly MonthDayDate QingMingDay = new MonthDayDate(4, 5);
            // 五一劳动节
            public static readonly MonthDayDate LabourDay = new MonthDayDate(5, 1);
            // 端午节
            public static readonly MonthDayDate DragonBoatFestival =
                new MonthDayDate(5, 5, CalendarKind.LunarCalendar);
            // 中秋节
            public static readonly MonthDayDate MidAutumnDay =
                new MonthDayDate(8, 15, CalendarKind.LunarCalendar);
            // 国庆节
            public static readonly MonthDayDate NationalDay =
                new MonthDayDate(10, 1);
    
            public MonthDayDate(int month, int day, CalendarKind calendar)
                : this()
            {
                Month = month;
                Day = day;
                Calendar = calendar;
            }
    
            public MonthDayDate(int month, int day)
                : this(month, day, CalendarKind.Gregorian)
            {
            }
    
            public int Month { get; private set; }
            public int Day { get; private set; }
            public CalendarKind Calendar { get; private set; }
    
            public static bool operator ==(MonthDayDate d1, MonthDayDate d2)
            {
                return d1.Month == d2.Month
                    && d1.Day == d2.Day
                    && d1.Calendar == d2.Calendar;
            }
    
            public static bool operator !=(MonthDayDate d1, MonthDayDate d2)
            {
                return !(d1 == d2);
            }
        }
    
        public enum CalendarKind
        {
            // 公历(阳历)
            Gregorian,
            // 中国农历(阴历)
            LunarCalendar
        }
    }

    注意,在Model里面,我们添加了业务逻辑,比如Holiday的ToDateTimeRange方法里面依赖了外部的农历到公历的转换逻辑,这里依赖了外部逻辑,是否属于不良好的设计?于是我有必要声明,此代码为半实验性代码,非生产代码。既然此处依赖一些外部方法逻辑,那么我也把这部分代码列出(DateUtility.cs):

    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace HelloBaby
    {
        public class DateUtility
        {
            /// <summary>
            /// 获取时间段枚举
            /// </summary>
            public static IEnumerable<DateTime> RangeDay(DateTime starting, DateTime ending)
            {
                for (DateTime d = starting; d <= ending; d = d.AddDays(1))
                {
                    yield return d;
                }
            }
    
            /// <summary>
            /// 农历转公历
            /// </summary>
            public static DateTime ConvertLunarYearDate(int year, int month, int day)
            {
                ChineseLunisolarCalendar calendar = new ChineseLunisolarCalendar();
    
                return calendar.ToDateTime(year, month, day, 0, 0, 0, 0);
            }
        }
    }

    业务

    程序写到这里已经完成了一大半了,接下来就是一些判断的规则逻辑,这里使用扩展方法来实现(DateExtensions.cs):

    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace HelloBaby
    {
        public static class DateExtensions
        {
            /// <summary>
            /// 判断日期是否是节假日,使用默认放假规定
            /// </summary>
            public static bool IsHoliday(this DateTime date)
            {
                return date.IsHoliday(DefaultHoliday.Holidays);
            }
    
            /// <summary>
            /// 判断日期是否是节假日,使用指定放假规定
            /// </summary>
            public static bool IsHoliday(this DateTime date, IEnumerable<Holiday> holidays)
            {
                return holidays.Any(
                    d => d.ToDateTimeRange(date.Year).Contains(date)
                    );
            }
        }
        
    }

    提示:在项目的实践中,我们尽量遵循TDD的开发模式,尤其是针对业务处理层的代码,单元测试代码这里就不贴了,省略掉。

    应用

    好了,至此我们的领域和业务规则部分已经完成了,可以开始我们的应用了,在一个以数据为中心的年代,有了数据,我们就可以发挥想象,为所欲为,在这里我还是简单的回到最初的问题点上,因为虽然借此机会练练手写写代码,但初衷还是要帮老婆解决问题啊,于是我的应用场景为计算98天产假里面包含的国家法定节日,我在此主要使用LINQ查询:

    internal static void PrintSolutions(int days)
    {
        var sample = DateUtility.RangeDay(
            new DateTime(2014, 1, 1),
            new DateTime(2015, 12, 31));
    
        var holidayCollection = DefaultHoliday.Holidays;
    
        var solutions =
            from begin in sample
            from end in sample
            let range = DateUtility.RangeDay(begin, end)
            where range.Count() == days
            select new
            {
                Begin = begin,
                End = end,
                HolidayCount = range.Count(d => d.IsHoliday())
            };
    
        // 显式查询集合,避免多次查询
        // 空间换时间的典型场景啊!!!
        var local = solutions.ToList();
    
        var groups =
            from all in local
            group all by all.HolidayCount into g
            select new 
            { 
                Holidays = g.Key,
                SolutionCount = g.Count()
            };
    
        var best =
            from all in local
            where all.HolidayCount == 0
            select all;
    
        Console.WriteLine("group results:");
        foreach (var group in groups)
        {
            Console.WriteLine("{0} solutions for {1} holidays",
                group.SolutionCount, group.Holidays);
        }
    
        Console.WriteLine();
        Console.WriteLine("best results:");
        foreach (var one in best)
        {
            Console.WriteLine("from {0:yyyy-MM-dd} to {1:yyyy-MM-dd}",
                one.Begin, one.End);
        }
    }

    之前和老婆聊天的时候,我对她的初步方案表示认同,因为98天假期只会跨越一天元旦假期而已,我觉得98天假期不跨越法定节日基本上不太会有吧,就算有,这种方案也不多见,虽然我已经安慰老婆了,但是我还是耐心做做测试,我使用2014年初到2015年末作为时间样本,测试结果,原来我以为不可能有98天没法定节日的,结果发现,2015年还真有这么仅有的一个时间段,98天不经过一个法定节日,算出来是:2015-06-21 到 2015-09-26。呵呵,老婆反正你是没戏咯

    结语

    虽然文章里面一些措辞是奔着解决问题去的,说实话,最终目的还是练手,自己比较懒,只看东西不爱动手,但还是觉得要有开放的心态。本人关于LINQ的查询还存在一些疑问,那就是涉及到它背后的执行查询的性能?就好像写SQL语句同一个查询有不同的写法,不同的写法有不同的性能考量,所以我们一般都尽量遵循规范来写查询,那么LINQ to Object是如何规范做这一切的?以后其他比较通用的查询Provider(比如LINQ to Entity,虽然我仔细阅读过一些书和案例,始终觉得还没摸透),看来,有空还可以再探究探究,欢迎有想法的朋友们交流!希望可以结交到有识之士!

    最后再吐槽一下:为毛上海的陪产假只有区区3天啊,而且还是针对晚育的情况,相比其他省市的政策,太不人性化了嘛!!!

  • 相关阅读:
    Super
    多态
    方法覆盖 和toString方法的作用
    Static 静态+this
    构造方法 + 继承
    使用方法重载的优缺点
    Erlang 简介与样例分析
    Assassin's Creed 4: Black Flag --《刺客信条4; 黑旗》
    DEVIL MAY CRY V:《鬼泣5》
    Valiant Hearts: The Great War -- 《勇敢的心》
  • 原文地址:https://www.cnblogs.com/fecktty2013/p/csharp-practice-holidaycalculator.html
Copyright © 2011-2022 走看看