Sample Calculations

Sherwin Daganato edited this page Oct 11, 2017 · 2 revisions

How do I check whether a given date lies within a certain range of days?

``````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 ) )
``````

How do I check whether two dates and times lie more or less than a given time interval apart?

``````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 );
``````

How do I verify whether someone has a certain age?

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 )
;
``````

How can I calculate the day of the week if I want to consider Sunday the first day of the week?

``````sub my_day_of_week {
my \$dt = shift;

my \$dow = ( \$dt->day_of_week + 1 );
return \$dow > 7 ? \$dow % 7 : \$dow;
}
``````

How do I calculate the number of the week of month the given date lies in?

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;
}
``````

How do I calculate the date of the Wednesday of the same week as the current date?

``````# 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 );
}
``````

How do I find next Monday's date?

``````my \$next_monday = DateTime->today->add(days => 8 - DateTime->today->day_of_week);
``````

How do I find yesterday's date?

``````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.

How do I calculate the last and the next Saturday for any given date?

``````# 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
``````

How can I calculate the last business day (payday!) of a month?

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.

How can I find what day the third Friday of a month is on?

``````# 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
``````

How can I iterate through a range of dates?

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++;
}
``````

How can I create a list of dates in a certain range?

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 )
);
``````

How can I calculate the difference in days between dates?

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).

How can I compare two DateTime::Duration objects?

TODO

Clone this wiki locally
You can’t perform that action at this time.
Press h to open a hovercard with more details.