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