2025年9月20日

〔SAS〕如何 精準計算工作日,扣除國定假日

AI聊天機器人的創作

這一篇所有程式均透過Gemini或ChatGPT完成,包含連文章主要說明內容,當然有做過適當的人工潤飾。
用AI寫SAS程式,自己經驗不是很好,相對Python或R來說,SAS程式很容易有錯,這一次算是難得成功,而且有很多方法確實也是超過我的能力了,把這樣的經驗和程式分享給大家

如何用SAS 精準計算工作日

手邊有兩個日期,想計算中間到底有多少個「工作天」?這不僅要扣除週末,還得考慮國定假日,甚至麻煩的補班日。如果只靠簡單的日期相減,結果一定不對。

這篇文章將用AI聊天機器人所產生的程式來解決這個問題,將程式可拆解成 5 個步驟,並讓你輕鬆套用。

0.事先說明

關於程式與程式的解釋,均由AI產生,沒有做太多的更動,某些語法我也沒有徹底瞭解,或許這就是AI時代,確認程式可以在最短時間內完成,並達成目的即可。

1. 範例資料準備

在開始之前,先建立一個包含不同情境的範例資料集 Service_Date

/* 建立一個包含不同情境的範例資料集 */ data Service_Date; format SERV_DT_day CREATED_DAY yymmdd10.; input caseno $ SERV_DT_day : yymmdd10. CREATED_DAY : yymmdd10.; datalines; Case01 2025-01-20 2025-01-24 /* 跨一般週末 */ Case02 2025-01-27 2025-02-03 /* 跨春節連假 */ Case03 2025-02-07 2025-02-10 /* 跨補班日 2025-02-08 */ Case04 2025-04-01 2025-04-07 /* 跨兒童節/清明節連假 */ Case05 2025-05-29 2025-06-02 /* 跨端午節 */ Case06 2025-10-09 2025-10-13 /* 跨國慶日 */ Case07 2025-09-26 2025-10-02 /* 跨中秋節 */ Case08 2025-02-10 2025-02-07 /* 開始日期晚於結束日期 */ ; run;

2. 建立假日與補班日清單

先告訴 SAS 哪些日子是假日或補班日。這兩組資料將獨立儲存,以方便日後維護,如果是不同年度,可以請AI幫你產生喔,不過還是建議要檢查一下。

/* 步驟 1: 建立一個包含國定假日的資料集 */ data holidays; input holiday : yymmdd10.; format holiday yymmdd10.; datalines; 2025-01-01 /* 新年 */ 2025-01-27 /* 小年夜(彈性放假) */ 2025-01-28 /* 除夕 */ 2025-01-29 /* 春節 */ 2025-01-30 /* 春節 */ 2025-01-31 /* 春節 */ 2025-02-01 /* 春節 */ 2025-02-02 /* 春節 */ 2025-02-28 /* 和平紀念日 */ 2025-04-03 /* 兒童節 */ 2025-04-04 /* 清明節 */ 2025-05-01 /* 勞動節 */ 2025-05-30 /* 端午節 */ 2025-10-06 /* 中秋節 */ 2025-10-10 /* 國慶日 */ ; run; /* 步驟 4: 建立一個包含補班日的資料集 */ data makeup_workdays; input workday : yymmdd10.; format workday yymmdd10.; datalines; 2025-02-08 /* 春節補班日 */ ; run;

3. 將假日清單格式化

這是整個邏輯最聰明的地方。把所有國定假日做成一個特殊的「格式」,之後只要用這個格式去檢查日期,就能快速判斷是否為假日。

/* 步驟 2 & 3: 動態產生假日格式 */ proc format; value HOLIDAY low-high = 1 ; run; data holiday_fmt_ctrl; set holidays; fmtname = 'HOLIDAY'; type = 'N'; start = holiday; end = holiday; label = '0'; run; proc format cntlin=holiday_fmt_ctrl; run;
  • proc format: 這是 SAS 專門用來建立格式的程式。
  • data holiday_fmt_ctrl: 我們建立一個「控制資料集」,它的功能就是告訴 proc format 怎麼建立格式。
  • label = '0': 這行是關鍵!我們將每個國定假日的標籤都設定為 0,方便之後判斷。
  • proc format cntlin=...: 讀取控制資料集,將「格式」正式建立起來。

4. 核心邏輯:計算工作日

現在,將假日與補班日的資訊整合起來,並對每一筆資料進行計算。

/* 步驟 5: 計算每個個案的工作日並輸出到新資料集 */ data Service_Date_out; /* 讀取補班日清單到記憶體中 */ array makeup_days[100] _temporary_; retain n_makeup_days 0; if _n_ = 1 then do; do until(eof); set makeup_workdays end=eof; n_makeup_days + 1; makeup_days[n_makeup_days] = workday; end; end; set Service_Date; if SERV_DT_day > CREATED_DAY then do; work_days = -1; output; end; else do; work_days = 0; do current_date = SERV_DT_day to CREATED_DAY; /* 1. 檢查是否為補班日 */ is_makeup_workday = 0; do i = 1 to n_makeup_days; if current_date = makeup_days[i] then do; is_makeup_workday = 1; leave; end; end; /* 2. 判斷是否為平日且非假日 */ is_weekday = (weekday(current_date) not in (1, 7)); is_holiday = (put(current_date, HOLIDAY.) = '0'); /* 3. 最終判斷:是補班日,或是一般工作日 */ if is_makeup_workday or (is_weekday and not is_holiday) then do; work_days + 1; end; end; output; end; drop is_makeup_workday is_weekday is_holiday i current_date n_makeup_days; run;
  • array..._temporary_: 建立一個臨時陣列,將補班日存入其中,這能大幅提高程式效率。
  • if _n_ = 1: 這是 SAS 的一個技巧,確保這段程式碼只在第一次讀取資料時執行,將所有補班日載入陣列。
  • do...to...: 建立一個迴圈,逐一檢查開始日期到結束日期之間的每一天。
  • weekday(current_date) not in (1, 7): 判斷該天是否為平日(1=星期日,7=星期六)。
  • put(current_date, HOLIDAY.) = '0': 利用我們前面建立的格式,判斷是否為國定假日
  • if is_makeup_workday or...: 這就是工作日計數的最終邏輯。只要符合補班日平日且非假日其中一個條件,就將工作天數加一。

5. 驗證結果

最後,我們使用 proc print 來檢視計算結果,確認程式是否正確。

/* 輸出最終結果 */ proc print data=Service_Date_out; run;