Phoenix.pm: Processing Dates

Michael Friedman friedman at highwire.stanford.edu
Wed Feb 25 12:12:08 CST 2004


Vic,

Wow, that's thorough. I've learned several things just from reading 
your example.

I would like to add the cheater's way out for your PS, though. The way 
I turn text dates into the seconds-from-epoch integer is to use 
Time::ParseDate. It un-formats about a hundred different ways to write 
a date and converts them to seconds. For example, you can give it 
strings like:

"10/4/2003"
"Mon Jan  2 04:24:27 1995" 	# Oracle format
"2 months from now"
"last Tuesday"

and it just works. :-) See the perldoc for more details, of course.

============
use Time::ParseDate;	# exports parsedate() by default

$seconds = parsedate($string); 	# also takes a hash of options, like UK 
formatting
============

Thanks,
-- Mike


On Feb 24, 2004, at 9:44 PM, Victor Odhner wrote:

> Was Re: Phoenix.pm: Thursday Meeting Automated Pre-Announcement
>
> Hi, Scott.
>
> This is by no means to demean on your admirable shot
> at automatic notification.  You're among friends here,
> and it gives us a chance to talk about Perl munging.
>
> I enjoy date manipulation.  I've done *lots* of that
> over the past 25 years, and it just isn't all that
> tricky in most cases.  So I've taken this occasion
> to ramble on a bit with my acquired (ahem) wisdom ...
>
> (Hey, if anyone replies to this bloated message,
> please don't quote it all!)
>
> I have used POSIX::mktime, but otherwise the standard
> functions in Perl are about all I have ever needed.
>
> As usual when I get to designing, I got a little
> carried away here.  The concept below will keep track
> of multiple event notifications for multiple mailing
> lists, even more than one a day.  Actually making it
> work, as the professor would say, is an exercise for
> the student ...
>
> Scott Walters wrote:
>> I shouldn't be trying to work on the dates directly
>> Myself, but I've actually never worked with dates
>> before! Amazing, I know.  Atleast nothing more complex
>> than grouping by month and recording and logging.
>
> Always convert dates to integers before trying to
> do any arithmetic on them.  I say this because I've
> been where you were today, and the code gets *very*
> complicated before you get it right.  The localtime
> function is gold.
>
> Oops.  You are calling gmtime instead of localtime.
> In Phoenix, you will be wrong if this runs before 7 a.m.,
> or maybe before 8 a.m. during daylight savings.
> The purpose of localtime is to take care of all that
> for you.  Even if everything else runs on Universal
> Time, you can set the time zone for -- or even inside --
> this process.
>
> Next problem:  you are doing all your computing with
> respect to today's date, but you are not checking the
> day of the meeting.  It's ok to make a computation
> and trust your results, but it's better to check them.
> See below ...
>
> Eden Li suggested this:
>   my $thursdayith = (localtime (time() + 6*24*60*60))[3];
>
> The expression is good -- I tend to use 86400 because
> I know the number, but 24*60*60 is self-documenting
> and takes no time for the Perl compiler to convert to
> 86400.  Still, it's a mistake to bury the "6" in with the
> other numbers.  They won't change, but the 6 will --
> count on it.
>
> Programs oversimplify the real world, so it is best to
> keep our assumptions out in the open.  Something like this:
>
>   my @daysahead = ( 6, 2 );
>     # For all events in %schedule, we will send out
>     # messages six days and two days in advance.
>     # A more sophisticated design could vary the intervals
>     # for individual events ...
>
>   my %schedule = (
>     "1:4" = # First week: Thursday
> 	[ "PerlMongers|7:00 P.M.|Bowne|PM" ],
>     "3:2" = # Third week: Tuesday
> 	[ "PerlMongers|7:00 P.M.|TBD|PM" ],
>   );
>
>   my %eventdesc = (
>     'PerlMongers' => "Perl Mongers meeting",
>   );
>
>   my %location = (
>
> 'Bowne' => qq(Bowne, which is located at 1500 N. Central Avenue,
> which is on the Southwest corner of Central and McDowell.  The parking
> lot is gated, so just press the button on the intercom, and tell the
> receptionist that you are there for the Perl meeting.  Park in the lot
> that is straight ahead from the entrance on the South side of McDowell.
> Park in any uncovered, non-reserved space.  Proceed to the main lobby,
> which is on the Northeast side of the parking lot.  ),
>
> 'TBD' => "a location to be announced.  ",
>
>   );
>
>   my %addr = (
> 	'PM' => "phoenix-pm-list at happyfunball.pm.org",
>   );
>
> For your purposes, you don't need to ask about the time
> of day, so you can slice the localtime results like this:
>
>   for my $interval ( @daysahead )  {
>     my $secondsahead = $interval * 24*60*60;
>
>     my ( $mday, $mon, $year, $wday ) =
>        ( localtime( $secondsahead ) )[3..6]
>
>     ... now you know that the day in which
>     the time $secondsahead falls is in the future by
>     one of the intervals declared in @daysahead.
>
> Now when you do each day's run, you can use some sort of
> modulo function against $mday to figure out $whichweek that
> day is in, 1 thru 5.  Pair that with the day of week to
> make up a hash key:
>
>   $schd = $schedule{ "$whichweek:$wday" };
>
>   if ( defined $schd && ref $schd )  {
>     # $schd is a list reference.
>     for my $event ( @{$schd) )  {
>       my ( $what, $when, $where, $addy ) = split( /\|/, $event );
>       my $eventtext = $event{$what};
>       my $locationtext = $location{$where};
>       my $recipients = $addr{$addy};
>
>        ... send message here ...
>     }
>
> If that expression comes up non-empty, then there is a meeting
> on $mday of $mon + 1.  You can have as many arbitrary
> items on your schedule, as long as they are scheduled
> by day-number of week-number.  And instead of just setting
> these hash entries to 1 (True), you could store them
> with any amount of information about the event:
>   my %schedule = (
>     "1:4" = "PerlMongers",  # First week : Thursday
>     "3:4" = "PerlMongers",  # Third week : Thursday
>   );
>
> I like that you are suffixing the dates, but I think the
> current schedule could result in our having a meeting on
> the "12nd".  I'd lean towards something like this:
>
>   my @suffix = qw(
>    st nd rd th th th th th th th
>    th th th th th th th th th th
>    st nd rd th th th th th th th
>    st );
>
> Can you tell that I like table driven computing?  That's
> from 30+ years of experience:  listen to the old guy.
>
> It would be a good idea to mention the month the meeting
> is in.  Matter of taste.
>
> In case anyone is not aware of it, the GNU "date" command
> (standard on Linux systems, often available elsewhere)
> is extremely powerful.  There may be cases where a
> system call to that command would be more efficient
> (i.e., saving you hacking time) than working it out
> in Perl.  Virtually endless formatting capabilities,
> and expressions like "first tuesday after ...".
>
> Just for good measure, I've attached code to convert
> a text date into a "time" seconds value.
>
> Take care,
>
> Vic
>
> If you want to convert a text date into a time_t (epoch seconds) 
> value, you can use the standard Time::Local or POSIX::mktime.
> The following uses the latter, after cleaning up its input.
>
> sub ymd2time_t  {  # Input = year, month, day; Output - time_t.
>
>     # Validates month.  Does windowing for two-digit year.
>     my ( $Y, $M, $MD ) = ( @_, 0, 0, 0 );
>     my @input = ( $Y, $M, $MD );
>     my $Y_input = $Y;
>     my $tim = 0;
>
>     for my $v ( $Y, $M, $MD )
>     {
>         return $v  if $v =~/^.invalid date/;
>         return "[invalid date: ymd2time arguments = $Y $M $MD]\n"
>             if $v eq 0;
>     }
>
>     return "[invalid date: Month = $M]\n"
>         if $M < 1 || $M > 12;
>
>     $M -= 1;
>
>     return "[invalid date: Day-of-Month = $MD]\n"
>         if $MD < 1 || $MD > 31;
>
>     if ( $Y < 80 )  { # Window 2-digit year, pivot at 1/1/1980.
>         $Y += 2000;
>     }
>     elsif ( $Y <= 99 )  {  # Window 2-digit year
>         $Y += 1900;
>     }
>
>     $Y -= 1900;  # Now make it relative to 1900.
>
>     $tim = POSIX::mktime( 0, 0, 0, $MD, $M, $Y, 0, 0, 0 );
>
>     if ( !$tim )  { # Check against known limits, ONLY if mktime 
> failed.
>         return "[ymd2time_t failed for date = $input]\n";
>     }
>
>     return $tim;
>
> }
>
>
---------------------------------------------------------------------
Michael Friedman                  HighWire Press, Stanford Southwest
Phone: 480-456-0880                                   Tempe, Arizona
FAX:   270-721-8034                  <friedman at highwire.stanford.edu>
---------------------------------------------------------------------




More information about the Phoenix-pm mailing list