diff --git a/lib/date/day-of-week.sh b/lib/date/day-of-week.sh new file mode 100644 index 0000000..4af673f --- /dev/null +++ b/lib/date/day-of-week.sh @@ -0,0 +1,31 @@ +# Prints the ISO day of week for a given date in the Gregorian calendar +# 0 = Sunday, 1 = Monday, ..., 6 = Saturday +# +# This implementation works for all dates since the introduction of the +# Gregorian calendar. +# +# Usage: +# - day-of-week year month day +# - day-of-week 1970 1 1 +# - day-of-week 2020 2 29 +# - day-of-week 2070 10 20 + +: "${1:?missing year}" "${2:?missing month}" "${3:?missing day}" +# remove leading zeroes from arguments to prevent them from being interpreted +# as octal. The year is ignored because the Gregorian calendar was only +# introduced in the 16th century. +set -- "${1}" "${2#0}" "${3#0}" + +# RFC 3339, Appendix B + +set -- $(($2 >= 3 ? $1 : ($1 - 1))) $(($2 >= 3 ? ($2 - 2) : ($2 + 10))) $(($3)) +echo $(( + ( + (26 * $2 - 2) / 10 + + $3 + + ($1 % 100) + + (($1 % 100) / 4) + + ($1 / 400) + + (5 * ($1 / 100)) + ) % 7 +)) diff --git a/spec/date/day-of-week.spec b/spec/date/day-of-week.spec new file mode 100644 index 0000000..d3b379b --- /dev/null +++ b/spec/date/day-of-week.spec @@ -0,0 +1,106 @@ +Describe 'date/day-of-week' + EnableSandbox + + EnableLeakDetector + + SetupCommandFromFile day-of-week lib/date/day-of-week.sh + + # check if GNU date(1) is available + case $(@gdate --version 2>/dev/null) + in + (*'GNU coreutils'*) + __have_gdate=1 ;; + (*) + __have_gdate=0 ;; + esac + have_gdate() { + return $((! __have_gdate)) + } + skip_gdate() { + return $((__have_gdate)) + } + + # check if this shell has $RANDOM + have_random_var() { + case ${RANDOM-} in ('') ! : ;; esac + } + + + Context # static test values + Parameters + # year mon day expected day-of-week + 1970 1 1 4 + 1900 1 1 1 + 1900 2 28 3 + 1900 2 29 4 # NOTE: this is an invalid date + 1900 3 1 4 + 2000 1 1 6 + 2000 2 28 1 + 2000 2 29 2 + 2000 3 1 3 + 2020 3 1 0 + 2024 2 1 4 + 2024 2 28 3 + 2024 2 29 4 + 2024 3 1 5 + End + + It "calculates the day of week for $(@printf '%04u-%02u-%02u' $1 $2 $3)" + When run command day-of-week $1 $2 $3 + + The status should equal 0 + The stdout should equal "$4" + The stderr should equal '' + End + End + + It 'supports parameters with leading 0s' + When run command day-of-week 1970 01 01 + + The status should be success + The output should equal 4 + The error should equal '' + End + + Context # dynamic test values + Parameters:dynamic + n=10 + + if case ${RANDOM-} in ('') ! : ;; esac + then + while test $((n-=1)) -ge 0 + do + Y=$(( 1600 + (RANDOM % 1000) )) + m=$(( 1 + (RANDOM % 12) )) + case ${m} + in + (2) + d=$(( 1 + (RANDOM % (28 + (0==(Y % 4)&&(0!=(Y % 100)||0==(Y % 400))))) )) ;; + (1|3|5|7|8|10|12) + d=$(( 1 + (RANDOM % 31) )) ;; + (*) + d=$(( 1 + (RANDOM % 30) )) ;; + esac + %data $Y $m $d + done + else + : # skip: cannot generate random dates + fi + End + + gdate_check_dow() { + test "${gdate_check_dow}" -eq $(($(TZ=GMT0 @gdate --date="$1" +%u) % 7)) + } + + It "calculates the day of week for $(@printf '%04u-%02u-%02u' $1 $2 $3) (dynamic)" + Skip if 'date(1) is not GNU date' skip_gdate + + When run command day-of-week $1 $2 $3 + + The status should equal 0 + # NOTE: must check using function to call @gdate only if it is available + The stdout should satisfy gdate_check_dow "$(@printf '%04u-%02u-%02u' $1 $2 $3)" + The stderr should equal '' + End + End +End diff --git a/spec/support/bin/@gdate b/spec/support/bin/@gdate new file mode 100755 index 0000000..8af9be8 --- /dev/null +++ b/spec/support/bin/@gdate @@ -0,0 +1,7 @@ +#!/bin/sh -e +. "$SHELLSPEC_SUPPORT_BIN" +case ${OSTYPE} +in + (*darwin*) invoke gdate "$@" ;; + (*) invoke date "$@" ;; +esac