Date arithmetic in a shell script



  • So I wanted to use ISO 8601-like folder names for some log files... but the trouble is, if you put a leading 0 on the month, bash assumes that the number is base-8 (octal) and poops out if it's later than august. But fear not! There is a way to force the base of a number, to pretty much anything (yes, even base-10):

    #figure out what last month was in yyyy-mm format:
    
    month=`date +%m`
    month=$((10#$month - 1))
    if [ $month == 0 ] ; then
        month=12
        year=$((`date +%Y` - 1))
    else
        year=`date +%Y`
    fi
    
    if [ $month -lt 10 ]  ; then
        month="0$month"
    fi
    
    lastmonth="$year-$month"
    

    (Yes, it even works correctly in January.)



  •  dude...man pages are your friend

     date -d "1 month ago" +%Y-%m

     

     

     



  • that's not actually reliable (on December 31 the function returns November 31...oops, I mean December 1, giving you December); you want date -d "$(date +%Y-%m-15) -1 month". On freebsd, the syntax is date -v-1m +%Y-%m and it actually doesn't mess up in that way.



  • haha... and here my first thought about things like that is always "nah, the programmers wouldn't have bothered with strings as vague as '1 month ago'; that'd make the code hideously bloated with all the special fuzzy phrases..."



  • @Random832 said:

    that's not actually reliable (on December 31 the function returns November 31...oops, I mean December 1, giving you December); you want date -d "$(date +%Y-%m-15) -1 month". On freebsd, the syntax is date -v-1m +%Y-%m and it actually doesn't mess up in that way.

    no it doesn't; it doesn't know what a day is -- it deals only in months, and always subtracts one from the month.

    I just tested it by forcing the system time to dec. 31st and it correctly returns 2008-11.

    Using day values in something like this strikes me as sketchy because a "month" isn't a well-defined unit of time -- it can be 28,29,30,31 or slightly over 31 days long (as it was this December, because of the leap second). (I'm one of those crazy people that thinks "365.2425" when someone asks me how many days are in this or last year...)



  • Why is there this pervasive idiocy from libc upwards that suggests that a leading zero means octal?  That's a WTF in a big way!

    It gets to C,PHP,JavaScript,Bash, and goodness knows where else! (I think anything that uses atoi() is infected by the lunacy).

    After all,  0x....   means  hexadecimal, and 0b... means binary, so couldn't we for the one time in a trillion that we want to do things in octal  have a prefix like  0c....      (0o... would be a bit risky).

     It wouldn't even break backward compatibility - as it's a fair bet that nobody ever uses a leading zero deliberately, but we all have to remember to test for it to stop the octal madness.

     



  • @RichardNeill said:

    it's a fair bet that nobody ever uses a leading zero deliberately
     

    The only time I deal with a leading zero is when I try to stringparse august from an externally formatted source.


  • Discourse touched me in a no-no place

    @RichardNeill said:

    as it's a fair bet that nobody ever uses a leading zero deliberately

    Forms that auto-propel you to the next field (see elsethread)

    Phone numbers.

    Passwords.

     



  •  @RichardNeill said:

     It wouldn't even break backward compatibility - as it's a fair bet that nobody ever uses a leading zero deliberately, but we all have to remember to test for it to stop the octal madness.

     

    Just because it's a "fair bet" that nobody uses it, doesn't mean backward compatibility isn't broken.  Also, libc, stdlib and the like all assume decimal unless a radix parameter is available....if it's available...it should be used.



  • Sorry, what I meant to say by

       "...nobody uses a leading zero deliberately" is 

     "(probably) nobody who uses or encounters a leading zero has done it for the dleliberate purpose of being interpreted as octal.

     In other words, I'm arguing that the libraries should be changed, because nobody uses octal for anything, but many people accidentally encounter leading zero-related bugs when their decimal strings get interpreted as octal.

    This happened to me recently when I had to use the gmp_math stuff in PHP. The General precisions maths library uses strings to store arbitrary-length integers. If you want to do decimal-point maths, you have to manually convert the string. For instance:  21.034 would have to be split into 21  +  034 x 10^-3 (and the powers of 10 have to be processed longhand).  The obvious way to do this is to split the string at the decimal point, and then get "-3" from strlen(034). Which breaks, because of the stupid octal assumption.

    At any rate, I think that compilers should raise an error/warning when they see octal numbers, just like they do with constructs  like  "if (a=b)"  - while technically correct, virtually nobody means to write it.

     

      

     


  • Discourse touched me in a no-no place

    @RichardNeill said:

    Sorry, what I meant to say by

       "...nobody uses a leading zero deliberately" is 

     "(probably) nobody who uses or encounters a leading zero has done it for the dleliberate purpose of being interpreted as octal.

     In other words, I'm arguing that the libraries should be changed, because nobody uses octal for anything,

    chmod


  • @PJH said:

    chmod

    Yay, a use for octal! Alas, it's just assumed to be octal and the leading 0 isn't used, so the point stands.... I've been quite irritated at times with strtol and similar doing this.


  • Discourse touched me in a no-no place

    @joemck said:

    I've been quite irritated at times with strtol and similar doing this.
    But, but but... strtol() doesn't do that though. (Neither does atoi().)



  • @RichardNeill said:

    Why is there this pervasive idiocy from libc upwards that suggests that a leading zero means octal?  That's a WTF in a big way!

    It gets to C,PHP,JavaScript,Bash, and goodness knows where else! (I think anything that uses atoi() is infected by the lunacy).

    After all,  0x....   means  hexadecimal, and 0b... means binary, so couldn't we for the one time in a trillion that we want to do things in octal  have a prefix like  0c....      (0o... would be a bit risky).

     It wouldn't even break backward compatibility - as it's a fair bet that nobody ever uses a leading zero deliberately, but we all have to remember to test for it to stop the octal madness.

    You missed.  As one of the people who uses this, I know exactly where it comes from: C.

    Well, OK, I don't know *exactly* where it comes from.  I don't know if it was in the original K&R C, but it was in the first ANSI C.  As such, it was possibly added by one or more of the innumerable C variants in the Great C Schism that led to ANSI C.  (I know I've used a C compiler that didn't do it in its K&R compatibility mode, but I also know that it had other, ahem, issues with doing proper K&R C compatibility.)

    I've seen dozens of programs using octal deliberately.  (I was about to say 'hundreds', and then remembered that the last time I said 'hundreds' and later went back to check, it turned out to be 53.  I don't 53 constitutes hundreds, normally - not even in base 8.)

    Pre-edit: How the L would it get into libc before it got into C?!?!?



  • @RichardNeill said:

    Why is there this pervasive idiocy from libc upwards

    It doesn't start at libc. It starts at K&R, because at the time the C programming language was invented, octal constants were the norm. Everyone used octal, because it was 1972 and all the big iron had to make a choice of how to represent computer data in displays.

    1. Binary: far too difficult to track.
    2. Decimal: requires complex and time-consuming conversions from binary.
    3. Hexadecimal: requires alphanumeric display capability.

    Octal used only digits, which allowed the use of existing seven-segment and Nixie displays, and quite efficiently represented the usual 24 and 36 bit words of the IBM mainframe in an even number of digits. So, too, would a hex digit... but the display elements would have had to be custom-made. Seven-segment displays and Nixie tubes were readily and cheaply available as surplus.

    Yes, I'm old, but I made a fortune writing COBOL in 1999. And for me, that was nostalgic and pleasant. Kind of like being back in college.



  • @thetrivialstuff said:

    @Random832 said:

    that's not actually reliable (on December 31 the function returns November 31...oops, I mean December 1, giving you December); you want date -d "$(date +%Y-%m-15) -1 month". On freebsd, the syntax is date -v-1m +%Y-%m and it actually doesn't mess up in that way.

    no it doesn't; it doesn't know what a day is -- it deals only in months, and always subtracts one from the month.

    I just tested it by forcing the system time to dec. 31st and it correctly returns 2008-11.

    Using day values in something like this strikes me as sketchy because a "month" isn't a well-defined unit of time -- it can be 28,29,30,31 or slightly over 31 days long (as it was this December, because of the leap second). (I'm one of those crazy people that thinks "365.2425" when someone asks me how many days are in this or last year...)

    What do you mean "it doesn't know what a day is"? So ...-d "1 day ago" is meaningless? Regardless of the fact that it gets the right answer for you, the result of the function _is_ a full timestamp that goes all the way to seconds. It _used_ to work by converting to struct tm, subtracting [e.g.] one from the month value, and then converting back [mktime], which would indeed result in "November 31" -> December 1 for December 31. Yes, it subtracts one from the month, but it does NOT "deal only in months" - it gets a structure representing the entire date and time and subtracts one from the month of THAT, yielding an invalid value that's _supposed_ to [because it's the same value you'd get by adding a day to november 30] convert to December 1. Maybe this was fixed in some version, but getdate.y - which is where the work gets done, doesn't _seem_ to do anything different. FreeBSD's code is specifically documented to do the right thing here, but GNU's is not, it's been noted to do the wrong thing before, and I see nothing in the code that would indicate any change.

    Did you even actually test it? I did not make up this issue and I did not originate the workaround. http://2muchtea.wordpress.com/2007/12/31/getting-last-month-with-date-command/ http://www.gnu.org/software/automake/manual/tar/Relative-items-in-date-strings.html#Relative-items-in-date-strings



  • Ah, the pitfalls of using pronouns...

    "it" in my message = the script in the original post, not the date command.

    I think the statements "it doesn't know what a day is" and "deals only in months" apply nicely to the script in question; I know they don't apply to the date command.

    The result that the script interprets does not have a seconds component; it's just a digit or two that corresponds to the current month. Figuring out the previous month is then done by subtracting one from whatever number that is (in the script, not with date); that's what I meant by dealing only in months -- unless date happens to return November as the current month when the date is currently in December (I hope the bug you're talking about wouldn't cause something that obviously wrong to happen...), the script will get it right -- because 12 - 1 = 11.

    And yes, I actually tested the script; I did not test the date command using "ago" statements, though.



  • (Oh, I see! If you click on "in reply to" it tells you which message a given post was in reply to... I had assumed those were broken because they were all blank (on the main site they say things like "243157 in reply to 243155") --- anyway, in case you were wondering why I interpreted your post as being a reply to the original instead of that other reply, that's why.)


Log in to reply