NAME
    Date::Set - Date set math

SYNOPSIS
        use Date::Set;

        $a = Date::Set->event( at => '20020311' );      # 20020311
        $a->event( at => [ '20020312', '20020313' ] );  # 20020311,[20020312..20020313]
        $a->exclude( at => '20020312' );                # 20020311,(20020312..20020313]

DESCRIPTION
    Date::Set is a module for date/time sets. It allows you to generate
    groups of dates, like "every wednesday", and then find all the dates
    matching that pattern.

    This module is part of the Reefknot project http://reefknot.sf.net

    It requires Date::ICal and Set::Infinite.

  Limitations

    THIS IS PRELIMINARY INFORMATION. This API may change. Everything in 'OLD
    API' section is safe to use, but might get deprecated.

    Some internal operations still use the system's 'time' functions and are
    limited by epoch issues (no support for years outside the 1970-2038
    range).

    Date::Set does not implement timezones yet. All dates are in UTC.

    Date::ICal durations are not supported yet.

  IETF RFC 2445 (iCalendar)

    If you want to understand the context of this module, look at IETF RFC
    2445 (iCalendar). It specifies the syntax for describing recurring
    events.

    If you don't need iCalendar functionality, you may try to use
    Set::Infinite directly. Most of Date::Set is syntactic sugar for
    Set::Infinite functions.

    RFC2445 can be obtained for free at http://www.ietf.org/rfc/rfc2445.txt

  ISO 8601 week

    We use the words 'weekyear' and 'year' with special meanings.

    'year' is a period beginning in january first, ending in december 31.

    'weekyear' is the year, beginning in 'first week of year' and ending in
    'last week of year'. This year break is somewhere in late-december or
    begin-january, and it is NOT equal to 'first day of january'.

    However, 'first monday of year' is 'first monday of january'. It is not
    'first monday of first week'.

    ISO8601 cannot be obtained for free, as far as I know.

  What's a Date Set

    A Date Set is a collection of Dates.

    Date::ICal module defines what a 'date' is. Set::Infinite module defines
    what a 'set' is. This module puts them together.

    This module accepts both Date::ICal objects or string dates.

    These are Date Sets:

        ''                                          # empty

        '19971024T120000Z'                          # one date

        '19971024T120000Z', '19971025T120000Z'      # two dates

  Period

    A Date Set period is an infinite set: you can't count how many single
    dates are there inside the set, because it is 'continuous':

        '19971024T120000Z' ... '19971025T120000Z'   # all dates between days 24 and 25

        '19971024T120000Z' ... 'infinity'           # all dates after day 24

    A Date Set can have more date periods:

        '19971024T120000Z' ... '19971025T120000Z',  # all dates between days 24
        '19971124T120000Z' ... '19971125T120000Z'   # and 25, in october and in november

  Recurrence

    Sometimes a Date::Set have an infinity number of periods. This is what
    happen when you have a 'recurrence'.

    A recurrence is created by a 'recurrence rule':

        $recurr = Date::Set->event( rule = 'FREQ=YEARLY' );   # all possible years

    An unbounded recurrence like this cannot be printed. It would take an
    infinitely long roll of paper.

        print $recurr;  # "Too Complex"  

    You can limit a recurrence into a more useful period of time:

        $a->event( rule => 'FREQ=YEARLY;INTERVAL=2' );
        $a->during( start => $today, end => $year_2020 );

    The program waits until you ask for a particular recurrence before
    calculating it. This is implemented by module Set::Infinite, and it is
    based on 'functional programming'. If you are interested on how this
    works, take a look at Set::Infinite.

  Encapsulation

    Object-oriented programmers are told not to modify an object's data
    directly, and to use the object's methods instead.

    For most objects you don't see any difference, but for Date::Set
    objects, changing the internal data might break your program:

    - there are many internal formats/states for Date::Set objects, that are
    translated by the API at run-time (this behaviour is inherited from
    Set::Infinite). You must be sure what your object state will be. In
    other words, you might be asking for data that does not exist yet.

    - due to optimizations, modifying an object's internal data might break
    some function's results, since you might get a pointer into the
    memoization cache. In other words, two different objects might be
    sharing the same data.

  Open and closed intervals

    If we used integer arithmetic only, then the interval

        '20010101T000000' < date < '20020501T000000' 

    could be written [ '20010101T000001' .. '20020430T235959' ].

    This method doesn't work well for real numbers, so we use the 'open' and
    'closed' interval notation:

    A closed interval is an interval which includes its limit points. It is
    written with square brackets.

        [ '20010101' .. '20020501' ]    # '20010101' <= date <= '20020501'

    If you remove '20020501' from the interval, you get a half-open
    interval. The open side is written with parenthesis.

        [ '20010101' .. '20020501' )    # '20010101' <= date < '20020501'

    If you remove '20010101' and '20020501' from the interval, you get an
    open interval.

        ( '20010101' .. '20020501' )    # '20010101' < date < '20020501'

