Home > front end >  List months ending on the same day of the week
List months ending on the same day of the week

Time:12-12

This is an assignment. I have the following code:

#! /bin/bash

y=$1
if [ -z $1 ] # if year is not specified use the current year
    then y=(`date  %Y`)
fi

for m in {1..12}; do
    if [ $m -eq 12 ] # december exception
    then echo $(date -d $m/1/$y  %b) - $(date -d "$(($m 1))/1/$y"  %A)
          break 
    fi  
   echo $(date -d $m/1/$y  %b) - $(date -d "$(($m 1))/1/$y - 1 days"  %A) # print the last day of the week for the month
done

It lists the last day of the week for every month:

Jan - Monday
Feb - Monday
Mar - Thursday
Apr - Saturday
May - Tuesday
Jun - Thursday
Jul - Sunday
Aug - Wednesday
Sep - Friday
Oct - Monday
Nov - Wednesday
Dec - Saturday

Now I need to reverse it, so that it lists months ending on every day of the week like so:

Sunday - Jul
Monday - Jan Feb Oct
Tuesday - May
Wednesday - Aug Nov
Thursday - Mar Jun
Friday - Sep
Saturday - Apr Dec

I'm thinking of a nested loop,

for d in {1..7};

And storing months in an array?

CodePudding user response:

#! /usr/bin/env bash

# if year is not specified use the current year
declare -r year="${1:-$(date  %Y)}"

# associative array (aka hash table)
declare -A months_per_day=()
for m in {01..12}; do
    day_month=$(LANG=C date -d "${year}-${m}-01  1 month -1 day"  "%A %b")
    months_per_day[${day_month% *}] =" ${day_month#* }"
done

for day in Sunday Monday Tuesday Wednesday Thursday Friday Saturday; do
    echo "${day} -${months_per_day[${day}]:-}"
done

Output:

Sunday - Jul
Monday - Jan Feb Oct
Tuesday - May
Wednesday - Aug Nov
Thursday - Mar Jun
Friday - Sep
Saturday - Apr Dec

CodePudding user response:

Using jq your task can be solved:

INPUT='
Jan - Monday
Feb - Monday
Mar - Thursday
Apr - Saturday
May - Tuesday
Jun - Thursday
Jul - Sunday
Aug - Wednesday
Sep - Friday
Oct - Monday
Nov - Wednesday
Dec - Saturday
'

jq -Rrs '
  split("\n") |                 # split by lines
  map(select(length > 0) |      # remove empty lines
      split(" - ")) |           # split each line by "-"
  group_by(.[1]) |              # group by weekday
  map(.[0][1]   " - "   (map(.[0]) | join(" ")))[]   # generate output
' <<< "$INPUT"

Output

Friday - Sep
Monday - Jan Feb Oct
Saturday - Apr Dec
Sunday - Jul
Thursday - Mar Jun
Tuesday - May
Wednesday - Aug Nov

Remark

The output is sorted alphabetically.

CodePudding user response:

This answer refactors your implementation as a reusable getlastday function. Then, we go through the loop 7 times matching the getlastday to the matching months and print it out:

#!/bin/bash

getlastday() {
    if [ $m -eq 12 ]; then
        echo $(date -d "$(($m 1))/1/$y"  %A)
        return
    fi
    echo $(date -d "$(($m 1))/1/$y - 1 days"  %A) # print the last day of the week for the month
}

y=$1
if [ -z $1 ] # if year is not specified use the current year
    then y=(`date  %Y`)
fi

for d in Monday Tuesday Wednesday Thursday Friday Saturday Sunday; do
    months=()
    for m in {1..12}; do
        lastday=$(getlastday)
        if [ $lastday != $d ]; then continue; fi
        months =($(date -d $m/1/$y  %b))
    done
    echo $d - ${months[@]}
done

CodePudding user response:

Try to follow this algorithm to get the expected result:

# Initialize an array to hold the months that end on each day of the week

# Loop over the months from the starting month/year to the ending month/year

    # get the last day of the month

    # get the day of the week for the last day of the month

    # Append the month to the array for the corresponding day of the week

 
# Loop over the days of the week and print the list of months that end on each day

wish that it will help you

CodePudding user response:

Try this Shellcheck-clean code:

#! /bin/bash -p

year=${1-$(date  %Y)}

months=( Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec )
day_months=(    'Sunday    -'   'Monday    -'   'Tuesday   -'   'Wednesday -'
                'Thursday  -'   'Friday    -'   'Saturday  -'                )

for m in {1..12}; do
    last_day=$(date -d "$year-$m-1  1 month -1 day"  %w)
    day_months[last_day] =" ${months[m-1]}"
done

printf '%s\n' "${day_months[@]}"

CodePudding user response:

Using GNU awk for time functions, with lots of intermediate and descriptively named variables to make it easy to understand:

$ cat tst.sh
#!/usr/bin/env bash

awk -v year="$1" '
    BEGIN {
        OFS = " - "

        year = (year == "" ? strftime("%Y") : year)
        secsInDay = 24*60*60

        for ( mthNr=1; mthNr<=12; mthNr   ) {
            lastDayEpochSecs = mktime(year " " (mthNr 1) " 1 12 0 0") - secsInDay
            mthAbbrDayName = strftime("%b %A", lastDayEpochSecs)
            split(mthAbbrDayName,m)
            mthAbbr = m[1]
            dayName = m[2]
            mthNr2mthAbbr[mthNr] = mthAbbr
            mthAbbr2dayName[mthAbbr] = dayName
            dayName2mthAbbrs[dayName] = \
                (dayName in dayName2mthAbbrs ? dayName2mthAbbrs[dayName] " " : "" ) mthAbbr
        }

        for ( mthNr=1; mthNr<=12; mthNr   ) {
            mthAbbr = mthNr2mthAbbr[mthNr]
            dayName = mthAbbr2dayName[mthAbbr]
            print mthAbbr, dayName
        }

        print "\n--------\n"

        for ( dayName in dayName2mthAbbrs ) {
            mthAbbrs = dayName2mthAbbrs[dayName]
            print dayName, mthAbbrs
        }
    }
'

$ ./tst.sh
Jan - Monday
Feb - Monday
Mar - Thursday
Apr - Saturday
May - Tuesday
Jun - Thursday
Jul - Sunday
Aug - Wednesday
Sep - Friday
Oct - Monday
Nov - Wednesday
Dec - Saturday

--------

Tuesday - May
Friday - Sep
Sunday - Jul
Thursday - Mar Jun
Saturday - Apr Dec
Monday - Jan Feb Oct
Wednesday - Aug Nov

The above will be much faster than calling date multiple times in a shell loop and is trivial to modify to do anything else you need.

  • Related