using System; using System.Linq; using System.Xml.Linq; using Wellan.Common; /// /// 計算時數的類別 /// public class TimeCalc { public string 上班時間; public string 下班時間; public string 午休開始; public string 午休結束; public int 上班分鐘數; public int 午休分鐘數; public bool 彈性上班時間; private BhrdDAC bhrdDao = new BhrdDAC(WGuard1.Query); private RtmeDAC rtmeDao = new RtmeDAC(WGuard1.Query); private BpsnDAC bpsnDao = new BpsnDAC(WGuard1.Query); private BpaaDAC bpaaDao = new BpaaDAC(WGuard1.Query); private BtmeDAC btmeDao = new BtmeDAC(WGuard1.Query); public TimeCalc() { } /// /// 計算時數之前取得各個時間點 /// /// /// public void Init(string 員工編號, DateTime 日期) { 上班時間 = WGuard1.WSetR("UNRPARTIME", 1); 下班時間 = WGuard1.WSetR("UNRPARTIME", 2); 午休開始 = WGuard1.WSetR("UNRPARTIME", 3); 午休結束 = WGuard1.WSetR("UNRPARTIME", 4); 彈性上班時間 = WGuard1.WSetR("彈性上班時間", 1) == "Y"; // 取員工班表 var btmeItem = rtmeDao.SelectBtmeDay(員工編號, 日期); if (btmeItem != null && !String.IsNullOrEmpty(btmeItem.BTSTM) && !String.IsNullOrEmpty(btmeItem.BTETM) && !String.IsNullOrEmpty(btmeItem.BTRST) && !String.IsNullOrEmpty(btmeItem.BTRED)) { 上班時間 = btmeItem.BTSTM; 下班時間 = btmeItem.BTETM; 午休開始 = btmeItem.BTRST; 午休結束 = btmeItem.BTRED; } // 若員工班表沒有設定,則取專案預設班表 else { // 取員工專案年度接單編號 var rapyn = bpsnDao.GetProjectByDate(員工編號, 日期); if (!String.IsNullOrEmpty(rapyn)) { // 取專案預設班表編號 var bptme = bpaaDao.GetTimeTableNo(rapyn); if (!String.IsNullOrEmpty(bptme)) { // 取班表 btmeItem = btmeDao.SelectOne(bptme).FirstOrDefault(); if (btmeItem != null && !String.IsNullOrEmpty(btmeItem.BTSTM) && !String.IsNullOrEmpty(btmeItem.BTETM) && !String.IsNullOrEmpty(btmeItem.BTRST) && !String.IsNullOrEmpty(btmeItem.BTRED)) { 上班時間 = btmeItem.BTSTM; 下班時間 = btmeItem.BTETM; 午休開始 = btmeItem.BTRST; 午休結束 = btmeItem.BTRED; } } } } 上班分鐘數 = 計算分鐘數(上班時間, 下班時間) - 計算分鐘數(午休開始, 午休結束); 午休分鐘數 = 計算分鐘數(午休開始, 午休結束); } public static int 進整小時(int 分鐘) { double 渣渣 = ((double)分鐘 / 30d) % 1d; if (渣渣 > 0.5d) return (分鐘 / 60 + 1) * 60; else return (分鐘 / 60) * 60; } public static int 進半小時(int 分鐘) { double 渣渣 = ((double)分鐘 / 15d) % 1d; if (渣渣 > 0.5d) return (分鐘 / 30 + 1) * 30; else return (分鐘 / 30) * 30; } public static int 計算分鐘數(string tm1, string tm2) { DateTime dt1 = new DateTime(); DateTime dt2 = new DateTime(); dt1 = dt1.AddHours(Convert.ToInt32((tm1 + "00:00").Substring(0, 2))).AddMinutes(Convert.ToInt32((tm1 + "00:00").Substring(3, 2))); dt2 = dt2.AddHours(Convert.ToInt32((tm2 + "00:00").Substring(0, 2))).AddMinutes(Convert.ToInt32((tm2 + "00:00").Substring(3, 2))); return Convert.ToInt32((dt2 - dt1).TotalMinutes); } public static string 加分鐘數(string tm1, int minutes) { DateTime dt1 = new DateTime(); dt1 = dt1.AddHours(Convert.ToInt32((tm1 + "00:00").Substring(0, 2))).AddMinutes(Convert.ToInt32((tm1 + "00:00").Substring(3, 2))); dt1 = dt1.AddMinutes(minutes); return dt1.ToString("HH:mm"); } public int 計算午休分鐘數(string 起時, string 迄時) { if (起時.CompareTo(午休開始) <= 0 && 迄時.CompareTo(午休結束) >= 0) return 午休分鐘數; else if (起時.CompareTo(午休開始) >= 0 && 起時.CompareTo(午休結束) <= 0 && 迄時.CompareTo(午休結束) >= 0) return 計算分鐘數(起時, 午休結束); else if (起時.CompareTo(午休開始) <= 0 && 迄時.CompareTo(午休開始) >= 0 && 迄時.CompareTo(午休結束) <= 0) return 計算分鐘數(午休開始, 迄時); else if (起時.CompareTo(午休開始) >= 0 && 起時.CompareTo(午休結束) <= 0 && 迄時.CompareTo(午休開始) >= 0 && 迄時.CompareTo(午休結束) <= 0) return 計算分鐘數(起時, 迄時); else return 0; } public int 計算請假總時數(string 員工編號, DateTime 起日, DateTime 迄日, string 起時, string 迄時, string 假別, bool is進整小時) { Init(員工編號, 起日); if (String.IsNullOrEmpty(起時)) 起時 = 上班時間; if (String.IsNullOrEmpty(迄時)) 迄時 = 下班時間; int 分鐘數; // 起始與結束同一天 if (起日 == 迄日) { 分鐘數 = 計算分鐘數(起時, 迄時) - 計算午休分鐘數(起時, 迄時); } // 起始與結束不同天 else { DateTime std = 起日; 分鐘數 = 0; while (std <= 迄日) { Init(員工編號, std); // 不是假日,或是產假,要列入時數計算 if (!bhrdDao.IsHoliday(員工編號, std) || 假別 == RPAR_TYPE.T05_產假) { // 第一天 if (std == 起日) { 分鐘數 += 計算分鐘數(起時, 下班時間) - 計算午休分鐘數(起時, 下班時間); } // 最後一天 else if (std == 迄日) { 分鐘數 += 計算分鐘數(上班時間, 迄時) - 計算午休分鐘數(上班時間, 迄時); } // 中間其他天 else { 分鐘數 += 上班分鐘數; } } std = std.AddDays(1); } } if (is進整小時) 分鐘數 = 進半小時(分鐘數); return 分鐘數; } public int 計算請假跨日數(string 員工編號, DateTime 起日, DateTime 迄日, string 假別) { // 起始與結束同一天 if (起日 == 迄日) { return 1; } // 起始與結束不同天 else { DateTime std = 起日; int 天數 = 0; while (std <= 迄日) { // 不是假日,或是產假,要列入時數計算 if (!bhrdDao.IsHoliday(員工編號, std) || 假別 == RPAR_TYPE.T05_產假) { 天數++; } std = std.AddDays(1); } return 天數; } } public int 計算請假當日時數(string 員工編號, DateTime 日期, DateTime 起日, DateTime 迄日, string 起時, string 迄時, string 假別, bool is進整小時) { int 當日分鐘數 = 0; // 起始與結束同一天 if (起日 == 迄日) { Init(員工編號, 起日); if (String.IsNullOrEmpty(起時)) 起時 = 上班時間; if (String.IsNullOrEmpty(迄時)) 迄時 = 下班時間; 當日分鐘數 = 計算分鐘數(起時, 迄時) - 計算午休分鐘數(起時, 迄時); if (is進整小時) return 進整小時(當日分鐘數); else return 當日分鐘數; } // 起始與結束不同天 else { DateTime std = 起日; while (std <= 迄日) { Init(員工編號, std); // 不是假日,或是產假,要列入時數計算 if (!bhrdDao.IsHoliday(員工編號, std) || 假別 == RPAR_TYPE.T05_產假) { // 第一天 if (std == 起日) { 當日分鐘數 = 計算分鐘數(起時, 下班時間) - 計算午休分鐘數(起時, 下班時間); } // 最後一天 else if (std == 迄日) { 當日分鐘數 = 計算分鐘數(上班時間, 迄時) - 計算午休分鐘數(上班時間, 迄時); } // 中間其他天 else { 當日分鐘數 = 上班分鐘數; } // 只計算一天,就返回 if (日期 == std) { if (is進整小時) return 進整小時(當日分鐘數); else return 當日分鐘數; } } else 當日分鐘數 = 0; std = std.AddDays(1); } return 0; } } public int 計算加班時數(string 員工編號, DateTime 日期, string 起時, string 迄時, bool 休息日) { Init(員工編號, 日期); int 分鐘數 = 計算分鐘數(起時, 迄時) - 計算午休分鐘數(起時, 迄時); // 2018/3/1 之後休息日加班不進整 4 小時 if (休息日 && 日期.CompareTo(new DateTime(2018, 3, 1)) < 0) 分鐘數 = (int)(Math.Ceiling((decimal)分鐘數 / 240m) * 240m); return 分鐘數; } //public static void 拆分加班時數(int 分鐘數, bool 是假日, out int 前二, out int 後二, out int 假日) //{ // 前二 = 0; // 後二 = 0; // 假日 = 0; // if (是假日) // 假日 = 分鐘數; // else // { // if (分鐘數 < 120) // 前二 = 分鐘數; // else // { // 前二 = 120; // 後二 = 分鐘數 - 120; // } // } //} public int 計算差旅時數(string 員工編號, DateTime 起日, DateTime 迄日, string 起時, string 迄時) { Init(員工編號, 起日); if (String.IsNullOrEmpty(起時)) 起時 = 上班時間; if (String.IsNullOrEmpty(迄時)) 迄時 = 下班時間; int 分鐘數; // 起始與結束同一天 if (起日 == 迄日) { 分鐘數 = 計算分鐘數(起時, 迄時) - 計算午休分鐘數(起時, 迄時); } // 起始與結束不同天 else { DateTime std = 起日; 分鐘數 = 0; while (std <= 迄日) { Init(員工編號, std); // 第一天 if (std == 起日) { 分鐘數 += 計算分鐘數(起時, 下班時間) - 計算午休分鐘數(起時, 下班時間); } // 最後一天 else if (std == 迄日) { 分鐘數 += 計算分鐘數(上班時間, 迄時) - 計算午休分鐘數(上班時間, 迄時); } // 中間其他天 else { 分鐘數 += 上班分鐘數; } std = std.AddDays(1); } } return 分鐘數; } public int 計算差旅當日時數(string 員工編號, DateTime 日期, DateTime 起日, DateTime 迄日, string 起時, string 迄時) { Init(員工編號, 起日); if (String.IsNullOrEmpty(起時)) 起時 = 上班時間; if (String.IsNullOrEmpty(迄時)) 迄時 = 下班時間; int 分鐘數; // 起始與結束同一天 if (起日 == 迄日) { 分鐘數 = 計算分鐘數(起時, 迄時) - 計算午休分鐘數(起時, 迄時); if (日期 == 起日) return 分鐘數; else return 0; } // 起始與結束不同天 else { DateTime std = 起日; while (std <= 迄日) { Init(員工編號, std); 分鐘數 = 0; // 第一天 if (std == 起日) { 分鐘數 = 計算分鐘數(起時, 下班時間) - 計算午休分鐘數(起時, 下班時間); } // 最後一天 else if (std == 迄日) { 分鐘數 = 計算分鐘數(上班時間, 迄時) - 計算午休分鐘數(上班時間, 迄時); } // 中間其他天 else { 分鐘數 = 上班分鐘數; } if (日期 == std) return 分鐘數; std = std.AddDays(1); } } return 0; } public static DateTime 轉換時區(string 時區, DateTime 日期, string 時間) { // 解析30小時制(小時可能 >= 24,表示跨入隔日凌晨) var parts = 時間.Split(':'); var timeSpan = new TimeSpan(int.Parse(parts[0]), int.Parse(parts[1]), int.Parse(parts[2])); var sourceTime = 日期.Date.Add(timeSpan); if (string.IsNullOrEmpty(時區) || string.Compare(時區, "Taipei Standard Time") == 0) { return sourceTime; } // 重要:將 Kind 設為 Unspecified sourceTime = DateTime.SpecifyKind(sourceTime, DateTimeKind.Unspecified); // 轉換到目標時區 var sourceZone = TimeZoneInfo.FindSystemTimeZoneById("Taipei Standard Time"); var targetZone = TimeZoneInfo.FindSystemTimeZoneById(時區); var utcTime = TimeZoneInfo.ConvertTimeToUtc(sourceTime, sourceZone); var targetTime = TimeZoneInfo.ConvertTimeFromUtc(utcTime, targetZone); return targetTime; } public static string 轉換打卡時區(string 時區, DateTime 打卡日期, string 打卡時間) { var targetTime = 轉換時區(時區, 打卡日期, 打卡時間); // 輸出 30 小時制時分秒 // 06:00 之前算前一天,小時數加 24 int outputHours = targetTime.Hour; if (targetTime.TimeOfDay < new TimeSpan(6, 0, 0)) outputHours += 24; return string.Format("{0:00}:{1:00}:{2:00}", outputHours, targetTime.Minute, targetTime.Second); } } /// /// 30 小時制的日期類別 /// public class DateAndTime { /// /// 台灣工作日期 /// public DateTime TwWorkDate; /// /// 台灣日曆日期 /// public DateTime TwCalendarDate; /// /// 台灣時間 /// public string TwTime; /// /// 當地日期 /// public DateTime LocalDate; /// /// 當地時間 /// public string LocalTime; /// /// 時區名稱 /// public string TimezoneName; /// /// 時區ID /// public string TimeZoneID; /// /// 打卡的日期 /// public DateTime CardDate; public DateAndTime(DateTime twWorkDate, DateTime twCalendarDate, string twTime, DateTime localDate, string localTime, string timezoneName, string timezoneID) : base() { TwWorkDate = twWorkDate; TwCalendarDate = twCalendarDate; TwTime = twTime; LocalDate = localDate; LocalTime = localTime; TimezoneName = timezoneName; TimeZoneID = timezoneID; CardDate = string.IsNullOrEmpty(TimeZoneID) || string.Compare(TimeZoneID, "Taipei Standard Time") == 0 ? TwWorkDate : TwCalendarDate; } public static DateAndTime GetNow(string bpeno) { var twNow = DateTime.UtcNow.AddHours(8); var rasnDao = new RasnDAC(WGuard1.Query); string timezoneID; string timezoneName; rasnDao.GetTimeZone(bpeno, twNow, out timezoneID, out timezoneName); var twCalendarDate = twNow.Date; var twWorkDate = twNow.Date; var localNow = TimeCalc.轉換時區(timezoneID, twNow.Date, twNow.ToString("HH:mm:ss")); var localDate = localNow.Date; var twHourOffset = 0; var localHourOffset = 0; // 06:00 之前算前一天 if (twNow.TimeOfDay < new TimeSpan(6, 0, 0)) { twWorkDate = twWorkDate.AddDays(-1); twHourOffset = 24; } if (localNow.TimeOfDay < new TimeSpan(6, 0, 0)) { localDate = localDate.AddDays(-1); localHourOffset = 24; } return new DateAndTime( twWorkDate, twCalendarDate, string.Format("{0:00}:{1:00}:{2:00}", twNow.TimeOfDay.Hours + twHourOffset, twNow.TimeOfDay.Minutes, twNow.Second), localDate, string.Format("{0:00}:{1:00}:{2:00}", localNow.TimeOfDay.Hours + localHourOffset, localNow.TimeOfDay.Minutes, localNow.Second), timezoneName, timezoneID ); } }