0

I have a bash script which is supposed to increment a timestamp by 1 day to echo the next 5 days from the timestamp, however when it is run it produces the wrong output and has subtracted one hour instead.

Script

#!/bin/bash

TIME_STAMP="2023-06-03 06:21:33"

for i in {1..5}
do
    NEXT_DATE=$(date "+%Y-%d-%m %H:%M:%S" -d "$TIME_STAMP +$i day")
    echo $NEXT_DATE
done

Output

2023-04-06 06:21:33
2023-04-06 05:21:33
2023-04-06 04:21:33
2023-04-06 03:21:33
2023-04-06 02:21:33

As far as I can tell the date format is correct so I've got absouletly no idea why it isn't working correctly. If there's an easier way to do this I'd love to hear it!

S tommo
  • 165
  • 2
  • 5
  • 15
  • 1
    Does this answer your question? [How to increment a date in a Bash script](https://stackoverflow.com/questions/18706823/how-to-increment-a-date-in-a-bash-script) – Paolo Apr 20 '23 at 13:38
  • 1
    As the accepted answer in the other question mentions: `NEXT_DATE=$(date "+%Y-%m-%d %H:%M:%S" -d "$TIME_STAMP UTC +$i day")` – Paolo Apr 20 '23 at 13:39
  • Or, simply split off the time and just add that back on after the generated date. `for i in {1..5}; do NEXT_DATE=$(date "+%Y-%d-%m %H:%M:%S" -d "${TIME_STAMP% *} +$i days ${TIME_STAMP#* }"); echo $NEXT_DATE; done` – tripleee Apr 20 '23 at 13:43
  • 1
    @Paolo this is not a duplicate of [how-to-increment-a-date-in-a-bash-script](https://stackoverflow.com/questions/18706823/how-to-increment-a-date-in-a-bash-script) as that question doesn't have a time associated with the starting date. – Ed Morton Apr 20 '23 at 13:53
  • @EdMorton in this question OP is literally asking how to increase by days, which is exactly the same thing being asked in the question I linked – Paolo Apr 20 '23 at 13:55
  • @Paolo the OP is not asking how to increase a **date** by days as in the question you linked, they're asking how to increase a **timestamp** (date+time) by days which is what makes this a different question. The accepted answer for THAT question is the same as the code in THIS question which does not work for that reason - it's not the same question and it requires a different answer. – Ed Morton Apr 20 '23 at 13:57
  • Paolo: here there's a special caveat, because `date -d` bug on appending N days to a date string in case you specify hours:minutes:secondes. It's a corner case, better to have it for the records. This is not _that_ simple as other said 'duplicated answer' – Gilles Quénot Apr 20 '23 at 13:59
  • @EdMorton I think you are doing quite the nitpicking here, I've seen questions closed as duplicates for way less. The accepted answer to the other question even mentions how to resolve this OP's question... whatever. Thought you were by the book! – Paolo Apr 20 '23 at 14:04
  • 1
    @Paolo the question has been open about 45 minutes and has 4 answers so far, so I don't think it's nitpicking to assume it deserves an answer but if it is nitpicking then multiple people are doing it, maybe you should discuss with them too? Thank you for considering me as someone who goes by the book. – Ed Morton Apr 20 '23 at 14:09

6 Answers6

1

I think @KamilCuk's answer is the right approach if you're going to use date for this but here's an alternative assuming you don't care about DST:

$ cat tst.sh
#!/usr/bin/env bash

this_time_stamp='2023-06-03 06:21:33'

for i in {1..5}
do
    this_date="${this_time_stamp% *}"
    this_time="${this_time_stamp#* }"
    next_time_stamp="$(date '+%Y-%d-%m' -d "$this_date +$i day") $this_time"
    echo "$next_time_stamp"
done

$ ./tst.sh
2023-04-06 06:21:33
2023-05-06 06:21:33
2023-06-06 06:21:33
2023-07-06 06:21:33
2023-08-06 06:21:33

See Correct Bash and shell script variable capitalization for why I changed your variable names to lower case.

I'd strongly recommend you do not use a YYYY-DD-MM instead of YYYY-MM-DD or similar format, by the ,as it's hard to do anything with that afterwards, e.g. sorting or searching in a range.

Ed Morton
  • 188,023
  • 17
  • 78
  • 185
  • 2
    Why are you answering a duplicate? – Paolo Apr 20 '23 at 13:49
  • 1
    @Paolo It's not a duplicate of the question you suggested as that question doesn't have a time associated with the starting date. idk if there's some other question it IS a duplicate of. – Ed Morton Apr 20 '23 at 13:52
1

The problem is that the date string is being split and date only calculates something based on the time.

A workaround is to split off the time and add it back on after the generated date.

for i in {1..5}; do
     NEXT_DATE=$(date "+%Y-%d-%m %H:%M:%S" -d "${TIME_STAMP% *} +$i days ${TIME_STAMP#* }")
     echo "$NEXT_DATE"
done

Capturing the output in a variable just so you can then echo it is a useless echo but I imagine you want the variable for something more involved in your real use case.

Notice also that date -d is not portable; this will work on Linux, but not on many other platforms.

tripleee
  • 175,061
  • 34
  • 275
  • 318
1

The string is parsed as:

  • +$i - timezone in the format +HH
  • day - add a day

Add a +00 to make date parse the timezone as UTC.

$ for i in {1..5}; do NEXT_DATE=$(date "+%Y-%d-%m %H:%M:%S" -d "$TIME_STAMP +00 +$i day"); echo $NEXT_DATE; done
2023-04-06 08:21:33
2023-05-06 08:21:33
2023-06-06 08:21:33
2023-07-06 08:21:33
2023-08-06 08:21:33

or to use your current timezone offset:

$ tz=$(date +'%z')
$ for i in {1..5}; do NEXT_DATE=$(date "+%Y-%d-%m %H:%M:%S" -d "$TIME_STAMP $tz +$i day"); echo $NEXT_DATE; done
2023-04-06 06:21:33
2023-05-06 06:21:33
2023-06-06 06:21:33
2023-07-06 06:21:33
2023-08-06 06:21:33
Ed Morton
  • 188,023
  • 17
  • 78
  • 185
KamilCuk
  • 120,984
  • 8
  • 59
  • 111
0

With Perl with core modules (installed by default), without 'cheating' with date string, the most portable and robust solution I guess.
wink to @Ed ;)

perl -MTime::Piece -MTime::Seconds -sE '
    foreach my $i (1..5) {
        my $d = Time::Piece->strptime($mydate, "%Y-%d-%m %H:%M:%S");
        ($dow, $month, $dom, $hour, $year) = split / +/, $d +$i*ONE_DAY;
        my $dateold = Time::Piece->strptime(
            "$dow $month $dom $year $hour",
            "%a %b %d %Y %H:%M:%S");
        say $dateold->strftime("%Y-%d-%m %H:%M:%S");
    }
' -- -mydate='2023-06-03 06:21:33'
2023-07-03 06:21:33
2023-08-03 06:21:33
2023-09-03 06:21:33
2023-10-03 06:21:33
2023-11-03 06:21:33
Gilles Quénot
  • 173,512
  • 41
  • 224
  • 223
0

Simple,instead local time zone, we can use date with -u option for UTC zone, add '0000' in input time zone for UTC conversion

#!/bin/bash
UTC="2023-06-03 06:21:33+0000"
for days in {1..5}
do
    DATE_NEXT=$(date -u "+%Y-%m-%d %H:%M:%S" -d "$UTC +$days day")
    echo $DATE_NEXT
done

output:

2023-06-04 06:21:33
2023-06-05 06:21:33
2023-06-06 06:21:33
2023-06-07 06:21:33
2023-06-08 06:21:33
kaavannan
  • 1
  • 1
0

The problem is that the + sign is interpreted as timezone. We can just remove it:

for (( i=1 ; i<= 5 ; ++i )); do
    NEXT_DATE=$(date "+%Y-%d-%m %H:%M:%S" -d "$TIME_STAMP $i day")
    echo "$NEXT_DATE"
done

OUTPUT:
2023-04-06 06:21:33
2023-05-06 06:21:33
2023-06-06 06:21:33
2023-07-06 06:21:33
2023-08-06 06:21:33

In the same way, there is the same problem for previous days. In that case, we can use "$TIME_STAMP $i day ago" instead

I also remove the seq: it is always better to use C-style loops as seq is an external command and require to place the entire sequence in a buffer before splitting it into integers

fredc
  • 1