EchoAngel has asked for the wisdom of the Perl Monks concerning the following question:

Hello, someone's code has this
my $RunDateTime = `date +%Y%m%d%H%M%S`;
say returns something like 20041101174146 Which is basically 2004-11-01 17:41:46. Do you know how I can subtract 15 min from it? I was thinking of making a lot of if conditions (ie for 60 mins, 24 hours, number of days in a month, etc). Just wondering if there is a better way

Replies are listed 'Best First'.
Re: Date Manipulation Calculation Question
by duff (Parson) on Nov 01, 2004 at 23:18 UTC
    use POSIX qw(strftime); $now = time; $then = $now - 15 * 60; # 15 minutes ago; print map strftime("%Y%m%d%H%M%S\n", localtime($_)), $now, $then;

    All code untested, but use something like that rather than call the date(1) command.

Re: Date Manipulation Calculation Question
by tachyon (Chancellor) on Nov 01, 2004 at 23:34 UTC

    As duff points out time will give you the time in seconds since the epoch so it is easy to add or subtract whatever you want. gmtime and its friend localtime convert epoch seconds into all the date bits when called in list context or a nice human readable string when called in scalar context.

    my $epoch = time(); my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime($epo +ch); my $gm_str = gmtime(); # note that default epoch for gmtime is NOW() printf '%d seconds ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = %s %s %s ', $epoch, "($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)", $g +m_str, scalar(localtime); # need scalar(localtime) as print has list context __DATA__ 1099351947 seconds ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = (27,32,23,1,10 +,104,1,305,0) Mon Nov 1 23:32:27 2004 Tue Nov 2 10:32:27 2004

    cheers

    tachyon

Re: Date Manipulation Calculation Question
by Kyoichi (Novice) on Nov 01, 2004 at 23:25 UTC
    I'd recommend that you use localtime and then substract the number of seconds of 15 minutes ( that is 60*15 ), and then you can convert to whatever format you want with POSIX::strftime. If that solution is not that good, I suggest for you to take a look on Date::Manip and Time::Local for your doings. Greetings
Re: Date Manipulation Calculation Question
by poqui (Deacon) on Nov 01, 2004 at 23:25 UTC
    use DateTime;

    CPAN

    It puts the DateTime in a struct that makes it easier to manipulate.
Re: Date Manipulation Calculation Question
by pg (Canon) on Nov 02, 2004 at 03:05 UTC

    On a 32-bit machine, the valid range of epoch is: 0 - 2 ** 31 - 1 inclusive, and they translate into:

    1970 January 01 00:00:00 2038 January 19 03:14:07

    Here is the code:

    use POSIX; { my $t = strftime("%Y %B %d %H:%M:%S", gmtime(0)); print $t, "\n"; } { my $t = strftime("%Y %B %d %H:%M:%S", gmtime(2 ** 31 - 1)); print $t, "\n"; }
Re: Date Manipulation Calculation Question - performance alternative
by PhilHibbs (Hermit) on Nov 02, 2004 at 14:06 UTC
    Most everyone has replied with some form of "Convert it to seconds, do the math, convert it back again". This is an entirely acceptable answer, and absent any other requirements this is what I would do.

    There is, however, a possible reason not to do this if performance is an issue. Note that there would have to be a really good reason to do it this way, but here goes.

    Converting a string such as 20041101174146 to a number of seconds would take at least 6 mathematical operations for the 2004, 11, 01, 17, 41, and 46 components. Converting it back again will take another 6, then there's either the string splitting or modulus operations to break it up, and the opposites to put it back again. This could entail 25 discrete steps.

    In most cases, and certainly with the example above, this can be done with a substring, a subtract, a test for undererflow, and an assignment back to the substring. It is possible that adding a value to a time string such as the above could cause 5 overflows (19991231235959 + 1 second = 20000101000000), and this case would take considerably more computation than the generic convert-to-seconds approach, but in most cases there should be a performance gain.

      Converting a string such as 20041101174146 to a number of seconds would take at least 6 mathematical operations for the 2004, 11, 01, 17, 41, and 46 components.

      Well, one thing to recall here is that if the operation occurs regularly then caching the results will result in a signifigant speedup. For instance you could cache the year/month/day/hour results and then add in only the minutes and seconds. This means that a lot of the operations you mention here wouldn't have to happen unless the cache was empty and even then would only happen once.

      After a LOT of benchmarking I found the following routine works best for my situation (parsing millions of datestamps a day), obviously other usage cases may have different performance effects.

      { my (%hourcache,%mincache); sub D14_to_unix { my $hour = substr( $_[0], 0, 10 ); my $min = substr( $_[0], 10, 4 ); return +( $hourcache{$hour} ||= timelocal( 00, 00, substr( $hour, 8, 2 ), substr( $hour, 6, 2 ), substr( $hour, 4, 2 ) - 1, substr( $hour, 0, 4 ) - 1900 ) ) + ( $mincache{$min} ||= ( substr( $min, 0, 2 ) * 60 + substr( $min, 2, 2 ) ) ); } }
      ---
      demerphq

Re: Date Manipulation Calculation Question
by TedPride (Priest) on Nov 02, 2004 at 13:04 UTC
    @_ = localtime(time()-900); my $RunDateTime = ($_[5]+1900).sprintf('%02d%02d%02d%02d%02d',($_[4]+1 +),$_[3],$_[2],$_[1],$_[0]);
    This does what you want, but without the overhead of using a module or calling the system through ` `.
Re: Date Manipulation Calculation Question
by TomDLux (Vicar) on Nov 02, 2004 at 16:45 UTC

    How many million dates do you need to process per hour?

    Unless it's significant, modules are definitely your friend. I hate having to fix bugs because people thought it was too expensive to load a module, and so wrote their own routine to calculate, for example, "previous business day". Something changes in the input, or you come along to the leap year in the century which isn't a leap year, or the one in four centuries which IS a leap year, after all, and things don't work. Far better to use some code which has solved all those problems.

    I would suggest using unpack() to parse the date into variables, timelocal() to convert to epoch seconds, and strftime or sprintf to convert back to a timestamp.

    $t1 = 20041101174146 my ( $y, $m, $d, $hh,$mm, $ss ) = unpack ("A4A2A2A2A2A2", $a ) $epoch= timelocal( $ss, $mm, $hh, $d, $m-1, $y-1900 ) ( $ss, $mm, $hh, $d, $m, $y ) = localtime( $epoch - $seconds_to_subtra +ct) $t2 = sprintf( "%d%02d%02d%02d%02d%02d", $y+1900, $m+1, $d, $hh, $mm, +$ss ); # $t2 is 20041101172646

    Once you have your code working correctly, profile it. If time conversion is a major factor, you can look at writing custom conversions. But even then, leave the ordianry code in a routine so your testing can compare the two results, to ensure things are correct.

    --
    TTTATCGGTCGTTATATAGATGTTTGCA

Re: Date Manipulation Calculation Question
by TedPride (Priest) on Nov 02, 2004 at 14:52 UTC
    The object of getting the date / time in a fixed length string like this is to be able to perform comparisons on log file lines. Unless you have access to the log script and can change the time format to timestamp, time / localtime / sprintf is about as efficient as you can get.
Re: Date Manipulation Calculation Question
by geektron (Curate) on Nov 02, 2004 at 22:37 UTC
    i've been using Date::Calc and Add_Delta_* for things like this ...
Re: Date Manipulation Calculation Question
by Anonymous Monk on Nov 04, 2004 at 13:11 UTC
    use maketime. Perl has Cs maketime library built-in which is very good for dealing with dates. Do a search on maketime for examples.