
    @i                       d dl Zd dlZd dlZd dlmZ d dlmZmZ d dl	m
Z
mZmZmZmZ d dlZd dlZd dl d dlmZ d dlmZmZ dd	d
ddddddddddZej                  Zej2                  Zej                  Z G d dej8                        Zy)    N)	lru_cache)deque
namedtuple)IteratorListNoReturnTupleUnion)*)cmf_calendar)MOrelativedeltau   янвu   февu   марu   апрu   маяu   июнu   июлu   авгu   сенu   октu   нояu   дек)                        	   
         c                       e Zd Zej                  j
                  dz   Zej                  j                  ddgz   Zededede	e   fd       Z
e	 	 dFded	ed
ededef
d       Ze	 	 dGdededededee   f
d       Ze	 	 dHdededededee   dee   fd       Ze	 	 	 dIdedededed	ed
ededee   fd       Ze	 	 	 dJdedededed	ed
ededee   fd       Ze	 	 dKdededeeef   fd       ZedKdededefd       Zedddeeee   f   fd       Zedd dedefd       Ze	 	 dLdd deded!edef
d"       Z ed#d$      Zedee d e!jD                  jF                  f   dedefd%       Z$ed&        Z%e e&d'      d(               Z'i Z(i Z)e e*d)*      d+e dedefd,              Z+e	 dMdd d-ee eeef   defd.       Z,dNd/ed0edefd1Z-d2ed3edefd4Z.e	 dOdd d2ed3edefd5       Z/e	 dPdd d2ed6edefd7       Z0ed8        Z1ed9        Z2e e3d d d:d;<      d=               Z4d> Z5d? Z6d@ Z7dA Z8 fdBZ9 fdCZ:dD Z; fdEZ< xZ=S )QCmfCalendar)DayDatarecalc_calendarload_holidays
date_startdate_endreturnc              #      K   t        t        || z
  j                        dz         D ]  }| t        j                  |      z     yw)u   
        Генератор диапазона дат

        Args:
            date_start (Date): начальная дата
            date_end (Date): конечная дата

        Yields:
            Iterator[Date]: дата
        r   N)rangeintdaysdatetime	timedelta)r    r!   is      ./common/models/cmf_calendar.py_date_rangezCmfCalendar._date_range"   sG      sHz17781<= 	5Ax11!444	5s   AAdate	month_daypositionday_of_weekc                    |rHt        j                  | j                  | j                        d   }||k  r|}| j	                  |      }|S t        j
                         }|j                  | j                  | j                        }|D cg c]$  }||   j                  | j                  k(  s ||   & }	}|dk  r|	|   n|	d   }|S c c}w )u:  
        Вспомогательный метод для every_month и every_year
        Возвращает дату по указанному дню месяца или по позиции дня недели месяца

        Args:
            date (Date): дата
            month_day (int, optional): день месяца (1-31). Defaults to None.
            position (int, optional): позиция недели месяца. Defaults to None.
                                      (0-первая, 1-вторая, 2-третья, 3-четвертая, 4-последняя)
            day_of_week (int, optional): день недели. Defaults to None.
                                         (0-ПН, 1-ВТ, 2-СР, 3-ЧТ ,4-ПТ ,5-СБ, 6-ВС)

        Returns:
            Date: дата
        r   )dayr   )cl
monthrangeyearmonthreplaceCalendarmonthdatescalendar)
r,   r-   r.   r/   days_in_monthr1   calendarweeksweekday_of_weekss
             r*   _get_day_by_position_in_monthz)CmfCalendar._get_day_by_position_in_month1   s    $ MM$))TZZ@CMy()	,,9,-C 
 {{}H//		4::FE:?i$4CTCZCZ^b^h^hChD-iLi,4qL,x(l2>NC
 js   	!C+C
start_dateend_daterepeatsperiodc                     g }|d}| }|r3||dz  }n||kD  r	 |S |j                  |       |t        |      z  }|r3|S )u  
        Возвращает список дат для типа повторения "Ежедневно"

        Args:
            start_date (Date): начальная дата
            end_date (Date, optional): конечная дата. Defaults to None.
            repeats (int, optional): количество повторений. Defaults to None.
            period (int, optional): периодичность (каждый N день). Defaults to 1.

            Обязателен один из двух аргументов, либо end_date, либо repeats

        Returns:
            List[Date]: список дат
        Tr   r&   )appendr   )r@   rA   rB   rC   r&   current_dates         r*   	every_dayzCmfCalendar.every_dayP   sf    " G!1(
  KK%Mv66L      weekdaysc                     g }|d}| t        t        d            z   }|rS|D ]<  }|t        |      z   }|| k\  r"||dz  }n	||kD  rd} n|j                  |       |r< n |t        |      z  }|rS|S )u]  
        Возвращает список дат для типа повторения "Еженедельно"

        Args:
            start_date (Date): начальная дата
            end_date (Date, optional): конечная дата. Defaults to None.
            repeats (int, optional): количество повторений. Defaults to None.
            period (int, optional): периодичность (каждая N неделя). Defaults to 1.
            weekdays (List[int], optional): список дней недели. Defaults to None.
                                            (0-ПН, 1-ВТ, 2-СР, 3-ЧТ ,4-ПТ ,5-СБ, 6-ВС)

            Обязателен один из двух аргументов, либо end_date, либо repeats

        Returns:
            List[Date]: список дат
        Tr2   )weekdayr   F)r<   )r   r   rF   )	r@   rA   rB   rC   rJ   r&   rG   rL   r1   s	            r*   
every_weekzCmfCalendar.every_weekr   s    & G!M"R&$AA# "]7%CC*$'1x"'KK$ M77L   rI   c                    g }|d}| }|rt         j                  ||||      }	|	| k\  r!||dz  }n|	|kD  r	 |S |j                  |	       |j                  dz
  |z   }
