Number of Mondays Falls on the First of the month - linux

I want a command line can display number of Monday(s) which fall(s) on the first of the month in a given year without using sed or awk commands
I have this command that display the first date of the current month
date -d "-0 month -$(($(date +%d)-1)) days"

With GNU date, you can read input from a file (or standard input):
printf '%s\n' 2021-{01..12}-01 | date -f- +%u | grep -c 1
This prints dates for the first of each month in a year, then formats them as "weekday" (where 1 is "Monday"), then counts the number of Mondays.
To parametrize the year, replace 2021 with a variable containing the year; wrapped in a function:
mondays() {
local year=$1
printf '%s\n' "$year"-{01..12}-01 | date -f- +%u | grep -c 1
}

Using a for loop, this can be accomplished as follows.
for mon in {01..12}; do date -d "2021-$mon-01" +%u; done | grep -c 1
Breakdown
We iterate through the numbers 01 to 12 representing the months.
We call date passing in the custom date value with the first date of each month in the year. We use +%u to return the day of week where 1 represents Monday.
Lastly we count the number of 1s using grep -c or grep --count
Note, the desired year has been hard coded as 2021. The current year can be used as:
for mon in {01..12}; do date -d "$(date +%Y)-$mon-01" +%u; done | grep -c 1
This can also all be put into a function and the desired year passed in as an argument:
getMondays() {
for mon in {01..12}; do date -d "$1-$mon-01" +%u; done | grep -c 1
}

I implemented it as:
for ((i=1,year=2021,mondays=0; i< 12; i++)) {
if [ $(date -d "$i/1/$year" +%u) -eq 1 ]
then
let "mondays++"
fi
}
echo "There are $mondays Mondays in $year."
That said, I like Mushfiq's answer. Quite elegant.

Related

Find the next nearest value (bash)

