Beefy Boxes and Bandwidth Generously Provided by pair Networks
good chemistry is complicated,
and a little bit messy -LW
 
PerlMonks  

Attack of the killer timestamps

by chexmix (Hermit)
on Mar 20, 2008 at 20:15 UTC ( [id://675292]=perlquestion: print w/replies, xml ) Need Help??

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

Good day, Monks!

As loath as I am to expose the true weakness of my brains in this austere, sacred realm, well, I'm at a point in my path to enlightenment where I should probably do just that. And so I am going to post an example of the kind of "CCNTwBnOGbSWDRGIY" (Crud Code Nailed Together with Boogers 'n' Other Goo by Someone Who Doesn't Really Get It Yet) I am so far capable of, for your delectation and my edification.

The genesis of the eldritch thing below is that I need to pass start and stop time values and a "pad" value (in seconds) for a database query. The pad value will be subtracted from the start value and added to the stop value to yield a time range which will be "pad value larger" on either end than the stop and start times indicate.

I had considered trying to let Sybase handle all the time-stuff but then found the "timelocal" function in Time::Local and its inverse, the builtin localtime, and thought, well, let's see if I can make it work in Perl.

The posted code only takes a single time value and has a 300 second padding hardcoded in. This was for simplicity's sake. It seems to work (It chokes though on a year like 2202), but wow, is it ugly. In particular I am sure there is something wrong with the way I am getting a properly formatted string back out of localtime. There is a lot of stuff that feels as though it should be pulled into subroutines and made more elegant ...

I invite and welcome comments, suggestions on how to do this better. I know it's pretty rugose and squamous at this point :^) It felt like something that should have been easy and wasn't as easy as it looked (especially when I found I couldn't use split properly on the string returned by localtime). But I am probably missing a lot of things.

#!/usr/bin/perl use warnings; use strict; use Time::Local; # Get timestamp input from user print "Enter a timestamp in mm/dd/yyyy hh:mm:ss format: "; chomp(my $stamp = <STDIN>); # Split out various fields for timelocal my($date, $time) = split / /, $stamp; my($month, $day, $year) = split /\//, $date; my($hour, $minute, $second) = split /:/, $time; # Both timelocal and localtime understand month as in 0 - 11 range $month -= 1; # Get time value in seconds since Epoch with timelocal my $now = timelocal($second,$minute,$hour,$day,$month,$year); # Add padding (this will become interactive) my $paddednow = $now + 300; # Get sensible timestamp back out with localtime my $answer = localtime($paddednow); # Convert result to array for laborious processing of fields my @Answer = split //, $answer; my $Amon = $Answer[4].$Answer[5].$Answer[6]; my $Aday = $Answer[8].$Answer[9]; my $Atime = $Answer[11].$Answer[12].$Answer[13].$Answer[14].$Answer[15 +].$Answer[16].$Answer[17].$Answer[18]; my $Ayear = $Answer[20].$Answer[21].$Answer[22].$Answer[23]; # Declare hash for conversion of monthname values my %monthy = ( "Jan" => "01", "Feb" => "02", "Mar" => "03", "Apr" => "04", "May" => "05", "Jun" => "06", "Jul" => "07", "Aug" => "08", "Sep" => "09", "Oct" => "10", "Nov" => "11", "Dec" => "12" ); # Handle leading zero issue with day of month my %daything = ( " 1" => "01", " 2" => "02", " 3" => "03", " 4" => "04", " 5" => "05", " 6" => "06", " 7" => "07", " 8" => "08", " 9" => "09" ); my $Amonth = $monthy{$Amon}; my $Arealday; if ($Aday < 10) { $Arealday = $daything{$Aday}; } else { $Arealday = $Aday } # Cobble together result in same format as input my $Adate = join "/", $Amonth, $Arealday, $Ayear; my $realanswer = join " ", $Adate, $Atime; print "$stamp + 5 minutes is $realanswer\n";

Replies are listed 'Best First'.
Re: Attack of the killer timestamps
by jwkrahn (Abbot) on Mar 20, 2008 at 20:43 UTC
    It seems to work (It chokes though on a year like 2202)

    That is because localtime is based on the Unix concept of the number of seconds since the epoch of January 1, 1970 and has historically been represented as a 32 bit number which only goes up to the year 2038.

    This may work better:

    #!/usr/bin/perl use warnings; use strict; use POSIX qw/ mktime strftime /; # Get timestamp input from user print 'Enter a timestamp in mm/dd/yyyy hh:mm:ss format: '; chomp( my $stamp = <STDIN> ); my ( $month, $day, $year, $hour, $minute, $second ) = $stamp =~ m! (\d ++) / (\d+) / (\d+) \s+ (\d+) : (\d+) : (\d+) !x; # Get time value in seconds since Epoch with mktime my $now = mktime $second, $minute, $hour, $day, $month - 1, $year - 19 +00; my $realanswer = strftime '%m/%d/%Y %H:%M:%S', localtime $now + 300; print "$stamp + 5 minutes is $realanswer\n";
      I especially like how this compresses and elides things -- it's FAR more Perl-y than what I wrote.

      I'm still very much learning how to speak/write this stuff in anything like an effective way.

        Well, being a poet with a really nice grasp of English vocabulary should assist in becoming a conversant to fluent Perl programmer. Perl is very sensitive to context and has a plethora of its own vocabulary. Some people in fact deride it for these things, but others revel in them. My impression of you from this thread is that if you apply yourself to learning Perl's vocabulary and grammar as you have to those of English, you will be a master. All it takes is time, effort, a bit of intelligence, and the ability to handle symbolic thought. You're clearly, then, well on your way. Take heart and take practice, and you'll be writing like an old hand soon.
        use Class::Date qw(date -DateParse); $Class::Date::DATE_FORMAT="%Y/%m/%d %H:%M:%S"; print date('2008/01/01 10:00:00') + 300, "\n", date('2008-01-01 10:00: +00') + '5m';


        update: golf opened ;)


        holli, /regexed monk/
Re: Attack of the killer timestamps
by kyle (Abbot) on Mar 20, 2008 at 20:39 UTC

    I think your parsing of localtime would be a lot easier once you realize that it returns a list if used in a list context.

    #print "Enter a timestamp in mm/dd/yyyy hh:mm:ss format: "; #chomp(my $stamp = <STDIN>); my $stamp = '11/02/2007 11:02:07'; # Split out various fields for timelocal my($date, $time) = split / /, $stamp; my($month, $day, $year) = split /\//, $date; my($hour, $minute, $second) = split /:/, $time; # Both timelocal and localtime understand month as in 0 - 11 range $month -= 1; # Get time value in seconds since Epoch with timelocal my $now = timelocal($second,$minute,$hour,$day,$month,$year); # Add padding (this will become interactive) my $paddednow = $now + 300; my @padded_time = localtime $paddednow; $padded_time[5] += 1900; # year $padded_time[4]++; # month printf "$stamp + 5 minutes is %02d/%02d/%d %02d:%02d:%02d\n", @padded_time[4,3,5, 2,1,0]; # 11/02/2007 11:02:07 + 5 minutes is 11/02/2007 11:07:07

    This is a fairly easy way to do this, but one of the date modules that have already been mentioned might make it even easier.

      "I think your parsing of localtime would be a lot easier once you realize that it returns a list if used in a list context."

      Many thanks - that is far clea(n|r)er.

Re: Attack of the killer timestamps
by apl (Monsignor) on Mar 20, 2008 at 20:31 UTC
    You could replace
    my %daything = ( " 1" => "01", " 2" => "02", " 3" => "03", " 4" => "04", " 5" => "05", " 6" => "06", " 7" => "07", " 8" => "08", " 9" => "09" ); my $Arealday; if ($Aday < 10) { $Arealday = $daything{$Aday}; } else { $Arealday = $Aday }
    with
    my $Arealday = sprintf( '%02d', $Aday );
Re: Attack of the killer timestamps
by dwm042 (Priest) on Mar 20, 2008 at 20:29 UTC
    Not speaking speifically to the issue of talking with Sybase, but to date manipulations in general, there are some good date modules in CPAN, including Date::Simple, Date::Calc and Date::Manip.

    Update: fixed typo.
Re: Attack of the killer timestamps
by mpeppler (Vicar) on Mar 21, 2008 at 17:12 UTC
    I had considered trying to let Sybase handle all the time-stuff
    Personally that's what I might do, using the T-SQL dateadd() call, something like
    select ... from ... where the_date between dateadd(ss, -$seconds, $start_date) and dateadd(ss, $seconds, $end_date)
    (using placeholders of course, $var used here for illustration purposes)

    But then I'm really a database developer writing tons of SQL, these days... :-)

    Michael

      Thanks for pointing that out - it might make the task a lot easier.
A reply falls below the community's threshold of quality. You may see it by logging in.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://675292]
Approved by pc88mxer
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others perusing the Monastery: (5)
As of 2024-03-29 08:49 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found