#!/bin/sh
#
#  tzdiff - displays timezone differences with localtime.
#
#  Copyright (c) 2016 - 2024 Masato Minda
#  All rights reserved.
#
#  Redistribution and use in source and binary forms, with or without
#  modification, are permitted provided that the following conditions
#  are met:
#  1. Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
#  2. Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
#
#  THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
#  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
#  ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
#  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
#  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
#  OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
#  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
#  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
#  OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
#  SUCH DAMAGE.
#
#  $Id: tzdiff,v 1.55 2024/04/02 02:03:55 minmin Exp $

version=1.2.1
format='%F %R %Z'
max=10

__check_match () {
	if [ `echo ${1} | wc -w` -eq 1 ]; then
		return 0
	fi
	ls -dF ${1}
	exit 1
}

__set_zoneinfo () {
	case "${1}" in
	*\**)
		__check_match "${1}*"
		zi=`echo ${1}*`
		;;

	*)	zi=${1}
		;;
	esac

	while [ ! -f ${zi} ]; do
		if [ -d ${zi} ]; then
			ls -dF ${zi}/*
			exit 1
		fi

		__check_match "${zi}*"
		zi=`echo ${zi}*`
		if [ ! -e ${zi} ]; then
			__check_match "*/${zi}"
			zi=`echo */${zi}`
			if [ ! -e ${zi} ]; then
				__check_match "*/${zi}"
				zi=`echo */${zi}`
				if [ ! -e ${zi} ]; then
					ls -F
					exit 2
				fi
			fi
		fi
	done

	case ${zi} in
	*/*)	city=${zi##*/} ;;
	*)	city=${zi} ;;
	esac
}

__make_column () {
	if [ ${H_flag} -ne 0 ]; then
		echo "%s"
		return
	fi

	_ltd="$(__date_with_format ${tis} ${1})"
	if [ -n "${2}" ]; then
		if [ ${#2} -gt ${#_ltd} ]; then
			echo "%-${#2}s"
		else
			echo "%-${#_ltd}s"
		fi
	else
		if [ ${#1} -gt ${#_ltd} ]; then
			echo "%-${#1}s"
		else
			echo "%-${#_ltd}s"
		fi
	fi
}

__usage () {
	cat <<- EOT >&2
	Usage: ${0##*/} [-0lvHN] [-n count] [-f format] [-t start] timezone [timezone ...] [count] [0]
	    -0:  round down to hour
	    -n:  max hours (default: 10)
	    -l:  display full timezone name, only city in default
	    -f:  output format (using '+output_fmt' of 'date' command)
	    -t:  specify the start time (YYYY-mm-ddTHH:MM[Z], etc...)
	    -v:  show version.
	    -H:  scripting mode (with timezone name)
	    -HH: scripting mode (without timezone name)
	    -N:  without localtime
	EOT
	exit 2
}

__version () {
	echo "${0##*/} ${version}"
	exit 0
}

__setup () {
	if date --version >/dev/null 2>&1; then
		# for GNU date
		g_date=YES
		__date_with_format () {
			if [ $# -eq 2 ]; then
				TZ=${2} date --date=@${1} +"${format}"
			else
				date --date=@${1} +"${format}"
			fi
		}
	else
		# for traditional date
		__date_with_format () {
			if [ $# -eq 2 ]; then
				TZ=${2} date -j -r ${1} +"${format}"
			else
				date -j -r ${1} +"${format}"
			fi
		}
	fi
}

__set_time () {
	if [ -n "${st}" ]; then
		if [ -n "${g_date}" ]; then
			tis=`date --date="${st}" +%s` || exit
		else
			case "${st}" in
			[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9]T[012][0-9]:[0-5][0-9] | \
			[0-9][0-9][0-9][0-9][01][0-9][0-3][0-9]T[012][0-9][0-5][0-9] | \
			[0-9][0-9][0-9][0-9][01][0-9][0-3][0-9][012][0-9][0-5][0-9] )
				# COMMAND_MODE is a macOS-specific environment variable.
				# See compat(5) on macOS.
				tis=`COMMAND_MODE=legacy date -j +%s $(echo ${st} | tr -d 'T:-')` || exit
				;;
			[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9]T[012][0-9]:[0-5][0-9]Z | \
			[0-9][0-9][0-9][0-9][01][0-9][0-3][0-9]T[012][0-9][0-5][0-9]Z | \
			[0-9][0-9][0-9][0-9][01][0-9][0-3][0-9][012][0-9][0-5][0-9]Z )
				tis=`COMMAND_MODE=legacy TZ=UTC date -j +%s $(echo ${st} | tr -d 'TZ:-')` || exit
				;;
			*)	__usage ;;
			esac
		fi
	else
		tis=`date +%s`
	fi

	if [ x${zero_flag} = x"YES" ]; then
		tis=$(( (${tis} / 3600) * 3600))	# round down to hour
	fi
}

# ----- begin tzdiff -----

cd /usr/share/zoneinfo || exit
__setup

H_flag=0
while getopts '0hlvHNn:f:t:' cmd_arg; do
	case "${cmd_arg}" in
	0)	zero_flag=YES ;;
	l)	l_flag=YES ;;
	n)	max=${OPTARG} ;;
	f)	format="${OPTARG}" ;;
	t)	st=${OPTARG} ;;
	v)	__version ;;
	H)	H_flag=$((H_flag + 1)) ;;
	N)	N_flag=YES ;;
	h|*)	__usage ;;
	esac