|j                  |
dz  z   }|
dz  dz   }
|j                  ||
      }|r|S )u  
        Возвращает список дат для типа повторения "Ежемесячно"
        Расчитывает даты в двух режимах:
            1 - по указанному дню, если установлен параметр month_day
            2 - по позиции дня недели, если установлены параметры position и day_of_week

        Args:
            start_date (Date): начальная дата
            end_date (Date, optional): конечная дата. Defaults to None.
            repeats (int, optional): количество повторений. Defaults to None.
            period (int, optional): периодичность (каждый N месяц). Defaults to 1.
            month_day (int, optional): день месяца (1-31). Defaults to None.
            position (int, optional): позиция недели месяца. Defaults to None.
                                      (0-первая, 1-вторая, 2-третья, 3-четвертая, 4-последняя)
            day_of_week (int, optional): день недели. Defaults to None.
                                         (0-ПН, 1-ВТ, 2-СР, 3-ЧТ ,4-ПТ ,5-СБ, 6-ВС)

            Обязателен один из двух аргументов, либо end_date, либо repeats

        Returns:
            List[Date]: список дат
        Tr-   r.   r/   r   r   )r5   r6   )r   r?   rF   r6   r5   r7   )r@   rA   rB   rC   r-   r.   r/   r&   rG   r1   r6   r5   s               r*   every_monthzCmfCalendar.every_month   s    4 G!;;LT]EM[f < hC j #qLG8^  C  &&*V3E$$u{2DBJNE'//T/GL! $ rI   r6   c                     g }|d}| j                  |d      }|rat        j                  ||||      }	|	| k\  r!||dz  }n|	|kD  r	 |S |j                  |	       |j                  |j                  dz         }|ra|S )u  
        Возвращает список дат для типа повторения "Ежегодно"
        Расчитывает даты в двух режимах:
            1 - по указанному дню, если установлены параметры month и month_day
            2 - по позиции дня недели месяца, если установлены параметры month, position и day_of_week

        Args:
            start_date (Date): начальная дата
            end_date (Date, optional): конечная дата. Defaults to None.
            repeats (int, optional): количество повторений. Defaults to None.
            month (int, optional): месяц (1-Янв, 2-Фев, ..., 12-Дек). Defaults to None.
            month_day (int, optional): день месяца (1-31). Defaults to None.
            position (int, optional): позиция недели месяца. Defaults to None.
                                      (0-первая, 1-вторая, 2-третья, 3-четвертая, 4-последняя)
            day_of_week (int, optional): день недели. Defaults to None.
                                         (0-ПН, 1-ВТ, 2-СР, 3-ЧТ ,4-ПТ ,5-СБ, 6-ВС)

            Обязателен один из двух аргументов, либо end_date, либо repeats

        Returns:
            List[Date]: _description_
        Tr   r6   r1   rO   )r5   )r7   r   r?   rF   r5   )
r@   rA   rB   r6   r-   r.   r/   r&   rG   r1   s
             r*   
every_yearzCmfCalendar.every_year   s    4 G!))1)=;;LT]EM[f < hC j #qLG8^ 	 C '//\5F5F5J/KL  rI   	from_timeto_timec                 j   t         j                  j                         }t         j                   j                  ||       } t         j                   j                  ||      }t        j                  dd      |j	                         k(  r|t        j
                  d      z  }| |k\  rt        dd       | |fS )u  
        Нормализует интервал времени и проверяет его на корректность.
        Так как у datetime нет времени 24:00,
        то время окончания 00:00 конвертируется в 00:00 следующего дня,
        как будто это интервал, например, 18:00 - 24:00

        Args:
            from_time (Time, optional): время начала интервала. Defaults to None.
            to_time (Time, optional): время окончания интервала. Defaults to None.

        Returns:
            Tuple[Datetime, Datetime]: дата и время начала, дата и время окончания
        r      hoursuo   Время начала должно быть раньше времени окончания интервалаTabort)r'   r,   todaycombinetimer(   	cmf_alert)rT   rU   r\   s      r*   normalize_time_intervalz#CmfCalendar.normalize_time_interval  s      ##%%%--eY?	##++E7;==A',,.0x))33G  H  PT  U'!!rI   c                 ~    | r|syt         j                  j                  | |      \  } }|| z
  j                         dz  S )u?  
        Вычисляет в минутах интервал времени

        Args:
            from_time (Time, optional): время начала. Defaults to None.
            to_time (Time, optional): время окончания. Defaults to None.

        Returns:
            int: минуты
        r   <   )modelsr   r`   total_seconds)rT   rU   s     r*   get_interval_minutesz CmfCalendar.get_interval_minutes  sC     #//GG	SZ[	7)#224r99rI   	exclusionzmodels.CmfCalendarExcludec                    g }d}|j                   j                  }|j                  j                  }|j                  }|j                  }|j
                  dk(  r(|j                  dk(  rd}| j                  |||      }||fS |j
                  dk(  r0d}| j                  ||||j                  |j                        }||fS |j
                  dk(  r;d	}| j                  ||||j                  |j                  |j                  
      }||fS |j
                  dk(  r&d}| j                  |||||j                        }||fS |j
                  dk(  r1d}| j                  |||||j                  |j                        }||fS |j
                  dk(  rEd}t        d      D cg c]  }t        |d| d      s| }	}| j                  |||||	      }||fS |j
                  dk(  r%|j                  dk7  rd}| j                  ||||      }||fS c c}w )uw  
        Расчитывает и возвращает исключительные дни и приоритет исключения
        Приоритеты исключений (7-самый высокий, 1-самый низкий):
            7 - Ежедневно (каждый день)
            6 - Ежегодно (в указанный день)
            5 - Ежегодно (по позиции дня недели месяца)
            4 - Ежемесячно (в указанный день)
            3 - Ежемесячно (по позиции дня недели)
            2 - Еженедельно
            1 - Ежедневно (каждый N день)

        Args:
            exclusion (CmfCalendarExclude): исключение

        Returns:
            Tuple[int, List[Date]]: приоритет, список дат
        NrH   r   r   every_year_dayr   )r6   r-   every_year_week_dayr   )r6   r.   r/   every_month_dayr   )r-   every_month_week_dayr   )r.   r/   rM   r   r/   F)rJ   )period_start_datevalueperiod_end_daterC   repeat_timesrepeat_typerH   rS   r6   r-   month_week_positionmonth_day_weekrP   r$   getattrrM   )
clsrf   r&   priorityr@   rA   rC   rB   r)   rJ   s
             r*   calc_excluded_dayszCmfCalendar.calc_excluded_days0  sh   & 0066
,,22!!((  K/I4D4D4IH==Xw?DD ~C ""&66H>>*h(19CVCV " XD< ~9 ""&;;H>>*h(1+4+H+H.7.F.F " HD2 ~+ ""&77H??:x#)Y5H5H # JD$ ~! ""&<<H??:x&,5,I,I/8/G/G # ID ~ ""l2H#(8\awyKPQsBSUZ/[\H\>>*hRZ>[D ~ ""k1i6F6F!6KH==XwGD~ ]s   H
2H
r;   c           	      \   d}g }|j                         d }t        j                  j                  |ddgg ddd|gdd	|gg
      }d}|D ](  }| j	                  |      \  }	}
|	||
v s|	|kD  s%|	}|}* |rT|j
                  j                  }|j                  j                  }|dk(  r|j                  d |j                  D               nt        j                  j                  |ddgdd|gdd	|gg
      }|rt        || d      j                  dk(  r|j                  ddg       |j                  }t        || d      j                  }t        || d      j                  }|dk(  r$|j                  fd|j                  D               t        j                  j                  ||      }|sQt        j                  ||      }|_        |j"                  |_        |j$                  |_        |j&                  |_        ||_        ||_        ||_        |j/                          |S )u   
        Пересчет указанного дня календаря

        Args:
            calendar (CmfCalendar): объект календаря
            date (Date): дата

        Returns:
            NoReturn
        Nr1   r   intervals.*rp   !=Nrl   <=rn   >=parentfieldsfilterr   workc              3      K   | ]N  }|j                   j                  j                  d       |j                  j                  j                  d       g P yw%H:%MN)rT   rm   strftimerU   .0intervals     r*   	<genexpr>z*CmfCalendar.recalc_date.<locals>.<genexpr>  sN      !@%- #+"4"4":":"C"CG"LhN^N^NdNdNmNmnuNv!w !@s   AA_typedefaultdefault_workweek.*default_workweek.intervals.*_intervals_total_minutesc              3      K   | ]g  }|j                   j                  k(  rL|j                  j                  j                  d       |j                  j                  j                  d       g i ywr   )day_weekrm   rT   r   rU   )r   r   rL   s     r*   r   z*CmfCalendar.recalc_date.<locals>.<genexpr>  sc      !f%-(BSBSBYBY]dBd #+"4"4":":"C"CG"LhN^N^NdNdNmNmnuNv!w !fs   A-A0r~   r,   )rL   rc   CmfCalendarExcludelistrv   exclude_typerm   intervals_total_minutesextend	intervalsCmfCalendarWorkWeekgetrs   load_fieldsdefault_workweekCmfCalendarDayr   r5   r6   r1   day_typeinterval_total_minutesinterval_jsonsave)rt   r;   r,   r1   r   day_num
exclusionsexclusion_priorityrf   ru   excluded_daysr   r   calendar_dayrL   s                 @r*   recalc_datezCmfCalendar.recalc_dater  st    	,,.y/ ..33'+$dD1"D$/ 4 

 # 	 I&)&<&<Y&G#Hm}$4F)F%-"	  ''--H%(%@%@%F%F"6!   !@14!@ @ ,,00]+,dD9<MtUY;Z[ 1 C '#'%'89??9L$$&:<Z%[\//swiu$56<<H%,SWI=U2V%W%]%]"6!   !f14!f f ,,00t0L!00t0LL$+L! $		L!%L#xxL (.D+%."rI   Tback_recalcc                 (   |?t         j                   j                         j                         }|j                  dd      }|&|t	        d      z   t        j
                  d      z
  }| j                  ||      D ]  }| j                  ||        y)u$  
        Пересчет дней календаря
        Если интервал не задан, делает пересчет от начала текущего года +2 года, итого 3 года

        Args:
            calendar (CmfCalendar): объект календаря
            date_start (Date, optional): начальная дата
            date_end (Date, optional): конечная дата
            back_recalc (bool, optional): пересчет назад только текущего года
        Nr   rR   r   )yearsrE   )r'   r\   r,   r7   r   r(   r+   r   )rt   r;   r    r!   r   r\   r,   s          r*   recalc_calendar_dayzCmfCalendar.recalc_calendar_day  s     %%++-224EQA6J!M$::X=O=OUV=WWHOOJ9 	,DOOHd+	,rI   r   z&type,intervals,intervals_total_minutesc                 x    t        j                  t        j                  |d            }| j	                  ||      S )u  
        Получает день календаря

        Args:
            calendar (Union[str, CmfCalendar, CmfType]): календарь
            date (Date): дата

        Returns:
            day (dict): информация о дне календаря
        r   )sysinterncmfutilget_obj_id_by_any_get_day)rt   r;   r,   calendar_ids       r*   get_dayzCmfCalendar.get_day  s0     jj!:!:8]!ST||K..rI   c                  X    t        dd        t        j                  j                          y )NCmfCalendar:day_cache_clear)cmf_emit_server_eventrc   r   _day_cache_clear_kwargss    r*   day_cache_clearzCmfCalendar.day_cache_clear  s    ;TB++-rI   r   c                      t         j                  j                  j                          t         j                  j                  j                          t         j                  j                  j                          y N)rc   r   r   cache_clear_day_cache_int_internclear_day_cache_data_internr   s    r*   r   zCmfCalendar._day_cache_clear  sL     	##//10066811779rI   i  )maxsizer   c                 F   t         j                  j                  dd| gdd|ggg d      }|s7t        j	                  | dg      }t        d|d	d
|j                   dd       t         j                  j                  t        j                  |j                        t        d |j                  D              t         j                  j                  j                  |j                   |j                               }t         j                  j"                  j                  ||      S )N	parent_id=r,   )r   r   r   r   r   dirty)r   u	   День z%d.%m.%Yu*    не найден в календаре "uO   ". Попробуйте запустить пересчет календаря.TrZ   c              3   |   K   | ]4  }t        j                  |d          t        j                  |d         f 6 yw)r   r   N)r   r   r   s     r*   r   z'CmfCalendar._get_day.<locals>.<genexpr>  s7      3 HQK(#**Xa[*AB3s   :<)rc   r   sgetr   get_obj_by_idr_   namer   r   r   r   r   tupler   r   
setdefaultr   r   )r   r,   r1   r;   dds        r*   r   zCmfCalendar._get_day  s    ##(( #{3fc45HIJ ) 

 ,,[',KH	$x0Z[c[h[hZi jf gnrt ''JJs||$ 3 # 1 13 4 44??@Z@Z\_\v\vw
 !!88CCBKKrI   r5   c                 v   |)t         j                   j                         j                  }n@t        |t         j                        st        |t         j                         r|j                  }t        j                  t        |      dd      }t        j                  t        |      dd      }t               }|}||k  rX|j                         |j                  |j                  |j                  ddg d||<   |t        j                  d      z  }||k  rX|j                  dd	g       |j                  }t        j                  j                  |d
dgddd|gdd|ggdd|gdd|ggdd|gdd|ggg      }|j!                         D ]  \  }	}
|	j                         }d| }t#        || d      j$                  }t#        || d      j$                  }||
d<   ||
d<   |dk(  s]|j&                  D cg c]e  }|j(                  j$                  |k(  rJ|j*                  j$                  j-                  d      |j.                  j$                  j-                  d      gg c}|
d<    |D ]^  }|j0                  j$                  }||j2                  k  s*d}g }|j                         }d| }t#        || d      j$                  }|dk(  r|t        j                  d      z  }a|dk(  rt#        || d      j$                  }|j&                  D cg c]e  }|j(                  j$                  |k(  rJ|j*                  j$                  j-                  d      |j.                  j$                  j-                  d      gg }}|j5                  |      }|r||d<   ||d<   ||d<   |t        j                  d      z  }||j2                  k  r5a t        j6                  j                  |d
dgg dddd|gdd|ggdd|gdd|ggdd|gdd|gggg      }|D cg c]#  }|j8                  r| j;                  |      |fz   % }}t=        |      D ]  \  }}}d}g }|j>                  j$                  }|dk(  rw|j@                  j$                  }|j&                  D cg c]L  }|j*                  j$                  j-                  d      |j.                  j$                  j-                  d      gN }}|D ]%  }	|j5                  |	      }|s||d<   ||d<   ||d<   '  t        jB                  j                  ||d
g      }|D ]  }|jE                  |j                  j$                  d      }
|
s,|
j5                  dg       jG                          |
j!                         D ]  \  }}tI        |||        |jJ                  s~|jM                  d        |j!                         D ]Q  \  }	}
t        jC                  ||	      }|
j!                         D ]  \  }}tI        |||        |jM                          S yc c}w c c}w c c}w c c}w ) uQ  
        Расчитывает все дни года с учетом рабочих недель и исключений и сохраняет их в БД

        Args:
            calendar (CmfCalendar): объект календаря
            year (Union[str, int, Date, Datetime], optional): год. Defaults to None.
        Nr   r      r   )r   r5   r6   r1   r   r   r   rE   r   r   r   rx   ORrl   r|   r{   rn   r}   r1   r   r   r   r   r   r   r   r   ry   )r~   r5   r   T	only_datar   )'r'   nowr5   
isinstancer,   r%   dictrL   r6   r1   r(   r   r   rc   r   r   itemsrs   rm   r   r   rT   r   rU   rl   rn   r   r   rp   rv   sortedr   r   r   popsortsetattr
is_changedr   )rt   r;   r5   start_date_yearend_date_yeardays_of_yearrG   r   	workweeksr,   day_datarL   r   r   r   r   workweekcurrent_period_dater   day_of_yearr   rf   excluded_dates_datescalendar_daysr   
field_namerm   s                                r*   calc_one_yearzCmfCalendar.calc_one_year  s    <$$((*//Dhmm,
4ARAR0S99D"--D	1a8 c$iR8 v&m+(002$))%++#'' *+!#*L& H..A66L m+ 	24RST#44..33'%t_=@SUY[h?ij#T?;>OQUWd=ef%t_=@QSWYf?gh	 4 	
	 +002 	ND(llnGG9oG/G9E1BCIIH%,-='Jb?c%d%j%j"#+HZ 1GH-.6! %5$>$>- (BSBSBYBY]dBd ''--66w?AQAQAWAWA`A`ahAij-)	 " 	BH"*"<"<"B"B%)A)AA)*&	-557y/"8y->?EEy('8+=+=1+EE'v%-4X'Jb?c-d-j-j* )1(:(:!$h>O>O>U>UY`>` "++11::7CXEUEUE[E[EdEdelEmn!I !
 +../BC.6K
+<RK 893<K0#x'9'9q'AA#1 &)A)AA	B< ..33'+)4ADWY]_lCmn'?BSUY[hAij)4ADUW[]jCkl	 4 

  ,64'&22 00;ylJ 4 4 $*.#9 	=Aui%&"I --33H6!)2)J)J)P)P& %.$7$7  ''--66w?AQAQAWAWA`A`ahAij	   =*..t4.6K
+<RK 893<K0=	=& --22(WZV[2\) 	6L#''(9(9(?(?FH_b1668)1)9 =%JL*e<=  ** %%%5	6 +002 	 ND(!00t0LL%-^^%5 9!
Ej%89 	 k-,!:4s   /A*X'
A*X,(X1AX6
back_yearsforcec                    | j                  g d       |t        | j                        }|dk  s|dkD  rt        dd       | j                  s|syt
        j
                  j                         j                  }|r||z
  n|}|t        | j                        z   }d}t        ||      D ]i  }|s t        j                  t        |      dd      }t        j                  j                  | |       t        j                  t        |      d	d
      | _        k || _        | j!                          d| _        | j#                  d       y)u  
        Пересчитывает дни календаря.
        Пересчет начинается от текущего года вперед на количество лет,
        указанных в параметре calc_num_years календаря.
        Если в параметре back_years указано положительное число, то пересчет начнется с прошлых лет.
        Например:
            back_years = 3
            calc_num_years = 5
            текущий год - 2023
            Будут пересчитаны года с 2020 по 2027 включительно

        Args:
            force (bool, optional): принудительный пересчет дней календаря. Defaults to False.
            back_years (int, optional): количество прошлых лет. Defaults to 0.
        )r   calc_num_yearscalc_num_back_yearsNr   r   u@   Параметр back_years должен быть от 0 до 10TrZ   r   r   r   Fr   )r   r%   r   r_   r   r'   r   r5   r   r$   r,   rc   r   r   
calc_until	calc_fromr   r   )selfr   r   current_year	from_yearto_yearr   r5   s           r*   r   zCmfCalendar.recalc_calendar  s     	KLT556J>Z"_X`de

e((,,.331;L:-	T%8%8!99	)W- 	?D$MM#d)Q:	,,T48&mmCIr2>DO		?
 # 	
		D	!rI   from_dtto_dtc                 <   d}t        j                  t        j                  t        | j                  xs d                  }|j	                         }|j	                         }t
        j                  j                  dd| gg ddd|gdd|ggg d	
      }|D ]  }|j                  |k7  r"|j                  |k7  r||j                  dz  z  }5|j                  D ]=  }	t         j                   j                  |	d   d      j                         }
t         j                   j                  |	d   d      j                         }t         j                   j                  |j                  |
|      }t         j                   j                  |j                  ||      }|j                         t        j                  dd      k(  r|t        j                  d      z  }t        ||      }t        ||      }||k  rt        ||z
  j                               nd}||z  }@  |S )u  
        Получает дельту рабочего времени в секундах между начальной и конечной датой и временем

        Args:
            from_dt (datetime): дата и время начала диапазона
            to_dt (datetime): дата и время конца диапазона

        Returns:
            work_timedelta (int): дельта рабочего времени в секундах
        r   secondsr~   r   )r   r   r   r,   r|   r{   )r,   r   r   r   rb   r   r   tzinforE   )r'   timezoner(   r%   r,   rc   r   slistr   r   strptimer^   r]   maxminrd   )r   r   r   work_timedeltatzr@   rA   	work_dayswork_dayr   rT   rU   interval_startinterval_endstartendtime_periods                    r*   get_work_timedeltazCmfCalendar.get_work_timedelta  s    x11#dmm>Pq:QRS\\^
::<))//3%)z*VT8,D
 G 0 
	 " 	.H}}
*x}}/H("A"AB"FF$22 
.$--66x{GLQQS	"++44Xa['JOOQ!)!2!2!:!:8==)\^!:!_'0088XZ8[$$&(--1*== H$6$6A$>>LG^4%.DICKc3;"="="?@UV+-
.	." rI   c                    d }d}t        j                  t        j                  t        |j                  xs d                  }|j	                         }|j	                         }	||	k  rr| j                  ||      }
|xr( ||j	                         k(  xs ||j	                         k(  }|
j                  dk7  r|s|t        j                  d      z  }o|r|}|
}|j                  dk7  r{||j	                         k(  r|t        j                  d      z  }n,||j	                         k(  r|t        j                  d      z  }| j                  ||      }|j                  dk7  r{|}
||j	                         k7  r=||j	                         k7  r*||
j                  z  }|t        j                  d      z  }Q|
j                  D ]  }t         j                   j                  |d   d      j                         }t         j                   j                  |d   d      j                         }t         j                   j                  |||      }t         j                   j                  |||      }|j                         t        j                  dd      k(  r|t        j                  d      z  } |||||      }||z  } |t        j                  d      z  }||	k  rr|S )	u  
        Получает продолжительность рабочего времени в минутах из указанного диапазона дат

        Args:
            calendar (CmfCalendar): объект календаря
            from_dt (datetime): дата и время начала диапазона
            to_dt (datetime): дата и время конца диапазона

        Returns:
            minutes_sum (int): сумма минут
        c                 |    t        | |      }t        ||      }||k  rt        ||z
  j                         dz        S y)Nrb   r   )r   r   r%   rd   )r   r   r  r  r  r  s         r*   	intersectz3CmfCalendar.get_duration_minutes.<locals>.intersect  sA    0Ee\*Cs{C%K6682=>>rI   r   r   r   r   rE   r   r   )r'   r   r(   r%   r,   r   typer   r   r   r^   r]   )rt   r;   r   r   force_include_endsr  minutes_sumr  rG   rA   r1   force_includecur_datecur_dayr   rT   rU   r  r  inters                       r*   get_duration_minutesz CmfCalendar.get_duration_minutes  s   	 x11#h>O>O>TST:UVW||~::<h&++h5C.sLGLLN4R4rVbfkfpfpfrVrMxx6!- 2 2 :: (llf,#w||~5 H$6$6A$>>%5 H$6$6A$>>!kk(H=G llf, w||~-,%**,2Ns::: 2 2 ::MM %$--66x{GLQQS	"++44Xa['JOOQ!)!2!2!:!:<[]!:!^'0088wWY8Z$$&(--1*== H$6$6A$>>L!'5.,Ou$% H..A66LK h&N rI   durationc                    |s|S t        j                  t        j                  t        |j                  xs d                  }d}|dk  rd}t	        |      }|j                         }|dkD  r| j                  ||      }|s|j                  nt        t        |j                              }|D ]9  }	t         j                   j                  |	d   d      j                         }
t         j                   j                  |	d   d      j                         }t         j                   j                  ||
|      }t         j                   j                  |||      }|j                         t        j                  dd      k(  r|t        j                  d      z  }|st        ||      n|}|rt        ||      n|}|||k  rt        ||z
  j                         d	z        ndz  }|dk  s: n |t        j                  |sdnd
      z  }|dkD  r|r$t        j                  t	        |            z   }|S t        j                  t	        |            z
  }|S )u$  
        Рассчитывает дату и время на основе длительности от указанной даты и времени

        Args:
            calendar (CmfCalendar): объект календаря
            from_dt (Datetime): дата и время от которой производится расчет
            duration (int, optional): длительность в минутах. Defaults to 0.

        Returns:
            to_dt (Datetime): рассчитанная дата и время
        r   r   FTr   r   r   rE   rb   r2   )minutes)r'   r   r(   r%   absr,   r   r   r   reversedr   r^   r]   r   r   rd   )rt   r;   r   r  r  reverserG   r1   r   r   rT   rU   r  r  r  r  r   s                    r*   get_date_by_durationz CmfCalendar.get_date_by_durationU  s    Nx11#h>O>O>TST:UVWa<G8}H||~l++h5C-4$x?V:WI% $--66x{GLQQS	"++44Xa['JOOQ!)!2!2!:!:<[]!:!^'0088wWY8Z$$&(--1*== H$6$6A$>>L<CG^44;c'<0us{Cu ; ; = BCXYYq= H..WA"MML' l* "X%7%7H%NNE  !8#5#5c(m#LLErI   c                 r   t         j                  j                  | |j                               }t	        j
                  t	        j                  t        | j
                  xs d                  }|j                  }t        |      dz
  }|D ]  }t        j                  j                  |d   d      j                         }t        j                  j                  |d   d      j                         }t        j                  j                  |j                         ||      }	t        j                  j                  |j                         ||      }
|	j                         t	        j                  dd      k(  r)|
j                         t	        j                  dd      k(  r y|
j                         t	        j                  dd      k(  rHt        j                  j                  |
t        j                  j                  j                         |      }
||	k\  r||
k  r y||	k  r y||
k\  r|dk(  r y|dz  } y)	u=   
        Вычисляет флаг calendar_paused
        r;   r,   r   r   r   r   r   FT)rc   r   r   r,   r'   r   r(   r%   r   lenr   r^   r]   r   )r;   
check_dater1   r  r   r)   r   rT   rU   r  r  s              r*   get_sla_cycle_calendar_pausedz)CmfCalendar.get_sla_cycle_calendar_paused  s      (((AR(Sx11#h>O>O>TST:UVWMM		NQ! 	H ))228A;HMMOI''00!gFKKMG%..66z7H)\^6_N#,,44Z__5FXZ4[L""$a(;;@Q@Q@SW_WdWdefhiWj@j  "hmmAq&99'0088xGXGXG\G\GaGaGcln8o^+
\0I ~-|+6FA3	4 rI   c                    t         j                  j                  | |j                               }t	        j
                  t	        j                  t        | j
                  xs d                  }|j                  }d}t        |      dz
  }|D ]  }t        j                  j                  |d   d      j                         }t        j                  j                  |d   d      j                         }	t        j                  j                  |j                         ||      }
t        j                  j                  |j                         |	|      }|
j                         t	        j                  dd      k(  rX|j                         t	        j                  dd      k(  r1|t	        j                  d      z  }|j                  ddd	      }|} n|j                         t	        j                  dd      k(  rHt        j                  j                  |t        j                  j                  j                         |      }||
k\  r	||k  r|} n||
k  r|
} n||k\  r|dk(  r n|dz  } t        j                  j                  |t        j                  j                  j                         |      }|t	        j                  d
      z  }|st        j!                  | |      }|S )uX   
        Вычисляет next_time_update - сл дата пересчета
        r  r   r   Nr   r   r   rX   )minutesecondmicrosecondrE   r;   r  )rc   r   r   r,   r'   r   r(   r%   r   r  r   r^   r]   r7   r   r   get_sla_cycle_next_time_update)r;   r  r1   r  r   resultr)   r   rT   rU   r  r  s               r*   r&  z*CmfCalendar.get_sla_cycle_next_time_update  s      (((AR(Sx11#h>O>O>TST:UVWMM		NQ! "	H ))228A;HMMOI''00!gFKKMG%..66z7H)\^6_N#,,44Z__5FXZ4[L ""$a(;;@Q@Q@SW_WdWdefhiWj@jh00q99
'//qPQ/R
#   "hmmAq&99'0088xGXGXG\G\GaGaGcln8o^+
\0I &~-'|+6 FAE"	H &&..z8;L;L;P;P;U;U;W`b.c
h((a00
 ??^h?iFrI   z	@minutelyr   )	only_once
system_jobscheduleru   c                  >   t         j                   j                         } dd| gg dg dg}g d}d\  }}	 t        j                  j	                  |||||z   g      }|sy ||z  }|D ]  }|j
                  |_        t        j                  |j                  j                  |j
                  j                        |_        t        j                  |j                  j                  |j
                  j                        |j
                  _        |j                           )	Nnext_time_updater{   )	stop_timer   NULL)currentr   T)	sla_goalzsla_goal.calendarzsla_goal.calendar.timezonecodepause_interval_stop_timepause_interval_start_timelast_time_updater,  calendar_paused)r   d   )r   r   slicer%  )r'   r   rc   CmfSDeskSlaCycler   r,  r4  r   r   r0  r;   rm   r5  r&  r   )r   r   r   r  limitcyclescycles          r*   celery_minutely_hookz CmfCalendar.celery_minutely_hook  sB    ##%  s+&"
: u,,11W\^cfk^kVl1mFUNE ).)?)?&(3(Q(Q[`[i[i[r[r  @E  @V  @V  @\  @\(Q  )]%/:/Y/Ychcqcqczcz  HM  H^  H^  Hd  Hd/Y  0e&&,

 rI   c                   & ddl m} ddlm} ddd}t        j                  j                         j                  }||dz   f}|D ]k  } |t        j                  dd	t        |            j                  d
      }|j                         sFt        |dd      5 }t        j                  |j                               }	ddd       	j!                  d      }
||
k7  rt"        j%                  d|
 d| d|        g }|	j!                  d      D ci c]  }|d   |d    }}|	j!                  d      }|	j!                  d      }|D ]   }|j!                  d      &t        j                  j'                  |
 d& d      j)                         }|j*                   dt,        |j.                      d|j                   }t0        j2                  j5                  dd| gg dg dd d!d|gd"d|gggd#$      }|r|j!                  d%      }|j!                  d&      }|r|j!                  |      }n|j!                  |      }d}t7        t9        &fd'|      d      }t7        t9        &fd(|      d      }|r|d)   }n|r|d*   }|rft        j                  j'                  |
 d| d      j)                         }|d+|j*                   dt,        |j.                      d|j                   z  }d,|d-d.| j:                   }d/|| d0| d1|||j.                  |j*                  |j=                         |j*                  dz
  d2z  d3d4
}t0        j2                  j!                  d5d6d7| gd8gd39      }|st0        j3                  | :      }|j?                         D ]  \  }}tA        |||        |jB                  rd#|_!        |jD                  r|jG                  d3;       |dk7  r|jI                  d<g       | jI                  d=g       | jJ                  }rj=                         }n|j=                         }|jL                  } t0        jN                  jQ                  ||d8g>      }! || |!      }"|"D ]{  \  }#}$|$rL|#st0        jS                  |:      }#|$jT                  |#_*        |$jV                  |#_+        |$jX                  |#_,        n	|#rd3|#_!        |#jD                  sj|#jG                  d3;       } n<|jI                  d?g       |jL                  D ]  }#d3|#_!        |#jG                  d3;        |j[                          |j]                          |jG                  d3;       |j_                  |       # t0        j2                  jQ                  d5d@|gd5d6d,| dAgg dBgC      }%|%D ]  }|ja                  d3D        n | jI                  dEg       | jc                          | jG                  d3;       y# 1 sw Y   .xY wc c}w )Fu  
        Метод для кнопки "Загрузить праздники".
        Добавляет в исключения календаря выходные/празничные дни из файла в формате JSON.
        Исключения добавляются на текущий и следующий год (при наличии соответствующих файлов).
            - файлы с данными находятся в каталоге /opt/eva-app/cmf/contrib/calendar/
            - имя файла должено содержать только год (например, 2024.json)
        Добавления не происходит, если пользователь уже добавил исключение на такую же дату.
        При повторном выполнении метода происходит перезагрузка/восстановление/удаление,
        если поменялись данные в файле.
        r   )zip_longest)Pathu   Выходной деньu   Рабочий день)r   r   r   contribr;   z.jsonrzutf-8)encodingNr5   u   Год (uL   ) указанный в файле не соответствует году u    в имени файла holidaysidr   	transfersr   r,   .z%Y.%m.%d r~   r   )rp   r   rH   )rC   r   r   r   rl   rn   F)r   is_autor  holidayc                 ,    | j                  d      k(  S )Nfromr   itemdate_s    r*   <lambda>z+CmfCalendar.load_holidays.<locals>.<lambda>T  s    TXXf=MQV=V rI   c                 ,    | j                  d      k(  S )NtorL  rM  s    r*   rP  z+CmfCalendar.load_holidays.<locals>.<lambda>U  s    488D>U;R rI   rR  rK  u    за exclude_z%Y_%m_%d:rH   z ()r   T)
rp   r1  r   rl   rn   r6   r-   rr   rq   rH  r1  LIKE%r   )r   r   include_deleted)r~   r   rx   r   )r~   r   r   r   zNOT INz_%)rH  r   T)r   )r   r   )2	itertoolsr>  pathlibr?  r'   r   r5   config
CMF_FOLDERstrwith_suffixexistsopenjsonloadsreadr   loggingwarningr   r,   r1   MONTHS_SHORTr6   rc   r   r   nextr   r1  rL   r   r   cmf_deletedr   r   r   r   r   CmfCalendarWorkWeekIntervalr   CmfCalendarExcludeIntervalrT   rU   interval_minutes_calc_intervals_total_minutes_calc_exclude_typerF   delete_set_as_dirty)'r   r>  r?  	day_typesr   r   r5   	file_pathfcalendar_datacalendar_yearexclude_codesrI  rC  rE  r   	date_datar,   date_strexcluder   r   transfertransfer_fromtransfer_totransfer_dater1  exclude_datafieldrm   r   rL   exclude_intervalsworkweek_intervalsr   exclude_intervalworkweek_intervalexcludesrO  s'                                         @r*   r   zCmfCalendar.load_holidays  sR    	*  +(
	
  ((,,.33|a/0 V	+DV..	:s4yQ]]^efI##%iw7 51 $

1668 45 *--f5M}$(=/ :MMQFRlmvlw!y zMFSFWFWXbFcd7wv6dHd%))+6I!%%g.E" x+	!f-((11]O1UG2LjY^^`"hhZqdjj)A(B!DII;O !3388!3-/PRd 3S$?BSUXZ^A_` " 9   %==0#--	2#<<0D$==2D#H$(0VXa)bdh$iM"&v.RT]'^`d"eK$#0#6$#.v#6(0(9(9(B(B,oQxj9:)GGKtv & f]->->,?qmNaNaAb@ccderewewdx$yy "$x$))= $/ #fBxj2)-'+!ZZ!%&*lln,0HHqLQ+>#  !3377"FavJ75$( 8 
 $77t7DG$0$6$6$8 3LE5GUE23 &&*/G'%%LL4L0q= ''8$$&8%9:'+'<'<$$"/"7"7"9"&,,.(/(9(9%)/)K)K)P)P/!( #u *Q *& !,,=?Q RI?H B;(*;,#3393T3T\c3T3d 09J9T9T,67H7P7P,4@Q@b@b,=-;?,8+66,11D1AB ''6,3,=,= >(7;(4(---=> 557**,t,$$T*qx+x 0055=18D6!45&>5 H
 $ +T*+kV	+p 	'#		D	!i5 5 es   $W(W5(W2	c                     d| _         y)u   
        Помечает, что календарь необходимо пересчитать и сбрасывает кэш дней
        TN)r   r   s    r*   ro  zCmfCalendar._set_as_dirty  s     
rI   c                 Z   | j                   j                  sy| j                   st        dd       d| j                   d}t        j
                  j                  dd| j                  gg d	gd
g      }|r+d|_         |j                  d       d|j                   d| }t        |       y)uX   
        Изменяет флаг "Календарь по умолчанию"
        Nu   Для сброса флага "Календарь по умолчанию" вы должны установить его у любого другого календаря.TrZ   uf   Во всех новых проектах будет использоваться календарь ""rD  rz   )
is_defaultr   Tr  r   Fr   u^   Сбросили флаг "Календарь по умолчанию" у календаря "z". )	r  r   r_   r   rc   r   r   rD  r   )r   msgcurrent_default_calendars      r*   _set_is_defaultzCmfCalendar._set_is_default  s     )) z BFG ww{  xA  xA  wB  BC  D#)#5#5#9#9tTWW%) !> $: $
  $27$/$))D)9..F.K.K-LCPSuVC#rI   c                     | j                   j                  rd| _         | j                  j                  rd| _        | j                  j                  rd| _        yy)u}   
        Исправляет нулевые значения, если приходит null выставляет 0
        r   N)r   is_nullr   r   r  s    r*   fix_null_valuezCmfCalendar.fix_null_value  sK     ==  DM&&"#D##++'(D$ ,rI   c                 *    t         |          g dz   S )N)r   r   r   )supersave_preload_fields)r   	__class__s    r*   r  zCmfCalendar.save_preload_fields  s    w*,/dddrI   c                    | j                   rst        | dd       rt        j                  j	                  | j
                  g d      }|j                  | _        |j                  | _        |j                  | _        |j                  D ]  }|j                         }| |_        d|_        |j                  d       |j                  dg       |j                  D ]+  }|j                         }||_        |j                  d       - |j                   r|j"                  r|| _         |j&                  D ]w  }|j                         }	| |	_        |	j                  d       |j                  dg       |j                  D ]+  }|j                         }|	|_        |j                  d       - y nt(        j(                  j+                         j-                         j.                  }
t1        t(        j(                  j+                  |
      j3                         j5                               | _        t        j7                  | d	      }t9        d
      D ]  }t;        |d| dd        |j                  d       || _        | j=                          | j?                          t1        | j                        dkD  rtA        dd       t1        | j                        dk  rtA        dd       t1        | j                        dkD  rtA        dd       t1        | j                        dk  rtA        dd       | j                  jB                  s| j                  jB                  r| jE                          tG        | 4  |i |S )Ntemplate)r   zworkweeks.*zexclusions.*)rD  r   FTr   rx   u   По умолчанию)r~   r   r   r1   r   weekend   u\   Количество лет расчета не должно быть больше 30 летrZ   r   u]   Количество лет расчета не должно быть меньше 1 годаr   un   Количество лет расчета в прошлое не должно быть больше 10 летr   uf   Количество лет расчета в прошлое не должно быть меньше 0)$is_newrs   rc   r   r   r  r   r   r   r   cloner~   systemr   r   r   rl   rn   r   r   r'   r   
astimezoner   r%   	utcoffsetrd   r   r$   r   r  r  r_   r   ro  r  )r   argskwargstemplate_calendartemplate_workweeknew_workweektemplate_intervalnew_intervaltemplate_exclusionnew_exclusionr  r   r)   r  s                r*   r   zCmfCalendar.save  s;   ;;tZ.$*$6$6$:$:}}? %; %! !2 : :&7&F&F#+<+P+P():)D)D =%#4#:#:#<L*.L'*/L' %%%5%11=/B->-H-H :)'8'>'>'@.:+$))D)9:
 (99,B^B^0<-= +<*F*F 	:&$6$<$<$>M+/M(!&&&6&22M?C-?-I-I :)'8'>'>'@.;+$))D)9:	: &&**,779@@ #H$5$5$9$9"$=$G$G$I$W$W$Y Z "55THa5bq AAHA3eni@A-(0%t""#b(t  }A  Bt""#a'u  ~B  Ct''(2-  G  OS  Tt''(1,~  GK  L))T-E-E-P-P w|T,V,,rI   c           	   #     K   d }d}|}t        g       }d }d}	d}
	 |swddd|gg dgg}|r.|	s|j                  dd	|g       |r|
s|j                  dd	|g       |j                  t        j                  j                  d
d	| g|gg ddgddg             |s\| j                  j                         }d }|sd| j                   d}|S d| j                   d| j                  j                          }|S |j                         }|j                  |k(  r;|j                  dk7  r* ||      }|j                  |_        |j                  |_        d}	|j                  |k(  r`|j                  dk7  rO|r#|j                  |_        |j                  |_        n* ||      }|j                  |_        |j                  |_        d}
|j                  dk(  r|}| t        j                  |j                  j                   t        j"                  j%                               t'        j(                  d      z   j                         }|dz  }'w)Nc                 h    t        t        |             D ]  }| |   j                  dk(  s| |   c S  y )Nr   )r$   r  r   )bufferr)   s     r*   _get_next_work_dayz1CmfCalendar.work_days.<locals>._get_next_work_day4  s6    3v;' %!9%%/!!9$%rI   r   FTr   r,   r|   )r   ==r   r  r~   )r,   r   r   r   zparent.timezoner   )r   r   order_byr7  u   Календарь "u   " не рассчитан.u   " рассчитан до r   r   rE   )r   rF   r   rc   r   r   r   loadr   popleftr,   r   r   r   Datetimer]   rm   r   r^   r'   r(   )r   r@   r  finish_dater  n	next_dater  prev_work_daystart_date_givenfinish_date_givendate_filterr   messager  next_work_days                   r*   r  zCmfCalendar.work_days2  sQ    	%
 	r !#vtY&?A[%\]%+#**FD*+EF"+<#**FD++FG))..!)4 6Dq"( "g	 /  !__113
! 4TYYK?YZG  !5TYYK?YZ^ZiZiZnZnZpYqrG~~'H}}
*$$.$6v$>M-:-H-HH*6C6Z6ZH3#' }}+$$.$1>1L1L.:G:^:^7(:6(B1>1L1L.:G:^:^7$(!  F* (N!))(--*=*=x||?P?P?RSV^VhVhnoVppvvxIFAo s   IIc           
         | j                  dg       | j                  rt        dd       t        j                  j                  | dg      }|r=dj                  |D cg c]  }d|j                   d c}      }t        d	| d       t        j                  j                  | 
      }|rt        d| dd       t        j                  j                  | ddg      }|rTdj                  |D cg c])  }d|j                  j                   d|j                   d+ c}      }	t        d|	 d       t        
| 4  |i |S c c}w c c}w )Nr  u   Нельзя удалить календарь по умолчанию. Сначала назначьте любой другой календарь, как календарь по умолчанию.TrZ   r   )r;   r   z, r  uk   Невозможно удалить календарь, он используется в проектах: )r;   uY   Невозможно удалить календарь, он используется у u    пользователейzparent.namez: ue   Невозможно удалить календарь, он используется в целях: )r   r  r_   rc   
CmfProjectr   joinr   	CmfPersoncountCmfSDeskSlaGoalr~   r  rn  )r   r  r  projectsprojectprojects_strperson_count	sla_goalsslasla_goals_strr  s             r*   rn  zCmfCalendar.deletey  st   ,(?? Z " $$))4)I99%Rg',,q&9%RSL  D  EQ  DR  S "''--t-<qr~q  @[  \ " **//v}F]/^	 IIU^&_c3::??*;2chhZq'I&_`M}  L  ~M  N " w~t.v.. &S '`s    E	5.E)NNN)NNr   )NNr   N)NNr   NNN)NNNNNN)NN)NNTr   )NF)F)r   )>__name__
__module____qualname__r   r   ui_meta_skipapi_methodsstaticmethodDater   r+   r%   r?   r   rH   rM   rP   rS   Timer	   r  r`   re   classmethodrv   r   r   boolr   r   r   r
   r]  cmfr   CmfTyper   r   on_server_eventr   r   r   r   r   r   r   r	  r  r  r   r&  cmf_deferred_jobr<  r   ro  r  r  r  r   r  rn  __classcell__)r  s   @r*   r   r      s@   ++88<GL**66:
 
K
 5 5 5$ 5 5 CGOSD S 03ILX\ < JN !d d C &*4j B _`)-(t (t (S (Y\ (!#Y(26t*( (T LP6:=A1 1 1c 11031!17:1FJ4j1 1f KO7;<@-t -t -S --14- -69-EI$Z- -^ 2604"4 ")-"9>x?Q9R" "6 : :d :c : :$ ?+F ?5QTVZ[_V`Q`Ka ? ?B K= K K K KZ FJ04,= ,(,,?C,)-,9A, ,. $LMG/uS-9K9K%KL /TX /]d / / . .
 23: 4 :
 tLc L L' L  L, >BS ] S !#sD(":;S GOS  S j,"# ,"T ,"h ,"\,( ,8 , ,\ 05DM DH DU] D:=D DL @A2M 2&.2:=2FN2 2h & &P 7 7r VXY Z :p"d4)eB-HEN/ /rI   r   )r;   r3   r'   r^   	functoolsr   collectionsr   r   typingr   r   r   r	   r
   r   pytzcmf.includecommon.fieldsr   dateutil.relativedeltar   r   rf  r  r,   r  r  r    rI   r*   <module>r     s        ) 9 9 
   & 4 HHHhH	 }}}}v/,** v/rI   