"NEW" API
SUBROUTINE METHODS
    These methods perform everything as side-effects to the object's data
    structures. They return the object itself, modified.

  event HASH

        $a->event ( rule => $rule );
        $a->event ( at => $date );
        $a->event ( start => $start, end => $end );

        $a = Date::Set->event ( at => $date );    # constructor

    Timeline diagram to explain 'event' effect
         parameter contents:

            $a = .........[**************]...................   # period
            $b = ................[****************]..........   # period
            $c = ............................[***********]...   # period

         $a->event( at => $b )

            $a = .........[***********************]..........   # bigger period

         $a->event( at => $c )

            $a = .........[**************]...[***********]...   # two periods

    Inserts events in a Date::Set. Use 'event' to create or enlarge a Set.

    Calling 'event' without parameters returns 'forever', that is: (-Inf ..
    Inf)

    rule
        adds the dates from a recurrence rule, as defined in RFC2445.

        This is a simple list of dates. These dates are not 'periods', they
        have no duration.

            $a->event( rule => 'FREQ=YEARLY;INTERVAL=2;COUNT=10;BYMONTH=1,2,3' );

        Optimization tip: rules that have start/end dates might execute
        faster.

        A rule might have a DTSTART:

            $a->event( rule => 'DTSTART=19990101Z;FREQ=YEARLY;INTERVAL=2;COUNT=10;BYMONTH=1,2,3' );

            $a->event( dtstart => '19990101Z', rule => 'FREQ=YEARLY;INTERVAL=2;COUNT=10;BYMONTH=1,2,3' );

    at  adds more dates or periods to the set

            $a->event( at => '19971024T120000Z' );      # one event

            $a->event( at => [ '19971024T120000Z', '19971025T120000Z' ] );  # a period

            $a->event( at => $set );                    # one Date::Set

            $a->event( at => [ $set1, $set2 ] );        # two Date::Sets

            $a->event( at => [ [ '19971024T120000Z', '19971025T120000Z' ] ] );  # one period

            $a->event( at => [ [ '19971024T120000Z', '19971025T120000Z' ],
                               [ '19971027T120000Z', '19971028T120000Z' ] ] );  # two periods

        If 'rule' is used together with 'at' it will add the recurring
        events that are inside that period only. The period is a 'boundary':

            $a->event( rule  => 'FREQ=YEARLY',
                       at    => [ [ '20010101', '20030101' ] ] );    # 2001, 2002, 2003

    start, end
        add a time period to the set:

            $a->event( start => '19971024T120000Z' );  # one period that goes forever until +infinity

            $a->event( end   => '19971025T120000Z' );  # one period that existed forever since -infinity

            $a->event( start => '19971024T120000Z', 
                       end   => '19971025T120000Z' );  # one period

        if 'at' is used together with 'start'/'end' it will add the periods
        that are inside that boundaries only:

            $a->event( at    => [ [ '20010101', '20090101' ] ],
                       end   => '20020101' );                      # period starting 2001, ending 2002

            $a->event( at    => [ [ '20010101', '20090101' ] ],
                       start => '20070101' );                      # period starting 2007, ending 2009

        if 'rule' is used together with 'start'/'end' it will add the
        recurring events that are inside that boundaries only:

            $a->event( rule  => 'FREQ=YEARLY',
                       start => '20010101', 
                       end   => '20030101' );                       # 2001, 2002, 2003

        you can mix 'at' and 'start'/'end' boundary effects to 'rule':

            $a->event( rule  => 'FREQ=YEARLY',
                       at    => [ [ '20010101', '20090101' ] ],
                       end   => '20020101' );                       # 2001, 2002

    Timeline diagram to explain 'event' effect with bounded recurrence rule
         parameter contents:

            $a = .........[**************]...................   # period
            $b = ................[****************]..........   # period
            $r = ...*...*...*...*...*...*...*...*...*...*...*   # unbounded recurrence rule

         $a->event( rule => $r, at => $b )

            $a = .........[**************]..*...*............   # period and two occurrences

  exclude HASH

  during HASH

        $a->exclude ( at => $date );
        $a->exclude ( rule => $rule );
        $a->exclude ( start => $start, end => $end );

        $a->during ( at => $date );
        $a->during ( rule => $rule );
        $a->during ( start => $start, end => $end );

    Timeline diagram to explain 'exclude' and 'during' effect
         parameter contents:

            $a = .........[**************]...................
            $b = ................[****************]..........

         $a->exclude( at => $b )

            $a = .........[******)...........................

         $a->during( at => $b )

            $a = ................[*******]...................

    Calling 'exclude' or 'during' without parameters returns 'never', that
    is: () the empty set.

    'exclude' excludes events from a Date::Set

    'during' put start/end boundaries on a Date::Set

    In other words: 'exclude' cuts out everything that MATCH it, and
    'during' cuts out everything that DON'T match it.

    Use 'exclude' and 'during' to limit a Set size. You can use 'exclude'
    and 'during' to put boundaries on an infinitely recurring Set.

    at
            $a->exclude( at => '19971024T120000Z' );

            $a->exclude( at => $set );

            $a->during( at => [ '19971024T120000Z', '19971025T120000Z' ] );  # a period

            $a->during( at => [ $set1, $set2 ] );

        'exclude at' deletes these dates from the set

        'during at' limits the set to these boundaries only.

    rule
        a recurrence rule as defined in RFC2445

            $a->exclude( rule => 'FREQ=YEARLY;INTERVAL=2;COUNT=10;BYMONTH=1,2,3' );

            $a->during( rule => $rule1 );

        'exclude rule' deletes from the set all the dates defined by the
        rule. The RFC 2445 states that the DTSTART date will not be excluded
        by a rule, so it isn't.

        'during rule' limits the set to the dates defined by the rule. If
        the set does not contain any of the dates, it gets empty

    start, end
        a time period

            $a->exclude( start => '19971024T120000Z', end => '19971025T120000Z' );

            $a->during( start => '19971024T120000Z' );  # limit to forever from start until +infinity

            $a->exclude( end => '19971025T120000Z' );   # delete everything since -infinity until end

        'exclude start' deletes from the set all dates including 'start' and
        after it

        'exclude end' deletes from the set all dates before and including
        'end'

        'exclude start,end' deletes from the set all dates between and
        including 'start' and 'end'

        'during start' limits the set to the dates including 'start' and
        after it. If there are no dates after 'start', the set gets empty.

        'during end' limits the set to the dates before and including 'end'.
        If there are no dates before 'end', the set gets empty.

        'during start,end' limits the set to the dates between and including
        'start' and 'end'. If there are no dates in that period, the set
        gets empty.

  wkst STRING

        Date::Set::wkst('SU');

    Sets/reads the module's global "week start day".

    The parameter must be one of 'MO' (default), 'TU', 'WE', 'TH', 'FR',
    'SA', or 'SU'.

    The effect if to change the 'week' boundaries. It also changes when the
    first week of year begins, affecting 'weekyear' operations.

    It has no effect on 'weekday' operations, like 'first tuesday of month'
    or 'last friday of year'.

    Return value is current wkst value.

