#!/usr/bin/env bash
# Desc: Baltakatei's verbose date command
# Usage: bkdatev [args]
# Example: bkdatev --date="2001-09-11T09:02:59-04"
# Version: 1.0.0
# Depends: GNU Coreutils 8.32, Bash 3.2.57
# Ref/Attrib: [1] "ISO 8601". Wikipedia. https://en.wikipedia.org/wiki/ISO_8601
#             [2] "Changing the Locale in Wine" https://stackoverflow.com/a/16428951
#             [3] "Shanghai vs Beijing" https://bugs.launchpad.net/ubuntu/+source/libgweather/+bug/228554
# Notes:  * Check `ls -R /usr/share/zoneinfo` for time zone names.
#         * Check `cat /usr/share/i18n/SUPPORTED` for supported locales.
#         * For list of valid locales, see: https://manpages.ubuntu.com/manpages/bionic/man3/DateTime::Locale::Catalog.3pm.html
#         * Locations chosen for population, personal signifiance, and spatial coverage.
#         * For International Atomic Time (TAI), use offsets from UTC provided in `/usr/share/zoneinfo/leap-seconds.list`.
#         * Compatibility with macOS may be limited if any arguments
#             are provided when running `bkdatev`; e.g. passing a
#             `--date` option to `bkdatev` will fail.