Let's say I have some holiday data (holiday_master.csv) in columns, something like
...
20200320 Vernal Equinox Day
20200429 Showa Day
20200503 Constitution Day
20200505 Green Day
20200720 Children's Day
20200811 Sea Day
...
Given this set of data, I want to find the next closest holiday from the given date.
For example if the input is 20200420, 20200429 Showa Day is expected.
If the input is 20200620, 20200720 Children's Day is expected.
I have a feeling that awk has the necessary functionality to do this, but any solution that works in a bash script is welcome.
Would you please try the bash script:
#!/bin/bash
input="20200428" # or assign to whatever
< "holiday_master.csv" sort -nk1,1 | # sort the csv file by date and pass to the while loop
while read -r date desc; do
if (( date >= input )); then # if the date is greater than or equal to the input
echo "$date" "$desc" # then print the line
break # and exit the loop
fi
done
Assuming no two days will ever have the same date...
DATE=<some desired input date>
awk "{print (\$1 - $DATE"' "\t" $0)}' calendar.txt | sed '/^-/d' | sort | head -n 1 | awk '{$1=""; print $0}'
Explanation
awk "{print (\$1 - $DATE"' "\t" $0)}' calendar.txt: Prepend a column to the input.txt file describing the difference between the desired input date and the date column
sed '/^-/d': Remove all lines beginning with -. Dates with negative differences have already passed.
sort: Sort the remaining entries from least to greatest (based upon the difference column)
head -n 1: Select only the first row (The lowest difference)
awk '{$1=""; print $0}': Print all but the first column
Prettier script version
#!/bin/bash
# Usage: script <Date> <Calendar file>
DATE=${1:--1}
CAL=${2:-calendar.txt}
# Arg check and execute
if[ ! -f $CAL ]
then
echo "File not found: $CAL"
echo "Usage: script <Date> <Calendar file>"
elif [ $DATE -le 0 ]
then
echo "Invalid date: $DATE"
echo "Usage: script <Date> <Calendar file>"
elif [ $(echo "$DATE" | grep -Ewo -- '-?[0-9]+' | wc -l) -eq 0 ]
then
echo "Invalid date: $DATE"
echo "Usage: script <Date> <Calendar file>"
else
awk '{print ($1 - '"$DATE"' "\t" $0)}' $CAL | sed '/^-/d' | sort | head -n 1 | awk '{$1=""; print $0}'
fi
As you use YYYYMMDD format we might compare it just like numbers (note: year is greater than month, month is greater than day). So you can use AWK following way, let:
20200320 Vernal Equinox Day
20200429 Showa Day
20200503 Constitution Day
20200505 Green Day
20200720 Children's Day
20200811 Sea Day
be file named holidays.txt then:
awk 'BEGIN{inputdate=20200420}{if($1>inputdate){print $2;exit}}' holidays.txt
output:
Showa
Explanation: in BEGIN I set inputdate to 20200420 then when line with greater number in 1st column is found I print content of 2nd column and exit (otherwise later dates would be printed too). Note that AWK does automatically parse number when asked to do comparison (> in this case) so you do not have to care about conversion yourself - you could even do inputdate="20200420" and it would work too.
This solution assumes that all dates in file are already sorted.
Using awk and assuming the source data is comma separated:
awk -F, -v dayte="20200420" '
BEGIN {
"date -d "dayte" +%s" | getline dat1
{
{
"date -d "$1" +%s" | getline dat2;
dat3=dat2-dat1;
if (dat3 > 0 )
{
hols[dat3]=$2
}
}
END {
asorti(hols,hols1,"#ind_num_asc");
print hols[hols1[1]]
}
' holiday_master.csv
One liner:
awk -F, -v dayte="20200420" 'BEGIN { "date -d "dayte" +%s" | getline dat1 } { "date -d "$1" +%s" | getline dat2;dat3=dat2-dat1;if (dat3 > 0 ) { hols[dat3]=$2 } } END { asorti(hols,hols1,"#ind_num_asc");print hols[hols1[1]] }' holiday_master.csv
Set the field separator to , and set a variable dayte to the date we wish to check. In the BEGIN block, we pass the dayte variable through to date command via an awk pipe/getline and read the epoch result into the variable dat1. We do the same with the first column on the master file ($1) and read this into dat2. We take the difference between the epoch dates and read the result into dat3. Only if the result is positive (in the future) do we then use dat3 for an index in a "hols" array, with the holiday description as the value. In the END block, we sort the indexes of hols into a news hols1 array basing the sort on ascending, numeric indexes. We then take the first index of the new hols1 array to attain the holiday that is closest to the dayte variable.
Assuming the holiday list file is sorted by date as you have given, the below would work
$ awk -v dt="20200420" ' (dt-$1)<0 { print;exit } ' holiday.txt
20200429 Showa Day
$ awk -v dt="20200620" ' (dt-$1)<0 { print;exit } ' holiday.txt
20200720 Children's Day
$
If the holiday file is not sorted, then you can use below
$ shuf holiday.txt | awk -v dt="20200420" ' dt-$1<0 { a[(dt-$1)*-1]=$0 } END { asort(a); print a[1] } '
20200429 Showa Day
$ shuf holiday.txt | awk -v dt="20200620" ' dt-$1<0 { a[(dt-$1)*-1]=$0 } END { asort(a); print a[1] } '
20200720 Children's Day

bash and awk script get to previous month date in format YYYY-MM-DD

I need to get last month date in format YYYY-MM-DD, suppose if run script in May on any date then it should return me Last date of April month in format 2018-04-30.
Would be good if can get the same work done with awk statement also.
I tried below code and able to get output in below form but i need in format YYYY-MM-DD
Apr 30 2018
Code:
#!/bin/bash
cur_month=`date +%m`
cur_year=`date +%Y`
prev_month=$(($cur_month-1))
# Check to see if this is January
if [ $prev_month -lt 1 ]
then
prev_year=$(($cur_year-1))
prev_month=12
LastDayOfpreviousMonth=`cal $prev_month $cur_year | grep -v "^$" | sed -n '1p;$p' | tr "\n" " " | awk '{print substr($1,1,3),$NF,$2}'`
else
LastDayOfpreviousMonth=`cal $prev_month $cur_year | grep -v "^$" | sed -n '1p;$p' | tr "\n" " " | awk '{print substr($1,1,3),$NF,$2}'`
fi
echo $LastDayOfpreviousMonth
Input file: In this file in column14 need to put value of date for last month in form YYYY-MM-DD(2018-04-30)
EPOS|EPOS_OUTWARD_B2B_OSR_TTML_TTSL_$LDPM_20180504_V1.0.txt|||AD|Outward|ANP|Receipt cum invoice|42018|37AAACT2438A1ZS|INV|TAX|IN37201800001452|2018-04-17||||1||||SOHANRAJ RAJ KUMAR||||37||||||9984||Telecommunication services||||300|0||9|27|9|27|||||354||||||||||
EPOS|EPOS_OUTWARD_B2B_OSR_TTML_TTSL_$LDPM_20180504_V1.0.txt|||AD|Outward|ANP|Receipt cum invoice|42018|37AAACT2438A1ZS|INV|TAX|IN37201800001426|2018-04-12||||1||||MOLLITE SIVARAMA KRISHNA||||37||||||9984||Telecommunication services||||300|0||9|27|9|27|||||354||||||||||
EPOS|EPOS_OUTWARD_B2B_OSR_TTML_TTSL_$LDPM_20180504_V1.0.txt|||GJ|Outward|GUJ|Receipt cum invoice|42018|24AAACT2438A1ZZ|INV|TAX|IN24201800000651|2018-04-07||||1||||AIREN LTD ||||24||||||9984||Telecommunication services||||500|0||9|45|9|45|||||590||||||||||
GNU date itself is capable of handling this task perfectly and reliably, no need for other magic.
$ date -d "$(date +%Y-%m-01) - 1 day" "+%Y-%m-%d"
2018-04-30
Even though it is evident that the OP's dateformat is unix based, it is worth mentioning that there are two general calendars in play. The Julian calendar and the Gregorian calendar. Both calendars have a different concept of what a leap year is.
note: at this time, most countries adopt the Gregorian calendar. However, it was not at once officially adopted by all countries. This should be kept in mind when making historical research. In Great Britain, for instance , the change was made as late as in A.D. 1752, and in Turkey not before 1927. (Ref Astronomical Algorithms by Jean Meeus)
With this in mind, and depending whether your digital file is from Turkey from 1920 or 2011 or Great Britain from 1740 or 1789, I present two awk solutions
In a leap year, the month February has 29 days, where in a common year it has 28 days.
In the Julian calendar, a year is a leap (or bissextile) year of 366 days if its numerical designation is divisible by 4. All other years are common years (365 days).
Using awk, as this was the OP's request, we can write the following function that would do the requested conversion :
awk 'function julian(str, y,m,d) {
y=substr(str,1,4); m=substr(str,6,2)+0;
if (m==1) {y=y-1; m=12} else {m=m-1}
if (((m-1)%7)%2==0) {d=31} else {d=30}
if (m==2) { d = (y%4==0) ? 29 : 28 }
return sprintf("%0.4d-%0.2d-%0.2d",y,m,d)
}
{ print $14, julian($14) }' <file>
In the Gregorian calendar, the same rule holds as in the Julian calendar, with the following exception: the centurial years that are not divisible by 400 are common years.
awk 'function gregorian(str, y,m,d) {
y=substr(str,1,4); m=substr(str,6,2)+0;
if (m==1) {y=y-1; m=12} else {m=m-1}
if (((m-1)%7)%2==0) {d=31} else {d=30}
if (m==2) { d = (y%4!=0) ? 28 : (y%100==0 && y%400!=0 ? 28 : 29 ) }
return sprintf("%0.4d-%0.2d-%0.2d",y,m,d)
}
{ print $14, gregorian($14) }' <file>
As the UNIX calendar is Gregorian, we can also use the GNU Awk Time Functions. This however will only work from 1970-02-01 till 2038-01-19 for 32-bit systems and 292277026596-04-01 for 64-bit systems (see here)
awk 'function unix(str, tmp) {
tmp=substr(str,1,7)" 01 00 00 00"; gsub("-"," ",tmp);
return strftime("%Y-%m-%d",mktime(tmp) - 1) }
{ print $14, unix($14) }' <file>

