Format: text/x-python
⬇ Download File (5.4 KB)
   1  # Copyright 2010-2023 Kurt McKee <contactme@kurtmckee.org>
   2  # Copyright 2002-2008 Mark Pilgrim
   3  # All rights reserved.
   4  #
   5  # This file is a part of feedparser.
   6  #
   7  # Redistribution and use in source and binary forms, with or without
   8  # modification, are permitted provided that the following conditions are met:
   9  #
  10  # * Redistributions of source code must retain the above copyright notice,
  11  #   this list of conditions and the following disclaimer.
  12  # * Redistributions in binary form must reproduce the above copyright notice,
  13  #   this list of conditions and the following disclaimer in the documentation
  14  #   and/or other materials provided with the distribution.
  15  #
  16  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS'
  17  # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  18  # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  19  # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  20  # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  21  # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  22  # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  23  # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  24  # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  25  # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  26  # POSSIBILITY OF SUCH DAMAGE.
  27  
  28  import re
  29  import time
  30  
  31  # ISO-8601 date parsing routines written by Fazal Majid.
  32  # The ISO 8601 standard is very convoluted and irregular - a full ISO 8601
  33  # parser is beyond the scope of feedparser and would be a worthwhile addition
  34  # to the Python library.
  35  # A single regular expression cannot parse ISO 8601 date formats into groups
  36  # as the standard is highly irregular (for instance is 030104 2003-01-04 or
  37  # 0301-04-01), so we use templates instead.
  38  # Please note the order in templates is significant because we need a
  39  # greedy match.
  40  _iso8601_tmpl = [
  41      'YYYY-?MM-?DD',
  42      'YYYY-0MM?-?DD',
  43      'YYYY-MM',
  44      'YYYY-?OOO',
  45      'YY-?MM-?DD',
  46      'YY-?OOO',
  47      'YYYY',
  48      '-YY-?MM',
  49      '-OOO',
  50      '-YY',
  51      '--MM-?DD',
  52      '--MM',
  53      '---DD',
  54      'CC',
  55      '',
  56  ]
  57  
  58  _iso8601_re = [
  59      tmpl.replace(
  60      'YYYY', r'(?P<year>\d{4})').replace(
  61      'YY', r'(?P<year>\d\d)').replace(
  62      'MM', r'(?P<month>[01]\d)').replace(
  63      'DD', r'(?P<day>[0123]\d)').replace(
  64      'OOO', r'(?P<ordinal>[0123]\d\d)').replace(
  65      'CC', r'(?P<century>\d\d$)')
  66      + r'(T?(?P<hour>\d{2}):(?P<minute>\d{2})'
  67      + r'(:(?P<second>\d{2}))?'
  68      + r'(\.(?P<fracsecond>\d+))?'
  69      + r'(?P<tz>[+-](?P<tzhour>\d{2})(:(?P<tzmin>\d{2}))?|Z)?)?'
  70      for tmpl in _iso8601_tmpl]
  71  try:
  72      del tmpl
  73  except NameError:
  74      pass
  75  _iso8601_matches = [re.compile(regex).match for regex in _iso8601_re]
  76  try:
  77      del regex
  78  except NameError:
  79      pass
  80  
  81  
  82  def _parse_date_iso8601(date_string):
  83      """Parse a variety of ISO-8601-compatible formats like 20040105"""
  84      m = None
  85      for _iso8601_match in _iso8601_matches:
  86          m = _iso8601_match(date_string)
  87          if m:
  88              break
  89      if not m:
  90          return
  91      if m.span() == (0, 0):
  92          return
  93      params = m.groupdict()
  94      ordinal = params.get('ordinal', 0)
  95      if ordinal:
  96          ordinal = int(ordinal)
  97      else:
  98          ordinal = 0
  99      year = params.get('year', '--')
 100      if not year or year == '--':
 101          year = time.gmtime()[0]
 102      elif len(year) == 2:
 103          # ISO 8601 assumes current century, i.e. 93 -> 2093, NOT 1993
 104          year = 100 * int(time.gmtime()[0] / 100) + int(year)
 105      else:
 106          year = int(year)
 107      month = params.get('month', '-')
 108      if not month or month == '-':
 109          # ordinals are NOT normalized by mktime, we simulate them
 110          # by setting month=1, day=ordinal
 111          if ordinal:
 112              month = 1
 113          else:
 114              month = time.gmtime()[1]
 115      month = int(month)
 116      day = params.get('day', 0)
 117      if not day:
 118          # see above
 119          if ordinal:
 120              day = ordinal
 121          elif params.get('century', 0) or \
 122                   params.get('year', 0) or params.get('month', 0):
 123              day = 1
 124          else:
 125              day = time.gmtime()[2]
 126      else:
 127          day = int(day)
 128      # special case of the century - is the first year of the 21st century
 129      # 2000 or 2001 ? The debate goes on...
 130      if 'century' in params:
 131          year = (int(params['century']) - 1) * 100 + 1
 132      # in ISO 8601 most fields are optional
 133      for field in ['hour', 'minute', 'second', 'tzhour', 'tzmin']:
 134          if not params.get(field, None):
 135              params[field] = 0
 136      hour = int(params.get('hour', 0))
 137      minute = int(params.get('minute', 0))
 138      second = int(float(params.get('second', 0)))
 139      # weekday is normalized by mktime(), we can ignore it
 140      weekday = 0
 141      daylight_savings_flag = -1
 142      tm = [year, month, day, hour, minute, second, weekday,
 143            ordinal, daylight_savings_flag]
 144      # ISO 8601 time zone adjustments
 145      tz = params.get('tz')
 146      if tz and tz != 'Z':
 147          if tz[0] == '-':
 148              tm[3] += int(params.get('tzhour', 0))
 149              tm[4] += int(params.get('tzmin', 0))
 150          elif tz[0] == '+':
 151              tm[3] -= int(params.get('tzhour', 0))
 152              tm[4] -= int(params.get('tzmin', 0))
 153          else:
 154              return None
 155      # Python's time.mktime() is a wrapper around the ANSI C mktime(3c)
 156      # which is guaranteed to normalize d/m/y/h/m/s.
 157      # Many implementations have bugs, but we'll pretend they don't.
 158      return time.localtime(time.mktime(tuple(tm)))
 159