#!/usr/bin/env sh # report-range.sh — 算出報表期間的起訖日期與標籤(供 jsc-log:report 呼叫)。 # 用法: # report-range.sh {daily|weekly|monthly|yearly} [基準日期 yyyy-MM-dd] # 基準日期省略時用今天。輸出一行 TSV: # {start}{end}{label}{period} # start、end 皆為 yyyy-MM-dd,含頭含尾。 # label:daily=2026-08-26、weekly=2026-W35、monthly=2026-08、yearly=2026 # # 週的定義採 ISO-8601:週一起算、週日結束,標籤用 ISO 週次。worklog 頁內以工作週的 # 週五整理內容,週次與那個週五落在同一週,兩邊對得起來。 # # 日期運算優先用 GNU date -d,退回 BSD date -v。兩者都不支援就 exit 4,不自己算閏年: # 手刻日期運算出錯的方式很安靜,報表會少算一天而沒有人發現。 # # 結束碼: 0=成功 2=用法錯誤 4=系統的 date 不支援日期運算 set -eu usage() { echo "用法:report-range.sh {daily|weekly|monthly|yearly} [yyyy-MM-dd]" >&2 exit 2 } period="${1:-}" case "$period" in daily|weekly|monthly|yearly) ;; *) usage ;; esac base="${2:-}" if [ -z "$base" ]; then base=$(date +%Y-%m-%d) else case "$base" in [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]) ;; *) echo "基準日期格式要是 yyyy-MM-dd:$base" >&2; exit 2 ;; esac fi # $1=基準日 $2=偏移(例 -3 days、+1 month)$3=輸出格式 shift_date() { if date -d "$1 $2" "+$3" >/dev/null 2>&1; then date -d "$1 $2" "+$3" return 0 fi # BSD:偏移寫法不同(-v-3d),只轉換這裡真的用到的幾種 _bsd=$(printf '%s' "$2" | sed 's/ days\{0,1\}/d/; s/ months\{0,1\}/m/; s/ years\{0,1\}/y/; s/^+/+/') if date -j -f %Y-%m-%d -v"$_bsd" "$1" "+$3" >/dev/null 2>&1; then date -j -f %Y-%m-%d -v"$_bsd" "$1" "+$3" return 0 fi echo "系統的 date 不支援日期運算,算不出報表期間" >&2 exit 4 } fmt() { shift_date "$1" "+0 days" "$2"; } case "$period" in daily) start="$base"; end="$base" label=$(fmt "$base" %Y-%m-%d) ;; weekly) # ISO 週:週一為第 1 天。%u 給 1(週一)到 7(週日)。 dow=$(fmt "$base" %u) start=$(shift_date "$base" "-$((dow - 1)) days" %Y-%m-%d) end=$(shift_date "$start" "+6 days" %Y-%m-%d) label=$(fmt "$base" %G-W%V) ;; monthly) start=$(fmt "$base" %Y-%m-01) end=$(shift_date "$(shift_date "$start" "+1 month" %Y-%m-%d)" "-1 day" %Y-%m-%d) label=$(fmt "$base" %Y-%m) ;; yearly) start=$(fmt "$base" %Y-01-01) end=$(fmt "$base" %Y-12-31) label=$(fmt "$base" %Y) ;; esac printf '%s\t%s\t%s\t%s\n' "$start" "$end" "$label" "$period"