done
shift $((OPTIND - 1))

fs='   '				# 3 spaces
[ ${H_flag} -ne 0 ] && fs='	'	# TAB

if [ $# -eq 0 ]; then
	ls -F
	exit
fi

id=0
while [ $# -gt 0 ]; do
	case $1 in
	[0-9]*)
		if [ ${1} -eq 0 ]; then
			zero_flag=YES
		else
			max=${1}
		fi
		;;
	*)
		__set_zoneinfo "${1}"
		eval tz${id}=${zi}
		eval city${id}=${city}
		id=$((id + 1))
		;;
	esac
	shift
done

if [ ${id} -eq 0 ]; then
	__usage
fi

__set_time

# make format
i=0
while [ ${i} -lt ${id} ]; do
	eval tz=\$tz${i}
	if [ -n "${l_flag}" ]; then
		eval fmt${i}=$(__make_column ${tz})
	else
		eval city=\$city${i}
		eval fmt${i}=$(__make_column ${tz} ${city})
	fi
	i=$((i + 1))
done

# print location
if [ ${H_flag} -lt 2 ]; then
	i=0
	while [ ${i} -lt ${id} ]; do
		eval tz=\$tz${i}
		eval city=\$city${i}
		eval fmt=\$fmt${i}

		i=$((i + 1))
		if [ ${i} -eq ${id} ]; then
			if [ -n "${l_flag}" ]; then
				printf "%s\n" "${tz}"
			else
				printf "%s\n" "${city}"
			fi
		else
			if [ -n "${l_flag}" ]; then
				printf "${fmt}${fs}" "${tz}"
			else
				printf "${fmt}${fs}" "${city}"
			fi
		fi
	done
fi

# print time
j=0
while [ ${j} -lt ${max} ]; do
	i=0
	while [ ${i} -lt ${id} ]; do
		eval tz=\$tz${i}
		eval fmt=\$fmt${i}
		printf "${fmt}" "$(__date_with_format ${tis} ${tz})"
		i=$((i + 1))
		[ -n "${N_flag}" -a ${i} -eq ${id} ] || printf "${fs}"
	done
	if [ -z "${N_flag}" ]; then
		__date_with_format ${tis}
	else
		echo
	fi
	tis=$((tis + 3600))
	j=$((j + 1))
done
