3

I have rather basic task but wasn't able to find proper solution to it. I want to iterate date interval since 2008 to current and need epoch values for each iteration of the loop. I am interested in iterating both years, halves and months as well.
I wrote such script

#!/bin/bash
initial_date=`date -d "2008-02-04 00:00:00 UTC" +%s`
end_date=`date +%s`
n=0
until [ $initial_date -gt $end_date ]; do
 echo $initial_date
 let n+=1
 readable_date=`date +"%Y-%m-%d %T" -d "1970-01-01 $initial_date sec"`
 echo $readable_date
 readable_date=`date -d "$readable_date + $n year"`
 initial_date=`date -d "$readable_date" +%s`
done

but its output is rather weird to me:

1202083200
2008-02-04 00:00:00
1233702000
2009-02-03 23:00:00
1265230800
2010-02-03 21:00:00
1296756000
2011-02-03 18:00:00
1328277600
2012-02-03 15:00:00
1359885600
2013-02-03 11:00:00
1391403600
2014-02-03 06:00:00
1422918000
2015-02-02 23:00:00
1454425200
2016-02-02 15:00:00

Why the year is not incremented properly? Shifted hours seems to me a side-effect of unix>>UTC>>unix conversion. Is there any direct (without reconversion) method for doing this?

P.S.
And yes, I checked this, this and this question and found no clear way of doing this. All they are based on incrementing number and converting date with the help of it which doesn't seem precise to me.

And yes, I thought about adding 60*60*24*30*365 to initial date but would it be correct? This approach doesn't consider leap years, months comprised of 31 days and so on.

3

Date used:

$ dateA="2008-02-04 00:00:00 UTC"

The first reason to get dates with shifted times is to request "local" dates:

$ date -d "$dateA"
Sun Feb  3 19:00:00 EST 2008

Which will be corrected if you specify a UTC time:

$ TZ=UTC0 date -d "$dateA"
Mon Feb  4 00:00:00 UTC 2008

Or better (make an habit of using it) use the -u option of date:

$ date -ud "$dateA"
Mon Feb  4 00:00:00 UTC 2008

A second reason is to request "relative items" with a sign:

$ date -ud "$(date +"%Y-%m-%d %T" -ud "$dateA") +1 year"  ### wrong
Tue Feb  3 23:00:00 UTC 2009

$ date -ud "$(date +"%Y-%m-%d %T" -ud "$dateA") 1 year"  ### better
Mon Feb  4 00:00:00 UTC 2008

But all the time problems (usually) go away when a "time zone" (-z) is used:

$ date -ud "$(date +"%Y-%m-%d %T %z" -ud "$dateA") +1 year"  ### Best
Wed Feb  4 00:00:00 UTC 2009

$ date -ud "$(date +"%Y-%m-%d %T %z" -ud "$dateA") 1 year"  ### Preferred
Wed Feb  4 00:00:00 UTC 2009

The issue is related to the way a date string is parsed, if the TZ value is missing, the +1 may be interpreted as a Time Zone value:

$ date -ud "2008-02-04 00:00:00 +3 year"
Tue Feb  3 21:00:00 UTC 2009

Even if an environment value for TZ has been set:

$ TZ=UTC0 date -ud "2008-02-04 00:00:00 +3 year"
Tue Feb  3 21:00:00 UTC 2009

Yes, the year was incresed once (2009 instead of 2008) but the +3 was used for a change of the time presented (not what was intended).

I repeat: the problem is that the +3 may be parsed as a Time Zone value.


Also, to convert (epoch) seconds to dates, GNU date may use "@"

$ dateAsec="$( date -ud "$dateA" +"%s" )

$ date -ud @"$dateAsec"
Mon Feb  4 00:00:00 UTC 2008

The script (using a (newdate) function) could be written like this:

#!/bin/bash
dateI="$(date -ud "2008-02-04 00:00:00 UTC" +%s)"
dateF="$(date -u +%s)"

newdate(){ date -ud "$(date +"%Y-%m-%d %T %z" -ud @"$dateI") $1 year" +"%s"; }

