NAME
Data::Range::Compare::Cookbook::Recipe_DateTime - DateTime Range HOWTO
SYNOPSIS
use DateTime;
use Data::Range::Compare;
my @vpn_a=
(
# start
DateTime->new(qw(year 2010 month 01 day 02 hour 10 minute 01 second 59))
,
# end
DateTime->new(qw(year 2010 month 01 day 02 hour 10 minute 05 second 47))
,
# start
DateTime->new(qw(year 2010 month 05 day 02 hour 07 minute 41 second 32))
,
# end
DateTime->new(qw(year 2010 month 05 day 02 hour 08 minute 00 second 16))
);
my @vpn_b= (
# start
DateTime->new(qw(year 2010 month 05 day 02 hour 07 minute 41 second 32))
,
# end
DateTime->new(qw(year 2010 month 05 day 02 hour 07 minute 58 second 13))
,
# start
DateTime->new(qw(year 2010 month 01 day 02 hour 10 minute 03 second 59))
,
# end
DateTime->new(qw(year 2010 month 01 day 02 hour 10 minute 04 second 37))
);
my @vpn_c=
(
DateTime->new(qw(year 2010 month 01 day 02 hour 10 minute 03 second 59))
,
DateTime->new(qw(year 2010 month 01 day 02 hour 10 minute 04 second 37))
,
DateTime->new(qw(year 2010 month 05 day 02 hour 07 minute 41 second 32))
,
DateTime->new(qw(year 2010 month 05 day 02 hour 07 minute 58 second 13))
,
DateTime->new(qw(year 2010 month 05 day 02 hour 07 minute 59 second 07))
,
DateTime->new(qw(year 2010 month 05 day 02 hour 08 minute 00 second 16))
,
DateTime->new(qw(year 2010 month 06 day 18 hour 10 minute 58 second 21))
,
DateTime->new(qw(year 2010 month 06 day 18 hour 22 minute 06 second 55))
);
my %helper;
# create a simple function to handle comparing dates
sub cmp_values { DateTime->compare( $_[0],$_[1] ) }
# Now set cmp_values in %helper
$helper{cmp_values}=\&cmp_values;
# create a simple function to calculate the next second
sub add_one { $_[0]->clone->add(seconds=>1) }
# Now set add_one in %helper
$helper{add_one}=\&add_one;
# create a simple function to calculate the previous second
sub sub_one { $_[0]->clone->subtract(seconds=>1) }
# quick and dirty formatting tool
sub format_range ($) {
my $s=$_[0];
join ' - '
,$s->range_start->strftime('%F %T')
,$s->range_end->strftime('%F %T')
}
# load the outage timestamps and output a little info
my @parsed;
my @vpn_name=qw(vpn_a vpn_b vpn_c);
foreach my $outages (\@vpn_a,\@vpn_b,\@vpn_c) {
my $id=shift @vpn_name;
my $row=[];
push @parsed,$row;
print "\nVPN_ID $id\n";
while(my ($dt_start,$dt_end)=splice(@$outages,0,2)) {
my $range=Data::Range::Compare->new(\%helper,$dt_start,$dt_end);
print format_range($range),"\n";
push @$row,$range;
}
}
# now compare our outages
my $sub=Data::Range::Compare->range_compare(\%helper,\@parsed);
while(my ($vpn_a,$vpn_b,$vpn_c)=$sub->()) {
next unless
!$vpn_a->missing
&&
!$vpn_b->missing
&&
!$vpn_c->missing;
my $common=Data::Range::Compare->get_common_range(
\%helper
,[$vpn_a,$vpn_c,$vpn_b]
);
my $common=Data::Range::Compare->get_common_range(
\%helper
,[$vpn_a,$vpn_c,$vpn_b]
);
print "\nCommon outage range: "
,format_range($common)
,"\n"
,"Total Downtime: Months: $outage->{months}"
," Days: $outage->{days} Minutes: $outage->{minutes}"
," Seconds: $outage->{seconds}\n"
,'vpn_a '
,format_range($vpn_a)
,' '
,($vpn_a->missing ? 'up' : 'down')
,"\n"
,'vpn_b '
,format_range($vpn_b)
,' '
,($vpn_b->missing ? 'up' : 'down')
,"\n"
,'vpn_c '
,format_range($vpn_c)
,' '
,($vpn_c->missing ? 'up' : 'down')
,"\n";
}
Output
VPN_ID vpn_a
2010-01-02 10:01:59 - 2010-01-02 10:05:47
2010-05-02 07:41:32 - 2010-05-02 08:00:16
VPN_ID vpn_b
2010-05-02 07:41:32 - 2010-05-02 07:58:13
2010-01-02 10:03:59 - 2010-01-02 10:04:37
VPN_ID vpn_c
2010-01-02 10:03:59 - 2010-01-02 10:04:37
2010-05-02 07:41:32 - 2010-05-02 07:58:13
2010-05-02 07:59:07 - 2010-05-02 08:00:16
2010-06-18 10:58:21 - 2010-06-18 22:06:55
Common outage range: 2010-01-02 10:03:59 - 2010-01-02 10:04:37
Total Downtime: Months: 0 Days: 0 Minutes: 0 Seconds: 38
vpn_a 2010-01-02 10:01:59 - 2010-01-02 10:05:47 down
vpn_b 2010-01-02 10:03:59 - 2010-01-02 10:04:37 down
vpn_c 2010-01-02 10:03:59 - 2010-01-02 10:04:37 down
Common outage range: 2010-05-02 07:41:32 - 2010-05-02 07:58:13
Total Downtime: Months: 0 Days: 0 Minutes: 16 Seconds: 41
vpn_a 2010-05-02 07:41:32 - 2010-05-02 08:00:16 down
vpn_b 2010-05-02 07:41:32 - 2010-05-02 07:58:13 down
vpn_c 2010-05-02 07:41:32 - 2010-05-02 07:58:13 down
DESCRIPTION
This Recipe provides an example based on DateTime object manipulation.
Lets say we have a bank with 3 vpns to its data center. When all of the vpns are off line the bank itself is unable to process customer transactions. This example shows how to calculate the exact time the bank is completely disconnected from the data center and when it comes back online.
Ingredients
This example relies on DateTime.
Creating %helper
Data::Range::Compare relies on %helper to do its internal work. This section explains how to populate %helper to work with DateTime.
Create a hash %helper.
Example
my %helper
Creating the "cmp_values" callback
"cmp_values" represents comparing 2 objects in the standard cmp or <=> way; lucky for us DateTime provides a comparative interface.
Example
# create a simple function to handle comparing dates sub cmp_values { DateTime->compare( $_[0],$_[1] ) } # Now set cmp_values in %helper $helper{cmp_values}=\&cmp_values
Creating the "add_one" callback
"add_one" represents the next value: With that in mind we have to decide if our next increment is just adding 1 or moving to the next Year, Month, Day, Hour, Min, Sec etc.
In our example we will be using the concept of next second. This makes "add_one" a fairly simple function to implement.
Example
# create a simple function to calculate the next second sub add_one { $_[0]->clone->add(seconds=>1) } # Now set add_one in %helper $helper{add_one}=\&add_one
Creating the "sub_one" callback
"add_one" represents the previous value. With that in mind we have to decide if our next decrement is just removing 1 or moving to the previous Year, Month, Day, Hour, Min, Sec etc.
In our example we will be using the concept of previous second. This makes "sub_one" a fairly simple function to implement.
Example
# create a simple function to calculate the previous second sub sub_one { $_[0]->clone->subtract(seconds=>1) } # Now set sub_one in our %helper $helper{sub_one}=\&sub_one
Creating our Objects to Compare
The constructor call to "new Data::Range::Compare(\%helper,$start,$end)" represents the creation of a new instance. Although this may be an over generalized concept, what $start and $end can contain is not.
In this example we are creating our Date::Range::Compare Objects with a $start and $end value made from 2 DateTime objects. Prior examples used letters of the alphabet, and the default values are integers, but in this case we are doing something special; we are creating an object that represents a range based on other objects: DateTime in particular.
Lets examine the loop that pulls the vpn arrays apart.
my @parsed;
Not to be overlooked, but this list will contain all of the data we intend to compare. Once a range has been created in our loop we push our range onto the selective anonymous hash.
foreach my $outages (\@vpn_a,\@vpn_b,\@vpn_c) { my $row=[]; push @parsed,$row;
Since our input data sets are just flat lists, we just need to pull out our ranges in pairs.
while(my ($dt_start,$dt_end)=splice(@$outages,0,2)) { my $range=Data::Range::Compare->new(\%helper,$dt_start,$dt_end); push @$row,$range;
Calculating our outages
Now we really get down to the details of the outage. So lets examine this code a little further.
my $sub=Data::Range::Compare->range_compare(\%helper,\@parsed);
This is where the process for comparison starts. We created the object that will compare and iterate through our @parsed ranges.
Ok, that said let's take a look at the logic of our while loop.
while(my ($vpn_a,$vpn_b,$vpn_c)=$sub->()) {
If you look back at the foreach call we made, you will notice our iteration through each set of outage related data. Each anonymous array @parsed represents in order: @vpn_a @vpn_b @vpn_c.
Our call to $sub->() is only slightly different than the data in @parsed. The variables in $vpn_a,$vpn_b,$vpn_c may not even exist in @parsed and that's because they represent ranges not found in a given list data source which brings us to the ->missing checks.
next unless !$vpn_a->missing && !$vpn_b->missing && !$vpn_c->missing;
This is a some what awkward way of looking at the data, but in reality it makes sense. We want to skip rows when some vpns are online. The reason for this is simple: when ranges are missing they represent that vpn being online.
my $common=Data::Range::Compare->get_common_range( \%helper ,[$vpn_a,$vpn_c,$vpn_b] );
We are using Data::Range::Compare->get_common_range to calculate the smallest common range given all of our current ranges. We only care about the shared down time from every single range.
my $outage=$common->range_end->subtract_datetime( $common->range_start ); This is a feature of DateTime itself and we used it to fetch the duration of the outage.
The final operation in our loop simply outputs all the data we wanted to see.
print "\nCommon outage range: " ,format_range($common) ,"\n" ,"Total Downtime: Months: $outage->{months}" ," Days: $outage->{days} Minutes: $outage->{minutes}" ," Seconds: $outage->{seconds}\n" ,'vpn_a ' ,format_range($vpn_a) ,' ' ,($vpn_a->missing ? 'up' : 'down') ,"\n" ,'vpn_b ' ,format_range($vpn_b) ,' ' ,($vpn_b->missing ? 'up' : 'down') ,"\n" ,'vpn_c ' ,format_range($vpn_c) ,' ' ,($vpn_c->missing ? 'up' : 'down') ,"\n";
AUTHOR
Michael Shipper
COPYRIGHT
Copyright 2010 Michael Shipper. All rights reserved.
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
SEE ALSO
Data::Range::Compare::Cookbook