207

I have a shell script that runs on Linux and uses this call to get yesterday's date in YYYY-MM-DD format:

date -d "1 day ago" '+%Y-%m-%d'

It works most of the time, but when the script ran yesterday morning at 2013-03-11 0:35 CDT it returned "2013-03-09" instead of "2013-03-10".

Presumably daylight saving time (which started yesterday) is to blame. I'm guessing the way "1 day ago" is implemented it subtracted 24 hours, and 24 hours before 2013-03-11 0:35 CDT was 2013-03-09 23:35 CST, which led to the result of "2013-03-09".

So what's a good DST-safe way to get yesterday's date in bash on Linux?

Ike Walker
  • 64,401
  • 14
  • 110
  • 109

10 Answers10

331

I think this should work, irrespective of how often and when you run it ...

date -d "yesterday 13:00" '+%Y-%m-%d'
Nicolas Raoul
  • 58,567
  • 58
  • 222
  • 373
tink
  • 14,342
  • 4
  • 46
  • 50
  • 1
    How would you assign this to a variable for use later on? – null May 04 '16 at 13:50
  • 10
    @null - same way you'd assign the output of any other shell command. variable=$( date -d "yesterday 13:00 " '+%Y-%m-%d' ) ... – tink Jul 05 '16 at 08:56
  • 1
    For GNU-date: `date -d yesterday 13:00 -I` – gogstad Mar 06 '17 at 08:06
  • This relies on the fact that switching between summer and winter time is always done during night, if I understand correctly? – Nicolas Raoul Mar 28 '17 at 03:38
  • What is the meaning of the space between `13:00` and `"`? The command seems to work without it. – Nicolas Raoul Mar 28 '17 at 03:42
  • @Nicolas Raoul: that space is a typo, doesn't need to be there. And yes, switching to / from daylight saving happens at 2 and 3 respectively. – tink Mar 28 '17 at 03:46
  • @tink: Is it always at 2AM and 3AM, in all countries? – Nicolas Raoul Mar 28 '17 at 08:29
  • 2
    @NicolasRaoul: It is not guaranteed; but it is a widely-followed convention to schedule this into early morning. On the other hand, there is IIRC no place where the switch would happen close to the local noon. So, "1300J is never on a DST boundary" is a reasonable *assumption*. – Piskvor left the building Mar 28 '17 at 10:24
  • This is not assuming that the switch between summer and winter time happens before 13:00 - it just assumes that today 13:00 - 24 hours is a time yesterday. So if the jump between summer and winter time is less than 11 hours, this should aways work. – Oliver Hankeln Jul 01 '19 at 12:19
  • Perfect answer! My case was to set the variable to the previous Monday and a similar approach worked: '$( date -d "last Monday 13:00 " '+%Y%m%d' )' – TheEsnSiavashi Jan 16 '22 at 20:27
86

Under Mac OSX date works slightly different:

For yesterday

date -v-1d +%F

For Last week

date -v-1w +%F
mit
  • 11,083
  • 11
  • 50
  • 74
Aries
  • 916
  • 6
  • 3
20

This should also work, but perhaps it is too much:

date -d @$(( $(date +"%s") - 86400)) +"%Y-%m-%d"
perreal
  • 94,503
  • 21
  • 155
  • 181
12

If you are certain that the script runs in the first hours of the day, you can simply do

  date -d "12 hours ago" '+%Y-%m-%d'

BTW, if the script runs daily at 00:35 (via crontab?) you should ask yourself what will happen if a DST change falls in that hour; the script could not run, or run twice in some cases. Modern implementations of cron are quite clever in this regard, though.

Community
  • 1
  • 1
leonbloy
  • 73,180
  • 20
  • 142
  • 190
8
date -d "yesterday" '+%Y-%m-%d'

To use this later:

date=$(date -d "yesterday" '+%Y-%m-%d')
suresh Palemoni
  • 1,138
  • 14
  • 12
  • 3
    While this code may resolve the OP's issue, it's better to include an explanation on how your code addresses the OP's issue. This way, future visitors can learn from your post, & apply it to their own code. SO is not a coding service, but a resource for knowledge. High quality, complete answers reinforce this idea, and are more likely to be upvoted. These features, plus the requirement that all posts be self-contained, are some strengths of SO as a platform that differentiates us from forums. You can edit to add additional info &/or to supplement your explanations with source documentation. – SherylHohman Jun 16 '20 at 06:13
7

