# NAME

DateTimeX::Seinfeld - Calculate Seinfeld chain length

# VERSION

This document describes version 1.000 of DateTimeX::Seinfeld, released January 11, 2014.

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

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.

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.