How to get Date Month values in linux date command as integers to work on

I want to convert date and month as integers.
for example.
if the current date as per the command "Date +%m-%d-%y" output, is this
09-11-17
Then I am storing
cur_day=`date +%d`
cur_month=`date +%m`
the $cur_day will give me 11 and $cur_month will give me 09.
I want to do some operations on the month as 09. like i want to print all the numbers up to 09.
like this 01,02,03,04,05,06,07,08,09
Same way I want to display all the numbers up to cur_day
like 01,02,03,04,05,06,07,08,09,10,11
Please tell me how can i do it.
Thanks in Advance.
For months:
$ printf ',%02d' $(seq 1 $(date +%m)) | sed 's/,/like this /; s/$/\n/'
like this 01,02,03,04,05,06,07,08,09
For days:
$ printf ',%02d' $(seq 1 $(date +%d)) | sed 's/,/like /; s/$/\n/'
like 01,02,03,04,05,06,07,08,09,10,11
printf will print according to a format. In this case, the format ,%02d formats the numbers with commas and leading zeros.
The sed command puts the string you want at the beginning of the line and adds a newline at the end.

Unix File listing with date as prefix in names

How can I list files based on names with date prefix & suffix. Ex: I have file with name as "http_access_2017-04-13.log" then how can I with files with last five days back files ??
Create you time stamps with date -d:
ago ()
{
date +%Y-%m-%d -d "$1 days ago"
}
for n in $(seq 5); do
echo http_access_$(ago $n).log
done
Try this command:
ls -lr http_access*.log | tail -5

