The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

DateTimeX::Seinfeld - Calculate Seinfeld chain length

VERSION

This document describes version 0.03 of DateTimeX::Seinfeld, released May 5, 2012.

SYNOPSIS

  use DateTimeX::Seinfeld;

  my $seinfeld = DateTimeX::Seinfeld->new(
    start_date => $starting_datetime,
    increment  => { weeks => 1 },
  );

  my $chains = $seinfeld->find_chains( \@list_of_datetimes );

  say "Longest chain: $chains->{longest}{length}";
  say "First event in longest chain: $chains->{longest}{start_event}";
  say "The current chain may continue"
    if $chains->{last}{end_period}
       >= $seinfeld->period_containing( DateTime->now );

DESCRIPTION

DateTimeX::Seinfeld calculates the maximum Seinfeld chain length from a sorted list of DateTime objects.

The term "Seinfeld chain" comes from advice attributed to comedian Jerry Seinfeld. He got a large year-on-one-page calendar and marked a big red X on every day he wrote something. The chain of continuous X's gave him a sense of accomplishment and helped motivate him to write every day. (Source: http://lifehacker.com/281626/jerry-seinfelds-productivity-secret)

This module calculates the length of the longest such chain of consecutive days. However, it generalizes the concept; instead of having to do something every day, you can make it every week, or every month, or any other period that can be defined by a DateTime::Duration.

Some definitions: period is the time period during which some event must occur in order to keep the chain from breaking. More than one event may occur in a single period, but the period is only counted once.

ATTRIBUTES

start_date

This is the DateTime (or a hashref acceptable to DateTime->new) of the beginning of the first period. All events passed to find_chains must be greater than or equal to this value. (required)

increment

This is the DateTime::Duration (or a hashref acceptable to DateTime::Duration->new) giving the length of each period. (required)

skip

This is a CodeRef that allows you to skip specified periods. It is called with one argument, the DateTime at which the period begins. If the CodeRef returns a true value, any events taking place during this period are instead considered to take place in the next period. (The CodeRef must not modify the DateTime object it was given.) (optional)

For example, to skip Sundays:

  skip => sub { shift->day_of_week == 7 }

Using skip does not change the start time of the next period (as reported by period_containing, start_period, or end_period). The idea is that events will not normally occur during skipped periods (or you probably shouldn't be skipping them). This means that it is possible for an event to be less than the start time of the period containing it.

METHODS

find_chains

  $info = $seinfeld->find_chains( \@events );
  $info = $seinfeld->find_chains( \@events, $info ); # continue search

This calculates Seinfeld chains from the events in @events (an array of DateTime objects which must be sorted in ascending order). Note that you must pass an array reference, not a list.

The return value is a hashref describing the results.

Two keys describe the number of periods. total_periods is the number of periods between the start_date and $info->{last}{end_period}. marked_periods is the number of periods that contained at least one event. If marked_periods equals total_periods, then the events form a single chain of the same length.

Two keys describe the chains: last (the last chain of events found) and longest (the longest chain found). These may be the same chain (in which case the values will be references to the same hash). If there are multiple chains of the same length, longest will be the first such chain. The value of each key is a hashref describing that chain with the following keys:

start_period

The DateTime of the start of the period containg the first event of the chain.

end_period

The DateTime of the start of the period where the chain broke (i.e. the first period that didn't contain an event). If this is greater than or equal to the period containing the current date (see "period_containing"), then the chain may still be extended. Otherwise, the chain is already broken, and a future event would start a new chain.

start_event

The DateTime of the first event in the chain (this is the same object that appeared in @events, not a clone).

end_event

The DateTime of the last event in the chain (again, the same object that appeared in @events).

length

The number of periods in the chain.

num_events

The number of events in the chain. This can never be less than length, but it can be more (if multiple events occurred in one period).

Note: If @events is empty, then last and longest will not exist in the hash. Otherwise, there will always be at least one chain, even if only of length 1.

If you are monitoring an ongoing sequence of events, it would be wasteful to have to start each search from the first event. Instead, you can pass the hashref returned by the first search to find_chains, along with just the new events. The hashref you pass will be modified (the same hashref will be returned). To simplify this, it is not necessary that last and longest reference the same hash if they are the same chain. If they have the same start_period, then find_chains will link them automatically (by setting $info->{longest} = $info->{last}). When continuing a search, the start_date is ignored. Instead, the search resumes from $info->{last}{end_period}.

The only fields that you must supply in order to continue a calculation are start_period, end_period, & length in $info->{last}, and start_period & length in $info->{longest}. However, any field that you don't supply can't be expected to hold valid data afterwards.

When continuing a calculation, @events should not include any dates before $info->{last}{end_event}. If you disregard this rule, any events less than $info->{last}{end_period} are considered to have occurred in the previous period (even if they actually occurred in an even earlier period).

period_containing

  $start = $seinfeld->period_containing( $date );

Returns the DateTime at which the period containing $date (a DateTime) begins.

Note: If $date occurs during a period that is skipped, then $start will be greater than $date. Otherwise, $start is always less than or equal to $date.

DIAGNOSTICS

start_date (%s) must be before first date (%s)

You must not pass an event to find_chains that occurs before the start_date of the first period.

CONFIGURATION AND ENVIRONMENT

DateTimeX::Seinfeld requires no configuration files or environment variables.

DEPENDENCIES

DateTimeX::Seinfeld requires Moose, namespace::autoclean, MooseX::Types::DateTime, MooseX::Types::Moose, and Perl 5.10.0 or later.

INCOMPATIBILITIES

None reported.

BUGS AND LIMITATIONS

No bugs have been reported.

AUTHOR

Christopher J. Madsen <perl AT cjmweb.net>

Please report any bugs or feature requests to <bug-DateTimeX-Seinfeld AT rt.cpan.org> or through the web interface at http://rt.cpan.org/Public/Bug/Report.html?Queue=DateTimeX-Seinfeld.

You can follow or contribute to DateTimeX-Seinfeld's development at http://github.com/madsen/datetimex-seinfeld.

COPYRIGHT AND LICENSE

This software is copyright (c) 2012 by Christopher J. Madsen.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.

DISCLAIMER OF WARRANTY

BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, OR CORRECTION.

IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENSE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.