更新於 2024/11/24閱讀時間約 16 分鐘

Notion Formula 2.0 § 工作日計算

練習題_例行公事提醒表

繼之前文章有提到如何判斷月底是哪一天,我們來解這個練習題吧~

一樣我們透過let()自定義名稱,簡化公式複雜的撰寫。若要判斷標籤內容對應的狀態,我們可以使用contains()ifs()編輯公式。

當確認公式編輯無誤後,再開一個List View,使用篩選器功能,當提醒的欄位為True時就顯示。

lets(
本月第一天, dateSubtract(now(), date(now()) - 1, "days"),
次月第一天, dateAdd(本月第一天, 1, "months"),
月底, date(dateSubtract(次月第一天, 1, "days")),
ifs(
contains(prop("頻率"), "每天"), true,
contains(prop("頻率"), formatDate(now(), "ddd")), true,
contains(prop("頻率"), date(now())), true,
contains(prop("頻率"), 月底), if(月底 == date(now()), true, false),
false
)
)

P.S. 如果你對處理例行公事的設計有興趣,我還準備了一個進階功能的免費模板,詳情可參考 👉 Notion 模板 § 例行公事。這個模板能透過觸發器設定,讓當日完成的例行公事自動從列表中隱藏,保持列表的清爽與井然有序。

如果你已有想法,不妨自行設計!但若想省時,也可以直接前往〈Notion 模板 § 例行公事〉複製這個模板,輕鬆、快速套用到你的系統中。

工作日計算_基礎版

我曾經寫過一篇文章〈Notion日期相關公式_工作日計算〉,如同之前文章提到的,已經有支援避開周末的小功能(Avoid weekends),但基於我們需要練習,還是來嘗試把這個公式改成 2.0 的模式。

工作日定義:相對於周末和國定假日,工作日通常是一周中的五天。之前的文章沒有談到如何排除國定假日,今天我們來挑戰加入其他假期的概念,看看如何進行計算。

不過,任何事情都需要循序漸進,所以先來個簡單版的工作日計算吧。

計算日期區間的總天數與總週數

/* 總天數 */
dateBetween(dateEnd(prop("日期時間")), dateStart(prop("日期時間")), "days") + 1

/* 總週數 */
dateBetween(dateEnd(prop("日期時間")), dateStart(prop("日期時間")), "weeks") + 1

周末天數的計算

如何計算日期區間內的周末天數,這是整個公式中較具挑戰的部分,我們將分為兩個部分來討論。

若是比較簡單的情境,日期區間跨過一個完整周末(即兩天的非工作日),我們可以直接使用「週數」進行計算,只需計算總週數並乘以二即可。但有可能的狀況是,我們的日期區間只佔了周末的一天,就無法使用簡單的方式計算了。 由於計算時總天數總週數都需要在公式中+1,我特別命名了區間天數區間週數,以便後續計算更加方便。

/* 解釋使用,若要使用請補滿公式內容 */

lets(
開始日期, prop("日期時間").dateStart(),
結束日期, prop("日期時間").dateEnd(),
開始日期_周幾, prop("日期時間").dateStart().day(),
....
區間周數, dateBetween(結束日期, 開始日期, "weeks"),
周末天數, 區間周數 * 2,
......
)

我想僅用公式說明。可能比較難想像,這裡舉個具體的例子。若我們的專案時間是 2024-10-092024-10-26,總天數是 18 天,工作天是 13 天。

若我們以總週數 × 2(3 × 2 = 6)計算周末天數,結果會是錯誤的。正確的算法應該是區間週數 × 2(2 × 2 = 4),再加上剩餘的周末天數。

(當然你也可以使用總周數-1的方式,根據個人的習慣選擇。)

接下來我們來討論比較複雜的部分 — 如何計算剩餘的周末天數。

  1. 開始日期_周幾 + 區間天數 % 7:計算從開始日期到區間結束時,剩餘的天數落在一周的周幾。區間天數 % 7 計算的是不足一週的天數。
    • 開始日期_周幾 → 3 (周三)。
    • 區間天數 % 7 → 17 % 7 = 3 (%是餘數)。
  2. 5:這是用來判斷剩餘天數中是否跨過了週五,超過5表示有跨到周末(周六、周日)。
    • 在上面的例子,結合第一點,3 + 3 - 5 = 1,表示只跨過 1 天(周六)。
  3. max(..., 0)min(..., 2)
    • max() 取列表中的最大值,用來保證結果不為負數,表示週五後的天數。
    • min() 限制周末天數最多為兩天(週六、週日),以確保不會超過周末。
lets(
......
剩餘周末天數, min(max(開始日期_周幾 + 區間天數 % 7 - 5, 0), 2),
......
)

工作日計算_基礎版公式

lets(
開始日期, prop("日期時間").dateStart(),
結束日期, prop("日期時間").dateEnd(),
開始日期_周幾, prop("日期時間").dateStart().day(),
區間天數, dateBetween(結束日期, 開始日期, "days"),
總天數, 區間天數 + 1,
區間周數, dateBetween(結束日期, 開始日期, "weeks"),
周末天數, 區間周數 * 2,
剩餘周末天數, min(max(開始日期_周幾 + 區間天數 % 7 - 5, 0), 2),
總天數 - (周末天數 + 剩餘周末天數)
)

工作日計算_進階版

國定假日

若要加入國定假日或是一些特殊節日時,我們可以開另一個資料庫來管理這些節日,之後用匹配資料的方式判斷。

記得所有的國定假日要關聯每一個專案,這樣以後就可以自動化判斷了。

設置的方法是從專案的資料庫,開一個Default Template,裡面的那個資料頁面模板直接關聯所有的國定假日。