FUNCTION METHODS
    These methods perform operations and return the changed data. They
    return a new object. The original object is never modified. There are no
    side-effects.

  fevent, fexclude, fduring HASH

        $b = $a->fevent ( at => $date,
                          date_set => $set,
                          rule => $rule,
                          start => $start, end => $end );

    Functions equivalent to event() , exclude() , and during() subroutines.

    These functions return a new Date::Set. They DON'T MODIFY the object, as
    the subroutines event/exclude/during do.

        $b = $a->fevent ( at => $date );

    is the same as:

        $b = $a->copy;
        $b->event ( at => $date );

"OLD" API
  period

    Deprecated. Replaced by 'event'.

        period( time => [time1, time2] )

        or

        period( start => Date::ICal,  end => Date::ICal )

    This routine is a constructor. Returns a time period bounded by the
    dates specified when called in a scalar context.

  dtstart

    Sets DTSTART time.

        dtstart( start => time1 )

    Returns set intersection [time1 .. Inf)

    time1 is added to the set.

    'dtstart' puts a limit on when the event starts. If the event already
    starts AFTER dtstart, it will not change.

    This is a function. It doesn't change the object.

  dtend

    Deprecated. Replaced by 'event/during'.

        dtend( end => time1 )

    Returns set intersection (Inf .. time1]

    'dtend' puts a limit on when the event finishes. If the event already
    finish BEFORE dtend, it will not change.

  duration

        duration( unit => 'months', duration => 10 )

    All intervals are modified to 'duration'.

    'unit' parameter can be years, months, days, weeks, hours, minutes, or
    seconds.

  recur_by_rule

    Deprecated. Replaced by 'event'.

  exclude_by_rule

    Deprecated. Replaced by 'exclude'.

  recur_by_date

    Deprecated. Replaced by 'event'.

  exclude_by_date

    Deprecated. Replaced by 'exclude'.

  occurrences

    Deprecated. Replaced by 'during'.

  next_year, next_month, next_week, next_day, next_hour, next_minute, next_weekyear ($date_set)

  this_year, this_month, this_week, this_day, this_hour, this_minute, this_weekyear ($date_set)

  prev_year, prev_month, prev_week, prev_day, prev_hour, prev_minute, prev_weekyear ($date_set)

        $next  = next_month( $date_set ) 
        $whole = this_year ( $date_set )    # [20010101..20020101)

    Returns the next/prev/this unit of time for a given period.

    It answers questions like, "when is next month for the given period?",
    "which years are covered by this period?"

  as_years, as_months, as_weeks, as_days, as_hours, as_minutes, as_weekyears ($date_set)

        as_months( date-set ) 
        as_weeks ( date-set ) 

    Returns the given period in a 'unit of time' form.

    It answers questions like, "which months we have in this period?",
    "which years are covered by this period?"

    See also previous note on 'weekyear' in 'About ISO 8601 week'.

