-
Notifications
You must be signed in to change notification settings - Fork 49
Sample Calculations
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 ? ] ( ( FAQ : Basic Usage ) )
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.
# 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 );
}
my $next_monday = DateTime->today->add(days => 8 - DateTime->today->day_of_week);
my $yesterday = DateTime->today->subtract(days => 1);
Note the subtle difference between this and the alternative
my $this_moment_one_day_ago = DateTime->now( time_zone => 'local' )->subtract( days => 1 );
In the former the time component is truncated to zero.
# 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 (link to a non-existent wiki in a page link - FAQ: Durations Sets Spans)
# 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
How can I calculate the difference between two times, but without counting Saturdays and Sundays or calculate business time between two times?
use Time::Business;
my $btime = Time::Business->new(
{
WORKDAYS => [ 1, 2, 3, 4, 5 ],
STARTIME => 900,
ENDTIME => 1700,
}
);
$start = time();
$end = time() + 86400;
$seconds = $btime->calctime( $start, $end );
To get the number of business time in days hours minutes you can :
my $bus_string = $btime = workTimeString($seconds)
Which will return something like "1 day 3 hours 22 minutes". Which is 1 day business time where 1 day is STARTIME-ENDTIME (in this case 8 hours).
TODO