yell() { echo "$0: $*" >&2; }
die() { yell "$*"; exit 111; }
must() { "$@" || die "cannot $*"; }
insertStr() {
    # Desc: Inserts a string into another string at a specified position.
    # Input: arg1: str   str_rec  String to receive insertion
    #        arg2: int   pos      Insertion position (0 = append to front)
    #        arg3: str   str_ins  String to be inserted
    # Output: stdout:    Combined string
    # Version: 0.0.2
    # Depends: BK-2020-03: yell(), die(), must()
    # Ref/Attrib: * BK-2020-03: https://gitlab.com/baltakatei/baltakatei-exdev/
    #             * Cooper, Mendel. “Advanced Bash-Scripting Guide: Manipulating Strings”. tldp.org https://tldp.org/LDP/abs/html/string-manipulation.html

    local str_rec pos str_ins re len_str_rec;
    local pfx_pos_start pfx_len pfx;
    local sfx_pos_start sfx_len sfx;

    # Check args
    if [[ $# -ne 3 ]]; then
        yell "ERROR:Invalid argument count:$#";
        return 1; fi;
    re='^[0-9]+$';
    if [[ ! "$2" =~ $re ]]; then
        yell "ERROR:Not an int:$2";
        return 1; fi;
    str_rec="$1";
    pos="$2";
    str_ins="$3";

    # Calculate string stats
    len_str_rec="${#str_rec}";

    # Form prefix
    pfx_pos_start="0";
    pfx_len="$pos";
    pfx="${str_rec:$pfx_pos_start:$pfx_len}";

    # Form suffix
    sfx_pos_start="$(( pos ))";
    sfx_len="$(( len_str_rec - pos ))";
    sfx="${str_rec:$sfx_pos_start:$sfx_len}";

    # Print output to stdout
    printf "%s%s%s\n" "$pfx" "$str_ins" "$sfx";
}; # Insert string provided at indicated position via stdout
line_sep() {
    # Input: var: n_ln
    local skip_every=4;
    ((n_ln++));
    if ! ((n_ln % "$skip_every")); then
        printf "\n";
    fi;
    return 0;
}; # periodically print separating blank line
get_tz_offset() {
    # Desc: Get from 'date' the timezone UTC offset in a way
    #   compatible with both GNU Coreutils and BSD versions.
    # Input: env var: TZ  (time zone for date; e.g. 'America/Denver')
    #        args: $@     # passed onto `date`
    # Depends: date (GNU Coreutils 8.32 or BSD), rev
    local ntz ntz ntz_out;
    local last2;

    # Get numeric time zone string in way compatible with GNU Coreutils and BSD
    ntz="$(date "+%z" "$@")"; # e.g. "+0530"

    # Check if last two characters are trailing zeros that can be removed.
    last2="${ntz:3:2}"; # assumes $ntz is 5 characters (i.e. "±HHMM")
    #last2="$(rev <<< $ntz)" && last2="${last2:0:2}" && last2="$(rev <<< "$last2")";
    if [[ "$last2" == "00" ]]; then
        ## ntz_out is truncated by 2 characters
	len_ntz="${#ntz}";
	len_ntz_out="$(( len_ntz - 2 ))";
	ntz_out=""${ntz:0:$len_ntz_out};
    else
        ## ntz_out is ntz with semicolon inserted after HH
        ntz_out="$(insertStr "$ntz" 3 ":" )";
    fi;
    
    # Output via stdout
    printf "%s" "$ntz_out";
}; # Format numeric time zone (for BSD date compatibility)
print_dateline() {
    # Input: var: $id
    #        var: $fs_1
    #        var: $fs_2
    #        var: $fs_3
    #        args: $@   # passed on to `date`
    #        env var: TZ (time zone for date; e.g. 'America/Denver')
    # Output: stdout
    # Depends: printf, date
    #          get_tz_offset()
    # Ref/Attrib: * Truncate string in printf https://stackoverflow.com/a/46812677
    local s_1 s_2 s_2_tz s_3 s_4;

    s_1="$id";
    s_2="$(date "$@" "$fs_1")"; # ISO-8601 without numeric timezone
    s_3="$(date "$@" "$fs_2")"; # Alternate ISO-8601 expressions
    s_4="$(date "$@" "$fs_3")"; # locale-specific date strings
    
    # Append numeric timezone to $s_2 with appropriate format
    #   (e.g. '-07' for 'Arizona', '+05:45' for 'Asia/Kathmandu')
    s_2_tz="$(get_tz_offset "$@")";
    s_2="$( printf "%s%s" "$s_2" "$s_2_tz" )";

    printf "%-10.10s %-25.25s (%-20.20s) (%s)" "$s_1" "$s_2" "$s_3" "$s_4";
    printf "\n";

    unset fs_1 fs_2 fs_3 fs_4;
    }; # print line of dates
main() {
    n_ln=0; # for line_sep()
    unset LC_TIME; # Fall back to time zone-specific locale settings.
    
    # format strings
    fs_iso8601="+%Y-%m-%dT%H:%M:%S"; # typical ISO-8601 without timezone
    fs_iso8601_etc="+%G-W%V-%u, %Y-%j"; # alternate ISO-8601 dates
    fs_locale="+%Z; %A; %c"; # locale-specific date strings

    # vars for print_dateline()
    fs_1="$fs_iso8601";
    fs_2="$fs_iso8601_etc";
    fs_3="$fs_locale";

    # UTC (pop. (2021): 7,837,000,000)
    (
        export TZ=UTC;
        id="UTC";
        fs_3="+%s seconds since 1970-01-01T00:00+00";
        print_dateline "$@";
    ); line_sep;

    # Hawaii
    (
        export TZ=US/Hawaii;
        export LANG="haw-US.UTF8";
        id="HAWAII";
        print_dateline "$@";
    ); line_sep;

    # Los Angeles, USA
    (
        export TZ=America/Los_Angeles;
        export LANG="en_US.UTF-8";
        id="LOS ANGELES";
        print_dateline "$@";
    ); line_sep;

    # Denver, USA (pop. (2021): 711,463)
    (
        export TZ=America/Denver;
        export LANG="en_US.UTF-8";
        id="DENVER";
        print_dateline "$@";
    ); line_sep;

    # Chicago, USA (pop. (2021): 711,463)
    (
        export TZ=America/Chicago;
        export LANG="en_US.UTF-8";
        id="CHICAGO";
        print_dateline "$@";
    ); line_sep;

    # Mexico City, Mexico (pop. (2018): 21,804,515)
    (
        export TZ=America/Mexico_City;
        export LANG="es_MX.UTF8";
        id="MEXICO CITY";
        print_dateline "$@";
    ); line_sep;

    # Panama City, Panama
    (
        export TZ=America/Panama;
        export LANG="es_PA.UTF8";
        id="PANAMA CITY";
        print_dateline "$@";
    ); line_sep;

    # New York, USA (pop. (2018): 20,140,470)
    (
        export TZ=America/New_York;
        export LANG="en_US.UTF-8";
        id="NEW YORK";
        print_dateline "$@";
    ); line_sep;

    # São Paulo, Brazil
    (
        export TZ=America/Sao_Paulo;
        export LANG="pt_BR.UTF8";
        id="SAO PAULO";
        print_dateline "$@";
    ); line_sep;

    # Buenos Aires
    (
        export TZ=America/Argentina/Buenos_Aires;
        export LANG="es_AR.UTF8";
        id="BUENOS AIRES";
        print_dateline "$@";
    ); line_sep;

    # London, England
    (
        export TZ=Europe/London;
        export LANG="en_GB.UTF-8";
        id="LONDON";
        print_dateline "$@";
    ); line_sep;

    # Kinshasa, Africa
    (
        export TZ=Africa/Kinshasa;
        export LANG="ln_CD.UTF8";
        id="KINSHASA";
        print_dateline "$@";
    ); line_sep;

    # Lagos, Africa
    (
        export TZ=Africa/Lagos;
        export LANG="en_NG.UTF8";
        id="LAGOS";
        print_dateline "$@";
    ); line_sep;

    # Paris, France
    (
        export TZ=Europe/Paris;
        export LANG="fr_FR.UTF8";
        id="PARIS";
        print_dateline "$@";
    ); line_sep;

    # Stockholm, Sweden
    (
        export TZ=Europe/Stockholm;
        export LANG="sv_SE.UTF8";
        id="STOCKHOLM";
        print_dateline "$@";
    ); line_sep;

    # Helsinki, Finland
    (
        export TZ=Europe/Helsinki;
        export LANG="fi_FI.UTF8";
        id="HELSINKI";
        print_dateline "$@";
    ); line_sep;

    # Cairo, Egypt
    (
        export TZ=Africa/Cairo;
        export LANG="ar_EG.UTF8";
        id="CAIRO";
        print_dateline "$@";
    ); line_sep;

    # Gaza City, Palestine (pop. (2017): 590,481)
    (
        export TZ=Asia/Gaza;
        export LANG="ar_JO.UTF-8"; # ar_PS is missing as of 2023-10-18, using ar_JO as closest substitute.
        id="GAZA";
        print_dateline "$@";
    ); line_sep;

    # Tel Aviv, Israel (pop. (2021): 467,875)
    (
        export TZ=Asia/Tel_Aviv;
        export LANG="he_IL.UTF-8";
        id="TEL AVIV";
        print_dateline "$@";
    ); line_sep;

    # Athens (pop. (2020): 3,526,887)
    (
        export TZ=Europe/Athens;
        export LANG="el_GR.UTF8";
        id="ATHENS";
        print_dateline "$@";
    ); line_sep;

    # Istanbul (pop. (2020): 13,719,061)
    (
        export TZ=Asia/Istanbul;
        export LANG="tr_TR.UTF8";
        id="ISTANBUL";
        print_dateline "$@";
    ); line_sep;

    # Tehran, Iran
    (
        export TZ=Asia/Tehran;
        export LANG="fa_IR.UTF8";
        id="TEHRAN";
        print_dateline "$@";
    ); line_sep;

    # Moscow, Russia
    (
        export TZ=Europe/Moscow;
        export LANG="ru_RU.UTF-8";
        id="MOSCOW";
        print_dateline "$@";
    ); line_sep;

    # Kyiv, Ukraine (pop. (2021): 2,962,180)
    (
        export TZ=Europe/Kyiv;
        export LANG="uk_UA.UTF-8";
        id="KYIV";
        print_dateline "$@";
    ); line_sep;

    # Delhi, India (pop. (2018): 29,000,000)
    (
        export TZ=Asia/Kolkata;
        export LANG="hi_IN.UTF-8";
        id="DELHI";
        print_dateline "$@";
    ); line_sep;

    # Jakarta, Indonesia (pop. (2018): 33,430,285)
    (
        export TZ=Asia/Jakarta;
        export LANG="id_ID.UTF8";
        id="JAKARTA";
        print_dateline "$@";
    ); line_sep;

    # Singapore, Singapore (pop (2018): 5,792,000)
    (
        export TZ=Asia/Singapore;
        export LANG="en_SG.UTF-8";
        id="SINGAPORE";
        print_dateline "$@";
    ); line_sep;

    # Beijing, China (pop. (2018): 19,618,000)
    (
        export TZ=Asia/Shanghai; # [3]
        export LANG="zh_CN.UTF-8";
        id="BEIJING";
        print_dateline "$@";
    ); line_sep;

    # Taipei, Taiwan (pop (2019): 7,034,084)
    (
        export TZ=Asia/Taipei; # [3]
        export LANG="zh_TW.UTF-8";
        id="TAIPEI";
        print_dateline "$@";
    ); line_sep;    
    
    # Tokyo, Japan (pop. (2018): 37,274,000)
    (
        export TZ=Asia/Tokyo;
        export LANG="ja_JP.UTF8";
        id="TOKYO";
        print_dateline "$@";
    ); line_sep;

    # Seoul, South Korea (pop. (2018): 25,514,000)
    (
        export TZ=Asia/Seoul;
        export LANG="ko_KR.UTF8";
        id="SEOUL";
        print_dateline "$@";
    ); line_sep;

    # Pyongyang, North Korea
    (
        export TZ=Asia/Pyongyang;
        export LANG="ko_KP.UTF8";
        id="PYONGYANG";
        print_dateline "$@";
    ); line_sep;
    
    # Sydney, Australia
    (
        export TZ=Australia/Sydney;
        export LANG="en_AU.UTF8";
        id="SYDNEY";
        print_dateline "$@";
    ); line_sep;

    # Guam
    (
        export TZ=Pacific/Guam;
        export LANG="en_GU.UTF8";
        id="GUAM";
        print_dateline "$@";
    ); line_sep;

    # Auckland, New Zealand
    (
        export TZ=Pacific/Auckland;
        export LANG="en_NZ.UTF8";
        id="AUCKLAND";
        print_dateline "$@";
    ); line_sep;
    
    return 0;
}; # main program

main "$@";

# Author: Steven Baltakatei Sandoval
# License: GPLv3+