The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

OP::Recur - Object class to represent recurring points in time

SYNOPSIS

  use OP;

  my $recur = OP::Recur->new();

  #
  # every:
  #
  # Every X UNITS ie "every 3 minutes"
  #
  $recur->every(3, MIN); # Time::Consts constant
  $recur->every(180);    # Implicit UNITS is Seconds (1)

  #
  # at, exceptAt:
  #
  # At [Year]/[Month]/[Day] [Hour]:[Minute]:Seconds
  #
  # Except At [Year]/[Month]/[Day] [Hour]:[Minute]:Seconds
  #
  $recur->at(*YYYY,*MM,*DD,*hh,*mm,ss);
  $recur->exceptAt(*YYYY,*MM,*DD,*hh,*mm,ss);

  #
  # on, exceptOn:
  #
  # On Nth [Weekday] [in Month] [of Year]
  #
  # Except On Nth [Weekday] [in Month] [of Year]
  #
  $recur->on(Nth,*WDAY,*MM,*YYYY);
  $recur->exceptOn(Nth,*WDAY,*MM,*YYYY);

  #
  # each, exceptEach:
  #
  # Each [Weekday] [in Month] [of Year]
  #
  # Except Each [Weekday] [in Month] [of Year]
  #
  $recur->each(*WDAY,*MM,*YYYY);
  $recur->exceptEach(*WDAY,*MM,*YYYY);

  #
  # C<loop> and C<coloop> will execute code at the specified
  # intervals, in blocking or non-blocking fashion. See examples.
  #

The methods every, at/exceptAt, on/exceptOn, and each/exceptEach may be called as many times as needed, overlaying rules to create complex recurrence loops. If called without any arguments, these methods return an OP::Array instance containing the specific Recur:: helper instances which were added.

INSTANCE GETTERS

INSTANCE SETTERS

  • $recur->every(X,[UNITS]);

    The every method adds a new OP::Recur::Every instance, which represents a recurring fixed time interval.

    UNITS may be any number where 1 is equal to 1 second. The constants available in the Time::Consts module work well for this. Sub-second or floating point values for either argument are acceptable.

    Omitting a UNITS argument implies Seconds (1) as a base unit.

      #
      # Fixed interval, eg every 3 minutes:
      #
      my $recur = every(3,MIN); # See Time::Consts
    
      #
      # Another example; every 500 milliseconds:
      #
      $recur->every(500,MSEC); # See Time::Consts
  • $recur->at([YYYY],[MM],[DD],[hh],[mm],ss)

  • $recur->exceptAt([YYYY],[MM],[DD],[hh],[mm],ss)

    The at method adds a new OP::Recur::At instance, which represents an interval bound to calendar time.

    The exceptAt method follows the same pattern, but is used to declare excluded times rather than included ones.

    The magic constant "LAST" may be used for DD to indicate the final day in a given month. LAST only works this way for at and exceptAt rules.

    A field may be wildcarded by providing undef in its place, but note that at consumes args in a non-traditional reverse order, and also acts like a multi-method where the number of arguments determines the most significant base value (examples below).

    The at constructor supports 1-6 arguments, always ordered from most significant (ie Year) to least significant (Seconds). The different possible modes of usage are shown here:

      #
      # at(YYYY,MM,DD,hh,mm,ss)
      #
      # Describes a one-time occurrence,
      # eg December 21 2012 at midnight:
      #
      $recur->at(2012,12,21,00,00,00);
    
      #
      # at(MM,DD,hh,mm,ss)
      #
      # Describes a yearly recurrence,
      # eg Every day in January at midnight:
      #
      $recur->at(01,undef,00,00,00);
    
      #
      # at(DD,hh,mm,ss)
      #
      # Describes a monthly recurrence,
      # eg Last day of each month at midnight:
      #
      $recur->at(LAST,00,00,00);
    
      #
      # Describes a daily recurrence,
      # at(hh,mm,ss)
      #
      # eg Every day at noon:
      #
      $recur->at(12,00,00);
    
      #
      # at(mm,ss)
      #
      # Describes an hourly recurrence,
      # eg Every hour at :45 after:
      #
      $recur->at(45,00);
    
      #
      # at(ss)
      #
      # Describes an every-minute recurrence,
      # eg Every minute at 30 seconds:
      #
      $recur->at(30);

    Hopefully, the above examples illustrate a usage pattern for at.

  • $recur->on(Nth,WDAY,*MM,*YYYY)

  • $recur->exceptOn(Nth,WDAY,*MM,*YYYY)

    The on method adds a new OP::Recur::On instance, which represents an ordinal weekday in an optional month/year, ie "The second Thursday [in June] [2010]". MM and YYYY are wild if undef.

    WDAY is a day 1-7 where monday = 1, and MM is a month between 1 and 12. YYYY is the actual year, such as "2009". The constants available in OP::Enum::DaysOfWeek and OP::Enum::WeeksOfMonth are suitable for WDAY and MM, respectively.

    The exceptOn method follows the same pattern, but is used to declare excluded times rather than included ones.

      use OP::Enum::DaysOfWeek;
      use OP::Enum::WeeksOfMonth;
    
      #
      # Recur on the 1st monday of january, 2010
      #
      $recur->on(1,MON,JAN,2010);
    
      #
      # Recur every first monday in january
      #
      $recur->on(1,MON,JAN);
    
      #
      # Recur every first monday
      #
      $recur->on(1,MON);
    
      #
      # Recur every first day
      #
      $recur->on(1);
  • $recur->each(*WDAY,*MM,*YYYY)

  • $recur->exceptEach(*WDAY,*MM,*YYYY)

    each is just like on, but without ordinality.

    The each method adds a new OP::Recur::Each instance, which represents a recurring weekday, optionally within a month/year, ie "Each Thursday [in June] [2010]". WDAY, MM, and YYYY are optional args, but should be given as undef.

    WDAY is a day 1-7 where monday = 1, and MM is a month between 1 and 12. YYYY is the actual year, such as "2009". The constants available in OP::Enum::DaysOfWeek and OP::Enum::WeeksOfMonth are suitable for WDAY and MM, respectively.

    The exceptEach method follows the same pattern, but is used to declare excluded times rather than included ones.

      use OP::Enum::DaysOfWeek;
      use OP::Enum::WeeksOfMonth;
    
      #
      # Recur each monday in january 2010
      #
      $recur->on(MON,JAN,2010);
    
      #
      #
      # Recur each monday in january
      #
      $recur->on(MON,JAN);
    
      #
      # Recur each monday
      #
      $recur->on(MON);