n=0
while 
    dateN="$( newdate "$n" )"
    (( dateN < dateF ));
do
    dateNhuman="$(date +"%Y-%m-%d %T %z" -ud @"$dateN")"
    echo "dateN=$dateN --- $dateNhuman ---> $dateF $(( $dateF - $dateN )) $n"
    (( n++ ))
done

Results:

dateN=1202083200 --- 2008-02-04 00:00:00 +0000 ---> 1470543626 268460426 0
dateN=1233705600 --- 2009-02-04 00:00:00 +0000 ---> 1470543626 236838026 1
dateN=1265241600 --- 2010-02-04 00:00:00 +0000 ---> 1470543626 205302026 2
dateN=1296777600 --- 2011-02-04 00:00:00 +0000 ---> 1470543626 173766026 3
dateN=1328313600 --- 2012-02-04 00:00:00 +0000 ---> 1470543626 142230026 4
dateN=1359936000 --- 2013-02-04 00:00:00 +0000 ---> 1470543626 110607626 5
dateN=1391472000 --- 2014-02-04 00:00:00 +0000 ---> 1470543626 79071626 6
dateN=1423008000 --- 2015-02-04 00:00:00 +0000 ---> 1470543626 47535626 7
dateN=1454544000 --- 2016-02-04 00:00:00 +0000 ---> 1470543626 15999626 8

All years appear and all seems correct.

  • BTW, what is the difference between signed and unsigned relative items? The first sample is marked as wrong by you and the second one as best ^) – Suncatcher Aug 7 '16 at 10:24
  • What concerns time zone: the more elegant way I found is to specify it throughout the script. It can be done via export TZ=UTC statement in the beginning of file. – Suncatcher Aug 7 '16 at 10:28
  • @Suncatcher Wrong has the time changed. Best is using the %z (time zone) indication (which avoids the signed isssue). – user79743 Aug 7 '16 at 19:38
  • @Suncatcher The correct value should be UTC0, three letters (an optional sign) and a digit. There are systems that may not default to utc time with some odd string (GNU does). Try TZ=sdshdk. – user79743 Aug 7 '16 at 19:43
  • @Suncatcher Please read added detail after the "Preferred" line. A global setting of UTC0 will not ensure a correct parsing of the date in all cases. As shown: TZ=UTC0 date -ud "2008-02-04 00:00:00 +3 year" will still fail. – user79743 Aug 7 '16 at 20:55
3

An iterator on a relative date from the starting point may make more sense:

#!/usr/bin/env bash

START='2008-02-04 00:00:00 UTC'
END=$( date +%s )

for yearnum in $( seq 1 999 ); do
  NUDATE=$( date -d "$START + $yearnum year" +%s )
  PUNY_HUMAN_DATE=$( date -d "@$NUDATE" "+%Y-%m-%d %T %Z" )
  if [[ $NUDATE -gt $END ]]; then
    break
  fi
  echo $NUDATE $PUNY_HUMAN_DATE
done

Which results in something like:

-bash-4.1$ bash iter
1233705600 2009-02-04 00:00:00 UTC
1265241600 2010-02-04 00:00:00 UTC
1296777600 2011-02-04 00:00:00 UTC
1328313600 2012-02-04 00:00:00 UTC
1359936000 2013-02-04 00:00:00 UTC
1391472000 2014-02-04 00:00:00 UTC
1423008000 2015-02-04 00:00:00 UTC
1454544000 2016-02-04 00:00:00 UTC
-bash-4.1$ 
  • There are need not variables and echo date -d "$START + $yearnum year" +"%s %Y-%m-%d %T %Z" – Costas Aug 6 '16 at 17:40
  • Thanks for good and compact script. My problem was that I didn't consider time zones and wasn't aware of syntax $X + $Y years +%s where relative items can be used together with %s format. – Suncatcher Aug 7 '16 at 10:22
  • However, such output is true only if the system time zone is UTC which is not my case. To make it work like this one should explicitly specify timezone TZ=UTC – Suncatcher Aug 7 '16 at 10:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.