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

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.