BLOCKING LOOP

  • $recur->loop($sub), break

    Execute the received sub at the defined time interval, within a loop.

    To exit the loop from within the sub, call OP::Recur::break.

      use OP;
    
      use Time::Consts qw| :ALL |;
    
      my $recur = OP::Recur->new();
    
      #
      # Mix n match
      #
      $recur->every(5,SEC);
      $recur->every(2,SEC);
    
      $recur->loop( sub {
        my $now = shift;
    
        print "Doing something at $now...\n";
    
        break if $now > BEDTIME;
      } );

NON-BLOCKING LOOP

Non-blocking loops utilize Coro, and are compatible with POE (see notes below).

  • $recur->coloop($sub), snooze($secs), break

    coloop executes the received sub at the defined time interval, within a cooperative Coro thread. It does not wait for the loop to return; you must call snooze($secs) or Coro::cede to yield interpreter control back to the loop, and likewise from within the loop to yield control back to any waiting threads.

    snooze is like Perl's sleep, except that $secs may be a floating point value, and snooze doesn't block Coro threads. In the context of a coroutine, snooze cedes control of the interpreter back to any threads which need to do work, and they will do the same in turn. When the specified time has elapsed, the thread will stop ceding and resume work. snooze otherwise just works like a hi-res version of sleep.

    break breaks the loop, invoking Perl's last.

      use OP;
    
      use Time::Consts qw| :ALL |;
    
      my $beep = OP::Recur->new();
    
      #
      # Start beeping in thread A
      #
      $beep->every(4.2,SEC);
    
      $beep->coloop( sub {
        my $now = shift;
    
        print "Beep at $now...\n";
    
        break if $now > BEDTIME;
      } );
    
      #
      # Start booping in thread B
      #
      my $boop = OP::Recur->new();
    
      $boop->every(2.5,SEC);
    
      $boop->coloop( sub {
        my $now = shift;
    
        print "Boop at $now.. boop boop!\n";
    
        break if $now > DOOMSDAY
      } );
    
      #
      # Insert your main loop here:
      #
      while(1) {
        snooze(.001);
      }

POE Compatibility

In addition to invoking cede in Coro, snooze invokes POE::Kernel's run_one_timeslice method at each time tick, allowing the developer to interlace POE and Coro threads.

      $recur->coloop( sub {
        #
        # Non-blocking action based on a POE::Component:
        #
        POE::Session->create( ... );
      } );
    
      #
      # Insert your main loop here:
      #
      while(1) {
        ##### XXX This is now handled by snooze().
        ##### POE::Kernel->run_one_timeslice;
    
        snooze(.001);
      }

SEE ALSO

This file is part of OP.