NAME

Data::Range::Compare::Cookbook::Recipe_DateTime - DateTime Range HOWTO

SYNOPSIS

  use DateTime;
  use Data::Range::Compare;
  
  # these lists of strings represent our vpn outages
  my @vpn_a=
    # outage start         Outage End
  (
    '2010-01-02 10:01:59',  '2010-01-02 10:05:47'
    ,'2010-05-02 07:41:32', '2010-05-02 08:00:16'
  );
  my @vpn_b=
    # outage start          Outage End
  (
    '2010-01-02 10:03:59',  '2010-01-02 10:04:37'
    ,'2010-05-02 07:41:32', '2010-05-02 07:58:13'
  );
  my @vpn_c=
    # outage start          Outage End
  (
    '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'
  );
  
  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) }
  
  # parse the outage timestamp 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;
    print "\nVPN_ID $id\n";
    my $row=[];
    push @parsed,$row;
    while(my ($start,$end)=splice(@$outages,0,2)) {
      print $start,"\t",$end,"\n";
      my %args_start=(time_zone => "EST");
      my %args_end=(time_zone => "EST");
      @args_start{qw(year month day hour minute second)} =
	($start=~ /(\d+)/g);
      @args_end{qw(year month day hour minute second)} =
	($end=~ /(\d+)/g);
      my $dt_start=DateTime->new(%args_start);
      my $dt_end=DateTime->new(%args_end);
      my $range=Data::Range::Compare->new(\%helper,$dt_start,$dt_end);
      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-01-02 10:03:59     2010-01-02 10:04:37
2010-05-02 07:41:32     2010-05-02 07:58:13

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. The following 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. So this section explains how populate %helper to work with DateTime.

Create a hash %helper

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 do 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 out "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 do 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 out "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;

  • while(my ($start,$end)=splice(@$outages,0,2)) {

    This may be some what confusing, but ranges are just something with a start and end value. Since our input data sets are just flat lists, we just need to pull out our ranges in pairs.

    while(my ($start,$end)=splice(@$outages,0,2)) {
      print $start,"\t",$end,"\n";
      my %args_start=(time_zone => "EST");
      my %args_end=(time_zone => "EST");
      @args_start{qw(year month day hour minute second)}=($start=~ /(\d+)/g);
      @args_end{qw(year month day hour minute second)}=($end=~ /(\d+)/g);
      my $dt_start=DateTime->new(%args_start);
      my $dt_end=DateTime->new(%args_end);
      my $range=Data::Range::Compare->new(\%helper,$dt_start,$dt_end);
      push @$row,$range;
    }

    Slightly More complex to look at.

    my %args_start=(time_zone => "EST");
    my %args_end=(time_zone => "EST");

    These hashes will be used, once populated as our arguments to the DateTime constructor call.

    @args_start{qw(year month day hour minute second)}=($start=~ /(\d+)/g);
    @args_end{qw(year month day hour minute second)}=($end=~ /(\d+)/g);

    This is a shortcut using the syntax of perl, we are simply setting keys in %args_start and %args_end to the values extracted from our regex.

    Constructor call to DateTime

    my $dt_start=DateTime->new(%args_start);
    my $dt_end=DateTime->new(%args_end);

    As mentioned above we pass %args_start and %args_end to DateTime, creating our range start and end objects. From here we call the Data::Range::Compare constructor to create our instance and add it to the list of lists for comparison.

    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 lets 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. Witch 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 shard 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