#!/bin/bash
# Weekday
# Author: Perette Barella
# @(#) $Id: weekday 52 2019-08-30 13:28:35Z perette $

function usage
{
	cat 1>&2 << EOF
Usage: $arg0 [options] <week> <weekday> <month> <year>
Options:
  -? -h, --help      Display help.
EOF
}

function help
{
	usage
	cat 1>&2 << EOF

Example: '$arg0 4 mon 11 2002' => '25', the fourth Monday of November, 2002.
Notes:
  week:    - 1-4     return nth weekday of the month
           - 5       return day as in 1-4, or exit with status 1 if there is
                     no fifth occurance of the weekday in the specified month.
           - last    return last occurance of the weekday in specified month,
                     whether it is fourth or fifth occurance.
  weekday: - 0-7     Sunday, Monday, Monday, ... , Saturday, Sunday
           - letter  First letter, except N=Sunday, R=Thursday
           - name    Name or 3-letter or longer abbreviation.
  month    - 1-12    January .. December
           - name    Name or 3-letter or longer abbreviation.
  year     Use 4-digit years.  
EOF
}

function get_nth_day_of_month
{
        [ $# -lt 4 ] && return 1
        typeset weeknum="$1" number="$1" weekday="$2" monthname="$3" year="$4"
	[ "$number" = "last" ] && number=5
        typeset -i day offset bump result month days

        case "$weekday" in
                0|[nN]|[sS]un*|7)       day=0 ;;
                1|[mM]|[mM]on*) day=1 ;;
                2|[tT]|[tT]ue*) day=2 ;;
                3|[wW]|[wW]ed*) day=3 ;;
                4|[rR]|[Tt]hu*) day=4 ;;
                5|[fF]|[fF]ri*) day=5 ;;
                6|[sS]|[sS]at*) day=6 ;;
                *)      exit 1
        esac
	case "$monthname" in
		1|01|[jJ]an*) month=1; days=31 ;;
		2|02|[fF]eb*) month=2; days=28 ;;
		3|03|[mM]ar*) month=3; days=31 ;;
		4|04|[aA]pr*) month=4; days=30 ;;
		5|05|[mM]ay*) month=5; days=31 ;;
		6|06|[jJ]un*) month=6; days=30 ;;
		7|07|[jJ]ul*|[jJ]ly) month=7; days=31 ;;
		8|08|[aA]ug*) month=8; days=31 ;;
		9|09|[sS]ep*) month=9; days=30 ;;
		10|[oO]ct*) month=10; days=31 ;;
		11|[nN]ov*) month=11; days=30 ;;
		12|[dD]ec*) month=12; days=31 ;;
	esac

        set $(cal $month $year | tail -n+3 | head -1 | sed $'s/\b.//')
        offset=$#

        let "result = day + 1 + ((number - 1) * 7) + offset"
        let "bump = 7 - offset"
        [ $day -ge $bump ] && let "result = result - 7"

	if [ $result -gt $days ]
	then
		[ "$weeknum" = "5" ] && return 2
		[ "$weeknum" = "last" ] || return 1
		let "result = result - 7"
	fi

        printf "%s\n" "$result"
        return 0
}

function test_ok
{
        typeset result expected="$1"
        shift 1
        result=$(get_nth_day_of_month "$@") ||
                printf "%s\n" "Failed: get_nth_day_of_month returned failure."
        [ $result -eq $expected ] ||
                printf "%s\n" "Failed: get_nth_day_of_month $* returned $result, expected $expected."
}

arg0=$(basename $0)
if [ "$1" = "RUN_UNIT_TEST_NOW" ]
then
        # Month starting in middle
        test_ok 1 1 fri 11 2002
        test_ok 2 1 sat 11 2002
        test_ok 3 1 sun 11 2002
        test_ok 4 1 mon 11 2002
        test_ok 7 1 thu 11 2002
        test_ok 7 1 thu 11 2002
        test_ok 8 2 fri 11 2002
        test_ok 14 2 thu 11 2002
        test_ok 25 4 mon 11 2002
	test_ok 28 last thur 11 2002
	test_ok 29 last fri 11 2002

        # Month starting on Sunday
        test_ok 1  1 sun 12 2002
        test_ok 4  1 wed 12 2002
        test_ok 7  1 sat 12 2002
        test_ok 15 3 sun 12 2002
        test_ok 18 3 wed 12 2002
        test_ok 21 3 sat 12 2002

        # Month starting on Saturday
        test_ok 1  1 sat 06 2002
        test_ok 2  1 sun 06 2002
        test_ok 5  1 wed 06 2002
        test_ok 7  1 fri 06 2002
        test_ok 25 4 tue 06 2002
        test_ok 29 5 sat 06 2002
        test_ok 30 5 sun 06 2002

	# Current month, because highlighting breaks things
	test_ok 5  1 sat 05 2018
	test_ok 4  1 sat 05 2019

        get_nth_day_of_month 1 fri 11 &&
                printf "%s\n" "Failure: get_nth_day_of_month: success with 3 arguments."
elif [ "$1" = "-?" -o "$1" = "-h" -o "$1" = "--help" ]
then
	help
	exit 1
else
        get_nth_day_of_month "$@"
	result=$?
        if [ $result -ne 0 ]
        then
		if [ $result -eq 2 ]
		then
			printf "%s\n" "$arg0: No fifth day this month." 1>&2
		else
			printf "%s\n" "$arg0: Invalid arguments." 1>&2
			usage
		fi
		exit $result
        fi
fi
exit 0

