0

Problem: For parameters of a minimum and maximum date (ex: script.sh 20190801, 20201005) execute a function on each month within that date range. (ex: 20190801-20190831, 20190901-20190931 . . .)

Backstory: I have a function that generates a CSV based on data from a date range, however for large date ranges this takes too long to run. That in mind, I'm attempting to have this run per-month simultaneously and stitch the CSVs back together afterwards. I would attempt something like this in Python or Javascript and use a datetime library, but certain company limitations are requiring me to exclusively use bash/shell.

Below is my current idea, but it fails in most cases

I get my expected results from passing, for example: 20190101 20201231 to the below code, but I'm unsure how to modify this to support something like 20190105 20200820

DateMin=$1
DateMax=$2

if [ $DateMin -ge $DateMax ]; then
    echo "ERROR: Minimum Date Cannot be above Maximum Date"
    exit
fi

let DateDelta=DateMax-DateMin

for YearDelta in `seq $DateDelta -10000 0`
do
    let TempDelta=$YearDelta-1130
    for MonthDelta in `seq $YearDelta -100 $TempDelta`
    do
        let TempMin=$DateMax-$MonthDelta
        let TempMax=$DateMax-$MonthDelta+30
        call_to_function $TempMin $TempMax
    done
done

I created a repl.it here so it's easier to see: https://repl.it/repls/CanineAdmirableSeahorse#main.sh If you run "bash main.sh 20190101 20201231" that's the expected output.

2 Answers 2

0

How about using date program, which is there, and can calculate a lot with dates. Here is an example:

DateMinStr="${1:0:4}-${1:4:2}-01"
DateMaxStr="${2:0:4}-${2:4:2}-${2:6:2}"
DateMin=$(date -d $DateMinStr +%s)
DateMax=$(date -d $DateMaxStr +%s)

if [ $DateMin -ge $DateMax ]; then
    echo "ERROR: Minimum Date Cannot be above Maximum Date"
    exit
fi

DateCurrentStr=$DateMinStr
DateCurrent=$DateMin
while [ $DateCurrent -lt $DateMax ]
do
    TempMin=$(date -d $DateCurrentStr +%Y%m%d)
    TempMinStr=$(date -d $DateCurrentStr +%Y-%m-%d)
    TempMax=$(date -d "$TempMinStr + 1 month - 1 day" +%Y%m%d)
    TempMaxStr=$(date -d "$TempMinStr + 1 month - 1 day" +%Y-%m-%d)
    echo $TempMin $TempMax
    DateCurrent=$(date -d "$TempMaxStr + 1 day" +%s)
    DateCurrentStr=$(date -d "$TempMaxStr + 1 day" +%Y-%m-%d)
done

I don't think this example does exactly what you want, and is far from optimized, but gives you an idea how to do it.

0

Tools like dateutils are usually better suited for this kind of work but it can be done with shell primitives, here with a different set of parameters than yours, for brevity.

#!/bin/sh
# For a date range print start and end date per calendar month.

# Gregorian calendar: number of days in month.
# $1 year (>= 1600)
# $2 month (1..12), one leading '0' allowed for 1..9
# exitval: day count (28..31)
daysinmonth() {
    case "$2" in
    (2|02)
        test 0 -eq $(($1 % 4 || !($1 % 100) && $1 % 400))
        return $((28 + ($?<1))) ;;
    (4|6|9|11|04|06|09)
        return 30 ;;
    (*) return 31 ;;
    esac
}

# Print year,month,day in ISO 8601 basic format
isobasic() {
    printf '%d%02d%02d' "$1" "$2" "$3"
}

ylo="$1" mlo="${2#0}" dlo="${3#0}"      # range start
yhi="$4" mhi="${5#0}" dhi="${6#0}"      # range end
isohi=$(isobasic "${yhi}" "${mhi}" "${dhi}")
until 
    isolo=$(isobasic "${ylo}" "${mlo}" "${dlo}")
    test $(( isolo - isohi )) -gt 0
    # test "${isolo}" \> "${isohi}"     # dash, bash
do
    # compute end of month
    daysinmonth "${ylo}" "${mlo}"
    isoeom=$(isobasic "${ylo}" "${mlo}" "${?}")
    # print start, end date for partial/whole calendar month
    test "${isoeom%??}" != "${isohi%??}" || isoeom="${isohi}"
    printf '%s%s %s\n' "${PREFIX:-}" "${isolo}" "${isoeom}"
    # increase *lo values to first of next month
    dlo=1
    : $(( mlo = (mlo == 12 ? 1 : mlo + 1) ))
    : $(( ylo = (mlo > 1 ? ylo : ylo + 1) ))
done
# fail if start later than end date
return $(( isoeom == isohi ? 0 : 1 ))

Given the command

PREFIX='cmd ' ./script 2016 11 09 2020 3 5

output is:

cmd 20161109 20161130
cmd 20161201 20161231
cmd 20170101 20170131
cmd 20170201 20170228
...
cmd 20191201 20191231
cmd 20200101 20200131
cmd 20200201 20200229
cmd 20200301 20200305

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .