
    W                         S r SSKJr  SSKJr  SSKJr  SSKrSSKrSSKJr  Sr	Sr
S	rS
r " S S\5      r " S S\5      rSS jr " S S\5      rS rS rS r " S S\5      r " S S\5      rg)a  Implementation of scheduling for Groc format schedules.

A Groc schedule looks like '1st,2nd monday 9:00', or 'every 20 mins'. This
module takes a parsed schedule (produced by Antlr) and creates objects that
can produce times that match this schedule.

A parsed schedule is one of two types - an Interval or a Specific Time.
See the class docstrings for more.

Extensions to be considered:

  allowing a comma separated list of times to run
    )absolute_import)division)print_functionN   )groczarb@google.com (Anthony Baxter)hoursminutesc                       \ rS rSrSrg)NonExistentTimeError7    N__name__
__module____qualname____firstlineno____static_attributes__r       @lib/googlecloudsdk/appengine/googlecron/groctimespecification.pyr   r   7       r   r   c                       \ rS rSrSrg)AmbiguousTimeError:   r   Nr   r   r   r   r   r   :   r   r   r   c                 x   [         R                  " U 5      nUR                  5         UR                  (       aB  [	        UR
                  UR                  UR                  UR                  UR                  U5      $ [        UR                  UR                  UR                  UR                  UR                  U5      $ )aX  Factory function.

Turns a schedule specification into a TimeSpecification.

Arguments:
  schedule: the schedule specification, as a string
  timezone: the optional timezone as a string for this specification. Defaults
    to 'UTC' - valid entries are things like 'Australia/Victoria' or
    'PST8PDT'.

Returns:
  a TimeSpecification instance
)r   CreateParsertimespecperiod_stringIntervalTimeSpecificationinterval_minssynchronizedstart_time_stringend_time_stringSpecificTimeSpecificationordinal_setweekday_set	month_setmonthday_settime_string)scheduletimezoneparsers      r   GrocTimeSpecificationr,   >   s     X&&//$V%9%96;O;O%+%8%8%+%=%=%+%;%;XG G
 %V%7%79K9K%+%5%5v7J7J%+%7%7C Cr   c                   $    \ rS rSrSrS rS rSrg)TimeSpecificationZ   z#Base class for time specifications.c                     / n[        U5      U:  a3  U R                  U5      nUR                  U5        [        U5      U:  a  M3  U$ )zReturns the next n times that match the schedule, starting at time start.

Arguments:
  start: a datetime to start from. Matches will start from after this time.
  n:     the number of matching times to return

Returns:
  a list of n datetime objects
)lenGetMatchappend)selfstartnouts       r   
GetMatchesTimeSpecification.GetMatches]   sB     C
c(Q,mmE"e	jj c(Q, Jr   c                     [         e)E  Returns the next match after time start.

Must be implemented in subclasses.

Arguments:
  start: a datetime to start from. Matches will start from after this time.
    This may be in any pytz time zone, or it may be timezone-naive
    (interpreted as UTC).

Returns:
  a datetime object in the timezone of the input 'start'
)NotImplementedError)r4   r5   s     r   r2   TimeSpecification.GetMatchm   s
     r   r   N)r   r   r   r   __doc__r8   r2   r   r   r   r   r.   r.   Z   s    + r   r.   c                     [         c  U (       a  [        S5      egU (       a  [         R                  U 5      $ [         R                  $ )a  Converts a timezone string to a pytz timezone object.

Arguments:
  timezone_string: a string representing a timezone, or None

Returns:
  a pytz timezone object, or None

Raises:
  ValueError: if timezone_string is specified, but pytz module could not be
      loaded
Nz(need pytz in order to specify a timezone)pytz
ValueErrorr*   utc)timezone_strings    r   _GetTimezonerD   }   s6     
\ABB==))88Or   c                    [         c  U R                  SS9$ U(       aP  U R                  (       d  [         R                  R	                  U 5      n UR                  U R                  U5      5      $ U R                  (       aI  [         R                  R                  U R                  [         R                  5      5      R                  SS9$ U $ )a  Converts 't' to the time zone 'tzinfo'.

Arguments:
  t: a datetime object.  It may be in any pytz time zone, or it may be
    timezone-naive (interpreted as UTC).
  tzinfo: a pytz timezone object, or None.

Returns:
  a datetime object in the time zone 'tzinfo'
Ntzinfo)r@   replacerG   rB   localize	normalize
astimezone)trG   s     r   _ToTimeZonerM      s     
\99D9!!88
((

A
aALL011xx88all48845==T=JJ Hr   c                 z    U R                  S5      u  p[        R                  " [        U5      [        U5      5      $ )zConverts a string to a datetime.time object.

Arguments:
  time_string: a string representing a time ('hours:minutes')

Returns:
  a datetime.time object
:)splitdatetimetimeint)r(   hourstr	minutestrs      r   _GetTimerV      s0     #((-'	s7|S^	44r   c                   t   ^  \ rS rSrSr    S
U 4S jjrS rS r\S 5       r	\S 5       r
\S 5       rS	rU =r$ )r      a  A time specification for a given interval.

An Interval type spec runs at the given fixed interval. It has the following
attributes:
period - the type of interval, either 'hours' or 'minutes'
interval - the number of units of type period.
synchronized - whether to synchronize the times to be locked to a fixed
    period (midnight in the specified timezone).
start_time, end_time - restrict matches to a given range of times every day.
    If these are None, there is no restriction.  Otherwise, they are
    datetime.time objects.
timezone - the time zone in which start_time and end_time should be
    interpreted, or None (defaults to UTC).  This is a pytz timezone object.
c                 "  > [         [        U ]  5         US:  a  [        R                  " S5      eXl        X l        X0l        U R                  [        :X  a  U R
                  S-  U l	        OU R
                  S-  U l	        [        U5      U l        U R                  (       a  U(       a  [        S5      eU(       a  [        S5      eU R                  S:  d  SU R                  -  S:w  a  [        R                  " S	5      e[        R                  " SS5      U l        [        R                  " S
S5      U l        g U(       a3  U(       d  [        S5      e[#        U5      U l        [#        U5      U l        g U(       a  [        S5      eS U l        S U l        g )Nr   z"interval must be greater than zeroi  <   z>start_time_string may not be specified if synchronized is truez<end_time_string may not be specified if synchronized is trueiQ r   zFcan only use synchronized for periods that divide evenly into 24 hours   ;   z9end_time_string must be specified if start_time_string isz9start_time_string must be specified if end_time_string is)superr   __init__r   GrocExceptionintervalperiodr    HOURSsecondsrD   r*   rA   rQ   rR   
start_timeend_timerV   )r4   r`   ra   r    r!   r"   r*   	__class__s          r   r^   "IntervalTimeSpecification.__init__   sZ    

#T35!|CDDMK${{e]]T)dl]]R'dl *DM 	LN 	N	JL 	L
,,
UT\\%9a$?   "@ A 	A !a+dommB+dm	GI 	I !23do/dm	GI 	Idodmr   c                    U R                   cB  U[        R                  " U R                  S9-   nU[        R                  " UR                  S9-
  $ [        XR                  5      nU R                  X0R                   U R                  5      nX4-
  nUR                  S-  S-  UR                  -   nX`R                  -   U R                  -  nU[        R                  " XpR                  -  S9-   nU R                  (       a  U R                  R                  U5      nU R                  X0R                   U R                  5      n	U R                  U5      (       a  U R                  U5      (       a  X:  a  UnOU	n[        X!R                  5      $ )a  Returns the next match after 'start'.

Arguments:
  start: a datetime to start from. Matches will start from after this time.
    This may be in any pytz time zone, or it may be timezone-naive
    (interpreted as UTC).

Returns:
  a datetime object in the timezone of the input 'start'
)rc   rZ      )rd   rQ   	timedeltarc   secondrM   r*   _GetPreviousDateTimedaysrJ   _GetNextDateTime_TimeIsInRangerG   )
r4   r5   resultrL   rd   t_deltat_delta_secondsnum_intervalsinterval_timenext_start_times
             r   r2   "IntervalTimeSpecification.GetMatch   sA     x))$,,??fh((??? 	E==)A **1oot}}MJ nG||b(2-?O$||3DMX''1MOO }}mm--m<m ++ANOA4#6#6}#E#E'ff v||,,r   c                     U R                  XR                  U R                  5      nU R                  XR                  U R                  5      nX#:  a  gX:H  $ )zReturns true if 't' falls between start_time and end_time, inclusive.

Arguments:
  t: a datetime object, in self.timezone

Returns:
  a boolean
T)rl   rd   r*   re   )r4   rL   previous_start_timeprevious_end_times       r   ro   (IntervalTimeSpecification._TimeIsInRange*  sT     33A48MMC11!]]26--A.##r   c                     U R                  5       n [        R                  X1U5      nX@::  a  U$ U[        R                  " SS9-  nM6  )a!  Returns the latest datetime <= 't' that has the time target_time.

Arguments:
  t: a datetime.datetime object, in any timezone
  target_time: a datetime.time object, in any timezone
  tzinfo: a pytz timezone object, or None

Returns:
  a datetime.datetime object, in the timezone 'tzinfo'
r   rm   dater   _CombineDateAndTimerQ   rj   rL   target_timerG   r~   rp   s        r   rl   .IntervalTimeSpecification._GetPreviousDateTime>  sK     668D
(<<
V%f	
h  a((d r   c                     U R                  5       n [        R                  X1U5      nX@:  a  U$ U[        R                  " SS9-  nM6  )a"  Returns the earliest datetime > 't' that has the time target_time.

Arguments:
  t: a datetime.datetime object, in any timezone
  target_time: a datetime.time object, in any timezone
  tzinfo: a pytz timezone object, or None

Returns:
  a datetime.datetime object, in the timezone 'tzinfo'
r   r|   r}   r   s        r   rn   *IntervalTimeSpecification._GetNextDateTimeS  sK     668D
(<<
V%f	
h  a((d r   c           	         [         R                   " U R                  U R                  U R                  UR                  UR
                  UR                  5      nUc  U$  UR                  USS9$ ! [         a+    [        UR                  USS9UR                  USS95      s $ [         a>     U[         R                  " SS9-  n UR                  USS9s $ ! [         a     Of = fM<  f = f)a|  Creates a datetime object from date and time objects.

This is similar to the datetime.combine method, but its timezone
calculations are designed to work with pytz.

Arguments:
  date: a datetime.date object, in any timezone
  time: a datetime.time object, in any timezone
  tzinfo: a pytz timezone object, or None

Returns:
  a datetime.datetime object, in the timezone 'tzinfo'
Nis_dstTFr   )r	   )rQ   yearmonthdayhourminuterk   rI   r   minr   rj   )r~   rR   rG   naive_results       r   r   -IntervalTimeSpecification._CombineDateAndTimeh  s     $$TYY

DHHdii%)[[$++?L~__\$_77 7 
//,t/
4
//,u/
57 7   

 **155	d;
;# 	
		 
s6   A. .2C)"!C)CC)
C# C)"C##C))re   r`   ra   rc   rd   r    r*   )F r   N)r   r   r   r   r>   r^   r2   ro   staticmethodrl   rn   r   r   __classcell__rf   s   @r   r   r      sc    $ "!#!-^,-\$( ) )( ) )( % %r   r   c                   N   ^  \ rS rSrSr      SU 4S jjrS rS rS rSr	U =r
$ )	r#   i  aR  Specific time specification.

A Specific interval is more complex, but defines a certain time to run and
the days that it should run. It has the following attributes:
time     - the time of day to run, as 'HH:MM'
ordinals - first, second, third &c, as a set of integers in 1..5
months   - the months that this should run, as a set of integers in 1..12
weekdays - the days of the week that this should run, as a set of integers,
           0=Sunday, 6=Saturday
timezone - the optional timezone as a string for this specification.
           Defaults to UTC - valid entries are things like Australia/Victoria
           or PST8PDT.

A specific time schedule can be quite complex. A schedule could look like
this:
'1st,third sat,sun of jan,feb,mar 09:15'

In this case, ordinals would be {1,3}, weekdays {0,6}, months {1,2,3} and
time would be '09:15'.
c                   > [         [        U ]  5         U(       a  U(       a  [        S5      eUc  [	        [        SS5      5      U l        Oa[	        U5      U l        U R                  (       a@  [        U R                  5      S:  d  [        U R                  5      S:  a  [        SU-  5      eUc  [	        [        S5      5      U l	        Oa[	        U5      U l	        U R                  (       a@  [        U R                  5      S:  d  [        U R                  5      S:  a  [        SU-  5      eUc  [	        [        SS	5      5      U l
        Oa[	        U5      U l
        U R                  (       a@  [        U R                  5      S:  d  [        U R                  5      S
:  a  [        SU-  5      eU(       d  [	        5       U l        O[        U5      S:  a  [        S5      e[        U5      S:  a  [        S5      eU R                  (       aY  U R                   H-  n[        R                  " SU5      u  p[        U5      U	::  d  M-    O   [        S[        U5      < SW< 35      e[	        U5      U l        [        U5      U l        [!        U5      U l        g )Nz)cannot supply both monthdays and weekdaysr         z2ordinals must be between 1 and 5 inclusive, got %r   r   z>weekdays must be between 0 (sun) and 6 (sat) inclusive, got %r      z=months must be between 1 (jan) and 12 (dec) inclusive, got %rz#day of month must be greater than 0   z!day of month must be less than 32   zinvalid day of month, got day z
 of month )r]   r#   r^   rA   setrangeordinalsr   maxweekdaysmonths	monthdayscalendar
monthrangerV   rR   rD   r*   )r4   r   r   r   r   timestrr*   r   _ndaysrf   s             r   r^   "SpecificTimeSpecification.__init__  s	    

#T35IBCC%1+&dm(mdm	C.2c$--6H16L "$,- . 	. %(mdm(mdm	C.2c$--6H16L "$,- . 	. ~a%dkKdk	#dkk*Q.#dkk2BR2G "$*+ , 	, udn	Y!	>??	Y"	<==	[[E((E2(!^u$ !
 7:9~uN O O9~dn!DI *DMr   c                    [         R                  " X5      u  p4U R                  (       a-  [        U R                   Vs/ s H  oUU::  d  M
  UPM     sn5      $ / nUS-   S-  nU R                   HC  nU R
                   H0  nX-
  S-  S-   nUSUS-
  -  -  nXT::  d  M  UR                  U5        M2     ME     [        U5      $ s  snf )a  Returns matching days for the given year and month.

For the given year and month, return the days that match this instance's
day specification, based on either (a) the ordinals and weekdays, or
(b) the explicitly specified monthdays.  If monthdays are specified,
dates that fall outside the range of the month will not be returned.

Arguments:
  year: the year as an integer
  month: the month as an integer, in range 1-12

Returns:
  a list of matching days, as ints in range 1-31
r   r   )r   r   r   sortedr   r   r3   )	r4   r   r   	start_daylast_dayr   out_daysordinalweekdays	            r   _MatchingDays'SpecificTimeSpecification._MatchingDays  s     #--d:I~~DNNFNSXoSNFGG HQ!#I==]]'#q(A-qGaK  ?
//#
	 # ! ( Gs   	B?
B?c              #      #    [        U5      =p2US-
  nSn U Vs/ s H  ofU:  d  M
  UPM     nnU(       d  US-  nUnUS   nXE4v   M3  s  snf 7f)a  Creates a generator that produces results from the set 'matches'.

Matches must be >= 'start'. If none match, the wrap counter is incremented,
and the result set is reset to the full set. Yields a 2-tuple of (match,
wrapcount).

Arguments:
  start: first set of matches will be >= this value (an int)
  matches: the set of potential matches (a sequence of ints)

Yields:
  a two-tuple of (match, wrap counter). match is an int in range (1-12),
  wrapcount is a int indicating how many times we've wrapped around.
r   r   )r   )r4   r5   matches	potentialafter	wrapcountxs          r   _NextMonthGenerator-SpecificTimeSpecification._NextMonthGenerator  si      !/)IAIEI
'5iu91ii5 	Q		le 5s   A	A
A
!Ac                    [        XR                  5      R                  SS9nU R                  (       a&  U R	                  UR
                  U R                  5      n [        W5      u  pEUR                  SXBR                  U-   S9nU R                  UR                  U5      nUR                  UR
                  4UR                  UR
                  4:X  a#  U Vs/ s H  oUR                  :  d  M  UPM     nnU(       a  UR                  US   U R                  R                  U R                  R                  SSS9n	U R                  (       a"  [        b   U R                  R                  U	SS9n	U[        XR&                  5      :  a  [        XR&                  5      $ UR%                  S5        U(       a  M  GM^  s  snf ! [         a    [        U[        R                   5      n
[        U R                  R                  U	SS9[        R                   5      nX:  a  U R                  R                  U	SS9n	 NU R                  R                  U	S	S9n	 N["         a    UR%                  S5         GMy  f = f)
r;   NrF   Tr   )r   r   r   r   )r   r   r   rk   microsecondr   F)rM   r*   rH   r   r   r   nextr   r   r   rR   r   r   r@   rI   r   rB   r   poprG   )r4   r5   rd   r   r   	yearwrapscandidate_monthday_matchesr   r7   	start_utc dst_doubled_time_first_match_utcs               r   r2   "SpecificTimeSpecification.GetMatch  s#   $ UMM2::$:GJ{{''
(8(8$++Ff
fe"**u??Y#> + @o &&';';UCk!6!6
7JOO<F<L<L<N N #.E+Qjnn1Dq+E%%A99## &  ==T-
--((T(:C$ ;sLL11S,,/
/
//!
I K  F" $ >
 $E4884I/:&&s4&8$((0D,;MM**3t*<cMM**3u*=c% OOAs+   F9.F9F> >A5I05I0I0/I0)r   r   r   rR   r*   r   )NNNNz00:00N)r   r   r   r   r>   r^   r   r   r2   r   r   r   s   @r   r#   r#     s8    , 8+t<:G Gr   r#   )N)r>   
__future__r   r   r   r   rQ   r   r   
__author__r@   rb   MINUTES	Exceptionr   r   r,   objectr.   rD   rM   rV   r   r#   r   r   r   <module>r      s   ( '  %   .
 
9  C8   F06
5Q 1 QhR 1 Rr   