docs: def validate_analysis_period(self):
          """Get a collection where the header analysis_period aligns with datetimes.
  
          This means that checks for five criteria will be performed:
          1) All datetimes in the data collection are in chronological orderstarting
              from the analysis_period start hour to the end hour.
          2) No duplicate datetimes exist in the data collection.
          3) There are no datetimes that lie outside of the analysis_period time range.
          4) There are no datetimes that do not align with the analysis_period timestep.
          5) Datetimes for February 29th are excluded if is_leap_year is False on
              the analysis_period.
  
          Note that there is no need to run this check any time that a discontinous
          data collection has been derived from a continuous one or when the
          validated_a_period attribute of the collection is True.  Furthermore, most
          methods on this data collection will still run without a validated
          analysis_period.
          """
          a_per = self.header.analysis_period
          n_ap = [a_per.st_month, a_per.st_day, a_per.st_hour, a_per.end_month,
                  a_per.end_day, a_per.end_hour, a_per.timestep, a_per.is_leap_year]
  
          # make sure that datetimes are all in chronological order.
          sort_datetimes, sort_values = zip(*sorted(zip(self.datetimes, self.values)))
          if not a_per.is_reversed and not a_per.is_annual:
              if sort_datetimes[0].doy < a_per.st_time.doy:
                  n_ap[0] = sort_datetimes[0].month
                  n_ap[1] = sort_datetimes[0].day
              if sort_datetimes[-1].doy > a_per.end_time.doy:
                  n_ap[3] = sort_datetimes[-1].month
                  n_ap[4] = sort_datetimes[-1].day
          elif a_per.is_reversed:
              last_ind = None
              for i, date_t in enumerate(sort_datetimes):
                  last_ind = i if date_t.moy <= a_per.end_time.moy else last_ind
              if last_ind is not None:
                  last_ind = last_ind + 1
                  sort_datetimes = sort_datetimes[last_ind:] + sort_datetimes[:last_ind]
                  sort_values = sort_values[last_ind:] + sort_values[:last_ind]
              # If datetimes are outside the a_period range, just make it annual.
              # There's no way to know what side of the analysis_period should be etended.
              if sort_datetimes[0].doy > a_per.end_time.doy and \
                      sort_datetimes[0].doy < a_per.st_time.doy:
                  n_ap[0], n_ap[1], n_ap[3], n_ap[4] = 1, 1, 12, 31
                  sort_datetimes, sort_values = zip(*sorted(zip(
                      self.datetimes, self.values)))
  
          # check that no hours lie outside of the analysis_period
          if not a_per.is_annual:
              if a_per.st_hour != 0:
                  for date_t in sort_datetimes:
                      n_ap[2] = date_t.hour if date_t.hour < n_ap[2] else n_ap[2]
              if a_per.end_hour != 23:
                  for date_t in sort_datetimes:
                      n_ap[5] = date_t.hour if date_t.hour > n_ap[5] else n_ap[5]
  
          # check that there are no duplicate datetimes.
          for i in xrange(len(sort_datetimes)):
              assert sort_datetimes[i] != sort_datetimes[i - 1], 'Duplicate datetime ' \
                  'was found in the collection: {}'.format(sort_datetimes[i])
  
          # check that the analysis_period timestep is correct.
          mins_per_step = int(60 / n_ap[6])
          for date_t in sort_datetimes:
              if date_t.moy % mins_per_step != 0:
                  i = 0
                  valid_steps = sorted(a_per.VALIDTIMESTEPS.keys())
                  while date_t.moy % mins_per_step != 0 and i < len(valid_steps):
                      mins_per_step = int(60 / valid_steps[i])
                      i += 1
                  n_ap[6] = int(60 / mins_per_step)
  
          # check that the analysis_period leap_year is correct.
          if a_per.is_leap_year is False:
              for date_t in sort_datetimes:
                  if date_t.month == 2 and date_t.day == 29:
                      n_ap[7] = True
  
          # build a validated collection.
          new_ap = AnalysisPeriod(*n_ap)
          new_header = self.header.duplicate()
          new_header._analysis_period = new_ap
          new_coll = HourlyDiscontinuousCollection(new_header, sort_values, sort_datetimes)
          new_coll._validated_a_period = True
          return new_coll