zshで祝日計算
20140405追記
- 結局、Appleのicsを採用した
- GoogleCalendarだと銀行休業日なんてものがあっていいかなと思った
- クリスマスまで祝日になってて、日本では休日じゃないですしおすしってなった
概要
- Perl で何回か書いたことあったけど、シェルスクリプトで年末くらいにトライした
- きっかけは第N曜日って cal コマンドから簡単にとれるんじゃないか?
- cal して、tail して cut するだけだよね?
- dateコマンド色々
- よし!作れる(確信)
思い出す
- パースする条件ってなんだっけ?ということで
$ curl -s 'http://www.mozilla.org/projects/calendar/caldata/JapanHolidays.ics' | cut -d: -f1 | sort | uniq
Day
BEGIN
CATEGORIES
CREATED
DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Asia/Tokyo
DTSTAMP
DTSTART
DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Asia/Tokyo
END
LAST-MODIFIED
PRODID
RRULE
SUMMARY
TZID
TZNAME
TZOFFSETFROM
TZOFFSETTO
UID
VERSION
X-LIC-LOCATION
おkおk、とりあえず、祝日を計算するだけなら次の2つをだけみればいい
- DTSTART
- 祝日、もしくは繰り返し条件がある場合の最初の時間
- RULE
- 繰り返し条件、後述
RRULE をパースするのでパターン分け
- おもむろにRRULEだけ抽出
curl -s 'http://www.mozilla.org/projects/calendar/caldata/JapanHolidays.ics' | grep RRULE
$ curl -s 'http://www.mozilla.org/projects/calendar/caldata/JapanHolidays.ics' | grep RRULE | sort | uniq
RRULE:FREQ=MONTHLY;INTERVAL=12;BYDAY=2MO
RRULE:FREQ=MONTHLY;INTERVAL=12;BYDAY=3MO
RRULE:FREQ=YEARLY;COUNT=3;INTERVAL=4
RRULE:FREQ=YEARLY;COUNT=4;INTERVAL=4
RRULE:FREQ=YEARLY;INTERVAL=1
なし
- これは楽、無条件で DTSTART が実行日(大体、振替休日)
RRULE:FREQ=MONTHLY;INTERVAL=12;BYDAY=2MO
RRULE:FREQ=MONTHLY;INTERVAL=12;BYDAY=3MO
- DTSTARTの月の第N曜日が祝日
RRULE:FREQ=YEARLY;COUNT=4;INTERVAL=4
RRULE:FREQ=YEARLY;COUNT=3;INTERVAL=4
- 4年間隔でCOUNT回繰り返す祝日
RRULE:FREQ=YEARLY;INTERVAL=1
- 毎年、同じ月日が祝日
抽出条件を整理
RRULEがないケース
- 年が一致していればDTSTARTの月日を返す
:
以下を ;
で切った結果、要素が2つ
- DTSTARTの月日を返す
:
以下を ;
で切った結果、要素が3つ
-
YEARLY
のケース -
COUNT
回INTERBAL
を加算し続けて年が一致すれば
DTSTART
の月日を返す -
MONTHLY
のケース -
DTSTART
の月の第N曜日を返す
引数
- 年は必要
- 月もあってもいいかもくらい(grepすれば対象月だけとれるし)
実装
#!/usr/bin/zsh
readonly YEAR=$1
typeset -A DoW
DoW=('SU' 1 'MO' 2 'TU' 3 'WE' 4 'TH' 5 'FR' 6 'SA' 7)
set -ue
function get_Nth_wday () {
integer Nth="$1"
local dow=${DoW[$2]}
integer mm="$3"
echo "$(printf '%02d' $(ncal "${mm}" "${YEAR}" | tail -n+2 | tail -n+"$dow" | head -n1 | sed 's/ */ /g' | cut -d' ' -f$(expr 1 + ${Nth})))"
}
function parse_ics () {
integer dtstart=$1
typeset -a rrule
rrule=($(echo "$2" | sed 's/;/ /g'))
if [ "${#rrule}" -eq 2 ]; then
echo "${dtstart}" | sed 's/^[0-9]\{4\}//'
elif [[ "${rrule}" =~ 'YEARLY' ]] ; then
[[ "${rrule}" =~ 'COUNT=([0-9]*)' ]]
integer count=${match[1]}
[[ "${rrule}" =~ 'INTERVAL=([0-9]*)' ]]
integer interval=${match[1]}
[[ "${dtstart}" =~ '(....)(....)' ]]
integer yyyy=${match[1]}
local mmdd="${match[2]}"
for ((i=0; i <= $count; i += 1))
do
yyyy=$(expr "${yyyy}" + "$interval")
if [ $yyyy -eq $YEAR ] ; then
echo "${dtstart}" | sed 's/^....//'
fi
done
else
[[ "${rrule}" =~ 'BYDAY=(.)(..)' ]]
integer Nth="${match[1]}"
local dow="${match[2]}"
[[ "${dtstart}" =~ '....(..)..' ]]
local mm=$match[1]
echo "${mm}$(get_Nth_wday ${Nth} ${dow} ${mm})"
fi
}
() {
typeset -A holiday
local holidays
wget -q http://ical.mac.com/ical/Japanese32Holidays.ics -O - | \
while read result
do :
if [[ "${result}" =~ "^BEGIN:VEVENT" ]] ; then
:
holiday=(
rrule ''
dtstart ''
summary ''
)
elif [[ "${result}" =~ "^RRULE:(.*)" ]] ; then
holiday[rrule]="${match[1]}"
elif [[ "${result}" =~ "^DTSTART;.*:([0-9]*)" ]] ; then
holiday[dtstart]="${match[1]}"
elif [[ "${result}" =~ "^SUMMARY" ]] ; then
holiday[summary]="$(echo ${result} | cut -d: -f2 | cut -d' ' -f1)"
elif [[ "${result}" =~ "^END:VEVENT" ]] ; then
if [[ "${holiday[dtstart]}" = '' ]] ; then
continue
elif [[ "${holiday[rrule]}" = '' ]] ; then
if [[ "${holiday[dtstart]}" =~ "^${YEAR}" ]] ; then
[[ "${holiday[dtstart]}" =~ '....(....)' ]]
holidays="$holidays\n${YEAR}${match[1]}: ${holiday[summary]}"
fi
else
local mmdd="$(parse_ics "${holiday[dtstart]}" "${holiday[rrule]}")"
if [[ "${mmdd}" != '' ]] ; then
holidays="$holidays\n${YEAR}${mmdd}: ${holiday[summary]}"
fi
fi
fi
done
echo "${holidays}" | sort
}
実行結果(Apple)
- 銀行休業日とりたかったなー
$ zsh holidays.sh 2015
20150101: 元日
20150112: 成人の日
20150211: 建国記念の日
20150321: 春分の日
20150429: 昭和の日
20150503: 憲法記念日
20150504: みどりの日
20150505: こどもの日
20150506: 振替休日
20150720: 海の日
20150921: 敬老の日
20150922: 国民の休日
20150923: 秋分の日
20151012: 体育の日
20151103: 文化の日
20151123: 勤労感謝の日
20151223: 天皇誕生日
$ zsh holidays.sh 2014
20140101: 元日
20140113: 成人の日
20140211: 建国記念の日
20140321: 春分の日
20140429: 昭和の日
20140503: 憲法記念日
20140504: みどりの日
20140505: こどもの日
20140506: 振替休日
20140721: 海の日
20140915: 敬老の日
20140923: 秋分の日
20141013: 体育の日
20141103: 文化の日
20141123: 勤労感謝の日
20141124: 振替休日
20141223: 天皇誕生日
実行結果(GoogleCalendar)
- 年を食わせてあげる
$ zsh holidays.sh 2014
20140101: 元日
20140102: 銀行休業日
20140103: 銀行休業日
20140113: 成人の日
20140211: 建国記念の日
20140321: 春分の日
20140429: 昭和の日
20140503: 憲法記念日
20140504: みどりの日
20140505: こどもの日
20140506: みどりの日
20140721: 海の日
20140915: 敬老の日
20140923: 秋分の日
20141013: 体育の日
20141103: 文化の日
20141123: 勤労感謝の日
20141124: 勤労感謝の日
20141223: 天皇誕生日
20141225: クリスマス
20141231: 大晦日
$ zsh holidays.sh 2015
20150101: 元日
20150102: 銀行休業日
20150103: 銀行休業日
20150112: 成人の日
20150211: 建国記念の日
20150321: 春分の日
20150429: 昭和の日
20150503: 憲法記念日
20150504: みどりの日
20150505: こどもの日
20150506: 憲法記念日
20150720: 海の日
20150921: 敬老の日
20150922: 国民の休日
20150923: 秋分の日
20151012: 体育の日
20151103: 文化の日
20151123: 勤労感謝の日
20151223: 天皇誕生日
20151225: クリスマス
実行環境
$ cat /etc/debian_version
7.1