Now when I run above script.ksh with an argument(as shown by you in very first post), following will be the output.
./scrip.ksh 12-2-15
Number of weekdays are:20
Number of weekdays are:8
./scrip.ksh 12-10-14
Number of weekdays are:23
Number of weekdays are:8
./scrip.ksh 12-01-22
Number of weekdays are:22
Number of weekdays are:9
So you could do cal month year for above example dates and cross check the output too.
cal 2 15
February 15
Su Mo Tu We Th Fr Sa
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 2
cal 10 14
October 14
Su Mo Tu We Th Fr Sa
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
cal 1 22
January 22
Su Mo Tu We Th Fr Sa
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
EDIT: Adding non-one liner form of solution as follows too.
Please use code tags in all your posts as per forum rules for Inputs/commands/codes which you are using into your post. Could you please try following and let me know if this helps you.
The sample dates that were provided in post #1 seem to be in the format YYYY-MM-DD (although from the examples supplied and the lack of any specification, they could also be in the format YYYY-DD-MM ). RavinderSingh13's code seems to be treating the dates as though they were in the format DD-MM-YY . (Note that the outputs from cal shown in post #4 in this thread are from the years 0015, 0014, and 0022.) The code suggested in post #6 works correctly for inputs in the format DD-MM-YYYY , but not for inputs in the format YYYY-MM-DD . Note also that it invokes cal twice and awk four times for each date processed.
You might want to consider the following script which processes inputs in the format YYYY-MM-DD and only invokes cal once and awk once for each date processed:
#!/bin/ksh
IAm=${0##*/}
if [ $# -eq 0 ]
then printf 'Usage: %s YYYY-MM-DD...\n' "$IAm" >&2
exit 1
fi
# Process each operand given...
while [ $# -gt 0 ]
do # Grab year from start of the operand.
year=${1%%-*}
# Throw away the day from the end of the operand.
month=${1%-*}
# Throw away the year (leaving the month) from the operand.
month=${month#*-}
# Read the desired variable values from the following awk script...
read var120 var28 <<-EOF
$(cal $month $year | awk '
NR > 2 && NF {
# We have skipped over the leading header lines
# and any empty lines, so what we have left are
# the days in a given week. If we have seven
# days in this week, we have 2 weekend days and
# 5 weekdays, otherwise we have one weekend day
# and NF - 1 weekdays.
weekenddays += 1 + (NF == 7)
weekdays += NF - 1 - (NF == 7)
}
END { # Print the results...
print weekdays, weekenddays
}
')
EOF
printf '%s %d week-days and %d weekend-days in %s-%s\n' \
'Do whatever you want with' "$var120" "$var28" "$year" "$month"
shift
done
If you save this script in a file named tester and make it executable, invoking it with:
Do whatever you want with 22 week-days and 8 weekend-days in 2016-09
Do whatever you want with 21 week-days and 8 weekend-days in 2016-02
Do whatever you want with 23 week-days and 8 weekend-days in 2016-08
Do whatever you want with 20 week-days and 8 weekend-days in 2015-02
Do whatever you want with 21 week-days and 10 weekend-days in 2016-01
This was written and tested using the Korn shell on OS X, but will work with any shell that performs basic shell parameter expansions required by the POSIX standards. As always, if you want to try this on a Solaris/SunOS system, change awk to /usr/xpg4/bin/awk or nawk .
Thank you Don for nice code. I made little modifications into my previous code and now we need not to run cal and awk 1 time.
Hello ROCK_PLSQL,
You could give a try to following code too and as Don mentioned above too, you could save following values(I given in comments which variable value is which) to further as per your use too.
cat script_cal.ksh
MONTH=`echo $1 | cut -d- -f2`
YEAR=`echo $1 | cut -d- -f3`
VAL=`cal $MONTH $YEAR | awk 'NR>2 && NF{
if($0 ~ /^[[:space:]]/){
A[$NF]++;
};
if(($0 !~ /^[[:space:]]/) && NF<7){
B[$1]++;
};
if(NF==7){
B[$1]++;
A[$NF]++
};
Q=$NF
}
END {
print Q-length(A)-length(B)"-"length(A)+length(B);
}
'`
echo ${VAL##*-} ##This is value of weekends in a month var28
echo ${VAL%%-*} ##This is value of weekdays in a month var120
NOTE: Date should be passed into DD-MM-YYYY format.
Hi Ravinder,
Note that cal doesn't treat a 2-digit year operand as a year in the late 20th or early 21st century like the touch -t option-argument does; cal treats it as a literal 2 digit year in the 1st century. For example, look at the difference between February 2004 and February 04:
$ cal 2 2004
February 2004
Su Mo Tu We Th Fr Sa
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29
$ cal 2 04
February 4
Su Mo Tu We Th Fr Sa
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29
$
(Note: 8 weekend days in year 2004 and 9 weekends days in year 4.) So, if you're going to use cal , the entire year has to be supplied; not just the last two digits.
Note that using cut instead of awk to split month, day, and year out of the given operand still involves the very expensive fork and exec operations that could be done MUCH quicker just using parameter expansions built into the shell.
I've mentioned the dateutils suite of date utilities before. The reason that I like the codes is that they make sure the dates exist, they diagnose bad forms, etc.
Applied here to the problem, we first get a year-month sequence ( dconv ), then add a month ( dadd ), then generate a sequence of the days in the month ( dseq ), omitting the first day of the next month.
Then it's a matter of counting weekdays and weekENDS, a short grep-wc pipeline:
#!/usr/bin/env bash
# @(#) s1 Demonstrate count weekdays, weekENDs in a given month.
# Utility functions: print-as-echo, print-line-with-visual-space, debug.
# export PATH="/usr/local/bin:/usr/bin:/bin"
LC_ALL=C ; LANG=C ; export LC_ALL LANG
pe() { for _i;do printf "%s" "$_i";done; printf "\n"; }
pl() { pe;pe "-----" ;pe "$*"; }
em() { pe "$*" >&2 ; }
db() { ( printf " db, ";for _i;do printf "%s" "$_i";done;printf "\n" ) >&2 ; }
db() { : ; }
C=$HOME/bin/context && [ -f $C ] && $C dateutils.dconv dateutils.dadd dateutils.dseq
pe
if [ $# -gt 0 ]
then
set -- 2016-9.11
else
set -- 2016-09-10 2016-02-12 2016-08-05 2012-2-15 2012-10-14 2012-01-22 2017-02-29 2016-02-29
fi
for YMD
do
# YearMonthNow, YearMonthFuture.
dateutils.dconv -f "%Y-%m" "$YMD" > /dev/null
ES=$?
db " Exit status of dconv is :$ES: for date $YMD"
if [ $ES -ne 0 ]
then
pe
em " Skipping input, date has bad form or is impossible: \"$YMD\""
continue
else
YMN=$( dateutils.dconv -f "%Y-%m" "$YMD" )-01
fi
db " YMN is $YMN"
YMF=$( dateutils.dadd -f "%Y-%m" "$YMD" +1m )-01
db " YMF is $YMF"
ymd=$( dateutils.dseq -i "%Y-%m-%d" -f "%F %a" $YMN $YMF | sed '$d' )
db " ymd = $ymd"
weekdays=$( echo "$ymd" |
grep -E 'Mon|Tue|Wed|Thu|Fri' |
wc -l)
weekends=$( echo "$ymd" |
grep -E 'Sat|Sun' |
wc -l )
pl " For date $YMD:"
pe " Weekdays = $weekdays"
pe " WeekENDS = $weekends"
done
exit 0
producing:
$ ./s1
Environment: LC_ALL = C, LANG = C
(Versions displayed with local utility "version")
OS, ker|rel, machine: Linux, 3.16.0-4-amd64, x86_64
Distribution : Debian 8.4 (jessie)
bash GNU bash 4.3.30
dateutils.dconv dconv 0.3.1
dateutils.dadd dadd 0.3.1
dateutils.dseq dseq 0.3.1
-----
For date 2016-09-10:
Weekdays = 22
WeekENDS = 8
-----
For date 2016-02-12:
Weekdays = 21
WeekENDS = 8
-----
For date 2016-08-05:
Weekdays = 23
WeekENDS = 8
-----
For date 2012-2-15:
Weekdays = 21
WeekENDS = 8
-----
For date 2012-10-14:
Weekdays = 23
WeekENDS = 8
-----
For date 2012-01-22:
Weekdays = 22
WeekENDS = 9
Skipping input, date has bad form or is impossible: "2017-02-29"
-----
For date 2016-02-29:
Weekdays = 21
WeekENDS = 8
Included is a date in February that would not exist next year (2017), but exists this year (2016 being a leap year).