7

I want the date of the coming Saturday after a given date. For example, for $date1="30-AUG-2015", I want the result 05-SEP-2015.

I have tried all the commands below with no success:

samba@samba:~$ date1="30-AUG-2015"
samba@samba:~$ date2=$(date --date='"$date1" +next Saturday')
date: invalid date ‘"$date1" +next Saturday’

samba@samba:~$ date1="30-AUG-2015"
samba@samba:~$ date2=$(date --date1='next Saturday')
date: unrecognized option '--date1=next Saturday'
Try 'date --help' for more information.

samba@samba:~$ date1="30-AUG-2015"
samba@samba:~$ date -d "$date1 next saturday"
Sun Aug 30 00:00:00 IST 2015
samba@samba:~$ $date1 -d "next saturday"
30-AUG-2015: command not found
0

7 Answers 7

10

I highly recommend dateutils for things like this.

On Fedora Linux 21+ or CentOS/RHEL with EPEL:

dnf install dateutils 

(yum instead of DNF on older RHEL.)

On Debian-based systems, the package is also called dateutils but the commands are prefixed with dateutils. to disambiguate some of them from commands with the same names in unrelated packages (so, replace dateround with dateutils.dround below).

Then, just do:

dateround today sunday

You can use "today" or replace with an actual date:

$ dateround 2015-08-30 saturday
2015-09-05

If you need the input date to be in a specific format, like your 30-AUG-2015, you can use the -i or --input-format option, like:

$ dateround -i '%d-%b-%Y' 30-AUG-2015 saturday
2015-09-05

It's not immediately clear from the name "dateround", but this command rounds up — that is, it won't round back to the previous Sunday on Monday. It also defaults to returning the current day if it matches — like, if today is Sunday, and dateround today sunday will give today's date. If you want it to always be a future date, add the --next flag.

4

With ksh93:

$ LC_ALL=C ksh93 -c 'printf "%(%c)T\n" "30-Aug-2015 Saturday"'
Sat Sep  5 00:00:00 2015

Note that if the date is a Saturday, then it will return that same day, if you want the next Saturday, make it:

LC_ALL=C printf "%(%c)T\n" "30-Aug-2015 tomorrow Saturday"

Replace %c with the strftime spec you want:

$ LC_ALL=C printf "%(%d-%b-%Y)T\n" "03-Jan-2015 tomorrow saturday"
10-Jan-2015

To make it 10-JAN-2015, assign it to a variable declared with typeset -u:

typeset -u date=${
  LC_ALL=C printf "%(%d-%b-%Y)T\n" "03-Jan-2015 tomorrow saturday"
}

With the GNU implementation of date:

$ d=03-JAN-2015
$ LC_ALL=C date -d "$d +1 week -$(date -d "$d +1 day" +%w) day" '+%d-%^b-%Y'
10-JAN-2015
2
  • thanks for the quick reponse Sir. But my date can be any day of the week.
    – shiv
    Commented Aug 10, 2015 at 19:39
  • 2
    @shiv, see edit. Commented Aug 10, 2015 at 19:59
4

Single-line solution for any day week without loops

MON=1
TUE=2
WED=3
THU=4
FRI=5
SAT=6
SUN=7

echo "Today "          && date
echo "Next Monday "    && date -d "+ $(( ( (6 + $MON - $(date +%u)) % 7) + 1 )) days"
echo "Next Tuesday "   && date -d "+ $(( ( (6 + $TUE - $(date +%u)) % 7) + 1 )) days"
echo "Next Wednesday " && date -d "+ $(( ( (6 + $WED - $(date +%u)) % 7) + 1 )) days"
echo "Next Thursday "  && date -d "+ $(( ( (6 + $THU - $(date +%u)) % 7) + 1 )) days"
echo "Next Friday "    && date -d "+ $(( ( (6 + $FRI - $(date +%u)) % 7) + 1 )) days"
echo "Next Saturday "  && date -d "+ $(( ( (6 + $SAT - $(date +%u)) % 7) + 1 )) days"
echo "Next Sunday "    && date -d "+ $(( ( (6 + $SUN - $(date +%u)) % 7) + 1 )) days"

Output

Today
Thu, Nov 12, 2020 10:24:38 PM
Next Monday
Mon, Nov 16, 2020 10:24:39 PM
Next Tuesday
Tue, Nov 17, 2020 10:24:40 PM
Next Wednesday
Wed, Nov 18, 2020 10:24:40 PM
Next Thursday
Thu, Nov 19, 2020 10:24:41 PM
Next Friday
Fri, Nov 13, 2020 10:24:42 PM
Next Saturday
Sat, Nov 14, 2020 10:24:42 PM
Next Sunday
Sun, Nov 15, 2020 10:24:43 PM

Explanation

