tags:

views:

269

answers:

4

I have the following loop to calculate the dates of the current week and print them out. It works, but I am swimming in the amount of date/time possibilities in Perl and want to get your opinion on whether there is a better way. Here's the code I've written:

#!/usr/bin/env perl
use warnings;
use strict;

use DateTime;

# Calculate numeric value of today and the 
# target day (Monday = 1, Sunday = 7); the
# target, in this case, is Monday, since that's
# when I want the week to start
my $today_dt = DateTime->now;
my $today = $today_dt->day_of_week;
my $target = 1;

# Create DateTime copies to act as the "bookends"
# for the date range
my ($start, $end) = ($today_dt->clone(), $today_dt->clone());

if ($today == $target)
{
  # If today is the target, "start" is already set;
  # we simply need to set the end date
  $end->add( days => 6 );
}
else
{
  # Otherwise, we calculate the Monday preceeding today
  # and the Sunday following today
  my $delta = ($target - $today + 7) % 7;
  $start->add( days => $delta - 7 );
  $end->add( days => $delta - 1 );
}

# I clone the DateTime object again because, for some reason,
# I'm wary of using $start directly...
my $cur_date = $start->clone();

while ($cur_date <= $end)
{
  my $date_ymd = $cur_date->ymd;
  print "$date_ymd\n";
  $cur_date->add( days => 1 );
}

As mentioned, this works, but is it the quickest or most efficient? I'm guessing that quickness and efficiency may not necessarily go together, but your feedback is very appreciated.

+3  A: 

You can use the DateTime object to get the current day of the week as a number ( 1-7 ). Then just use that to find the current week's Monday. For example:

my $today = DateTime->now;
my $start = $today->clone;

# move $start to Monday
$start->subtract( days => ( $today->wday - 1 ) );   # Monday gives 1, so on monday we
                                                    # subtract zero. 

my $end = $start->clone->add( days => 7 );

The above is untested but the idea should work.

friedo
Wow, that's a lot simpler. Thanks!
ABach
+10  A: 

A slightly improved version of friedo's answer ...

my $start_of_week =
    DateTime->today()
            ->truncate( to => 'week' );

for ( 0..6 ) {
    print $start_of_week->clone()->add( days => $_ );
}

However, this assumes that Monday is the first day of the week. For Sunday, start with ...

my $start_of_week =
    DateTime->today()
            ->truncate( to => 'week' )
            ->subtract( days => 1 );

Either way, it's better to use the truncate method than re-implement it, as friedo did ;)

Dave Rolsky
Thank you for the updated code.
ABach
A quick question - does that for loop create a new DateTime clone every iteration? Is that okay to do in Perl?
ABach
Note that the Sunday version doesn't work properly. You need to `->add(days => 1)` before the `->truncate`, or starting with Sunday will give you the start of the previous week.
cjm
Cool, I wasn't aware of `truncate`
friedo
@cjm: That's a good point.I wonder if there should be a `->truncate( to => 'locale-week' )` variant that is locale-aware.
Dave Rolsky
Or maybe something like `->truncate( to => 'week', starting => 7 )`. Then you could have your weeks start on any day. (`starting` would of course default to 1 to be consistent with the current behavior.)
cjm
+1  A: 

This should work:

use POSIX; # for strftime
my $time = time ();
my $seconds = 24*60*60;
my @time = gmtime ();
$time = $time - $time[6] * $seconds;
for my $wday (0..6) {
    $time += $seconds;
    my @wday = gmtime ($time);
    print strftime ("%A %d %B %Y\n", @wday);
}

Gives me:

$ ./week.pl 
Monday 24 May 2010
Tuesday 25 May 2010
Wednesday 26 May 2010
Thursday 27 May 2010
Friday 28 May 2010
Saturday 29 May 2010
Sunday 30 May 2010

If you want to get weeks starting on Sunday, change $time[6] to ($time[6] + 1).

This assumes you want the GMT weeks. Change gmtime to localtime to get local time zone weeks.

Snake Plissken
If you're only using POSIX for strftime (which is indeed a fine reason to use it) then it would be better to say: use POSIX qw(strftime);Also, %F gives the YYYY-MM-DD format the original poster was after.
Grant McLean
Thanks. There are lots of other nits to pick in that code (what does `$wday` do?), but basically I wanted to offer an example of doing it without `DateTime`, which isn't necessary for this task.
Snake Plissken
+1  A: 

Would this work:

use strict;
use warnings;
use POSIX qw<strftime>;
my ( $day, $pmon, $pyear, $wday ) = ( localtime )[3..6];
$day -= $wday - 1; # Get monday
for my $d ( map { $day + $_ } 0..6 ) { 
    print strftime( '%A, %B %d, %Y', ( 0 ) x 3, $d, $pmon, $pyear ), "\n";
}

I'm printing them only as an illustration. You could store them as timestamps, like this:

use POSIX qw<mktime>;
my @week = map { mktime(( 0 ) x 3, $day + $_, $pmon, $pyear ) } 0..6;
Axeman
@Axeman - you are right. Sorry.
Snake Plissken
@Snake Plissken: No prob. Cool name. :)
Axeman