C#编程实践–帮老婆计算产假方案
2014-10-10 22:57 周信达 阅读(2123) 评论(12) 编辑 收藏 举报摘要
今天中午午休时,和老婆聊天,老婆还过几天就要请产假了,她在网上问我让我帮她数一下该怎么请假最划算,老婆是个会过日子的人,面对此种要求我当然义不容辞,不过想到这个问题我的第一反应是:这个怎么可以用数的呢?于是,我开始去了解2014年上海市最新的产假政策规定,大致概况如下:“产假加上晚育假一共128天,其中前面98天是正常产假,其中已经包括国家法定节日和双休日,后面30天是晚育假,只包含双休日,不包含国家法定节日,也就是说遇到国家法定节日则假期往后顺延”,注意黑体粗字描述,可以知道这里面的精打细算就体现在前面98天的正常产假。我们要做的就是尽量避免正常产假包含太多的国家法定节假日,否则用老婆的话说那就是“亏”了,注意我把“亏”字打引号,我的意思是在生活中我们不必太过于精打细算斤斤计较,如果过度了那么就容易失去生活情趣和心灵的自由,有句话说吃亏是福。但是,在不妨碍这种前提条件下,我们还是要努力争取,扯远了,这个问题又不复杂,所以,我何乐而不为呢?况且,最近已经准备开始写博了,经常看书看文章看博客,毕竟,纸上得来终觉浅,绝知此事要躬行,还是要经常实践实践,况且作为干这一行的,更要有开放和分享的心态,好了,废话已经很多了,开始代码的构思和实现。
更新
此文已更新,请看这里:C#编程实践--产假方案优化版
领域
初步定位,这是一个关于时间查询的应用,模型围绕时间建立,假期根据月份和日号来表示(公历和农历),另需要提供假期规则的定义,如下(DateModels.cs):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | 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):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | 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):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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查询:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | 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天啊,而且还是针对晚育的情况,相比其他省市的政策,太不人性化了嘛!!!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步