Idea is to calculate number of days until next day of week and add these number to current date. Let's take Saturday as example.

  • On Monday add 5 days
  • ...
  • On Friday add 1 day
  • On Saturday add 7 days
  • On Sunday add 6 days

So first iteration of algorithm looks like take current day of week date +%u, take mod 7 and add 1 because modulo is zero based:

( $(date +%u) % 7) + 1

However, it does not take into account that our sequence of days to add is descending (5,4,3,2,1,7,6). To accommodate this we have to subtract current day of week from 7, then take modulo and add 1:

( (7 - $(date +%u)) % 7) + 1

So we got working example for Monday (7 == 6 + $MON). To make it searching any week of day we should re-write it like

( (6 + <DAY> - $(date +%u)) % 7) + 1

where <DAY> should be any of variables above ($MON...$SUN)

Modification for Alpine Linux

Alpine Linux does not support -d "+ 3 days" so you either install coreutils (apk add coreutils) to make it working or do days arithmetic by yourself.

date -d "@$(($(date +%s) + 86400 * $(( ( (6 + $SAT - $(date +%u)) % 7) + 1 )) ))"

Explanation

$(date +%s) outputs number of seconds since ... you know! 86400 is number of seconds per day, date -d "@<number_of_seconds>" restores date from modified number of seconds.

Modification for Mac OS

It works like "Modification for Alpine Linux" except on MacOS, instead of date -d "@<number_of_seconds>" you write date -r "<number_of_seconds>", so

date -r "$(($(date +%s) + 86400 * $(( ( (6 + $SAT - $(date +%u)) % 7) + 1 )) ))"

See "Modification for Alpine Linux/Explanation" above for more details.

2

I presume from what you tried that you want a single command.  I couldn’t find one, but I adapted this answer to do what you want:

# Assume that date1 is already set to "30-AUG-2015"
try_date="$date1"
while [ "$(date --date="$try_date" +"%A")" != Saturday ]
do
    try_date=$(date --date="$try_date + 1 day" +"%d-%b-%Y")
done
date2="$try_date"
echo "$date2"

Output:

05-Sep-2015

You can change the while line to either

  • while [ "$(date --date="$try_date" +"%a")" != Sat ]
    or
  • while [ "$(date --date="$try_date" +"%5")" != 6 ]
    (use 1 for Monday … 7 for Sunday)

If $date1 is “29-Aug-2015” (which is a Saturday), then $date2 will be “29-Aug-2015” (i.e., the same day).  If you still want to get 05-Sep-2015 for this input (i.e., the next Saturday after $date1), change the first line to try_date=$(date --date="$date1 + 1 day" +"%d-%b-%Y").

If you really want the month in all capitals (e.g., “SEP”), use tr.

1

A solution a lot simpler to write is:

$ adate1="30-AUG-2015"

$ faketime "$adate1" date -d 'saturday' +'%d-%b-%Y'

05-Sep-2015

Of course, you need to install faketime.

0

My best answer to this question is:

IFS="#"; ((
for s_date in $(echo ${date1}' +'{1..7}' days#'); do 
date -d "${s_date}";
done
) | grep Sat
) | xargs -I {} date +%d-%b-%Y -d "{}"

Output:

05-Sep-2015

This creates a list of # separated strings like:

"30-AUG-2015 +1 days# 30-AUG-2015 +2 days# 30-AUG-2015 +3 days# ..."

That list is then looped through and passed to date, where I can grep for the day I'm looking for (e.g. Sat). Lastly I can use xargs to convert the output to the format you're looking for. The code can be put in a single line for convenience.

0

Maybe this is more straight forward than some of the other answers:

startdate=30-AUG-2015
weekday=saturday
date -d "$startdate + $(( (`date +%u -d $weekday` - `date +%u -d $startdate` + 7 ) % 7 )) day"  

That takes the $startdate and gives you the next $weekday. It's a little verbose, because date does not like adding "next Saturday" to an arbitrary date; instead, we can only add a number of days.

So, to break that command down with what it's doing:

date -d "$startdate + $((                                   `# begin arithmetic`
                           (  `date +%u -d $weekday`        `# what day of week (1..7) is the desired week day`
                              -                             `# subtract day of week of $startdate from the target $weekday`
                               `date +%u -d $startdate`     `# the week day of the $startdate`
                              + 7                           `# ensure value is positive (in case desired $weekday is before the $startdate weekday)`
                           )  % 7                           `# modulo 7 for actual number of days between the two weekdays`
                       )) day"                              `# end arithmetic and specify value as seconds`
1
  • Both code blocks will work as copy-and-paste. Note that the comments in the second block are inside of backticks, creating a subshell that only has a comment. Without spawning a subshell like that, bash will not accept comments in a multi-line command
    – Randall
    Commented Sep 22, 2022 at 20:55

You must log in to answer this question.

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