def init(num_expanded_year_digits=0, custom_dump_format=None, time_zone=None, assume_utc=False, cycling_mode=None): """Initialise workflow-setup-specific information.""" if cycling_mode in Calendar.default().MODES: Calendar.default().set_mode(cycling_mode) if time_zone is None: if assume_utc: time_zone = "Z" time_zone_hours_minutes = (0, 0) else: time_zone = get_local_time_zone_format(TimeZoneFormatMode.reduced) time_zone_hours_minutes = get_local_time_zone() else: time_zone_hours_minutes = TimePointDumper().get_time_zone(time_zone) WorkflowSpecifics.ASSUMED_TIME_ZONE = time_zone_hours_minutes WorkflowSpecifics.NUM_EXPANDED_YEAR_DIGITS = num_expanded_year_digits if custom_dump_format is None: if num_expanded_year_digits > 0: WorkflowSpecifics.DUMP_FORMAT = ( EXPANDED_DATE_TIME_FORMAT + time_zone ) else: WorkflowSpecifics.DUMP_FORMAT = DATE_TIME_FORMAT + time_zone else: WorkflowSpecifics.DUMP_FORMAT = custom_dump_format if "+X" not in custom_dump_format and num_expanded_year_digits: raise IllegalValueError( 'cycle point format', ('cylc', 'cycle point format'), WorkflowSpecifics.DUMP_FORMAT ) WorkflowSpecifics.iso8601_parsers = CylcTimeParser.initiate_parsers( dump_format=WorkflowSpecifics.DUMP_FORMAT, num_expanded_year_digits=num_expanded_year_digits, assumed_time_zone=WorkflowSpecifics.ASSUMED_TIME_ZONE ) (WorkflowSpecifics.point_parser, WorkflowSpecifics.interval_parser, WorkflowSpecifics.recurrence_parser) = WorkflowSpecifics.iso8601_parsers WorkflowSpecifics.abbrev_util = CylcTimeParser( None, None, WorkflowSpecifics.iso8601_parsers )
def setUp(self): self._start_point = "19991226T0930Z" # Note: the following timezone will be Z-ified *after* truncation # or offsets are applied. self._end_point = "20010506T1200+0200" self._parsers = { 0: CylcTimeParser( self._start_point, self._end_point, CylcTimeParser.initiate_parsers( assumed_time_zone=UTC_UTC_OFFSET_HOURS_MINUTES)), 2: CylcTimeParser( self._start_point, self._end_point, CylcTimeParser.initiate_parsers( num_expanded_year_digits=2, assumed_time_zone=UTC_UTC_OFFSET_HOURS_MINUTES)) }
def setUp(self): self._start_point = "19991226T0930Z" # Note: the following timezone will be Z-ified *after* truncation # or offsets are applied. self._end_point = "20010506T1200+0200" self._parsers = { 0: CylcTimeParser( self._start_point, self._end_point, CylcTimeParser.initiate_parsers( assumed_time_zone=UTC_UTC_OFFSET_HOURS_MINUTES ) ), 2: CylcTimeParser( self._start_point, self._end_point, CylcTimeParser.initiate_parsers( num_expanded_year_digits=2, assumed_time_zone=UTC_UTC_OFFSET_HOURS_MINUTES ) ) }
def __init__(self, dep_section, context_start_point=None, context_end_point=None): SequenceBase.__init__(self, dep_section, context_start_point, context_end_point) self.dep_section = dep_section if context_start_point is None: self.context_start_point = context_start_point elif isinstance(context_start_point, ISO8601Point): self.context_start_point = context_start_point else: self.context_start_point = ISO8601Point.from_nonstandard_string( context_start_point) if context_end_point is None: self.context_end_point = None elif isinstance(context_end_point, ISO8601Point): self.context_end_point = context_end_point else: self.context_end_point = ISO8601Point.from_nonstandard_string( context_end_point) self.offset = ISO8601Interval.get_null() self._cached_first_point_values = {} self._cached_next_point_values = {} self._cached_valid_point_booleans = {} self._cached_recent_valid_points = [] self.spec = dep_section self.abbrev_util = CylcTimeParser(self.context_start_point, self.context_end_point, WorkflowSpecifics.iso8601_parsers) # Parse_recurrence returns an isodatetime TimeRecurrence object # and a list of exclusion strings. self.recurrence, excl_points = self.abbrev_util.parse_recurrence( dep_section) # Determine the exclusion start point and end point try: exclusion_start_point = ISO8601Point.from_nonstandard_string( str(self.recurrence.start_point)) except IsodatetimeError: exclusion_start_point = self.context_start_point try: exclusion_end_point = ISO8601Point.from_nonstandard_string( str(self.recurrence.end_point)) except IsodatetimeError: exclusion_end_point = self.context_end_point self.exclusions = [] # Creating an exclusions object instead if excl_points: try: self.exclusions = ISO8601Exclusions(excl_points, exclusion_start_point, exclusion_end_point) except AttributeError: pass self.step = ISO8601Interval(str(self.recurrence.duration)) self.value = str(self.recurrence) # Concatenate the strings in exclusion list if self.exclusions: self.value += '!' + str(self.exclusions)
class ISO8601Sequence(SequenceBase): """A sequence of ISO8601 date time points separated by an interval. Note that an ISO8601Sequence object (may) contain ISO8601ExclusionSequences""" TYPE = CYCLER_TYPE_ISO8601 TYPE_SORT_KEY = CYCLER_TYPE_SORT_KEY_ISO8601 _MAX_CACHED_POINTS = 100 __slots__ = ('dep_section', 'context_start_point', 'context_end_point', 'offset', '_cached_first_point_values', '_cached_next_point_values', '_cached_valid_point_booleans', '_cached_recent_valid_points', 'spec', 'abbrev_util', 'recurrence', 'exclusions', 'step', 'value') @classmethod def get_async_expr(cls, start_point=None): """Express a one-off sequence at the initial cycle point.""" if start_point is None: return "R1" return "R1/" + str(start_point) def __init__(self, dep_section, context_start_point=None, context_end_point=None): SequenceBase.__init__(self, dep_section, context_start_point, context_end_point) self.dep_section = dep_section if context_start_point is None: self.context_start_point = context_start_point elif isinstance(context_start_point, ISO8601Point): self.context_start_point = context_start_point else: self.context_start_point = ISO8601Point.from_nonstandard_string( context_start_point) if context_end_point is None: self.context_end_point = None elif isinstance(context_end_point, ISO8601Point): self.context_end_point = context_end_point else: self.context_end_point = ISO8601Point.from_nonstandard_string( context_end_point) self.offset = ISO8601Interval.get_null() self._cached_first_point_values = {} self._cached_next_point_values = {} self._cached_valid_point_booleans = {} self._cached_recent_valid_points = [] self.spec = dep_section self.abbrev_util = CylcTimeParser(self.context_start_point, self.context_end_point, WorkflowSpecifics.iso8601_parsers) # Parse_recurrence returns an isodatetime TimeRecurrence object # and a list of exclusion strings. self.recurrence, excl_points = self.abbrev_util.parse_recurrence( dep_section) # Determine the exclusion start point and end point try: exclusion_start_point = ISO8601Point.from_nonstandard_string( str(self.recurrence.start_point)) except IsodatetimeError: exclusion_start_point = self.context_start_point try: exclusion_end_point = ISO8601Point.from_nonstandard_string( str(self.recurrence.end_point)) except IsodatetimeError: exclusion_end_point = self.context_end_point self.exclusions = [] # Creating an exclusions object instead if excl_points: try: self.exclusions = ISO8601Exclusions(excl_points, exclusion_start_point, exclusion_end_point) except AttributeError: pass self.step = ISO8601Interval(str(self.recurrence.duration)) self.value = str(self.recurrence) # Concatenate the strings in exclusion list if self.exclusions: self.value += '!' + str(self.exclusions) def get_interval(self): """Return the interval between points in this sequence.""" return self.step def get_offset(self): """Deprecated: return the offset used for this sequence.""" return self.offset def set_offset(self, i_offset): """Deprecated: alter state to i_offset the entire sequence.""" if self.recurrence.start_point is not None: self.recurrence.start_point += interval_parse(str(i_offset)) if self.recurrence.end_point is not None: self.recurrence.end_point += interval_parse(str(i_offset)) self._cached_first_point_values = {} self._cached_next_point_values = {} self._cached_valid_point_booleans = {} self._cached_recent_valid_points = [] self.value = str(self.recurrence) + '!' + str(self.exclusions) if self.exclusions: self.value += '!' + str(self.exclusions) @lru_cache(100) def is_on_sequence(self, point): """Return True if point is on-sequence.""" # Iterate starting at recent valid points, for speed. if self.exclusions and point in self.exclusions: return False for valid_point in reversed(self._cached_recent_valid_points): if valid_point == point: return True if valid_point > point: continue next_point = valid_point while next_point is not None and next_point < point: next_point = self.get_next_point_on_sequence(next_point) if next_point is None: continue if next_point == point: return True return self.recurrence.get_is_valid(point_parse(point.value)) def is_valid(self, point): """Return True if point is on-sequence and in-bounds.""" try: return self._cached_valid_point_booleans[point.value] except KeyError: is_valid = self.is_on_sequence(point) if (len(self._cached_valid_point_booleans) > self._MAX_CACHED_POINTS): self._cached_valid_point_booleans.popitem() self._cached_valid_point_booleans[point.value] = is_valid return is_valid def get_prev_point(self, point): """Return the previous point < point, or None if out of bounds.""" # may be None if out of the recurrence bounds res = None prev_point = self.recurrence.get_prev(point_parse(point.value)) if prev_point: res = ISO8601Point(str(prev_point)) if res == point: raise SequenceDegenerateError(self.recurrence, WorkflowSpecifics.DUMP_FORMAT, res, point) # Check if res point is in the list of exclusions # If so, check the previous point by recursion. # Once you have found a point that is *not* in the exclusion # list, you can return it. if self.exclusions and res in self.exclusions: return self.get_prev_point(res) return res def get_nearest_prev_point(self, point): """Return the largest point < some arbitrary point.""" if self.is_on_sequence(point): return self.get_prev_point(point) p_iso_point = point_parse(point.value) prev_cycle_point = None for recurrence_iso_point in self.recurrence: # Is recurrence point greater than arbitrary point? if recurrence_iso_point > p_iso_point: break recurrence_cycle_point = ISO8601Point(str(recurrence_iso_point)) if self.exclusions and recurrence_cycle_point in self.exclusions: break prev_cycle_point = recurrence_cycle_point if prev_cycle_point is None: return None if prev_cycle_point == point: raise SequenceDegenerateError(self.recurrence, WorkflowSpecifics.DUMP_FORMAT, prev_cycle_point, point) # Check all exclusions if self.exclusions and prev_cycle_point in self.exclusions: return self.get_prev_point(prev_cycle_point) return prev_cycle_point def get_next_point(self, point): """Return the next point > p, or None if out of bounds.""" try: return ISO8601Point(self._cached_next_point_values[point.value]) except KeyError: pass # Iterate starting at recent valid points, for speed. for valid_point in reversed(self._cached_recent_valid_points): if valid_point >= point: continue next_point = valid_point excluded = False while next_point is not None and (next_point <= point or excluded): excluded = False next_point = self.get_next_point_on_sequence(next_point) if next_point and next_point in self.exclusions: excluded = True if next_point is not None: self._check_and_cache_next_point(point, next_point) return next_point # Iterate starting at the beginning. p_iso_point = point_parse(point.value) for recurrence_iso_point in self.recurrence: if recurrence_iso_point > p_iso_point: next_point = ISO8601Point(str(recurrence_iso_point)) if next_point and next_point in self.exclusions: continue self._check_and_cache_next_point(point, next_point) return next_point return None def _check_and_cache_next_point(self, point, next_point): """Verify and cache the get_next_point return info.""" # Verify next_point != point. if next_point == point: raise SequenceDegenerateError(self.recurrence, WorkflowSpecifics.DUMP_FORMAT, next_point, point) # Cache the answer for point -> next_point. if (len(self._cached_next_point_values) > self._MAX_CACHED_POINTS): self._cached_next_point_values.popitem() self._cached_next_point_values[point.value] = next_point.value # Cache next_point as a valid starting point for this recurrence. if (len(self._cached_next_point_values) > self._MAX_CACHED_POINTS): self._cached_recent_valid_points.pop(0) self._cached_recent_valid_points.append(next_point) def get_next_point_on_sequence(self, point): """Return the on-sequence point > point assuming that point is on-sequence, or None if out of bounds.""" result = None next_point = self.recurrence.get_next(point_parse(point.value)) if next_point: result = ISO8601Point(str(next_point)) if result == point: raise SequenceDegenerateError(self.recurrence, WorkflowSpecifics.DUMP_FORMAT, point, result) # Check it is in the exclusions list now if result and result in self.exclusions: return self.get_next_point_on_sequence(result) return result def get_first_point(self, point): """Return the first point >= to point, or None if out of bounds.""" try: return ISO8601Point(self._cached_first_point_values[point.value]) except KeyError: pass p_iso_point = point_parse(point.value) for recurrence_iso_point in self.recurrence: if recurrence_iso_point >= p_iso_point: first_point_value = str(recurrence_iso_point) ret = ISO8601Point(first_point_value) # Check multiple exclusions if ret and ret in self.exclusions: return self.get_next_point_on_sequence(ret) if (len(self._cached_first_point_values) > self._MAX_CACHED_POINTS): self._cached_first_point_values.popitem() self._cached_first_point_values[point.value] = ( first_point_value) return ret return None def get_start_point(self): """Return the first point in this sequence, or None.""" for recurrence_iso_point in self.recurrence: point = ISO8601Point(str(recurrence_iso_point)) # Check for multiple exclusions if not self.exclusions or point not in self.exclusions: return point return None def get_stop_point(self): """Return the last point in this sequence, or None if unbounded.""" if (self.recurrence.repetitions is not None or ((self.recurrence.start_point is not None or self.recurrence.min_point is not None) and (self.recurrence.end_point is not None or self.recurrence.max_point is not None))): curr = None prev = None for recurrence_iso_point in self.recurrence: prev = curr curr = recurrence_iso_point ret = ISO8601Point(str(curr)) if self.exclusions and ret in self.exclusions: return ISO8601Point(str(prev)) return ret return None def __eq__(self, other): # Return True if other (sequence) is equal to self. if self.TYPE != other.TYPE: return False if self.value == other.value: return True return False def __lt__(self, other): return self.value < other.value def __str__(self): return self.value def __hash__(self) -> int: return hash(self.value)
def __init__(self, dep_section, context_start_point=None, context_end_point=None): SequenceBase.__init__(self, dep_section, context_start_point, context_end_point) self.dep_section = dep_section # cache is_on_sequence # see B019 - https://github.com/PyCQA/flake8-bugbear#list-of-warnings self.is_on_sequence = lru_cache(maxsize=100)(self._is_on_sequence) if (context_start_point is None or isinstance(context_start_point, ISO8601Point)): self.context_start_point = context_start_point else: self.context_start_point = ISO8601Point.from_nonstandard_string( context_start_point) if context_end_point is None: self.context_end_point = None elif isinstance(context_end_point, ISO8601Point): self.context_end_point = context_end_point else: self.context_end_point = ISO8601Point.from_nonstandard_string( context_end_point) self.offset = ISO8601Interval.get_null() self._cached_first_point_values = {} self._cached_next_point_values = {} self._cached_valid_point_booleans = {} self._cached_recent_valid_points = [] self.spec = dep_section self.abbrev_util = CylcTimeParser(self.context_start_point, self.context_end_point, WorkflowSpecifics.iso8601_parsers) # Parse_recurrence returns an isodatetime TimeRecurrence object # and a list of exclusion strings. self.recurrence, excl_points = self.abbrev_util.parse_recurrence( dep_section) # Determine the exclusion start point and end point try: exclusion_start_point = ISO8601Point.from_nonstandard_string( str(self.recurrence.start_point)) except IsodatetimeError: exclusion_start_point = self.context_start_point try: exclusion_end_point = ISO8601Point.from_nonstandard_string( str(self.recurrence.end_point)) except IsodatetimeError: exclusion_end_point = self.context_end_point self.exclusions = [] # Creating an exclusions object instead if excl_points: with contextlib.suppress(AttributeError): self.exclusions = ISO8601Exclusions(excl_points, exclusion_start_point, exclusion_end_point) self.step = ISO8601Interval(str(self.recurrence.duration)) self.value = str(self.recurrence) # Concatenate the strings in exclusion list if self.exclusions: self.value += '!' + str(self.exclusions)