INHERITED 'SET LOGIC' FUNCTIONS
    These methods are inherited from Set::Infinite.

  intersects

        $logic = $a->intersects($b);

  contains

        $logic = $a->contains($b);

  is_null

        $logic = $a->is_null;

  is_too_complex

    Sometimes a set might be too complex to print. It will happen when you
    ask for 'every year' (a recurrence) but don't specify a starting and
    ending date.

        $recurr = Date::Set->event( rule = 'FREQ=YEARLY' );  
        print $recurr;                   # "Too Complex" 
        print $recurr->is_too_complex;   # "1"
        $recurr->during( start => '20020101', end => '20050101' );
        print $recurr;                   # "20020101,20030101,20040101,20050101"
        print $recurr->is_too_complex;   # "0"

INHERITED 'SET' FUNCTIONS
    These methods are inherited from Set::Infinite.

  union

        $i = $a->union($b);     

  intersection

        $i = $a->intersection($b);

  complement

        $i = $a->complement;

        $i = $a->complement($b);

INHERITED 'SPECIAL' FUNCTIONS
    These methods are inherited from Set::Infinite.

  min, max

    Returns the 'begin' or 'end' of a set. 'date_ical' function returns the
    actual Date::ICal object they point to.

        $date1 = $set1->min->date_ical;    # the first Date::ICal object in the set

        $date2 = $set1->max->date_ical;    # the last Date::ICal object in the set

    Warning: modifying an object data might break your program.

  list

    Splits a set in simpler, 1-period sets.

        print $set1;              #  [20010101..20020101],[20030101..20040101]
        @subset = $set1->list;
        print $subset[0];         #  [20010101..20020101]
        print $subset[1];         #  [20030101..20040101]

        print $subset[0]->min->date_ical;    # 20010101
        print $subset[0]->max->date_ical;    # 20020101

    This shortcut might work for simple sets, but you should avoid it:

        print $set1->{list}->[0]->min->date_ical;    # 20010101 - DON'T DO THIS

    Complex sets might take a long time (and a lot memory) to 'list'.

    Unbounded recurrences should not be list'ed, because they generate
    infinite or even invalid (empty) lists. If you are not sure what type of
    set you have, you can test it with is_too_complex() function.

  size

  offset

  select

  quantize

  iterate

  new

    See Set::Infinite documentation.

  copy

        $b = $a->copy;

    Returns a copy of the object.

    This is useful if you want to use one of the subroutine methods, without
    changing the original object.