為了方便編輯公式,我們先在這個資料庫中,多開公式欄位,這個欄位要顯示國定假日放假的日期區間中,所有的日期。舉個例子,如清明節假期是2024-04-042024-04-07,那麼要顯示@April 4, 2024,@April 5, 2024,@April 6, 2024,@April 7, 2024

這部分比較困難的部分,在於 Notion Formula 中沒有迴圈的函數,所以我們利用repeat()達到類似的效果,迴圈效果請參考〈Notion Formula 2.0 § 基礎篇_2〉。

特別強調,這部分請在專案任務的資料庫中,針對專案日期區間也做一樣的效果。我們後面在進行比對運算時,需要讓他們都呈現 list 的資料結構。

lets(
d, prop("日期時間"),
天數, dateBetween(d.dateEnd(), d.dateStart(), "days"),
forLoop, "x".repeat(天數).split("x"),
if(
天數 == 0, d.dateStart(),
forLoop.map(d.dateStart().dateAdd(index, "days"))
)
)

專案任務的映射資料處理

首先,我們需要先映射國定假日的放假日期到專案任務資料庫中。

這裡要特別注意一點,我們需要使用flat()函數,讓所有的列表變成一個列表。如果沒有使用flat(),那它的架構其實是像這樣的(為了好閱讀,我把它們都變成英文字母代替):

[[A], [B, C, D, E, F, G, H], [I], [J, K, L, M]]

使用flat()後,這一長串列表就可以變成單一個列表,也方便我們之後比較:

[A, B, C, D, E, F, G, H, I, J, K, L, M]

lets(
放假日期, map(prop("國定假日"),current.prop("放假日期")).flat(),
放假日期
)

放假日期與專案日期比較

當我們把放假日期與專案日期兩個欄位都變成 list 的資料形式後,就可以拿來比較啦~~使用filter()includes()

lets(
........
List_1, 放假日期,
List_2, 專案日期,
重疊日期, List_2.filter(List_1.includes(current)),
.........
)

當我們匹配出重複的日期後,可以利用sort()reverse()at()判斷出這段日期區間裡的開始日期與結束日期。

PS. 這裡不要使用min()max(),乍看下可以顯示開始日期與結束日期,但因為資料屬性的因素,後面會無法操作其他運算喲~

lets(
........
List_1, 放假日期,
List_2, 專案日期,
重疊日期, List_2.filter(List_1.includes(current)),
重疊_開始日期, 重疊日期.sort().at(0),
重疊_結束日期, 重疊日期.sort().reverse().at(0),
........
)

再將前面那段計算一般專案日期區間工作日的公式,拿來套用在重疊日期的區間,兩者相減就是扣掉國定假日的工作日計算方法啦~~~下面看完整公式。

工作日計算_進階版公式

lets(
/* 工作日計算 */
開始日期, prop("日期時間").dateStart(),
結束日期, prop("日期時間").dateEnd(),
開始日期_周幾, prop("日期時間").dateStart().day(),
區間天數, dateBetween(結束日期, 開始日期, "days"),
總天數, 區間天數 + 1,
區間周數, dateBetween(結束日期, 開始日期, "weeks"),
周末天數, 區間周數 * 2,
剩餘周末天數, min(max(開始日期_周幾 + 區間天數 % 7 - 5, 0), 2),
基礎工作天數, 總天數 - (周末天數 + 剩餘周末天數),

/* 放假日期映射到專案資料庫 */
放假日期, map(prop("國定假日"),current.prop("放假日期")).flat(),

/* 專案日期 */
d, prop("日期時間"),
days, dateBetween(d.dateEnd(), d.dateStart(), "days"),
forLoop, "x".repeat(days).split("x"),
專案日期, if(
days == 0, d.dateStart(),
forLoop.map(d.dateStart().dateAdd(index, "days"))
),

/* 假期涵蓋工作日計算 */
List_1, 放假日期,
List_2, 專案日期,
重疊日期, List_2.filter(List_1.includes(current)),
重疊_開始日期, 重疊日期.sort().at(0),
重疊_結束日期, 重疊日期.sort().reverse().at(0),
重疊_開始日期_周幾, 重疊_開始日期.day(),
重疊_區間天數, dateBetween(重疊_結束日期, 重疊_開始日期, "days"),
重疊_總天數, if(empty(重疊日期), 0, 重疊_區間天數 + 1),
重疊_區間周數, dateBetween(重疊_結束日期, 重疊_開始日期, "weeks"),
重疊_周末天數, 重疊_區間周數 * 2,
重疊_剩餘周末天數, min(max(重疊_開始日期_周幾 + 重疊_區間天數 % 7 - 5, 0), 2),
重疊工作天數, 重疊_總天數 - (重疊_周末天數 + 重疊_剩餘周末天數),

"一般工作天: " + format(基礎工作天數) + "\n"
+ "重疊工作天: " + format(重疊工作天數) + "\n"
+ "真正工作天: " + format(基礎工作天數 - 重疊工作天數)
)

結語

這個「進階版工作日計算」花了我不少時間才完成,如果不需要將國定假日資料庫納入考量,根據以往的製作邏輯,應該能很快編輯完成。但因為我對新函數的功能還不夠熟悉,尤其是每種函數運作後的資料型態不太了解,使得整個過程相對辛苦。不過,完成這個練習讓我感到非常感動,未來只需定期更新國定假日的日期就可以持續運作啦~💪

這個公式明顯可以再精簡,不知道你有沒有想到呢?😂

如果你有任何想法,都可以與大家交流呀~

如果你想鼓勵我~歡迎請我喝杯咖啡 👉 贊助連結

範例頁面

例行公事表

工作日計算

參考資料




分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.