change the call from a bash script in a month to a week

I have 2 script in bash, and i have some files:
transaction-2012-01-01.csv.bz2
transaction-2012-01-02.csv.bz2
transaction-2012-01-03.csv.bz2
transaction-2012-01-04.csv.bz2
.
.
transaction-2012-01-31.csv.bz2
transaction-2012-02-01.csv.bz2
.
.
transaction-2012-02-28.csv.bz2
I have a script called script.sh
cat script.sh
YEAR_MONTH=$1
FILEPATH="transaction-$YEAR_MONTH*.csv.bz2"
bzcat $FILEPATH|strings|grep -v "code" >> output
And if you need call the script you can use other script
cat script2.sh
LAST_MONTH=$(date -d -1month +%Y"-"%m)
if [ $# -eq 1 ]; then
DATE=$1
else
DATE=$LAST_MONTH
fi
script.sh $DATE 1>output$DATE.csv 2>> log.txt
And it do cat the files in a month, but now i need call the script with a specific week in a year:
bash script2.sh 2012-01
where 2012 is the year and 01 is the month
Now i need call the script with:
bash script2.sh 2012 13
where 2012 is the year and 13 is the week in a year
Now i need the cat only to the files in the year and week that the user specified, no per month per week
But the format of the files do not help me!!!! because the name is transaction-year-month-day.csv.bz2, not transaction-year-week.csv.bz2
Take a look at the manpage for strftime. These are date format codes. For example:
$ date +"%A, %B %e, %Y at %I:%m:%S %p"
Will print out a date like:
Thursday, May 30, 2013 at 02:05:31 PM
Try to see why this works.
On some systems, the date command will have a -j switch. This means, don't set the date, but reformat the given date. This allows you to convert one date to another:
$ date -f"$input_format" "$string_date" +"$output_format"
The $input_format is the format of your input date. $string_date is the string representation of the date in your $input_format. And, $output_format is the format you want your date in.
The first two fields are easy. Your date is in YY-MM-DD format:
$ date -f"%Y-%m-%d" "$my_date_string"
The question is what can you do for the final format. Fortunately, there is a format for the week in the year. %V which represents the weeks at 01-53 and %W which represents the weeks as 00-53.
What you need to do is find the date string on your file name, then convert that to the year and week number. If that's the same as the input, you need to concatenate this file.
find $dir -type f | while read transaction_file
do
file_date=${transaction_file#transaction-} #Removes the prefix
file_date=${file_date%.csv.bz2} #Removes the suffix
weekdate=$(date -j -f"%Y-%m-%d" "$file_date" +"%Y %W")
[ "$weekdate" -eq "$desired_date" ] || continue
...
done
For example, someone puts in 2013 05 as the desired date, you will go through all of your files and find ones with dates in the range you want. NOTE: That the week of the year is zero filled. You may need to zero fill the input of the week number to match.

Resources