my $dt1 = DateTime->new(year => 2002, month => 3, day => 1);
my $dt2 = DateTime->new(year => 2002, month => 2, day => 11);
my $date = DateTime->new(year => 2002, month => 2, day => 23);
# Make sure $dt1 is less than $dt2
($dt1, $dt2) = ($dt2, $dt1) if $dt1 > $dt2;
# Truncate all dates to day resolution (skip this if you want
# to compare exact times)
$dt1->truncate( to => 'day' );
$dt1->truncate( to => 'day' );
$date->truncate( to => 'day' );
# Now do the comparison
if ($dt1 <= $date and $date <= $dt2) {
print '$date is between the given dates';
}
Or you can do it using DateTime::Span:
use DateTime::Span;
my $dt1 = DateTime->new(year => 2002, month => 3, day => 1);
my $dt2 = DateTime->new(year => 2002, month => 2, day => 11);
my $date = DateTime->new(year => 2002, month => 2, day => 23);
# Make sure $dt1 is less than $dt2
($dt1, $dt2) = ($dt2, $dt1) if $dt1 > $dt2;
# Make the span (use after and before if you want > and < rather
# than the >= and <= that start and end give)
my $span = DateTime::Span->from_datetimes(start => $dt1,
end => $dt2);
if ($span->contains($date)) {
print '$date is between the given dates';
}
See also Why do I need to truncate dates?
use DateTime::Duration;
my $dt1 = DateTime->new(year => 2002, month => 3, day => 1);
my $dt2 = DateTime->new(year => 2002, month => 2, day => 11);
# Make a duration object to represent the interval
$interval =
DateTime::Duration->new( days => 19, hours => 3, minutes => 12);
sub within_interval {
my ($dt1, $dt2, $interval) = @_;
# Make sure $dt1 is less than $dt2
($dt1, $dt2) = ($dt2, $dt1) if $dt1 > $dt2;
# If the older date is more recent than the newer date once we
# subtract the interval then the dates are closer than the
# interval
if ($dt2 - $interval < $dt1) {
return 1;
} else {
return 0;
}
}
print 'closer than $interval'
if within_interval($dt1, $dt2, $interval);
This is just an application of the How do I check whether two dates and times lie more or less than a given time interval apart?
Note that simply subtracting the dates and looking at the year component will not work. See How do I compare DateTime::Duration objects?
# Build a date representing their birthday
my $birthday = DateTime->new(year => 1974, month => 2, day => 11,
hour => 6, minute => 14);
# Make sure we are comparing apples to apples by truncating to days
# since you don't have to be 18 exactly by the minute, just to the day
$birthday->truncate( to => 'day' );
my $today = DateTime->today();
# Represent the range we care about
my $age_18 = DateTime::Duration->new( years => 18 );
print "You may be able to drink or vote..."
unless within_interval($birthday, $today, $age_18);
sub my_day_of_week {
my $dt = shift;
my $dow = ($dt->day_of_week + 1);
return $dow > 7 ? $dow % 7 : $dow;
}
For example:
April 1998
Mon Tue Wed Thu Fri Sat Sun
1 2 3 4 5 = week #1
6 7 8 9 10 11 12 = week #2
13 14 15 16 17 18 19 = week #3
20 21 22 23 24 25 26 = week #4
27 28 29 30 = week #5
Note that this is different from the ISO8601 definition of "weeks of the month". The ISO definition says that the first week containing a Thursday is week #1. This means that if the month starts on a Friday, then the first three days of that month (Friday through Sunday) are week #0. The DateTime module has a week_of_month() method which returns the ISO week number for the date.
# Takes as arguments:
# - The date
# - The day that we want to call the start of the week (1 is Monday, 7
# Sunday) (optional)
sub get_week_num {
my $dt = shift;
my $start_of_week = shift || 1;
# Work out what day the first of the month falls on
my $first = $dt->clone();
$first->set(day => 1);
my $wday = $first->day_of_week();
# And adjust the day to the start of the week
$wday = ($wday - $start_of_week + 7) % 7;
# Then do the calculation to work out the week
my $mday = $dt->day_of_month_0();
return int ( ($mday + $wday) / 7 ) + 1;
}
# Takes as arguments:
# - The date
# - The target day (1 is Monday, 7 Sunday)
# - The day that we want to call the start of the week (1 is Monday, 7
# Sunday) (optional)
# NOTE: This may end up in a different month...
sub get_day_in_same_week {
my $dt = shift;
my $target = shift;
my $start_of_week = shift || 1;
# Work out what day the date is within the (corrected) week
my $wday = ($dt->day_of_week() - $start_of_week + 7) % 7;
# Correct the argument day to our week
$target = ($target - $start_of_week + 7) % 7;
# Then adjust the current day
return $dt->clone()->add(days => $target - $wday);
}
# The date and target (1 is Monday, 7 Sunday)
my $dt = DateTime->new(year => 1998, month => 4, day => 3); # Friday
my $target = 6; # Saturday
# Get the day of the week for the given date
my $dow = $dt->day_of_week();
# Apply the corrections
my ($prev, $next) = ($dt->clone(), $dt->clone());
if ($dow == $target) {
$prev->add( days => -7 );
$next->add( days => 7 );
} else {
my $correction = ( $target - $dow + 7 ) % 7;
$prev->add( days => $correction - 7 );
$next->add( days => $correction );
}
# $prev is 1998-03-28, $next is 1998-04-04
Start from the end of the month and then work backwards until we reach a weekday.
my $dt = DateTime->last_day_of_month( year => 2003, month => 8 );
# day 6 is Saturday, day 7 is Sunday
while ( $dt->day_of_week >= 6 ) { $dt->subtract( days => 1 ) }
print "Payday is ", $dt->ymd, "\n";
This isn't the most efficient solution, but it's easy to understand.
# Define the meeting time and a date in the current month
my $meeting_day = 5; # (1 is Monday, 7 is Sunday)
my $meeting_week = 3;
my $dt = DateTime->new(year => 1998, month => 4, day => 4);
# Get the first of the month we care about
my $result = $dt->clone()->set( day => 1 );
# Adjust the result to the correct day of the week and adjust the
# weeks
my $dow = $result->day_of_week();
$result->add( days => ( $meeting_day - $dow + 7 ) % 7,
weeks => $meeting_week - 1 );
# See if we went to the next month
die "There is no matching date in the month"
if $dt->month() != $result->month();
# $result is now 1998-4-17
The following recipe assumes that you have 2 dates and want to loop over them. An alternate way would be to create a DateTime::Set and iterate over it.
my $start_dt = DateTime->new(year => 1998, month => 4, day => 7);
my $end_dt = DateTime->new(year => 1998, month => 7, day => 7);
my $weeks = 0;
for (my $dt = $start_dt->clone();
$dt <= $end_dt;
$dt->add(weeks => 1) ) {
$weeks++;
}
There are a few ways to do this, you can create a list of DateTime objects, create a DateTime::Set object that represents the list, or simply use the iterator from question How can I iterate through a range of dates?.
Of the three choices, the simple iteration is probably fastest, but you can not easily pass the list around. If you need to pass a list of dates around then DateTime::Set is the way to go since it doesn't generate the dates until they are needed and you can easily augment or filter the list. See What are DateTime::Set objects?
# As a Perl list
my $start_dt = DateTime->new(year => 1998, month => 4, day => 7);
my $end_dt = DateTime->new(year => 1998, month => 7, day => 7);
my @list = ();
for (my $dt = $start_dt->clone();
$dt <= $end_dt;
$dt->add(weeks => 1) )
{
push @list, $dt->clone();
}
# As a DateTime::Set. We use DateTime::Event::Recurrence to easily
# create the sets (see also DateTime::Event::ICal for more
# complicated sets)
use DateTime::Event::Recurrence;
use DateTime::Span;
my $set = DateTime::Event::Recurrence->daily(start => $start_dt,
interval => 7);
$set = $set->intersection(DateTime::Span->from_datetimes
(start => $start_dt, end => $end_dt ));
You need to use delta_days(). Twice.
my $dt1 = DateTime->new( year => 2009, month => 1, day => 1 ); my $dt2 = DateTime->new( year => 2010, month => 1, day => 1 ); my $days = $dt1->delta_days($dt2)->delta_days; # $days becomes 365
TODO
TODO