cal 與 dateShell 日期運算常借助 cal 取得月天數,date 做格式化與時間戳轉換。經典片段:
aaa=$(cal $mm $yy)
bbb=$(echo $aaa | awk '{print $NF}') # 取得該月天數
prev_date() {
local str=$1 days=$2
local yy=${str:0:4} mm=${str:4:2} dd=${str:6:2}
days=$((days - dd))
while (( days >= 0 )); do
((mm--))
if (( mm == 0 )); then mm=12; ((yy--)); fi
local dim=$(cal $mm $yy | awk '{print $NF}')
days=$((days - dim))
done
dd=$((0 - days))
printf "%04d%02d%02d" $yy $mm $dd
}
next_date() {
local str=$1 days=$2
local yy=${str:0:4} mm=${str:4:2} dd=${str:6:2}
days=$((days + dd))
while (( days > 0 )); do
local dim=$(cal $mm $yy | awk '{print $NF}')
if (( days <= dim )); then
dd=$days; days=0
else
days=$((days - dim))
((mm++))
if (( mm > 12 )); then mm=1; ((yy++)); fi
fi
done
printf "%04d%02d%02d" $yy $mm $dd
}
last_day_prev_month() {
local yy=$(date +%Y) mm=$(($(date +%m) - 1))
if (( mm == 0 )); then mm=12; ((yy--)); fi
local dd=$(cal $mm $yy | awk '{print $NF}')
printf "%04d%02d%02d" $yy $mm $dd
}
diff_days() {
local d1=$1 d2=$2
local yy1=${d1:0:4} mm1=${d1:4:2} dd1=${d1:6:2}
local yy2=${d2:0:4} mm2=${d2:4:2} dd2=${d2:6:2}
local days=$((dd2 - dd1))
while (( yy2 != yy1 || mm2 != mm1 )); do
((mm2--))
if (( mm2 == 0 )); then mm2=12; ((yy2--)); fi
local dim=$(cal $mm2 $yy2 | awk '{print $NF}')
days=$((days + dim))
done
echo $days
}
date(GNU/BSD)若系統支援 GNU date(Linux)或 BSD date(macOS),**強烈建議直接使用內建運算**,簡潔且無 Bug:
# Linux (GNU date)
date -d "20230101 5 days" +%Y%m%d # 5 天后
date -d "20230101 5 days ago" +%Y%m%d # 5 天前
date -d "last day" +%Y%m%d # 昨天
date -d "$(date +%Y%m01) 1 day ago" +%Y%m%d # 上月最後一天
# 兩日期差天數
echo $(( ($(date -d "20230202" +%s) - $(date -d "20230101" +%s)) / 86400 ))
# macOS (BSD date)
date -v+5d -j -f "%Y%m%d" "20230101" +%Y%m%d
date -v-5d -j -f "%Y%m%d" "20230101" +%Y%m%d
date -v-1d +%Y%m%d
date -v1d -v-1m -j -f "%Y%m%d" "$(date +%Y%m01)" +%Y%m%d
date_by_interval() {
local day=$1 interval=$2
local daysec=$(date -d "$day" +%s 2>/dev/null || date -j -f "%Y%m%d" "$day" +%s)
local resultsec=$((daysec + interval * 86400))
date -d "@$resultsec" +%Y%m%d 2>/dev/null || date -r $resultsec +%Y%m%d
}
# 用法:date_by_interval 20230101 7 -> 20230108
# date_by_interval 20230101 -7 -> 20221225
date 命令,避免手寫邏輯 Bug(閏年、跨月、跨年極易出錯)。date_by_interval 等函數,內部判斷 date 版本調用對應參數。date),再啟用基於 cal 的純 Shell 實現,並務必加上完整測試用例(含閏年、世紀年、跨世紀)。