docs: def fromISO8601TimeAndDate(klass, iso8601string, tzinfo=None):
          """Return a new Time instance from a string formated as in ISO 8601.
  
          If the given string contains no timezone, it is assumed to be in the
          timezone specified by the parameter `tzinfo`, or UTC if tzinfo is None.
          An input string with an explicit timezone will always override tzinfo.
  
          If the given iso8601string does not contain all parts of the time, they
          will default to 0 in the timezone given by `tzinfo`.
  
          WARNING: this function is incomplete. ISO is dumb and their standards
          are not free. Only a subset of all valid ISO 8601 dates are parsed,
          because I can't find a formal description of the format. However,
          common ones should work.
          """
  
          def calculateTimezone():
              if groups['zulu'] == 'Z':
                  return FixedOffset(0, 0)
              else:
                  tzhour = groups.pop('tzhour')
                  tzmin = groups.pop('tzmin')
                  if tzhour is not None:
                      return FixedOffset(int(tzhour), int(tzmin or 0))
              return tzinfo or FixedOffset(0, 0)
  
          def coerceGroups():
              groups['month'] = groups['month1'] or groups['month2']
              groups['week'] = groups['week1'] or groups['week2']
              # don't include fractional seconds, because it's not an integer.
              defaultTo0 = ['hour', 'minute', 'second']
              defaultTo1 = ['month', 'day', 'week', 'weekday', 'dayofyear']
              if groups['fractionalsec'] is None:
                  groups['fractionalsec'] = '0'
              for key in defaultTo0:
                  if groups[key] is None:
                      groups[key] = 0
              for key in defaultTo1:
                  if groups[key] is None:
                      groups[key] = 1
              groups['fractionalsec'] = float('.'+groups['fractionalsec'])
              for key in defaultTo0 + defaultTo1 + ['year']:
                  groups[key] = int(groups[key])
  
              for group, min, max in [
                  # some years have only 52 weeks
                  ('week', 1, 53),
                  ('weekday', 1, 7),
                  ('month', 1, 12),
                  ('day', 1, 31),
                  ('hour', 0, 24),
                  ('minute', 0, 59),
  
                  # Sometime in the 22nd century AD, two leap seconds will be
                  # required every year.  In the 25th century AD, four every
                  # year.  We'll ignore that for now though because it would be
                  # tricky to get right and we certainly don't need it for our
                  # target applications.  In other words, post-singularity
                  # Martian users, please do not rely on this code for
                  # compatibility with Greater Galactic Protectorate of Earth
                  # date/time formatting!  Apologies, but no library I know of in
                  # Python is sufficient for processing their dates and times
                  # without ADA bindings to get the radiation-safety zone counter
                  # correct. -glyph
  
                  ('second', 0, 61),
                  # don't forget leap years
                  ('dayofyear', 1, 366)]:
                  if not min <= groups[group] <= max:
                      raise ValueError, '%s must be in %i..%i' % (group, min, max)
  
          def determineResolution():
              if match.group('fractionalsec') is not None:
                  return max(datetime.timedelta.resolution,
                      datetime.timedelta(
                          microseconds=1 * 10 ** -len(
                              match.group('fractionalsec')) * 1000000))
  
              for testGroup, resolution in [
              ('second', datetime.timedelta(seconds=1)),
              ('minute', datetime.timedelta(minutes=1)),
              ('hour', datetime.timedelta(hours=1)),
              ('weekday', datetime.timedelta(days=1)),
              ('dayofyear', datetime.timedelta(days=1)),
              ('day', datetime.timedelta(days=1)),
              ('week1', datetime.timedelta(weeks=1)),
              ('week2', datetime.timedelta(weeks=1))]:
                  if match.group(testGroup) is not None:
                      return resolution
  
              if match.group('month1') is not None \
              or match.group('month2') is not None:
                  if self._time.month == 12:
                      return datetime.timedelta(days=31)
                  nextMonth = self._time.replace(month=self._time.month+1)
                  return nextMonth - self._time
              else:
                  nextYear = self._time.replace(year=self._time.year+1)
                  return nextYear - self._time
  
          def calculateDtime(tzinfo):
              """Calculate a datetime for the start of the addressed period."""
  
              if match.group('week1') is not None \
              or match.group('week2') is not None:
                  if not 0 < groups['week'] <= 53:
                      raise ValueError(
                          'week must be in 1..53 (was %i)' % (groups['week'],))
                  dtime = datetime.datetime(
                      groups['year'],
                      1,
                      4,
                      groups['hour'],
                      groups['minute'],
                      groups['second'],
                      int(round(groups['fractionalsec'] * 1000000)),
                      tzinfo=tzinfo
                  )
                  dtime -= datetime.timedelta(days = dtime.weekday())
                  dtime += datetime.timedelta(
                      days = (groups['week']-1) * 7 + groups['weekday'] - 1)
                  if dtime.isocalendar() != (
                      groups['year'], groups['week'], groups['weekday']):
                      # actually the problem could be an error in my logic, but
                      # nothing should cause this but requesting week 53 of a
                      # year with 52 weeks.
                      raise ValueError('year %04i has no week %02i' %
                                       (groups['year'], groups['week']))
                  return dtime
  
              if match.group('dayofyear') is not None:
                  dtime = datetime.datetime(
                      groups['year'],
                      1,
                      1,
                      groups['hour'],
                      groups['minute'],
                      groups['second'],
                      int(round(groups['fractionalsec'] * 1000000)),
                      tzinfo=tzinfo
                  )
                  dtime += datetime.timedelta(days=groups['dayofyear']-1)
                  if dtime.year != groups['year']:
                      raise ValueError(
                          'year %04i has no day of year %03i' %
                          (groups['year'], groups['dayofyear']))
                  return dtime
  
              else:
                  return datetime.datetime(
                      groups['year'],
                      groups['month'],
                      groups['day'],
                      groups['hour'],
                      groups['minute'],
                      groups['second'],
                      int(round(groups['fractionalsec'] * 1000000)),
                      tzinfo=tzinfo
                  )
  
  
          match = klass.iso8601pattern.match(iso8601string)
          if match is None:
              raise ValueError(
                  '%r could not be parsed as an ISO 8601 date and time' %
                  (iso8601string,))
  
          groups = match.groupdict()
          coerceGroups()
          if match.group('hour') is not None:
              timezone = calculateTimezone()
          else:
              timezone = None
          self = klass.fromDatetime(calculateDtime(timezone))
          self.resolution = determineResolution()
          return self