I need to calculate the current, previous and next year quarter from the current sysdate in the below format.
As per current date (02-Nov-2020), current quarter should be returned as 20204, previous quarter as 20203, next quarter as 20211. It should be dynamic and should work for all the date/ month. I am trying with some approach like below but it doesn't seems to be working. Please help me with this. Thanks in advance.
curr_yrqtr=$(date +%Y)$(( ($(date +%-m)-1)/3+1 ))
prev_yrqtr=$(date -d "-1 month" +%Y)$(( ($(date -d "-1 month" +%-m)-1)/3+1 ))
Seeing how the question has several different flags (bash, ksh, perl), I'm guessing the OP is open to any solution that generates the desired results.
If the OP's version of date includes support for the %q (quarter) format:
read -r curr_year curr_qtr < <(date '+%Y %q')
If the OP's version of date does not support the %q (quarter) format:
read -r curr_year curr_month < <(date '+%Y %m')
curr_qtr=$((curr_month / 3 + 1))
From here the rest of the code is the same regardless of the version of date:
prev_year="${curr_year}"
prev_qtr=$((curr_qtr - 1))
next_year="${curr_year}"
next_qtr=$((curr_qtr + 1))
case "${curr_qtr}" in
1) prev_year=$((curr_year - 1)) ; prev_qtr=4 ;;
4) next_year=$((curr_year + 1)) ; next_qtr=1 ;;
esac
echo "previous quarter : ${prev_year}${prev_qtr}"
echo "current quarter : ${curr_year}${curr_qtr}"
echo "next quarter : ${next_year}${next_qtr}"
Running the above where 'today' == 2 Nov 2020:
previous quarter : 20203
current quarter : 20204
next quarter : 20211
Your question has many different tags, this is a Perl solution:
#!/usr/bin/env perl
use strict;
use warnings;
my #now = localtime( time );
my $quarter = 1 + ($now[4]+1) % 4;
my $year = $now[5] + 1900;
my #q_prev = $quarter == 1 ? ($year-1, 4) : ($year, $quarter-1);
my #q_next = $quarter == 4 ? ($year+1, 1) : ($year, $quarter+1);
printf "%d%d\n", #q_prev;
printf "%d%d\n", $year, $quarter;
printf "%d%d\n", #q_next;
Change the day to 01 and then - or + 3 months to get the correct year and month number.
curr_yrqtr=$(date +%Y)$(($(date +%-m)/3+1))
prev_yrqtr=$(date -d "$(date +%Y%m01) -3 months" +%Y)$(($(date -d "$(date +%Y%m01) -3 months" +%-m)/3+1))
next_yrqtr=$(date -d "$(date +%Y%m01) +3 months" +%Y)$(($(date -d "$(date +%Y%m01) +3 months" +%-m)/3+1))
Using portable shell script you can say:
yrmo=$(date '+%Y%m')
: $((yr = yrmo / 100)) $((mo = yrmo % 100))
# let p = quarter number less 1 (0..3)
: $(( p = (mo-1)/3 ))
printf '%d-%02d:\t%d%d\t%d%d\t%d%d\n' \
"$yr" "$mo" \
$(( yr-(p<1) )) $(( p<1 ? 4 : p+0 )) \
$(( yr )) $(( p+1 )) \
$(( yr+(p>2) )) $(( p>2 ? 1 : p+2 ))
which adjusts the year if in 1st or 4th quarter (p being 0 or 3)
and folds the quarter into range 1..4.
Output:
2020-11: 20203 20204 20211
Related
Hi I have written down some bash to generate the date in format YYYYDDMM.
I know that is not perfect but the final thing will be:
Generate a range of 2 dates that are 31 days apart from each other and start with a minimum of today + one day and the older ones end at the end of this year. In format YYYYMMDD
month="$(awk -v min=1 -v max=12 'BEGIN{srand(); print int(min+rand()*(max-min+1))}')"
day="$(awk -v min=1 -v max=31 'BEGIN{srand(); print int(min+rand()*(max-min+1))}')"
year="$(date +%Y)"
if (( "${month}" < 10 )); then
month_proper="$(echo 0"${month}")"
else
month_proper="$(echo "${month}")"
fi
if (( "${day}" < 10 )); then
day_proper="$(echo 0"${day}")"
else
day_proper="$(echo "${day}")"
fi
echo month "${month}"
echo month with 0 if smaller than 10 : "${month_proper}"
echo day "${day}"
echo day with 0 smaller than 10 : "${day_proper}"
ok="$(date -d ""$year""${month_proper}""${day_proper}"" +"%Y%m%d")"
echo date with proper format "${ok}"
date -d "$year""${month_proper}""${day_proper}"
In which direction would I have to expand this script to get the final result? I already have the date generation, but there is no checking if there is one day ahead of today.
Requirements specify range nearly exactly (it is Dec-31 till Jan-31 next year), so you can write
a_fmt=`date -d "tomorrow" +%Y1231`
a_num=`date -d "$a_fmt" +%s`
b_num=$(( a_num + 31 * 24 * 3600 ))
b_fmt=`date -d #$b_num +%Y%m%d`
echo "Range is '$a_fmt'..'$b_fmt'"
I need to sort data on a weekly base and all i have are dates in a logfile.
Therefore to sort out data per week i would like to create a list with the dates of all mondays for a given year. I have tried to work something out and the only idea i currently have is to use ncal with year and month as argument looping over all months and extracting all mondays. Isn't there a more efficient way?
To get all mondays, by getting all dates and filtering by Mondays:
for i in `seq 0 365`
do date -d "+$i day"
done | grep Mon
Of course, you could also take a monday and keep incrementing by 7 days.
hope that's what you mean. Below can be changed to vary the output formats of the dates.
date command can be used for that, dunno if ncal is any more/less efficient.
I know you went for "binning" now, but here is a more readable v.
$ cat /tmp/1.sh
#!/bin/bash
test -z "$year" && {
echo "I expect you to set \$year environment variable"
echo "In return I will display you the Mondays of this year"
exit 1
}
# change me if you would like the date format to be different
# man date would tell you all the combinations you can use here
DATE_FORMAT="+%Y-%m-%d"
# change me if you change the date format above. I need to be
# able to extract the year from the date I'm shoing you
GET_YEAR="s/-.*//"
# this value is a week, in milliseconds. Changing it would change
# what I'm doing.
WEEK_INC=604800
# Use another 3-digit week day name here, to see dates for other week days
DAY_OF_WEEK=Mon
# stage 1, let's find us the first day of the week in this year
d=1
# is it DAY_OF_WEEK yet?
while test "$(date -d ${year}-1-${d} +%a)" != "$DAY_OF_WEEK"; do
# no, so let's look at the next day
d=$((d+1));
done;
# let's ask for the milliseconds for that DAY_OF_WEEK that I found above
umon=$(date -d ${year}-1-${d} +%s)
# let's loop until we break from inside
while true; do
# ndate is the date that we testing right now
ndate=$(date -d #$umon "$DATE_FORMAT");
# let's extract year
ny=$(echo $ndate|sed "$GET_YEAR");
# did we go over this year? If yes, then break out
test $ny -ne $year && { break; }
# move on to next week
umon=$((umon+WEEK_INC))
# display the date so far
echo "$ndate"
done
No need to iterate over all 365 or 366 days in the year. The following executes date at most 71 times.
#!/bin/bash
y=2011
for d in {0..6}
do
if (( $(date -d "$y-1-1 + $d day" '+%u') == 1)) # +%w: Mon == 1 also
then
break
fi
done
for ((w = d; w <= $(date -d "$y-12-31" '+%j') - 1; w += 7))
do
date -d "$y-1-1 + $w day" '+%Y-%m-%d'
done
Output:
2011-01-03
2011-01-10
2011-01-17
2011-01-24
2011-01-31
2011-02-07
2011-02-14
2011-02-21
2011-02-28
2011-03-07
. . .
2011-11-28
2011-12-05
2011-12-12
2011-12-19
2011-12-26
Another option that I've come up based on the above answers. The start and end date can now be specified.
#!/bin/bash
datestart=20110101
dateend=20111231
for tmpd in {0..6}
do
date -d "$datestart $tmpd day" | grep -q Mon
if [ $? = 0 ];
then
break
fi
done
for ((tmpw = $tmpd; $(date -d "$datestart $tmpw day" +%s) <= $(date -d "$dateend" +%s); tmpw += 7))
do
echo `date -d "$datestart $tmpw day" +%d-%b-%Y`
done
You can get the current week number using date. Maybe you can sort on that:
$ date +%W -d '2011-02-18'
07
I need to know the first monday of the current month using Cygwin bash.
One Liner:
d=$(date -d "today 1300" '+%Y%m01'); w=$(date -d $d '+%w'); i=$(( (8 - $w) % 7)); answer=$(( $d + $i ));
The result is stored in $answer. It uses working variables $d, $w, and $i.
Proof (assuming you just ran the one liner above):
echo $answer; echo $(date -d $answer '+%w')
Expected Result: Monday of current month in YYYYMMDD. On the next line, a 1 for the day of the week.
Expanded Proof (checks the next 100 month's Mondays):
for x in {1..100}; do d=$(date -d "+$x months 1300" '+%Y%m01'); w=$(date -d $d '+%w'); i=$(( (8 - $w) % 7)); answer=$(( $d + $i )); echo $answer; echo $(date -d $answer '+%w'); done | egrep -B1 '^[^1]$'
Expected Result: NOTHING
(If there are results, something is broken)
Breaking it down
The first statement gets the first day of the current month, and stores that in $d, formatted as YYYYMMDD.
The second statement gets the day of the week number of the date $d, and stores that in $w.
The third statement computes the increment of days to add and stores it in $i. Zero is perfectly valid here, because...
The last statement computes the sum of the date $d (as an integer) and the increment $i (as an integer). This works because the domain of the $i is 0 to 6, and we will always start at the first day of the month. This can quickly be converted back to a date variable (see Proof for example of this).
This has been tested on BASH v4.1 (CentOS 6), v4.4 (Ubuntu), and v5 (Archlinux)
A one-liner--I hope it's correct
d=$(date -d date +%Y%m"01" +%u);date -d date +%Y%m"0"$(((9-$d)%7))
the variable d contains the day of week (1..7) where 1 is Monday
then I print the current year and month changing the day with $((9-$d))
This should do it, but I have no Cygwin here to test:
#!/bin/bash
# get current year and month:
year=$( date +"%Y" )
month=$( date +"%m" )
# for the first 7 days in current month :
for i in {1..7}
do
# get day of week (dow) for that date:
dow=$( date -d "${year}-${month}-${i}" +"%u" )" "
# if dow is 1 (Monday):
if [ "$dow" -eq 1 ]
then
# print date of that Monday in default formatting:
date -d "${year}-${month}-${i}"
break
fi
done
See manpage date(1) for more information.
I am trying to write a shell script which is going to determine the difference in years, months and days between the present date and Easter from a user input year. For example the user inputs 1995 and the script should calculate how many years have passed since then and to convert these days into years, months and days and display the results.
I'm pasting all of my code
#!/bin/bash
echo "This script will show you on which day is Easter for the chosen year of the Gregorian calendar!"
x=0
read x
A=$((x % 19))
B=$((x / 100))
C=$((x % 100))
D=$((B / 4))
E=$((B % 4))
G=$(((8 * B + 13) / (25)))
H=$(((19 * A + B - D - G + 15) % (30)))
M=$(((A + 11 * H) / (319)))
J=$((C / 4))
K=$((C % 4))
L=$(((2 * E + 2 * J - K - H + M + 32) % (7)))
N=$(((H - M + L + 90) / (25)))
P=$(((H - M + L + N + 19) % (32)))
Z=$(date --date="$x-$N-$P" +%A)
echo
echo "Easter is `date --date="$x-$N-$P"`"
([ "`cal 2 $x | grep 29`" != "" ] && echo -e "\n$x is a leap year\n")
([ "`cal 2 $x | grep 29`" = "" ] && echo -e "\n$x is not a leap year\n")
yearnow=$(date +%Y)
year=$(($x - $yearnow))
year1=$(($yearnow - $x))
if (($x > $yearnow))
then
echo "There are $year years until Easter in $x."
else
echo "$year1 years have passed since Easter in $x."
fi
pmonths=0
if (($x > $yearnow))
then
pmonths=$(($year * 12))
echo "There are $pmonths months until Easter."
else
pmonths=$(($year1 * 12))
echo "$pmonths months have passed since Easter in $x."
fi
#checking and counting how many leap and normal years are there between the present year and the chosen one
counter=1
leapycounter=0
nycounter=0
if (($x > $yearnow))
then
while (($counter < $year))
do
leapy=$(($x + $counter))
if (($leapy == (($leapy / 4) - ($leapy / 100) + ($leapy / 400))))
then leapycounter=$(($leapycounter + 1))
else nycounter=$(($nycounter + 1))
fi
counter=$(($counter + 1))
done
fi
#checking if the present year is leap so the days left from this year can be calculated
if (($x > $yearnow))
then
datenow=$(date +%j)
datenow=`echo $datenow|sed 's/^0*//'`
if (($yearnow == (($yearnow / 4) - ($yearnow / 100) + ($yearnow / 400))))
then
datenow=$((366 - $datenow))
else
datenow=$((365 - $datenow))
fi
datethen=$(date --date="$x-$N-$P" +%j)
datethen=`echo $datethen|sed 's/^0*//'`
days=$(($datethen + $datenow))
lyc=$((($leapycounter * 366) + ($nycounter * 365)))
dayspassed=$(($lyc + $days))
echo "There are $dayspassed days until Easter."
else
datethen=$(date --date="$x-$N-$P" +%j)
datethen=`echo $datethen|sed 's/^0*//'`
if (($yearnow == (($yearnow / 4) - ($yearnow / 100) + ($yearnow / 400))))
then
datethen=$((366 - $datethen))
else
datethen=$((365 - $datethen))
fi
datenow=$(date +%j)
datenow=`echo $datenow|sed 's/^0*//'`
days=$(($datethen + $datenow))
lyc=$((($leapycounter * 366) + ($nycounter * 365)))
dayspassed=$(($lyc + $days))
echo "$dayspassed days have passed since Easter in $x."
fi
#this should be converting the days into years, months and days
dtomconst=$(((((365/12)*3)+(366/12))/4))
months=$(($dayspassed / $dtomconst))
monthsleft=$(($months % 12))
years=$(($months / 12))
daysleft=$((($dayspassed - ($monthsleft * $dtomconst)) - (365*$years)))
echo "months are $months"
echo "daysleft are $daysleft"
echo $years
months=$(($months + $monthsleft))
echo $monthsleft
echo "months after calculations: $months"
So the problem is that it doesn't calculate the days properly especially for past years. Also if the user inputs a year like 1888 the script displays a mistake and I don't know why.
If somebody can say a word or two about my problem I would be really grateful. Thank you in advance.
As pointed out in the comments, the challenge with the script will be determining the day on which Easter occurred for a given year as the date varies from year to year given the order of weeks within each year. Further complicating the difference calculate is the concept of month as leap-year varies the length of February. There is also the occasional leap-second thrown in for good measure.
However, as indicated in the comment, once you have arrived at Easter for a given year, you can let the date function do most of the remaining work for you. Given any date, you can pass that value to the date function and covert the value into seconds since epoch. Note, this doesn't directly help for years prior to 1970, but you can extend further back manipulating the number of seconds per year as a close approximation.
Once you have Easter, getting the current time, expressed in terms of seconds since epoch, is trivial with date. You then have a difference to work with, converting the number of seconds that represent the time from the chosen Easter and now that can then be expressed in terms of years, days, hours, minutes, seconds. Granted these will have to be augmented to account for leap effects depending on the level of exactness required.
The following is a simple example of approaching the time difference problem. The function included, provides the difference given the time in seconds and then declares (as needed) the years, days, hours, minutes and seconds represented by the time given as an argument. This doesn't solve all of your issues, but hopefully it will help as a framework for handling that information in an easier manner. Let me know if you have any questions about the content:
#!/bin/bash
## time difference function takes an argument in seconds, and then calculates,
# declares and fills variables 'years' 'days' 'hours' 'minutes' and 'seconds'
# representing the time in seconds given as the argument. The values are only
# declared as necessary allowing a test for their presence.
function sec2ydhms {
[ -n $1 ] || { printf "%s() error: insufficient arguments\n" "$FUNCNAME"; return 1; }
local secperday=$((24 * 3600))
local secperyr=$((365 * secperday))
local remain=$1
# printf "\nremain: %s\n\n" "$remain"
if [ "$remain" -ge "$secperyr" ]; then
declare -g years=$((remain/secperyr))
remain=$((remain - (years * secperyr)))
fi
if [ "$remain" -ge "$secperday" ]; then
declare -g days=$((remain/secperday))
remain=$((remain - (days * secperday)))
fi
if [ "$remain" -ge 3600 ]; then
declare -g hours=$((remain/3600))
remain=$((remain - (hours * 3600)))
fi
if [ "$remain" -ge 60 ]; then
declare -g minutes=$((remain/60))
fi
declare -g seconds=$((remain - (minutes * 60)))
}
oifs=$IFS # save old IFS, and set to only break on newline
IFS=$'\n' # allowing date formats containing whitespace
printf "\n Enter the date for Easter (in past): "
read edate # read date entered
eepoch=$(date -d "$edate" +%s) # convert Easter date to seconds since epoch
now=$(date +%s) # get current time since epoch
sec2ydhms $((now-eepoch)) # compute time from Easter in Y,D,H,M,S
## print time since Easter
printf "\n Time since %s:\n\n" "$(date -d #"${eepoch}")"
[ -n "$years" ] && printf " %4s years\n" "$years"
[ -n "$days" ] && printf " %4s days\n" "$days"
[ -n "$hours" ] && printf " %4s hours\n" "$hours"
[ -n "$minutes" ] && printf " %4s minutes\n" "$minutes"
[ -n "$seconds" ] && printf " %4s seconds\n\n" "$seconds"
exit 0
output:
$ bash easter.sh
Enter the date for Easter (in past): 03/21/1985
Time since Thu Mar 21 00:00:00 CST 1985:
29 years
254 days
21 hours
12 minutes
16 seconds
I have a data set with the following format
The first and second fields denote the dates (M/D/YYYY) of starting and ending of a study.
How one expand the data into the desired output format, taking into account the leap years using AWK or BASH scripts?
Your help is very much appreciated.
Input
7/2/2009 7/7/2009
2/28/1996 3/3/1996
12/30/2001 1/4/2002
Desired Output
7/7/2009
7/6/2009
7/5/2009
7/4/2009
7/3/2009
7/2/2009
3/3/1996
3/2/1996
3/1/1996
2/29/1996
2/28/1996
1/4/2002
1/3/2002
1/2/2002
1/1/2002
12/31/2001
12/30/2001
It can be done nicely with bash alone:
for i in `seq 1 5`;
do
date -d "2017-12-01 $i days" +%Y-%m-%d;
done;
or with pipes:
seq 1 5 | xargs -I {} date -d "2017-12-01 {} days" +%Y-%m-%d
If you have gawk:
#!/usr/bin/gawk -f
{
split($1,s,"/")
split($2,e,"/")
st=mktime(s[3] " " s[1] " " s[2] " 0 0 0")
et=mktime(e[3] " " e[1] " " e[2] " 0 0 0")
for (i=et;i>=st;i-=60*60*24) print strftime("%m/%d/%Y",i)
}
Demonstration:
./daterange.awk inputfile
Output:
07/07/2009
07/06/2009
07/05/2009
07/04/2009
07/03/2009
07/02/2009
03/03/1996
03/02/1996
03/01/1996
02/29/1996
02/28/1996
01/04/2002
01/03/2002
01/02/2002
01/01/2002
12/31/2001
12/30/2001
Edit:
The script above suffers from a naive assumption about the length of days. It's a minor nit, but it could produce unexpected results under some circumstances. At least one other answer here also has that problem. Presumably, the date command with subtracting (or adding) a number of days doesn't have this issue.
Some answers require you to know the number of days in advance.
Here's another method which hopefully addresses those concerns:
while read -r d1 d2
do
t1=$(date -d "$d1 12:00 PM" +%s)
t2=$(date -d "$d2 12:00 PM" +%s)
if ((t2 > t1)) # swap times/dates if needed
then
temp_t=$t1; temp_d=$d1
t1=$t2; d1=$d2
t2=$temp_t; d2=$temp_d
fi
t3=$t1
days=0
while ((t3 > t2))
do
read -r -u 3 d3 t3 3<<< "$(date -d "$d1 12:00 PM - $days days" '+%m/%d/%Y %s')"
((++days))
echo "$d3"
done
done < inputfile
You can do this in the shell without awk, assuming you have GNU date (which is needed for the date -d #nnn form, and possibly the ability to strip leading zeros on single digit days and months):
while read start end ; do
for d in $(seq $(date +%s -d $end) -86400 $(date +%s -d $start)) ; do
date +%-m/%-d/%Y -d #$d
done
done
If you are in a locale that does daylight savings, then this can get messed up if requesting a date sequence where a daylight saving switch occurs in between. Use -u to force to UTC, which also strictly observes 86400 seconds per day. Like this:
while read start end ; do
for d in $(seq $(date -u +%s -d $end) -86400 $(date -u +%s -d $start)) ; do
date -u +%-m/%-d/%Y -d #$d
done
done
Just feed this your input on stdin.
The output for your data is:
7/7/2009
7/6/2009
7/5/2009
7/4/2009
7/3/2009
7/2/2009
3/3/1996
3/2/1996
3/1/1996
2/29/1996
2/28/1996
1/4/2002
1/3/2002
1/2/2002
1/1/2002
12/31/2001
12/30/2001
Another option is to use dateseq from dateutils (http://www.fresse.org/dateutils/#dateseq). -i changes the input format and -f changes the output format. -1 must be specified as an increment when the first date is later than the second date.
$ dateseq -i %m/%d/%Y -f %m/%d/%Y 7/7/2009 -1 7/2/2009
07/07/2009
07/06/2009
07/05/2009
07/04/2009
07/03/2009
07/02/2009
$ dateseq 2017-04-01 2017-04-05
2017-04-01
2017-04-02
2017-04-03
2017-04-04
2017-04-05
I prefer ISO 8601 format dates - here is a solution using them.
You can adapt it easily enough to American format if you wish.
AWK Script
BEGIN {
days[ 1] = 31; days[ 2] = 28; days[ 3] = 31;
days[ 4] = 30; days[ 5] = 31; days[ 6] = 30;
days[ 7] = 31; days[ 8] = 31; days[ 9] = 30;
days[10] = 31; days[11] = 30; days[12] = 31;
}
function leap(y){
return ((y %4) == 0 && (y % 100 != 0 || y % 400 == 0));
}
function last(m, l, d){
d = days[m] + (m == 2) * l;
return d;
}
function prev_day(date, y, m, d){
y = substr(date, 1, 4)
m = substr(date, 6, 2)
d = substr(date, 9, 2)
#print d "/" m "/" y
if (d+0 == 1 && m+0 == 1){
d = 31; m = 12; y--;
}
else if (d+0 == 1){
m--; d = last(m, leap(y));
}
else
d--
return sprintf("%04d-%02d-%02d", y, m, d);
}
{
d1 = $1; d2 = $2;
print d2;
while (d2 != d1){
d2 = prev_day(d2);
print d2;
}
}
Call this file: dates.awk
Data
2009-07-02 2009-07-07
1996-02-28 1996-03-03
2001-12-30 2002-01-04
Call this file: dates.txt
Results
Command executed:
awk -f dates.awk dates.txt
Output:
2009-07-07
2009-07-06
2009-07-05
2009-07-04
2009-07-03
2009-07-02
1996-03-03
1996-03-02
1996-03-01
1996-02-29
1996-02-28
2002-01-04
2002-01-03
2002-01-02
2002-01-01
2001-12-31
2001-12-30
You can convert date to unix timestamp and then sequencing on it, you can even have granularity of nanoseconds if you want (with '%N' in date)
The following example prints time from 2020-11-07 00:00:00 to 2020-11-07 01:00:00 in intervals of 5 minutes
# total seconds past 1970-01-01 00:00:00 as observed on UTC timestamp in UTC
# you change TZ to represent time in your timezone like TZ="Asia/Kolkata"
start_time=$(date -u -d 'TZ="UTC" 2020-11-07 00:00:00' '+%s')
end_time=$(date -u -d 'TZ="UTC" 2020-11-07 01:00:00' '+%s')
# 60 seconds * 5 times (i.e. 5 minutes)
# you change interval according your needs or leave it to show every second
interval=$((60 * 5))
# generate sequence with intervals and convert back to timestamp in UTC
# again change TZ to represent timein your timezone
seq ${start_time} ${interval} ${end_time} |
xargs -I{} date -u -d 'TZ="UTC" #'{} '+%F %T'