今天中午午休時,和老婆聊天,老婆還過幾天就要請產假了,她在網上問我讓我幫她數一下該怎麼請假最劃算,老婆是個會過日子的人,面對此種要求我當然義不容辭,不過想到這個問題我的第一反應是:這個怎麼可以用數的呢?於是,我開始去了解2014年上海市最新的產假政策規定,大致概況如下:“產假加上晚育假一共128天,其中前面98天是正常產假,其中已經包括國家法定節日和雙休日,後面30天是晚育假,只包含雙休日,不包含國家法定節日,也就是說遇到國家法定節日則假期往後順延”,注意黑體粗字描述,可以知道這裡面的精打細算就體現在前面98天的正常產假。我們要做的就是盡量避免正常產假包含太多的國家法定節假日,否則用老婆的話說那就是“虧”了,注意我把“虧”字打引號,我的意思是在生活中我們不必太過於精打細算斤斤計較,如果過度了那麼就容易失去生活情趣和心靈的自由,有句話說吃虧是福。但是,在不妨礙這種前提條件下,我們還是要努力爭取,扯遠了,這個問題又不復雜,所以,我何樂而不為呢?況且,最近已經准備開始寫博了,經常看書看文章看博客,畢竟,紙上得來終覺淺,絕知此事要躬行,還是要經常實踐實踐,況且作為干這一行的,更要有開放和分享的心態,好了,廢話已經很多了,開始代碼的構思和實現。
初步定位,這是一個關於時間查詢的應用,模型圍繞時間建立,假期根據月份和日號來表示(公歷和農歷),另需要提供假期規則的定義,如下(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天啊,而且還是針對晚育的情況,相比其他省市的政策,太不人性化了嘛!!!
這是結構體指針中的一個符號,給你寫個程序解釋一下吧,例如:
#include<stdio.h>
struct STU //定義一個結構體
{
int num;
}stu;
int main()
{
struct STU *p; //定義一個結構體指針
p=stu; //p指向stu這個結構體變量
stu.num=100; //給結構體成員num附個初值
printf("%d",p->num); //輸出stu中的num的值
return;
}
看到了吧,->的作法就是在引用結構體中的變量!!
形式如:p->結構體成員(如p->num)
他的作用相當於stu.num或(*p).num
不知道這樣解釋你明不明白、、、、、不懂了call我,O(∩_∩)O~
望采納。
這是結構體指針中的一個符號,給你寫個程序解釋一下吧,例如:
#include<stdio.h>
struct STU //定義一個結構體
{
int num;
}stu;
int main()
{
struct STU *p; //定義一個結構體指針
p=stu; //p指向stu這個結構體變量
stu.num=100; //給結構體成員num附個初值
printf("%d",p->num); //輸出stu中的num的值
return;
}
看到了吧,->的作法就是在引用結構體中的變量!!
形式如:p->結構體成員(如p->num)
他的作用相當於stu.num或(*p).num
不知道這樣解釋你明不明白、、、、、不懂了call我,O(∩_∩)O~
望采納。