Here a solution that will work with Solaris and AIX as well.

Manipulating the Timezone is possible for changing the clock some hours. Due to the daylight saving time, 24 hours ago can be today or the day before yesterday.

You are sure that yesterday is 20 or 30 hours ago. Which one? Well, the most recent one that is not today.

echo -e "$(TZ=GMT+30 date +%Y-%m-%d)\n$(TZ=GMT+20 date +%Y-%m-%d)" | grep -v $(date +%Y-%m-%d) | tail -1

The -e parameter used in the echo command is needed with bash, but will not work with ksh. In ksh you can use the same command without the -e flag.

When your script will be used in different environments, you can start the script with #!/bin/ksh or #!/bin/bash. You could also replace the \n by a newline:

echo "$(TZ=GMT+30 date +%Y-%m-%d)
$(TZ=GMT+20 date +%Y-%m-%d)" | grep -v $(date +%Y-%m-%d) | tail -1
Walter A
  • 19,067
  • 2
  • 23
  • 43
5

you can use

date -d "30 days ago" +"%d/%m/%Y"

to get the date from 30 days ago, similarly you can replace 30 with x amount of days

Dan Pickard
  • 453
  • 6
  • 14
  • 1
    You should change your format to `%Y%m%d` to match the question. I upvoted anyway as it worked properly. – isapir Oct 07 '19 at 05:32
2

Just use date and trusty seconds:

As you rightly point out, a lot of the details about the underlying computation are hidden if you rely on English time arithmetic. E.g. -d yesterday, and -d 1 day ago will have different behaviour.

Instead, you can reliably depend on the (precisely documented) seconds since the unix epoch UTC, and bash arithmetic to obtain the moment you want:

date -d @$(( $(date +"%s") - 24*3600)) +"%Y-%m-%d"

This was pointed out in another answer. This form is more portable across platforms with different date command line flags, is language-independent (e.g. "yesterday" vs "hier" in French locale), and frankly (in the long-term) will be easier to remember, because well, you know it already. You might otherwise keep asking yourself: "Was it -d 2 hours ago or -d 2 hour ago again?" or "Is it -d yesterday or -d 1 day ago that I want?"). The only tricky bit here is the @.

Armed with bash and nothing else:

Bash solely on bash, you can also get yesterday's time, via the printf builtin:

 %(datefmt)T
     causes printf to output the date-time string resulting from using
     datefmt as a format string for strftime(3).  The  corresponding  argu‐
     ment  is an integer representing the number of seconds since the
     epoch.  Two special argument values may be used: -1 represents the
     current time, and -2 represents the time the shell was invoked.
     If no argument is specified, conversion behaves as if -1 had
     been  given.
     This is an exception to the usual printf behavior.

So,

# inner printf gets you the current unix time in seconds
# outer printf spits it out according to the format
printf "%(%Y-%m-%d)T\n" $(( $(printf "%(%s)T" -1) - 24*3600 ))

or, equivalently with a temp variable (outer subshell optional, but keeps environment vars clean).

(
  now=$(printf "%(%s)T" -1);
  printf "%(%Y-%m-%d)T\n" $((now - 24*3600));
)

Note: despite the manpage stating that no argument to the %()T formatter will assume a default -1, i seem to get a 0 instead (thank you, bash manual version 4.3.48)

init_js
  • 4,143
  • 2
  • 23
  • 53
2

You can use:

date -d "yesterday 13:55" '+%Y-%m-%d'

Or whatever time you want to retrieve will retrieved by bash.

For month:

date -d "30 days ago" '+%Y-%m-%d'
Daremitsu
  • 545
  • 2
  • 8
  • 24
1

As this question is tagged "DST safe":

And using fork to date command implie delay, there is a simple and more efficient way using pure bash built-in:

printf -v tznow '%(%z %s)T' -1
TZ=${tznow% *} printf -v yesterday '%(%Y-%m-%d)T' $(( ${tznow#* } - 86400 ))
echo $yesterday

This is a lot quicker on more system friendly than having to fork to date.

From version 5.0, there is a new variable $EPOCHSECONDS

printf -v tz '%(%z)T' -1
TZ=$tz printf -v yesterday '%(%Y-%m-%d)T' $(( EPOCHSECONDS - 86400 ))
echo $yesterday
bfontaine
  • 18,169
  • 13
  • 73
  • 107
F. Hauri - Give Up GitHub
  • 64,122
  • 17
  • 116
  • 137