COOKBOOK
  Create a new, empty set

        $a = Date::Set->new();

        $a = Date::Set->event( at => [] );

        $a = Date::Set->during();

  Exclude a date from a set

    TODO

  Adding a whole year

        $year = Date::Set->event( at => '20020101' );
        $a->event( at => (  $year->as_years ) );

    This is not the same thing, since it includes a bit of next year:

        $a->event( start => '20020101', end => '20030101' );

    This is not the same thing, since it misses a bit of this year (a
    fraction of last second):

        $a->event( start => '20020101', end => '20021231T235959' );

  Using 'during' and 'exclude' to put boundaries on a recurrence

        $a->event( rule => 'FREQ=YEARLY;INTERVAL=2' );
        $a->during( start => $today, end => $year_2020 );

        $a->event( rule => 'FREQ=YEARLY;INTERVAL=2' );
        $a->exclude( end => $today);
        $a->exclude( start => $year_2020 );

  Application of this/next/prev

    TODO

API INSTABILITIES
    These are more likely to change:

        - Some method and parameter names may change if we can find better names.

        - support to next/prev/this and as_xxx MAY be deleted in future versions
          if they don't prove to be useful.

        - 'duration' and 'period' methods MAY change in future versions, to generate open-ended sets.
        Possibly by using parameter names 'after' and 'before' instead of 'start' and 'end' 

        - accepting timezones

        - use more of Date::ICal methods for time calculations

    Some behaviour is yet undefined:

        - what happens when asked for '31st day of month', when month has less
        than 31 days?

        - does it work when using fractional seconds?

    These might change, but they are not likely:

        - Accepting string dates MAY be deleted in future versions. 

AUTHOR
    Flavio Soibelmann Glock <fglock@pucrs.br> with the Reefknot team.

    Jesse <>, srl <>, and Mike Heins <> contribute on coding style,
    documentation, and testing.

