Package Gnumed :: Package pycommon :: Module gmDateTime |
[frames] | no frames]

# Source Code for Module Gnumed.pycommon.gmDateTime

       1  __doc__ = """
       2  GNUmed date/time handling.
       3
       4  This modules provides access to date/time handling
       5  and offers an fuzzy timestamp implementation
       6
       7  It utilizes
       8
       9          - Python time
      10          - Python datetime
      11          - mxDateTime
      12
      13  Note that if you want locale-aware formatting you need to call
      14
      15          locale.setlocale(locale.LC_ALL, '')
      16
      17  somewhere before importing this script.
      18
      19  Note regarding UTC offsets
      20  --------------------------
      21
      22  Looking from Greenwich:
      23          WEST (IOW "behind"): negative values
      24          EAST (IOW "ahead"):  positive values
      25
      26  This is in compliance with what datetime.tzinfo.utcoffset()
      27  does but NOT what time.altzone/time.timezone do !
      28
      29  This module also implements a class which allows the
      30  programmer to define the degree of fuzziness, uncertainty
      31  or imprecision of the timestamp contained within.
      32
      33  This is useful in fields such as medicine where only partial
      34  timestamps may be known for certain events.
      35
      36  Other useful links:
      37
      38          http://joda-time.sourceforge.net/key_instant.html
      39  """
      40  #===========================================================================
      41  __author__ = "K. Hilbert <Karsten.Hilbert@gmx.net>"
      42  __license__ = "GPL v2 or later (details at http://www.gnu.org)"
      43
      44  # stdlib
      45  import sys, datetime as pyDT, time, os, re as regex, locale, logging
      46
      47
      48  # 3rd party
      49  import mx.DateTime as mxDT
      50  import psycopg2                                         # this will go once datetime has timezone classes
      51
      52
      53  if __name__ == '__main__':
      54          sys.path.insert(0, '../../')
      55  from Gnumed.pycommon import gmI18N
      56
      57
      58  _log = logging.getLogger('gm.datetime')
      59  _log.info(u'mx.DateTime version: %s', mxDT.__version__)
      60
      61  dst_locally_in_use = None
      62  dst_currently_in_effect = None
      63
      64  current_local_utc_offset_in_seconds = None
      65  current_local_timezone_interval = None
      66  current_local_iso_numeric_timezone_string = None
      67  current_local_timezone_name = None
      68  py_timezone_name = None
      69  py_dst_timezone_name = None
      70
      71  cLocalTimezone = psycopg2.tz.LocalTimezone                                      # remove as soon as datetime supports timezone classes
      72  cFixedOffsetTimezone = psycopg2.tz.FixedOffsetTimezone          # remove as soon as datetime supports timezone classes
      73  gmCurrentLocalTimezone = 'gmCurrentLocalTimezone not initialized'
      74
      75
      76  (       acc_years,
      77          acc_months,
      78          acc_weeks,
      79          acc_days,
      80          acc_hours,
      81          acc_minutes,
      82          acc_seconds,
      83          acc_subseconds
      84  ) = range(1,9)
      85
      86  _accuracy_strings = {
      87          1: 'years',
      88          2: 'months',
      89          3: 'weeks',
      90          4: 'days',
      91          5: 'hours',
      92          6: 'minutes',
      93          7: 'seconds',
      94          8: 'subseconds'
      95  }
      96
      97  gregorian_month_length = {
      98          1: 31,
      99          2: 28,          # FIXME: make leap year aware
     100          3: 31,
     101          4: 30,
     102          5: 31,
     103          6: 30,
     104          7: 31,
     105          8: 31,
     106          9: 30,
     107          10: 31,
     108          11: 30,
     109          12: 31
     110  }
     111
     112  avg_days_per_gregorian_year = 365
     113  avg_days_per_gregorian_month = 30
     114  avg_seconds_per_day = 24 * 60 * 60
     115  days_per_week = 7
     116
     117  #===========================================================================
     118  # module init
     119  #---------------------------------------------------------------------------
     120 -def init():
     121
     122          _log.debug('mx.DateTime.now(): [%s]' % mxDT.now())
     123          _log.debug('datetime.now()   : [%s]' % pyDT.datetime.now())
     124          _log.debug('time.localtime() : [%s]' % str(time.localtime()))
     125          _log.debug('time.gmtime()    : [%s]' % str(time.gmtime()))
     126
     127          try:
     128                  _log.debug('$TZ: [%s]' % os.environ['TZ'])
     129          except KeyError:
     130                  _log.debug('$TZ not defined')
     131
     132          _log.debug('time.daylight: [%s] (whether or not DST is locally used at all)' % time.daylight)
     133          _log.debug('time.timezone: [%s] seconds' % time.timezone)
     134          _log.debug('time.altzone : [%s] seconds' % time.altzone)
     135          _log.debug('time.tzname  : [%s / %s] (non-DST / DST)' % time.tzname)
     136          _log.debug('mx.DateTime.now().gmtoffset(): [%s]' % mxDT.now().gmtoffset())
     137
     138          global py_timezone_name
     139          py_timezone_name = time.tzname[0].decode(gmI18N.get_encoding(), 'replace')
     140
     141          global py_dst_timezone_name
     142          py_dst_timezone_name = time.tzname[1].decode(gmI18N.get_encoding(), 'replace')
     143
     144          global dst_locally_in_use
     145          dst_locally_in_use = (time.daylight != 0)
     146
     147          global dst_currently_in_effect
     148          dst_currently_in_effect = bool(time.localtime()[8])
     149          _log.debug('DST currently in effect: [%s]' % dst_currently_in_effect)
     150
     151          if (not dst_locally_in_use) and dst_currently_in_effect:
     152                  _log.error('system inconsistency: DST not in use - but DST currently in effect ?')
     153
     154          global current_local_utc_offset_in_seconds
     155          msg = 'DST currently%sin effect: using UTC offset of [%s] seconds instead of [%s] seconds'
     156          if dst_currently_in_effect:
     157                  current_local_utc_offset_in_seconds = time.altzone * -1
     158                  _log.debug(msg % (' ', time.altzone * -1, time.timezone * -1))
     159          else:
     160                  current_local_utc_offset_in_seconds = time.timezone * -1
     161                  _log.debug(msg % (' not ', time.timezone * -1, time.altzone * -1))
     162
     163          if current_local_utc_offset_in_seconds > 0:
     164                  _log.debug('UTC offset is positive, assuming EAST of Greenwich (clock is "ahead")')
     165          elif current_local_utc_offset_in_seconds < 0:
     166                  _log.debug('UTC offset is negative, assuming WEST of Greenwich (clock is "behind")')
     167          else:
     168                  _log.debug('UTC offset is ZERO, assuming Greenwich Time')
     169
     170          global current_local_timezone_interval
     171          current_local_timezone_interval = mxDT.now().gmtoffset()
     172          _log.debug('ISO timezone: [%s] (taken from mx.DateTime.now().gmtoffset())' % current_local_timezone_interval)
     173
     174          global current_local_iso_numeric_timezone_string
     175          current_local_iso_numeric_timezone_string = str(current_local_timezone_interval).replace(',', '.')
     176
     177          global current_local_timezone_name
     178          try:
     179                  current_local_timezone_name = os.environ['TZ'].decode(gmI18N.get_encoding(), 'replace')
     180          except KeyError:
     181                  if dst_currently_in_effect:
     182                          current_local_timezone_name = time.tzname[1].decode(gmI18N.get_encoding(), 'replace')
     183                  else:
     184                          current_local_timezone_name = time.tzname[0].decode(gmI18N.get_encoding(), 'replace')
     185
     186          # do some magic to convert Python's timezone to a valid ISO timezone
     187          # is this safe or will it return things like 13.5 hours ?
     188          #_default_client_timezone = "%+.1f" % (-tz / 3600.0)
     189          #_log.info('assuming default client time zone of [%s]' % _default_client_timezone)
     190
     191          global gmCurrentLocalTimezone
     192          gmCurrentLocalTimezone = cFixedOffsetTimezone (
     193                  offset = (current_local_utc_offset_in_seconds / 60),
     194                  name = current_local_iso_numeric_timezone_string
     195          )
     196  #===========================================================================
     197  # mxDateTime conversions
     198  #---------------------------------------------------------------------------
     199 -def mxdt2py_dt(mxDateTime):
     200
     201          if isinstance(mxDateTime, pyDT.datetime):
     202                  return mxDateTime
     203
     204          try:
     205                  tz_name = str(mxDateTime.gmtoffset()).replace(',', '.')
     206          except mxDT.Error:
     207                  _log.debug('mx.DateTime cannot gmtoffset() this timestamp, assuming local time')
     208                  tz_name = current_local_iso_numeric_timezone_string
     209
     210          if dst_currently_in_effect:
     211                  tz = cFixedOffsetTimezone (
     212                          offset = ((time.altzone * -1) / 60),
     213                          name = tz_name
     214                  )
     215          else:
     216                  tz = cFixedOffsetTimezone (
     217                          offset = ((time.timezone * -1) / 60),
     218                          name = tz_name
     219                  )
     220
     221          try:
     222                  return pyDT.datetime (
     223                          year = mxDateTime.year,
     224                          month = mxDateTime.month,
     225                          day = mxDateTime.day,
     226                          tzinfo = tz
     227                  )
     228          except:
     229                  _log.debug (u'error converting mx.DateTime.DateTime to Python: %s-%s-%s %s:%s %s.%s',
     230                          mxDateTime.year,
     231                          mxDateTime.month,
     232                          mxDateTime.day,
     233                          mxDateTime.hour,
     234                          mxDateTime.minute,
     235                          mxDateTime.second,
     236                          mxDateTime.tz
     237                  )
     238                  raise
     239  #===========================================================================
     240 -def format_dob(dob, format='%Y %b %d', encoding=None, none_string=None, dob_is_estimated=False):
     241          if dob is None:
     242                  if none_string is None:
     243                          return _('** DOB unknown **')
     244                  return none_string
     245
     246          dob_txt = pydt_strftime(dob, format = format, encoding = encoding, accuracy = acc_days)
     247          if dob_is_estimated:
     248                  return u'%s%s' % (u'\u2248', dob_txt)
     249
     250          return dob_txt
     251  #---------------------------------------------------------------------------
     252 -def pydt_strftime(dt=None, format='%Y %b %d  %H:%M.%S', encoding=None, accuracy=None):
     253
     254          if dt is None:
     255                  dt = pydt_now_here()
     256
     257          if encoding is None:
     258                  encoding = gmI18N.get_encoding()
     259
     260          try:
     261                  return dt.strftime(format).decode(encoding, 'replace')
     262          except ValueError:
     263                  _log.exception('Python cannot strftime() this <datetime>')
     264
     265          if accuracy == acc_days:
     266                  return u'%04d-%02d-%02d' % (
     267                          dt.year,
     268                          dt.month,
     269                          dt.day
     270                  )
     271
     272          if accuracy == acc_minutes:
     273                  return u'%04d-%02d-%02d %02d:%02d' % (
     274                          dt.year,
     275                          dt.month,
     276                          dt.day,
     277                          dt.hour,
     278                          dt.minute
     279                  )
     280
     281          return u'%04d-%02d-%02d %02d:%02d:%02d' % (
     282                  dt.year,
     283                  dt.month,
     284                  dt.day,
     285                  dt.hour,
     286                  dt.minute,
     287                  dt.second
     288          )
     289  #---------------------------------------------------------------------------
     290 -def pydt_now_here():
     291          """Returns NOW @ HERE (IOW, in the local timezone."""
     292          return pyDT.datetime.now(gmCurrentLocalTimezone)
     293  #---------------------------------------------------------------------------
     294 -def pydt_max_here():
     295          return pyDT.datetime.max.replace(tzinfo = gmCurrentLocalTimezone)
     296  #---------------------------------------------------------------------------
     297 -def wx_now_here(wx=None):
     298          """Returns NOW @ HERE (IOW, in the local timezone."""
     299          return py_dt2wxDate(py_dt = pydt_now_here(), wx = wx)
     300  #===========================================================================
     301  # wxPython conversions
     302  #---------------------------------------------------------------------------
     303 -def wxDate2py_dt(wxDate=None):
     304          if not wxDate.IsValid():
     305                  raise ValueError (u'invalid wxDate: %s-%s-%s %s:%s %s.%s',
     306                          wxDate.GetYear(),
     307                          wxDate.GetMonth(),
     308                          wxDate.GetDay(),
     309                          wxDate.GetHour(),
     310                          wxDate.GetMinute(),
     311                          wxDate.GetSecond(),
     312                          wxDate.GetMillisecond()
     313                  )
     314
     315          try:
     316                  return pyDT.datetime (
     317                          year = wxDate.GetYear(),
     318                          month = wxDate.GetMonth() + 1,
     319                          day = wxDate.GetDay(),
     320                          tzinfo = gmCurrentLocalTimezone
     321                  )
     322          except:
     323                  _log.debug (u'error converting wxDateTime to Python: %s-%s-%s %s:%s %s.%s',
     324                          wxDate.GetYear(),
     325                          wxDate.GetMonth(),
     326                          wxDate.GetDay(),
     327                          wxDate.GetHour(),
     328                          wxDate.GetMinute(),
     329                          wxDate.GetSecond(),
     330                          wxDate.GetMillisecond()
     331                  )
     332                  raise
     333  #---------------------------------------------------------------------------
     334 -def py_dt2wxDate(py_dt=None, wx=None):
     335          _log.debug(u'setting wx.DateTime from: %s-%s-%s', py_dt.year, py_dt.month, py_dt.day)
     336          # Robin Dunn says that for SetYear/*Month/*Day the wx.DateTime MUST already
     337          # be valid (by definition) or, put the other way round, you must Set() day,
     338          # month, and year at once
     339          wxdt = wx.DateTimeFromDMY(py_dt.day, py_dt.month-1, py_dt.year)
     340          return wxdt
     341  #===========================================================================
     342  # interval related
     343  #---------------------------------------------------------------------------
     344 -def format_interval(interval=None, accuracy_wanted=None, none_string=None, verbose=False):
     345
     346          if accuracy_wanted is None:
     347                  accuracy_wanted = acc_seconds
     348
     349          if interval is None:
     350                  if none_string is not None:
     351                          return none_string
     352
     353          years, days = divmod(interval.days, avg_days_per_gregorian_year)
     354          months, days = divmod(days, avg_days_per_gregorian_month)
     355          weeks, days = divmod(days, days_per_week)
     356          days, secs = divmod((days * avg_seconds_per_day) + interval.seconds, avg_seconds_per_day)
     357          hours, secs = divmod(secs, 3600)
     358          mins, secs = divmod(secs, 60)
     359
     360          tmp = u''
     361
     362          if years > 0:
     363                  if verbose:
     364                          if years > 1:
     365                                  tag = u' ' + _('years')
     366                          else:
     367                                  tag = u' ' + _('year')
     368                  else:
     369                          tag = _('interval_format_tag::years::y')[-1:]
     370                  tmp += u'%s%s' % (int(years), tag)
     371
     372
     373          if accuracy_wanted < acc_months:
     374                  return tmp.strip()
     375
     376          if months > 0:
     377                  if verbose:
     378                          if months > 1:
     379                                  tag = u' ' + _('months')
     380                          else:
     381                                  tag = u' ' + _('month')
     382                  else:
     383                          tag = _('interval_format_tag::months::m')[-1:]
     384                  tmp += u' %s%s' % (int(months), tag)
     385
     386          if accuracy_wanted < acc_weeks:
     387                  return tmp.strip()
     388
     389          if weeks > 0:
     390                  if verbose:
     391                          if weeks > 1:
     392                                  tag = u' ' + _('weeks')
     393                          else:
     394                                  tag = u' ' + _('week')
     395                  else:
     396                          tag = _('interval_format_tag::weeks::w')[-1:]
     397                  tmp += u' %s%s' % (int(weeks), tag)
     398
     399          if accuracy_wanted < acc_days:
     400                  return tmp.strip()
     401
     402          if days > 0:
     403                  if verbose:
     404                          if days > 1:
     405                                  tag = u' ' + _('days')
     406                          else:
     407                                  tag = u' ' + _('day')
     408                  else:
     409                          tag = _('interval_format_tag::days::d')[-1:]
     410                  tmp += u' %s%s' % (int(days), tag)
     411
     412          if accuracy_wanted < acc_hours:
     413                  return tmp.strip()
     414
     415          if hours > 0:
     416                  if verbose:
     417                          if hours > 1:
     418                                  tag = u' ' + _('hours')
     419                          else:
     420                                  tag = u' ' + _('hour')
     421                  else:
     422                          tag = u'/24'
     423                  tmp += u' %s%s' % (int(hours), tag)
     424
     425          if accuracy_wanted < acc_minutes:
     426                  return tmp.strip()
     427
     428          if mins > 0:
     429                  if verbose:
     430                          if mins > 1:
     431                                  tag = u' ' + _('minutes')
     432                          else:
     433                                  tag = u' ' + _('minute')
     434                  else:
     435                          tag = u'/60'
     436                  tmp += u' %s%s' % (int(mins), tag)
     437
     438          if accuracy_wanted < acc_seconds:
     439                  return tmp.strip()
     440
     441          if secs > 0:
     442                  if verbose:
     443                          if secs > 1:
     444                                  tag = u' ' + _('seconds')
     445                          else:
     446                                  tag = u' ' + _('second')
     447                  else:
     448                          tag = u's'
     449                  tmp += u' %s%s' % (int(mins), tag)
     450
     451          return tmp.strip()
     452  #---------------------------------------------------------------------------
     453 -def format_interval_medically(interval=None):
     454          """Formats an interval.
     455
     456          This isn't mathematically correct but close enough for display.
     457          """
     458          # more than 1 year ?
     459          if interval.days > 363:
     460                  years, days = divmod(interval.days, 364)
     461                  leap_days, tmp = divmod(years, 4)
     462                  months, day = divmod((days + leap_days), 30.33)
     463                  if int(months) == 0:
     464                          return u"%s%s" % (int(years), _('interval_format_tag::years::y')[-1:])
     465                  return u"%s%s %s%s" % (int(years), _('interval_format_tag::years::y')[-1:], int(months), _('interval_format_tag::months::m')[-1:])
     466
     467          # more than 30 days / 1 month ?
     468          if interval.days > 30:
     469                  months, days = divmod(interval.days, 30.33)
     470                  weeks, days = divmod(days, 7)
     471                  if int(weeks + days) == 0:
     472                          result = u'%smo' % int(months)
     473                  else:
     474                          result = u'%s%s' % (int(months), _('interval_format_tag::months::m')[-1:])
     475                  if int(weeks) != 0:
     476                          result += u' %s%s' % (int(weeks), _('interval_format_tag::weeks::w')[-1:])
     477                  if int(days) != 0:
     478                          result += u' %s%s' % (int(days), _('interval_format_tag::days::d')[-1:])
     479                  return result
     480
     481          # between 7 and 30 days ?
     482          if interval.days > 7:
     483                  return u"%s%s" % (interval.days, _('interval_format_tag::days::d')[-1:])
     484
     485          # between 1 and 7 days ?
     486          if interval.days > 0:
     487                  hours, seconds = divmod(interval.seconds, 3600)
     488                  if hours == 0:
     489                          return '%s%s' % (interval.days, _('interval_format_tag::days::d')[-1:])
     490                  return "%s%s (%sh)" % (interval.days, _('interval_format_tag::days::d')[-1:], int(hours))
     491
     492          # between 5 hours and 1 day
     493          if interval.seconds > (5*3600):
     494                  return "%sh" % int(interval.seconds // 3600)
     495
     496          # between 1 and 5 hours
     497          if interval.seconds > 3600:
     498                  hours, seconds = divmod(interval.seconds, 3600)
     499                  minutes = seconds // 60
     500                  if minutes == 0:
     501                          return '%sh' % int(hours)
     502                  return "%s:%02d" % (int(hours), int(minutes))
     503
     504          # minutes only
     505          if interval.seconds > (5*60):
     506                  return "0:%02d" % (int(interval.seconds // 60))
     507
     508          # seconds
     509          minutes, seconds = divmod(interval.seconds, 60)
     510          if minutes == 0:
     511                  return '%ss' % int(seconds)
     512          if seconds == 0:
     513                  return '0:%02d' % int(minutes)
     514          return "%s.%ss" % (int(minutes), int(seconds))
     515  #---------------------------------------------------------------------------
     516 -def is_leap_year(year):
     517          # year is multiple of 4 ?
     518          div, remainder = divmod(year, 4)
     519          # no -> not a leap year
     520          if remainder > 0:
     521                  return False
     522
     523          # year is a multiple of 100 ?
     524          div, remainder = divmod(year, 100)
     525          # no -> IS a leap year
     526          if remainder > 0:
     527                  return True
     528
     529          # year is a multiple of 400 ?
     530          div, remainder = divmod(year, 400)
     531          # yes -> IS a leap year
     532          if remainder == 0:
     533                  return True
     534
     535          return False
     536  #---------------------------------------------------------------------------
     537 -def calculate_apparent_age(start=None, end=None):
     538          """The result of this is a tuple (years, ..., seconds) as one would
     539          'expect' an age to look like, that is, simple differences between
     540          the fields:
     541
     542                  (years, months, days, hours, minutes, seconds)
     543
     544          This does not take into account time zones which may
     545          shift the result by one day.
     546
     547          <start> and <end> must by python datetime instances
     548          <end> is assumed to be "now" if not given
     549          """
     550          if end is None:
     551                  end = pyDT.datetime.now(gmCurrentLocalTimezone)
     552
     553          if end < start:
     554                  raise ValueError('calculate_apparent_age(): <end> (%s) before <start> (%s)' % (end, start))
     555
     556          if end == start:
     557                  return (0, 0, 0, 0, 0, 0)
     558
     559          # steer clear of leap years
     560          if end.month == 2:
     561                  if end.day == 29:
     562                          if not is_leap_year(start.year):
     563                                  end = end.replace(day = 28)
     564
     565          # years
     566          years = end.year - start.year
     567          end = end.replace(year = start.year)
     568          if end < start:
     569                  years = years - 1
     570
     571          # months
     572          if end.month == start.month:
     573                  if end < start:
     574                          months = 11
     575                  else:
     576                          months = 0
     577          else:
     578                  months = end.month - start.month
     579                  if months < 0:
     580                          months = months + 12
     581                  if end.day > gregorian_month_length[start.month]:
     582                          end = end.replace(month = start.month, day = gregorian_month_length[start.month])
     583                  else:
     584                          end = end.replace(month = start.month)
     585                  if end < start:
     586                          months = months - 1
     587
     588          # days
     589          if end.day == start.day:
     590                  if end < start:
     591                          days = gregorian_month_length[start.month] - 1
     592                  else:
     593                          days = 0
     594          else:
     595                  days = end.day - start.day
     596                  if days < 0:
     597                          days = days + gregorian_month_length[start.month]
     598                  end = end.replace(day = start.day)
     599                  if end < start:
     600                          days = days - 1
     601
     602          # hours
     603          if end.hour == start.hour:
     604                  hours = 0
     605          else:
     606                  hours = end.hour - start.hour
     607                  if hours < 0:
     608                          hours = hours + 24
     609                  end = end.replace(hour = start.hour)
     610                  if end < start:
     611                          hours = hours - 1
     612
     613          # minutes
     614          if end.minute == start.minute:
     615                  minutes = 0
     616          else:
     617                  minutes = end.minute - start.minute
     618                  if minutes < 0:
     619                          minutes = minutes + 60
     620                  end = end.replace(minute = start.minute)
     621                  if end < start:
     622                          minutes = minutes - 1
     623
     624          # seconds
     625          if end.second == start.second:
     626                  seconds = 0
     627          else:
     628                  seconds = end.second - start.second
     629                  if seconds < 0:
     630                          seconds = seconds + 60
     631                  end = end.replace(second = start.second)
     632                  if end < start:
     633                          seconds = seconds - 1
     634
     635          return (years, months, days, hours, minutes, seconds)
     636  #---------------------------------------------------------------------------
     637 -def format_apparent_age_medically(age=None):
     638          """<age> must be a tuple as created by calculate_apparent_age()"""
     639
     640          (years, months, days, hours, minutes, seconds) = age
     641
     642          # at least 1 year ?
     643          if years > 0:
     644                  if months == 0:
     645                          return u'%s%s' % (
     646                                  years,
     647                                  _('y::year_abbreviation').replace('::year_abbreviation', u'')
     648                          )
     649                  return u'%s%s %s%s' % (
     650                          years,
     651                          _('y::year_abbreviation').replace('::year_abbreviation', u''),
     652                          months,
     653                          _('m::month_abbreviation').replace('::month_abbreviation', u'')
     654                  )
     655
     656          # more than 1 month ?
     657          if months > 1:
     658                  if days == 0:
     659                          return u'%s%s' % (
     660                                  months,
     661                                  _('mo::month_only_abbreviation').replace('::month_only_abbreviation', u'')
     662                          )
     663
     664                  result = u'%s%s' % (
     665                          months,
     666                          _('m::month_abbreviation').replace('::month_abbreviation', u'')
     667                  )
     668
     669                  weeks, days = divmod(days, 7)
     670                  if int(weeks) != 0:
     671                          result += u'%s%s' % (
     672                                  int(weeks),
     673                                  _('w::week_abbreviation').replace('::week_abbreviation', u'')
     674                          )
     675                  if int(days) != 0:
     676                          result += u'%s%s' % (
     677                                  int(days),
     678                                  _('d::day_abbreviation').replace('::day_abbreviation', u'')
     679                          )
     680
     681                  return result
     682
     683          # between 7 days and 1 month
     684          if days > 7:
     685                  return u"%s%s" % (
     686                          days,
     687                          _('d::day_abbreviation').replace('::day_abbreviation', u'')
     688                  )
     689
     690          # between 1 and 7 days ?
     691          if days > 0:
     692                  if hours == 0:
     693                          return u'%s%s' % (
     694                                  days,
     695                                  _('d::day_abbreviation').replace('::day_abbreviation', u'')
     696                          )
     697                  return u'%s%s (%s%s)' % (
     698                          days,
     699                          _('d::day_abbreviation').replace('::day_abbreviation', u''),
     700                          hours,
     701                          _('h::hour_abbreviation').replace('::hour_abbreviation', u'')
     702                  )
     703
     704          # between 5 hours and 1 day
     705          if hours > 5:
     706                  return u'%s%s' % (
     707                          hours,
     708                          _('h::hour_abbreviation').replace('::hour_abbreviation', u'')
     709                  )
     710
     711          # between 1 and 5 hours
     712          if hours > 1:
     713                  if minutes == 0:
     714                          return u'%s%s' % (
     715                                  hours,
     716                                  _('h::hour_abbreviation').replace('::hour_abbreviation', u'')
     717                          )
     718                  return u'%s:%02d' % (
     719                          hours,
     720                          minutes
     721                  )
     722
     723          # between 5 and 60 minutes
     724          if minutes > 5:
     725                  return u"0:%02d" % minutes
     726
     727          # less than 5 minutes
     728          if minutes == 0:
     729                  return u'%s%s' % (
     730                          seconds,
     731                          _('s::second_abbreviation').replace('::second_abbreviation', u'')
     732                  )
     733          if seconds == 0:
     734                  return u"0:%02d" % minutes
     735          return "%s.%s%s" % (
     736                  minutes,
     737                  seconds,
     738                  _('s::second_abbreviation').replace('::second_abbreviation', u'')
     739          )
     740  #---------------------------------------------------------------------------
     741 -def str2interval(str_interval=None):
     742
     743          unit_keys = {
     744                  'year': _('yYaA_keys_year'),
     745                  'month': _('mM_keys_month'),
     746                  'week': _('wW_keys_week'),
     747                  'day': _('dD_keys_day'),
     748                  'hour': _('hH_keys_hour')
     749          }
     750
     751          str_interval = str_interval.strip()
     752
     753          # "(~)35(yY)"   - at age 35 years
     754          keys = '|'.join(list(unit_keys['year'].replace('_keys_year', u'')))
     755          if regex.match(u'^~*(\s|\t)*\d+(%s)*$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE):
     756                  return pyDT.timedelta(days = (int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]) * avg_days_per_gregorian_year))
     757
     758          # "(~)12mM" - at age 12 months
     759          keys = '|'.join(list(unit_keys['month'].replace('_keys_month', u'')))
     760          if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE):
     761                  years, months = divmod (
     762                          int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]),
     763                          12
     764                  )
     765                  return pyDT.timedelta(days = ((years * avg_days_per_gregorian_year) + (months * avg_days_per_gregorian_month)))
     766
     767          # weeks
     768          keys = '|'.join(list(unit_keys['week'].replace('_keys_week', u'')))
     769          if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE):
     770                  return pyDT.timedelta(weeks = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
     771
     772          # days
     773          keys = '|'.join(list(unit_keys['day'].replace('_keys_day', u'')))
     774          if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE):
     775                  return pyDT.timedelta(days = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
     776
     777          # hours
     778          keys = '|'.join(list(unit_keys['hour'].replace('_keys_hour', u'')))
     779          if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE):
     780                  return pyDT.timedelta(hours = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
     781
     782          # x/12 - months
     783          if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*12$', str_interval, flags = regex.LOCALE | regex.UNICODE):
     784                  years, months = divmod (
     785                          int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]),
     786                          12
     787                  )
     788                  return pyDT.timedelta(days = ((years * avg_days_per_gregorian_year) + (months * avg_days_per_gregorian_month)))
     789
     790          # x/52 - weeks
     791          if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*52$', str_interval, flags = regex.LOCALE | regex.UNICODE):
     792  #               return pyDT.timedelta(days = (int(regex.findall('\d+', str_interval)[0]) * days_per_week))
     793                  return pyDT.timedelta(weeks = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
     794
     795          # x/7 - days
     796          if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*7$', str_interval, flags = regex.LOCALE | regex.UNICODE):
     797                  return pyDT.timedelta(days = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
     798
     799          # x/24 - hours
     800          if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*24$', str_interval, flags = regex.LOCALE | regex.UNICODE):
     801                  return pyDT.timedelta(hours = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
     802
     803          # x/60 - minutes
     804          if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*60$', str_interval, flags = regex.LOCALE | regex.UNICODE):
     805                  return pyDT.timedelta(minutes = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
     806
     807          # nYnM - years, months
     808          keys_year = '|'.join(list(unit_keys['year'].replace('_keys_year', u'')))
     809          keys_month = '|'.join(list(unit_keys['month'].replace('_keys_month', u'')))
     810          if regex.match(u'^~*(\s|\t)*\d+(%s|\s|\t)+\d+(\s|\t)*(%s)+$' % (keys_year, keys_month), str_interval, flags = regex.LOCALE | regex.UNICODE):
     811                  parts = regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)
     812                  years, months = divmod(int(parts[1]), 12)
     813                  years += int(parts[0])
     814                  return pyDT.timedelta(days = ((years * avg_days_per_gregorian_year) + (months * avg_days_per_gregorian_month)))
     815
     816          # nMnW - months, weeks
     817          keys_month = '|'.join(list(unit_keys['month'].replace('_keys_month', u'')))
     818          keys_week = '|'.join(list(unit_keys['week'].replace('_keys_week', u'')))
     819          if regex.match(u'^~*(\s|\t)*\d+(%s|\s|\t)+\d+(\s|\t)*(%s)+$' % (keys_month, keys_week), str_interval, flags = regex.LOCALE | regex.UNICODE):
     820                  parts = regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)
     821                  months, weeks = divmod(int(parts[1]), 4)
     822                  months += int(parts[0])
     823                  return pyDT.timedelta(days = ((months * avg_days_per_gregorian_month) + (weeks * days_per_week)))
     824
     825          return None
     826  #===========================================================================
     827  # string -> date parser
     828  #---------------------------------------------------------------------------
     829 -def __single_char2py_dt(str2parse, trigger_chars=None):
     830          """This matches on single characters.
     831
     832          Spaces and tabs are discarded.
     833
     834          Default is 'ndmy':
     835                  n - Now
     836                  d - toDay
     837                  m - toMorrow    Someone please suggest a synonym !
     838                  y - Yesterday
     839
     840          This also defines the significance of the order of the characters.
     841          """
     842          if trigger_chars is None:
     843                  trigger_chars = _('ndmy (single character date triggers)')[:4].lower()
     844
     845          str2parse = str2parse.strip().lower()
     846
     847          if len(str2parse) != 1:
     848                  return []
     849
     850          if str2parse not in trigger_chars:
     851                  return []
     852
     853          now = mxDT.now()
     854          enc = gmI18N.get_encoding()
     855
     856          # FIXME: handle uebermorgen/vorgestern ?
     857
     858          # right now
     859          if str2parse == trigger_chars[0]:
     860                  return [{
     861                          'data': mxdt2py_dt(now),
     862                          'label': _('right now (%s, %s)') % (now.strftime('%A').decode(enc), now)
     863                  }]
     864
     865          # today
     866          if str2parse == trigger_chars[1]:
     867                  return [{
     868                          'data': mxdt2py_dt(now),
     869                          'label': _('today (%s)') % now.strftime('%A, %Y-%m-%d').decode(enc)
     870                  }]
     871
     872          # tomorrow
     873          if str2parse == trigger_chars[2]:
     874                  ts = now + mxDT.RelativeDateTime(days = +1)
     875                  return [{
     876                          'data': mxdt2py_dt(ts),
     877                          'label': _('tomorrow (%s)') % ts.strftime('%A, %Y-%m-%d').decode(enc)
     878                  }]
     879
     880          # yesterday
     881          if str2parse == trigger_chars[3]:
     882                  ts = now + mxDT.RelativeDateTime(days = -1)
     883                  return [{
     884                          'data': mxdt2py_dt(ts),
     885                          'label': _('yesterday (%s)') % ts.strftime('%A, %Y-%m-%d').decode(enc)
     886                  }]
     887
     888          return []
     889  #---------------------------------------------------------------------------
     890 -def __single_dot2py_dt(str2parse):
     891          """Expand fragments containing a single dot.
     892
     893          Standard colloquial date format in Germany: day.month.year
     894
     895          "14."
     896                  - the 14th of the current month
     897                  - the 14th of next month
     898          "-14."
     899                  - the 14th of last month
     900          """
     901          str2parse = str2parse.strip()
     902
     903          if not str2parse.endswith(u'.'):
     904                  return []
     905
     906          str2parse = str2parse[:-1]
     907          try:
     908                  day_val = int(str2parse)
     909          except ValueError:
     910                  return []
     911
     912          if (day_val < -31) or (day_val > 31) or (day_val == 0):
     913                  return []
     914
     915          now = mxDT.now()
     916          enc = gmI18N.get_encoding()
     917          matches = []
     918
     919          # day X of last month only
     920          if day_val < 0:
     921                  ts = now + mxDT.RelativeDateTime(day = abs(day_val), months = -1)
     922                  if abs(day_val) <= gregorian_month_length[ts.month]:
     923                          matches.append ({
     924                                  'data': mxdt2py_dt(ts),
     925                                  'label': _('%s-%s-%s: a %s last month') % (ts.year, ts.month, ts.day, ts.strftime('%A').decode(enc))
     926                          })
     927
     928          # day X of this month
     929          if day_val > 0:
     930                  ts = now + mxDT.RelativeDateTime(day = day_val)
     931                  if day_val <= gregorian_month_length[ts.month]:
     932                          matches.append ({
     933                                  'data': mxdt2py_dt(ts),
     934                                  'label': _('%s-%s-%s: a %s this month') % (ts.year, ts.month, ts.day, ts.strftime('%A').decode(enc))
     935                          })
     936
     937          # day X of next month
     938          if day_val > 0:
     939                  ts = now + mxDT.RelativeDateTime(day = day_val, months = +1)
     940                  if day_val <= gregorian_month_length[ts.month]:
     941                          matches.append ({
     942                                  'data': mxdt2py_dt(ts),
     943                                  'label': _('%s-%s-%s: a %s next month') % (ts.year, ts.month, ts.day, ts.strftime('%A').decode(enc))
     944                          })
     945
     946          # day X of last month
     947          if day_val > 0:
     948                  ts = now + mxDT.RelativeDateTime(day = day_val, months = -1)
     949                  if day_val <= gregorian_month_length[ts.month]:
     950                          matches.append ({
     951                                  'data': mxdt2py_dt(ts),
     952                                  'label': _('%s-%s-%s: a %s last month') % (ts.year, ts.month, ts.day, ts.strftime('%A').decode(enc))
     953                          })
     954
     955          return matches
     956  #---------------------------------------------------------------------------
     957 -def __single_slash2py_dt(str2parse):
     958          """Expand fragments containing a single slash.
     959
     960          "5/"
     961                  - 2005/                                 (2000 - 2025)
     962                  - 1995/                                 (1990 - 1999)
     963                  - Mai/current year
     964                  - Mai/next year
     965                  - Mai/last year
     966                  - Mai/200x
     967                  - Mai/20xx
     968                  - Mai/199x
     969                  - Mai/198x
     970                  - Mai/197x
     971                  - Mai/19xx
     972
     973          5/1999
     974          6/2004
     975          """
     976          str2parse = str2parse.strip()
     977
     978          now = mxDT.now()
     979          enc = gmI18N.get_encoding()
     980
     981          # 5/1999
     982          if regex.match(r"^\d{1,2}(\s|\t)*/+(\s|\t)*\d{4}$", str2parse, flags = regex.LOCALE | regex.UNICODE):
     983                  parts = regex.findall(r'\d+', str2parse, flags = regex.LOCALE | regex.UNICODE)
     984                  ts = now + mxDT.RelativeDateTime(year = int(parts[1]), month = int(parts[0]))
     985                  return [{
     986                          'data': mxdt2py_dt(ts),
     987                          'label': ts.strftime('%Y-%m-%d').decode(enc)
     988                  }]
     989
     990          matches = []
     991          # 5/
     992          if regex.match(r"^\d{1,2}(\s|\t)*/+$", str2parse, flags = regex.LOCALE | regex.UNICODE):
     993                  val = int(str2parse.rstrip(u'/').strip())
     994
     995                  # "55/" -> "1955"
     996                  if val < 100 and val >= 0:
     997                          matches.append ({
     998                                  'data': None,
     999                                  'label': '%s-' % (val + 1900)
    1000                          })
    1001
    1002                  # "11/" -> "2011"
    1003                  if val < 26 and val >= 0:
    1004                          matches.append ({
    1005                                  'data': None,
    1006                                  'label': '%s-' % (val + 2000)
    1007                          })
    1008
    1009                  # "5/" -> "1995"
    1010                  if val < 10 and val >= 0:
    1011                          matches.append ({
    1012                                  'data': None,
    1013                                  'label': '%s-' % (val + 1990)
    1014                          })
    1015
    1016                  if val < 13 and val > 0:
    1017                          # "11/" -> "11/this year"
    1018                          matches.append ({
    1019                                  'data': None,
    1020                                  'label': '%s-%.2d-' % (now.year, val)
    1021                          })
    1022                          # "11/" -> "11/next year"
    1023                          ts = now + mxDT.RelativeDateTime(years = 1)
    1024                          matches.append ({
    1025                                  'data': None,
    1026                                  'label': '%s-%.2d-' % (ts.year, val)
    1027                          })
    1028                          # "11/" -> "11/last year"
    1029                          ts = now + mxDT.RelativeDateTime(years = -1)
    1030                          matches.append ({
    1031                                  'data': None,
    1032                                  'label': '%s-%.2d-' % (ts.year, val)
    1033                          })
    1034                          # "11/" -> "201?-11-"
    1035                          matches.append ({
    1036                                  'data': None,
    1037                                  'label': '201?-%.2d-' % val
    1038                          })
    1039                          # "11/" -> "200?-11-"
    1040                          matches.append ({
    1041                                  'data': None,
    1042                                  'label': '200?-%.2d-' % val
    1043                          })
    1044                          # "11/" -> "20??-11-"
    1045                          matches.append ({
    1046                                  'data': None,
    1047                                  'label': '20??-%.2d-' % val
    1048                          })
    1049                          # "11/" -> "199?-11-"
    1050                          matches.append ({
    1051                                  'data': None,
    1052                                  'label': '199?-%.2d-' % val
    1053                          })
    1054                          # "11/" -> "198?-11-"
    1055                          matches.append ({
    1056                                  'data': None,
    1057                                  'label': '198?-%.2d-' % val
    1058                          })
    1059                          # "11/" -> "198?-11-"
    1060                          matches.append ({
    1061                                  'data': None,
    1062                                  'label': '197?-%.2d-' % val
    1063                          })
    1064                          # "11/" -> "19??-11-"
    1065                          matches.append ({
    1066                                  'data': None,
    1067                                  'label': '19??-%.2d-' % val
    1068                          })
    1069
    1070          return matches
    1071  #---------------------------------------------------------------------------
    1072 -def __numbers_only2py_dt(str2parse):
    1073          """This matches on single numbers.
    1074
    1075          Spaces or tabs are discarded.
    1076          """
    1077          try:
    1078                  val = int(str2parse.strip())
    1079          except ValueError:
    1080                  return []
    1081
    1082          # strftime() returns str but in the localized encoding,
    1083          # so we may need to decode that to unicode
    1084          enc = gmI18N.get_encoding()
    1085          now = mxDT.now()
    1086
    1087          matches = []
    1088
    1089          # that year
    1090          if (1850 < val) and (val < 2100):
    1091                  ts = now + mxDT.RelativeDateTime(year = val)
    1092                  matches.append ({
    1093                          'data': mxdt2py_dt(ts),
    1094                          'label': ts.strftime('%Y-%m-%d')
    1095                  })
    1096
    1097          # day X of this month
    1098          if (val > 0) and (val <= gregorian_month_length[now.month]):
    1099                  ts = now + mxDT.RelativeDateTime(day = val)
    1100                  matches.append ({
    1101                          'data': mxdt2py_dt(ts),
    1102                          'label': _('%d. of %s (this month): a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc))
    1103                  })
    1104
    1105          # day X of next month
    1106          if (val > 0) and (val < 32):
    1107                  ts = now + mxDT.RelativeDateTime(months = 1, day = val)
    1108                  matches.append ({
    1109                          'data': mxdt2py_dt(ts),
    1110                          'label': _('%d. of %s (next month): a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc))
    1111                  })
    1112
    1113          # day X of last month
    1114          if (val > 0) and (val < 32):
    1115                  ts = now + mxDT.RelativeDateTime(months = -1, day = val)
    1116                  matches.append ({
    1117                          'data': mxdt2py_dt(ts),
    1118                          'label': _('%d. of %s (last month): a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc))
    1119                  })
    1120
    1121          # X days from now
    1122          if (val > 0) and (val <= 400):                          # more than a year ahead in days ?? nah !
    1123                  ts = now + mxDT.RelativeDateTime(days = val)
    1124                  matches.append ({
    1125                          'data': mxdt2py_dt(ts),
    1126                          'label': _('in %d day(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
    1127                  })
    1128          if (val < 0) and (val >= -400):                         # more than a year back in days ?? nah !
    1129                  ts = now - mxDT.RelativeDateTime(days = abs(val))
    1130                  matches.append ({
    1131                          'data': mxdt2py_dt(ts),
    1132                          'label': _('%d day(s) ago: %s') % (abs(val), ts.strftime('%A, %Y-%m-%d').decode(enc))
    1133                  })
    1134
    1135          # X weeks from now
    1136          if (val > 0) and (val <= 50):                           # pregnancy takes about 40 weeks :-)
    1137                  ts = now + mxDT.RelativeDateTime(weeks = val)
    1138                  matches.append ({
    1139                          'data': mxdt2py_dt(ts),
    1140                          'label': _('in %d week(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
    1141                  })
    1142          if (val < 0) and (val >= -50):                          # pregnancy takes about 40 weeks :-)
    1143                  ts = now - mxDT.RelativeDateTime(weeks = abs(val))
    1144                  matches.append ({
    1145                          'data': mxdt2py_dt(ts),
    1146                          'label': _('%d week(s) ago: %s') % (abs(val), ts.strftime('%A, %Y-%m-%d').decode(enc))
    1147                  })
    1148
    1149          # month X of ...
    1150          if (val < 13) and (val > 0):
    1151                  # ... this year
    1152                  ts = now + mxDT.RelativeDateTime(month = val)
    1153                  matches.append ({
    1154                          'data': mxdt2py_dt(ts),
    1155                          'label': _('%s (%s this year)') % (ts.strftime('%Y-%m-%d'), ts.strftime('%B').decode(enc))
    1156                  })
    1157
    1158                  # ... next year
    1159                  ts = now + mxDT.RelativeDateTime(years = 1, month = val)
    1160                  matches.append ({
    1161                          'data': mxdt2py_dt(ts),
    1162                          'label': _('%s (%s next year)') % (ts.strftime('%Y-%m-%d'), ts.strftime('%B').decode(enc))
    1163                  })
    1164
    1165                  # ... last year
    1166                  ts = now + mxDT.RelativeDateTime(years = -1, month = val)
    1167                  matches.append ({
    1168                          'data': mxdt2py_dt(ts),
    1169                          'label': _('%s (%s last year)') % (ts.strftime('%Y-%m-%d'), ts.strftime('%B').decode(enc))
    1170                  })
    1171
    1172                  # fragment expansion
    1173                  matches.append ({
    1174                          'data': None,
    1175                          'label': '200?-%s' % val
    1176                  })
    1177                  matches.append ({
    1178                          'data': None,
    1179                          'label': '199?-%s' % val
    1180                  })
    1181                  matches.append ({
    1182                          'data': None,
    1183                          'label': '198?-%s' % val
    1184                  })
    1185                  matches.append ({
    1186                          'data': None,
    1187                          'label': '19??-%s' % val
    1188                  })
    1189
    1190          # day X of ...
    1191          if (val < 8) and (val > 0):
    1192                  # ... this week
    1193                  ts = now + mxDT.RelativeDateTime(weekday = (val-1, 0))
    1194                  matches.append ({
    1195                          'data': mxdt2py_dt(ts),
    1196                          'label': _('%s this week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc))
    1197                  })
    1198
    1199                  # ... next week
    1200                  ts = now + mxDT.RelativeDateTime(weeks = +1, weekday = (val-1, 0))
    1201                  matches.append ({
    1202                          'data': mxdt2py_dt(ts),
    1203                          'label': _('%s next week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc))
    1204                  })
    1205
    1206                  # ... last week
    1207                  ts = now + mxDT.RelativeDateTime(weeks = -1, weekday = (val-1, 0))
    1208                  matches.append ({
    1209                          'data': mxdt2py_dt(ts),
    1210                          'label': _('%s last week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc))
    1211                  })
    1212
    1213          if (val < 100) and (val > 0):
    1214                  matches.append ({
    1215                          'data': None,
    1216                          'label': '%s-' % (1900 + val)
    1217                  })
    1218
    1219          if val == 201:
    1220                  tmp = {
    1221                          'data': mxdt2py_dt(now),
    1222                          'label': now.strftime('%Y-%m-%d')
    1223                  }
    1224                  matches.append(tmp)
    1225                  matches.append ({
    1226                          'data': None,
    1227                          'label': now.strftime('%Y-%m')
    1228                  })
    1229                  matches.append ({
    1230                          'data': None,
    1231                          'label': now.strftime('%Y')
    1232                  })
    1233                  matches.append ({
    1234                          'data': None,
    1235                          'label': '%s-' % (now.year + 1)
    1236                  })
    1237                  matches.append ({
    1238                          'data': None,
    1239                          'label': '%s-' % (now.year - 1)
    1240                  })
    1241
    1242          if val < 200 and val >= 190:
    1243                  for i in range(10):
    1244                          matches.append ({
    1245                                  'data': None,
    1246                                  'label': '%s%s-' % (val, i)
    1247                          })
    1248
    1249          return matches
    1250  #---------------------------------------------------------------------------
    1251 -def __explicit_offset2py_dt(str2parse, offset_chars=None):
    1252          """
    1253                          Default is 'hdwmy':
    1254                          h - hours
    1255                          d - days
    1256                          w - weeks
    1257                          m - months
    1258                          y - years
    1259
    1260                  This also defines the significance of the order of the characters.
    1261          """
    1262          if offset_chars is None:
    1263                  offset_chars = _('hdwmy (single character date offset triggers)')[:5].lower()
    1264
    1265          str2parse = str2parse.strip()
    1266
    1267          # "+/-XXd/w/m/t"
    1268          if not regex.match(r"^(\+|-)?(\s|\t)*\d{1,2}(\s|\t)*[%s]$" % offset_chars, str2parse, flags = regex.LOCALE | regex.UNICODE):
    1269                  return []
    1270
    1271          # into the past ?
    1272          if str2parse.startswith(u'-'):
    1273                  is_future = False
    1274                  str2parse = str2parse[1:].strip()
    1275          else:
    1276                  is_future = True
    1277                  str2parse = str2parse.replace(u'+', u'').strip()
    1278
    1279          val = int(regex.findall(u'\d{1,2}', str2parse, flags = regex.LOCALE | regex.UNICODE)[0])
    1280          offset_char = regex.findall(u'[%s]' % offset_chars, str2parse, flags = regex.LOCALE | regex.UNICODE)[0].lower()
    1281
    1282          now = mxDT.now()
    1283          enc = gmI18N.get_encoding()
    1284
    1285          ts = None
    1286          # hours
    1287          if offset_char == offset_chars[0]:
    1288                  if is_future:
    1289                          ts = now + mxDT.RelativeDateTime(hours = val)
    1290                          label = _('in %d hour(s): %s') % (val, ts.strftime('%H:%M'))
    1291                  else:
    1292                          ts = now - mxDT.RelativeDateTime(hours = val)
    1293                          label = _('%d hour(s) ago: %s') % (val, ts.strftime('%H:%M'))
    1294          # days
    1295          elif offset_char == offset_chars[1]:
    1296                  if is_future:
    1297                          ts = now + mxDT.RelativeDateTime(days = val)
    1298                          label = _('in %d day(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
    1299                  else:
    1300                          ts = now - mxDT.RelativeDateTime(days = val)
    1301                          label = _('%d day(s) ago: %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
    1302          # weeks
    1303          elif offset_char == offset_chars[2]:
    1304                  if is_future:
    1305                          ts = now + mxDT.RelativeDateTime(weeks = val)
    1306                          label = _('in %d week(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
    1307                  else:
    1308                          ts = now - mxDT.RelativeDateTime(weeks = val)
    1309                          label = _('%d week(s) ago: %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
    1310          # months
    1311          elif offset_char == offset_chars[3]:
    1312                  if is_future:
    1313                          ts = now + mxDT.RelativeDateTime(months = val)
    1314                          label = _('in %d month(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
    1315                  else:
    1316                          ts = now - mxDT.RelativeDateTime(months = val)
    1317                          label = _('%d month(s) ago: %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
    1318          # years
    1319          elif offset_char == offset_chars[4]:
    1320                  if is_future:
    1321                          ts = now + mxDT.RelativeDateTime(years = val)
    1322                          label = _('in %d year(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
    1323                  else:
    1324                          ts = now - mxDT.RelativeDateTime(years = val)
    1325                          label = _('%d year(s) ago: %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
    1326
    1327          if ts is None:
    1328                  return []
    1329
    1330          return [{
    1331                  'data': mxdt2py_dt(ts),
    1332                  'label': label
    1333          }]
    1334  #---------------------------------------------------------------------------
    1335 -def str2pydt_matches(str2parse=None, patterns=None):
    1336          """Turn a string into candidate dates and auto-completions the user is likely to type.
    1337
    1338          You MUST have called locale.setlocale(locale.LC_ALL, '')
    1339          somewhere in your code previously.
    1340
    1341          @param patterns: list of time.strptime compatible date pattern
    1342          @type patterns: list
    1343          """
    1344          matches = []
    1345          matches.extend(__single_dot2py_dt(str2parse))
    1346          matches.extend(__numbers_only2py_dt(str2parse))
    1347          matches.extend(__single_slash2py_dt(str2parse))
    1348          matches.extend(__single_char2py_dt(str2parse))
    1349          matches.extend(__explicit_offset2py_dt(str2parse))
    1350
    1351          # try mxDT parsers
    1352          try:
    1353                  date = mxDT.Parser.DateFromString (
    1354                          text = str2parse,
    1355                          formats = ('euro', 'iso', 'us', 'altus', 'altiso', 'lit', 'altlit', 'eurlit')
    1356                  )
    1357                  matches.append ({
    1358                          'data': mxdt2py_dt(date),
    1359                          'label': date.strftime('%Y-%m-%d')
    1360                  })
    1361          except (ValueError, OverflowError, mxDT.RangeError):
    1362                  pass
    1363
    1364          # apply explicit patterns
    1365          if patterns is None:
    1366                  patterns = []
    1367
    1368          patterns.append('%Y-%m-%d')
    1369          patterns.append('%y-%m-%d')
    1370          patterns.append('%Y/%m/%d')
    1371          patterns.append('%y/%m/%d')
    1372
    1373          patterns.append('%d-%m-%Y')
    1374          patterns.append('%d-%m-%y')
    1375          patterns.append('%d/%m/%Y')
    1376          patterns.append('%d/%m/%y')
    1377
    1378          patterns.append('%m-%d-%Y')
    1379          patterns.append('%m-%d-%y')
    1380          patterns.append('%m/%d/%Y')
    1381          patterns.append('%m/%d/%y')
    1382
    1383          patterns.append('%Y.%m.%d')
    1384          patterns.append('%y.%m.%d')
    1385
    1386          for pattern in patterns:
    1387                  try:
    1388                          date = pyDT.datetime.strptime(str2parse, pattern).replace (
    1389                                  hour = 11,
    1390                                  minute = 11,
    1391                                  second = 11,
    1392                                  tzinfo = gmCurrentLocalTimezone
    1393                          )
    1394                          matches.append ({
    1395                                  'data': date,
    1396                                  'label': pydt_strftime(date, format = '%Y-%m-%d', accuracy = acc_days)
    1397                          })
    1398                  except AttributeError:
    1399                          # strptime() only available starting with Python 2.5
    1400                          break
    1401                  except OverflowError:
    1402                          # time.mktime() cannot handle dates older than a platform-dependant limit :-(
    1403                          continue
    1404                  except ValueError:
    1405                          # C-level overflow
    1406                          continue
    1407
    1408          return matches
    1409  #===========================================================================
    1410  # string -> fuzzy timestamp parser
    1411  #---------------------------------------------------------------------------
    1412 -def __explicit_offset(str2parse, offset_chars=None):
    1413          """
    1414                          Default is 'hdwm':
    1415                          h - hours
    1416                          d - days
    1417                          w - weeks
    1418                          m - months
    1419                          y - years
    1420
    1421                  This also defines the significance of the order of the characters.
    1422          """
    1423          if offset_chars is None:
    1424                  offset_chars = _('hdwmy (single character date offset triggers)')[:5].lower()
    1425
    1426          # "+/-XXd/w/m/t"
    1427          if not regex.match(u"^(\s|\t)*(\+|-)?(\s|\t)*\d{1,2}(\s|\t)*[%s](\s|\t)*$" % offset_chars, str2parse, flags = regex.LOCALE | regex.UNICODE):
    1428                  return []
    1429          val = int(regex.findall(u'\d{1,2}', str2parse, flags = regex.LOCALE | regex.UNICODE)[0])
    1430          offset_char = regex.findall(u'[%s]' % offset_chars, str2parse, flags = regex.LOCALE | regex.UNICODE)[0].lower()
    1431
    1432          now = mxDT.now()
    1433          enc = gmI18N.get_encoding()
    1434
    1435          # allow past ?
    1436          is_future = True
    1437          if str2parse.find('-') > -1:
    1438                  is_future = False
    1439
    1440          ts = None
    1441          # hours
    1442          if offset_char == offset_chars[0]:
    1443                  if is_future:
    1444                          ts = now + mxDT.RelativeDateTime(hours = val)
    1445                          label = _('in %d hour(s) - %s') % (val, ts.strftime('%H:%M'))
    1446                  else:
    1447                          ts = now - mxDT.RelativeDateTime(hours = val)
    1448                          label = _('%d hour(s) ago - %s') % (val, ts.strftime('%H:%M'))
    1449                  accuracy = acc_subseconds
    1450          # days
    1451          elif offset_char == offset_chars[1]:
    1452                  if is_future:
    1453                          ts = now + mxDT.RelativeDateTime(days = val)
    1454                          label = _('in %d day(s) - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
    1455                  else:
    1456                          ts = now - mxDT.RelativeDateTime(days = val)
    1457                          label = _('%d day(s) ago - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
    1458                  accuracy = acc_days
    1459          # weeks
    1460          elif offset_char == offset_chars[2]:
    1461                  if is_future:
    1462                          ts = now + mxDT.RelativeDateTime(weeks = val)
    1463                          label = _('in %d week(s) - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
    1464                  else:
    1465                          ts = now - mxDT.RelativeDateTime(weeks = val)
    1466                          label = _('%d week(s) ago - %s)') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
    1467                  accuracy = acc_days
    1468          # months
    1469          elif offset_char == offset_chars[3]:
    1470                  if is_future:
    1471                          ts = now + mxDT.RelativeDateTime(months = val)
    1472                          label = _('in %d month(s) - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
    1473                  else:
    1474                          ts = now - mxDT.RelativeDateTime(months = val)
    1475                          label = _('%d month(s) ago - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
    1476                  accuracy = acc_days
    1477          # years
    1478          elif offset_char == offset_chars[4]:
    1479                  if is_future:
    1480                          ts = now + mxDT.RelativeDateTime(years = val)
    1481                          label = _('in %d year(s) - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
    1482                  else:
    1483                          ts = now - mxDT.RelativeDateTime(years = val)
    1484                          label = _('%d year(s) ago - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
    1485                  accuracy = acc_months
    1486
    1487          if ts is None:
    1488                  return []
    1489
    1490          tmp = {
    1491                  'data': cFuzzyTimestamp(timestamp = ts, accuracy = accuracy),
    1492                  'label': label
    1493          }
    1494          return [tmp]
    1495  #---------------------------------------------------------------------------
    1496 -def __single_slash(str2parse):
    1497          """Expand fragments containing a single slash.
    1498
    1499          "5/"
    1500                  - 2005/                                 (2000 - 2025)
    1501                  - 1995/                                 (1990 - 1999)
    1502                  - Mai/current year
    1503                  - Mai/next year
    1504                  - Mai/last year
    1505                  - Mai/200x
    1506                  - Mai/20xx
    1507                  - Mai/199x
    1508                  - Mai/198x
    1509                  - Mai/197x
    1510                  - Mai/19xx
    1511          """
    1512          matches = []
    1513          now = mxDT.now()
    1514          if regex.match(u"^(\s|\t)*\d{1,2}(\s|\t)*/+(\s|\t)*$", str2parse, flags = regex.LOCALE | regex.UNICODE):
    1515                  val = int(regex.findall(u'\d+', str2parse, flags = regex.LOCALE | regex.UNICODE)[0])
    1516
    1517                  if val < 100 and val >= 0:
    1518                          matches.append ({
    1519                                  'data': None,
    1520                                  'label': '%s/' % (val + 1900)
    1521                          })
    1522
    1523                  if val < 26 and val >= 0:
    1524                          matches.append ({
    1525                                  'data': None,
    1526                                  'label': '%s/' % (val + 2000)
    1527                          })
    1528
    1529                  if val < 10 and val >= 0:
    1530                          matches.append ({
    1531                                  'data': None,
    1532                                  'label': '%s/' % (val + 1990)
    1533                          })
    1534
    1535                  if val < 13 and val > 0:
    1536                          matches.append ({
    1537                                  'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_months),
    1538                                  'label': '%.2d/%s' % (val, now.year)
    1539                          })
    1540                          ts = now + mxDT.RelativeDateTime(years = 1)
    1541                          matches.append ({
    1542                                  'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_months),
    1543                                  'label': '%.2d/%s' % (val, ts.year)
    1544                          })
    1545                          ts = now + mxDT.RelativeDateTime(years = -1)
    1546                          matches.append ({
    1547                                  'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_months),
    1548                                  'label': '%.2d/%s' % (val, ts.year)
    1549                          })
    1550                          matches.append ({
    1551                                  'data': None,
    1552                                  'label': '%.2d/200' % val
    1553                          })
    1554                          matches.append ({
    1555                                  'data': None,
    1556                                  'label': '%.2d/20' % val
    1557                          })
    1558                          matches.append ({
    1559                                  'data': None,
    1560                                  'label': '%.2d/199' % val
    1561                          })
    1562                          matches.append ({
    1563                                  'data': None,
    1564                                  'label': '%.2d/198' % val
    1565                          })
    1566                          matches.append ({
    1567                                  'data': None,
    1568                                  'label': '%.2d/197' % val
    1569                          })
    1570                          matches.append ({
    1571                                  'data': None,
    1572                                  'label': '%.2d/19' % val
    1573                          })
    1574
    1575          elif regex.match(u"^(\s|\t)*\d{1,2}(\s|\t)*/+(\s|\t)*\d{4}(\s|\t)*$", str2parse, flags = regex.LOCALE | regex.UNICODE):
    1576                  parts = regex.findall(u'\d+', str2parse, flags = regex.LOCALE | regex.UNICODE)
    1577                  fts = cFuzzyTimestamp (
    1578                          timestamp = mxDT.now() + mxDT.RelativeDateTime(year = int(parts[1]), month = int(parts[0])),
    1579                          accuracy = acc_months
    1580                  )
    1581                  matches.append ({
    1582                          'data': fts,
    1583                          'label': fts.format_accurately()
    1584                  })
    1585
    1586          return matches
    1587  #---------------------------------------------------------------------------
    1588 -def __numbers_only(str2parse):
    1589          """This matches on single numbers.
    1590
    1591          Spaces or tabs are discarded.
    1592          """
    1593          if not regex.match(u"^(\s|\t)*\d{1,4}(\s|\t)*$", str2parse, flags = regex.LOCALE | regex.UNICODE):
    1594                  return []
    1595
    1596          # strftime() returns str but in the localized encoding,
    1597          # so we may need to decode that to unicode
    1598          enc = gmI18N.get_encoding()
    1599          now = mxDT.now()
    1600          val = int(regex.findall(u'\d{1,4}', str2parse, flags = regex.LOCALE | regex.UNICODE)[0])
    1601
    1602          matches = []
    1603
    1604          # that year
    1605          if (1850 < val) and (val < 2100):
    1606                  ts = now + mxDT.RelativeDateTime(year = val)
    1607                  target_date = cFuzzyTimestamp (
    1608                          timestamp = ts,
    1609                          accuracy = acc_years
    1610                  )
    1611                  tmp = {
    1612                          'data': target_date,
    1613                          'label': '%s' % target_date
    1614                  }
    1615                  matches.append(tmp)
    1616
    1617          # day X of this month
    1618          if val <= gregorian_month_length[now.month]:
    1619                  ts = now + mxDT.RelativeDateTime(day = val)
    1620                  target_date = cFuzzyTimestamp (
    1621                          timestamp = ts,
    1622                          accuracy = acc_days
    1623                  )
    1624                  tmp = {
    1625                          'data': target_date,
    1626                          'label': _('%d. of %s (this month) - a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc))
    1627                  }
    1628                  matches.append(tmp)
    1629
    1630          # day X of next month
    1631          if val <= gregorian_month_length[(now + mxDT.RelativeDateTime(months = 1)).month]:
    1632                  ts = now + mxDT.RelativeDateTime(months = 1, day = val)
    1633                  target_date = cFuzzyTimestamp (
    1634                          timestamp = ts,
    1635                          accuracy = acc_days
    1636                  )
    1637                  tmp = {
    1638                          'data': target_date,
    1639                          'label': _('%d. of %s (next month) - a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc))
    1640                  }
    1641                  matches.append(tmp)
    1642
    1643          # day X of last month
    1644          if val <= gregorian_month_length[(now + mxDT.RelativeDateTime(months = -1)).month]:
    1645                  ts = now + mxDT.RelativeDateTime(months = -1, day = val)
    1646                  target_date = cFuzzyTimestamp (
    1647                          timestamp = ts,
    1648                          accuracy = acc_days
    1649                  )
    1650                  tmp = {
    1651                          'data': target_date,
    1652                          'label': _('%d. of %s (last month) - a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc))
    1653                  }
    1654                  matches.append(tmp)
    1655
    1656          # X days from now
    1657          if val <= 400:                          # more than a year ahead in days ?? nah !
    1658                  ts = now + mxDT.RelativeDateTime(days = val)
    1659                  target_date = cFuzzyTimestamp (
    1660                          timestamp = ts
    1661                  )
    1662                  tmp = {
    1663                          'data': target_date,
    1664                          'label': _('in %d day(s) - %s') % (val, target_date.timestamp.strftime('%A, %Y-%m-%d').decode(enc))
    1665                  }
    1666                  matches.append(tmp)
    1667
    1668          # X weeks from now
    1669          if val <= 50:                           # pregnancy takes about 40 weeks :-)
    1670                  ts = now + mxDT.RelativeDateTime(weeks = val)
    1671                  target_date = cFuzzyTimestamp (
    1672                          timestamp = ts
    1673                  )
    1674                  tmp = {
    1675                          'data': target_date,
    1676                          'label': _('in %d week(s) - %s') % (val, target_date.timestamp.strftime('%A, %Y-%m-%d').decode(enc))
    1677                  }
    1678                  matches.append(tmp)
    1679
    1680          # month X of ...
    1681          if val < 13:
    1682                  # ... this year
    1683                  ts = now + mxDT.RelativeDateTime(month = val)
    1684                  target_date = cFuzzyTimestamp (
    1685                          timestamp = ts,
    1686                          accuracy = acc_months
    1687                  )
    1688                  tmp = {
    1689                          'data': target_date,
    1690                          'label': _('%s (%s this year)') % (target_date, ts.strftime('%B').decode(enc))
    1691                  }
    1692                  matches.append(tmp)
    1693
    1694                  # ... next year
    1695                  ts = now + mxDT.RelativeDateTime(years = 1, month = val)
    1696                  target_date = cFuzzyTimestamp (
    1697                          timestamp = ts,
    1698                          accuracy = acc_months
    1699                  )
    1700                  tmp = {
    1701                          'data': target_date,
    1702                          'label': _('%s (%s next year)') % (target_date, ts.strftime('%B').decode(enc))
    1703                  }
    1704                  matches.append(tmp)
    1705
    1706                  # ... last year
    1707                  ts = now + mxDT.RelativeDateTime(years = -1, month = val)
    1708                  target_date = cFuzzyTimestamp (
    1709                          timestamp = ts,
    1710                          accuracy = acc_months
    1711                  )
    1712                  tmp = {
    1713                          'data': target_date,
    1714                          'label': _('%s (%s last year)') % (target_date, ts.strftime('%B').decode(enc))
    1715                  }
    1716                  matches.append(tmp)
    1717
    1718                  # fragment expansion
    1719                  matches.append ({
    1720                          'data': None,
    1721                          'label': '%s/200' % val
    1722                  })
    1723                  matches.append ({
    1724                          'data': None,
    1725                          'label': '%s/199' % val
    1726                  })
    1727                  matches.append ({
    1728                          'data': None,
    1729                          'label': '%s/198' % val
    1730                  })
    1731                  matches.append ({
    1732                          'data': None,
    1733                          'label': '%s/19' % val
    1734                  })
    1735
    1736          # day X of ...
    1737          if val < 8:
    1738                  # ... this week
    1739                  ts = now + mxDT.RelativeDateTime(weekday = (val-1, 0))
    1740                  target_date = cFuzzyTimestamp (
    1741                          timestamp = ts,
    1742                          accuracy = acc_days
    1743                  )
    1744                  tmp = {
    1745                          'data': target_date,
    1746                          'label': _('%s this week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc))
    1747                  }
    1748                  matches.append(tmp)
    1749
    1750                  # ... next week
    1751                  ts = now + mxDT.RelativeDateTime(weeks = +1, weekday = (val-1, 0))
    1752                  target_date = cFuzzyTimestamp (
    1753                          timestamp = ts,
    1754                          accuracy = acc_days
    1755                  )
    1756                  tmp = {
    1757                          'data': target_date,
    1758                          'label': _('%s next week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc))
    1759                  }
    1760                  matches.append(tmp)
    1761
    1762                  # ... last week
    1763                  ts = now + mxDT.RelativeDateTime(weeks = -1, weekday = (val-1, 0))
    1764                  target_date = cFuzzyTimestamp (
    1765                          timestamp = ts,
    1766                          accuracy = acc_days
    1767                  )
    1768                  tmp = {
    1769                          'data': target_date,
    1770                          'label': _('%s last week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc))
    1771                  }
    1772                  matches.append(tmp)
    1773
    1774          if val < 100:
    1775                  matches.append ({
    1776                          'data': None,
    1777                          'label': '%s/' % (1900 + val)
    1778                  })
    1779
    1780          if val == 200:
    1781                  tmp = {
    1782                          'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_days),
    1783                          'label': '%s' % target_date
    1784                  }
    1785                  matches.append(tmp)
    1786                  matches.append ({
    1787                          'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_months),
    1788                          'label': '%.2d/%s' % (now.month, now.year)
    1789                  })
    1790                  matches.append ({
    1791                          'data': None,
    1792                          'label': '%s/' % now.year
    1793                  })
    1794                  matches.append ({
    1795                          'data': None,
    1796                          'label': '%s/' % (now.year + 1)
    1797                  })
    1798                  matches.append ({
    1799                          'data': None,
    1800                          'label': '%s/' % (now.year - 1)
    1801                  })
    1802
    1803          if val < 200 and val >= 190:
    1804                  for i in range(10):
    1805                          matches.append ({
    1806                                  'data': None,
    1807                                  'label': '%s%s/' % (val, i)
    1808                          })
    1809
    1810          return matches
    1811  #---------------------------------------------------------------------------
    1812 -def __single_dot(str2parse):
    1813          """Expand fragments containing a single dot.
    1814
    1815          Standard colloquial date format in Germany: day.month.year
    1816
    1817          "14."
    1818                  - 14th current month this year
    1819                  - 14th next month this year
    1820          """
    1821          if not regex.match(u"^(\s|\t)*\d{1,2}\.{1}(\s|\t)*$", str2parse, flags = regex.LOCALE | regex.UNICODE):
    1822                  return []
    1823
    1824          val = int(regex.findall(u'\d+', str2parse, flags = regex.LOCALE | regex.UNICODE)[0])
    1825          now = mxDT.now()
    1826          enc = gmI18N.get_encoding()
    1827
    1828          matches = []
    1829
    1830          # day X of this month
    1831          ts = now + mxDT.RelativeDateTime(day = val)
    1832          if val > 0 and val <= gregorian_month_length[ts.month]:
    1833                  matches.append ({
    1834                          'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_days),
    1835                          'label': '%s.%s.%s - a %s this month' % (ts.day, ts.month, ts.year, ts.strftime('%A').decode(enc))
    1836                  })
    1837
    1838          # day X of next month
    1839          ts = now + mxDT.RelativeDateTime(day = val, months = +1)
    1840          if val > 0 and val <= gregorian_month_length[ts.month]:
    1841                  matches.append ({
    1842                          'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_days),
    1843                          'label': '%s.%s.%s - a %s next month' % (ts.day, ts.month, ts.year, ts.strftime('%A').decode(enc))
    1844                  })
    1845
    1846          # day X of last month
    1847          ts = now + mxDT.RelativeDateTime(day = val, months = -1)
    1848          if val > 0 and val <= gregorian_month_length[ts.month]:
    1849                  matches.append ({
    1850                          'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_days),
    1851                          'label': '%s.%s.%s - a %s last month' % (ts.day, ts.month, ts.year, ts.strftime('%A').decode(enc))
    1852                  })
    1853
    1854          return matches
    1855  #---------------------------------------------------------------------------
    1856 -def str2fuzzy_timestamp_matches(str2parse=None, default_time=None, patterns=None):
    1857          """
    1858          Turn a string into candidate fuzzy timestamps and auto-completions the user is likely to type.
    1859
    1860          You MUST have called locale.setlocale(locale.LC_ALL, '')
    1861          somewhere in your code previously.
    1862
    1863          @param default_time: if you want to force the time part of the time
    1864                  stamp to a given value and the user doesn't type any time part
    1865                  this value will be used
    1866          @type default_time: an mx.DateTime.DateTimeDelta instance
    1867
    1868          @param patterns: list of [time.strptime compatible date/time pattern, accuracy]
    1869          @type patterns: list
    1870          """
    1871          matches = __single_dot(str2parse)
    1872          matches.extend(__numbers_only(str2parse))
    1873          matches.extend(__single_slash(str2parse))
    1874          ms = __single_char2py_dt(str2parse)
    1875          for m in ms:
    1876                  matches.append ({
    1877                          'data': cFuzzyTimestamp (
    1878                                  timestamp = m['data'],
    1879                                  accuracy = acc_days
    1880                          ),
    1881                          'label': m['label']
    1882                  })
    1883          matches.extend(__explicit_offset(str2parse))
    1884
    1885          # try mxDT parsers
    1886          try:
    1887                  # date ?
    1888                  date_only = mxDT.Parser.DateFromString (
    1889                          text = str2parse,
    1890                          formats = ('euro', 'iso', 'us', 'altus', 'altiso', 'lit', 'altlit', 'eurlit')
    1891                  )
    1892                  # time, too ?
    1893                  time_part = mxDT.Parser.TimeFromString(text = str2parse)
    1894                  datetime = date_only + time_part
    1895                  if datetime == date_only:
    1896                          accuracy = acc_days
    1897                          if isinstance(default_time, mxDT.DateTimeDeltaType):
    1898                                  datetime = date_only + default_time
    1899                                  accuracy = acc_minutes
    1900                  else:
    1901                          accuracy = acc_subseconds
    1902                  fts = cFuzzyTimestamp (
    1903                          timestamp = datetime,
    1904                          accuracy = accuracy
    1905                  )
    1906                  matches.append ({
    1907                          'data': fts,
    1908                          'label': fts.format_accurately()
    1909                  })
    1910          except (ValueError, mxDT.RangeError):
    1911                  pass
    1912
    1913          if patterns is None:
    1914                  patterns = []
    1915
    1916          patterns.append(['%Y-%m-%d', acc_days])
    1917          patterns.append(['%y-%m-%d', acc_days])
    1918          patterns.append(['%Y/%m/%d', acc_days])
    1919          patterns.append(['%y/%m/%d', acc_days])
    1920
    1921          patterns.append(['%d-%m-%Y', acc_days])
    1922          patterns.append(['%d-%m-%y', acc_days])
    1923          patterns.append(['%d/%m/%Y', acc_days])
    1924          patterns.append(['%d/%m/%y', acc_days])
    1925
    1926          patterns.append(['%m-%d-%Y', acc_days])
    1927          patterns.append(['%m-%d-%y', acc_days])
    1928          patterns.append(['%m/%d/%Y', acc_days])
    1929          patterns.append(['%m/%d/%y', acc_days])
    1930
    1931          patterns.append(['%Y.%m.%d', acc_days])
    1932          patterns.append(['%y.%m.%d', acc_days])
    1933
    1934
    1935          for pattern in patterns:
    1936                  try:
    1937                          fts = cFuzzyTimestamp (
    1938                                  timestamp = pyDT.datetime.fromtimestamp(time.mktime(time.strptime(str2parse, pattern[0]))),
    1939                                  accuracy = pattern[1]
    1940                          )
    1941                          matches.append ({
    1942                                  'data': fts,
    1943                                  'label': fts.format_accurately()
    1944                          })
    1945                  except AttributeError:
    1946                          # strptime() only available starting with Python 2.5
    1947                          break
    1948                  except OverflowError:
    1949                          # time.mktime() cannot handle dates older than a platform-dependant limit :-(
    1950                          continue
    1951                  except ValueError:
    1952                          # C-level overflow
    1953                          continue
    1954
    1955          return matches
    1956  #===========================================================================
    1957  # fuzzy timestamp class
    1958  #---------------------------------------------------------------------------
    1959 -class cFuzzyTimestamp:
    1960
    1961          # FIXME: add properties for year, month, ...
    1962
    1963          """A timestamp implementation with definable inaccuracy.
    1964
    1965          This class contains an mxDateTime.DateTime instance to
    1966          hold the actual timestamp. It adds an accuracy attribute
    1967          to allow the programmer to set the precision of the
    1968          timestamp.
    1969
    1970          The timestamp will have to be initialzed with a fully
    1971          precise value (which may, of course, contain partially
    1972          fake data to make up for missing values). One can then
    1973          set the accuracy value to indicate up to which part of
    1974          the timestamp the data is valid. Optionally a modifier
    1975          can be set to indicate further specification of the
    1976          value (such as "summer", "afternoon", etc).
    1977
    1978          accuracy values:
    1979                  1: year only
    1980                  ...
    1981                  7: everything including milliseconds value
    1982
    1983          Unfortunately, one cannot directly derive a class from mx.DateTime.DateTime :-(
    1984          """
    1985          #-----------------------------------------------------------------------
    1986 -        def __init__(self, timestamp=None, accuracy=acc_subseconds, modifier=''):
    1987
    1988                  if timestamp is None:
    1989                          timestamp = mxDT.now()
    1990                          accuracy = acc_subseconds
    1991                          modifier = ''
    1992
    1993                  if (accuracy < 1) or (accuracy > 8):
    1994                          raise ValueError('%s.__init__(): <accuracy> must be between 1 and 8' % self.__class__.__name__)
    1995
    1996                  if isinstance(timestamp, pyDT.datetime):
    1997                          timestamp = mxDT.DateTime(timestamp.year, timestamp.month, timestamp.day, timestamp.hour, timestamp.minute, timestamp.second)
    1998
    1999                  if type(timestamp) != mxDT.DateTimeType:
    2000                          raise TypeError('%s.__init__(): <timestamp> must be of mx.DateTime.DateTime or datetime.datetime type' % self.__class__.__name__)
    2001
    2002                  self.timestamp = timestamp
    2003                  self.accuracy = accuracy
    2004                  self.modifier =  modifier
    2005          #-----------------------------------------------------------------------
    2006          # magic API
    2007          #-----------------------------------------------------------------------
    2008 -        def __str__(self):
    2009                  """Return string representation meaningful to a user, also for %s formatting."""
    2010                  return self.format_accurately()
    2011          #-----------------------------------------------------------------------
    2012 -        def __repr__(self):
    2013                  """Return string meaningful to a programmer to aid in debugging."""
    2014                  tmp = '<[%s]: timestamp [%s], accuracy [%s] (%s), modifier [%s] at %s>' % (
    2015                          self.__class__.__name__,
    2016                          repr(self.timestamp),
    2017                          self.accuracy,
    2018                          _accuracy_strings[self.accuracy],
    2019                          self.modifier,
    2020                          id(self)
    2021                  )
    2022                  return tmp
    2023          #-----------------------------------------------------------------------
    2024          # external API
    2025          #-----------------------------------------------------------------------
    2026 -        def strftime(self, format_string):
    2027                  if self.accuracy == 7:
    2028                          return self.timestamp.strftime(format_string)
    2029                  return self.format_accurately()
    2030          #-----------------------------------------------------------------------
    2031 -        def Format(self, format_string):
    2032                  return self.strftime(format_string)
    2033          #-----------------------------------------------------------------------
    2034 -        def format_accurately(self, accuracy=None):
    2035                  if accuracy is None:
    2036                          accuracy = self.accuracy
    2037
    2038                  if accuracy == acc_years:
    2039                          return unicode(self.timestamp.year)
    2040
    2041                  if accuracy == acc_months:
    2042                          return unicode(self.timestamp.strftime('%m/%Y'))        # FIXME: use 3-letter month ?
    2043
    2044                  if accuracy == acc_weeks:
    2045                          return unicode(self.timestamp.strftime('%m/%Y'))        # FIXME: use 3-letter month ?
    2046
    2047                  if accuracy == acc_days:
    2048                          return unicode(self.timestamp.strftime('%Y-%m-%d'))
    2049
    2050                  if accuracy == acc_hours:
    2051                          return unicode(self.timestamp.strftime("%Y-%m-%d %I%p"))
    2052
    2053                  if accuracy == acc_minutes:
    2054                          return unicode(self.timestamp.strftime("%Y-%m-%d %H:%M"))
    2055
    2056                  if accuracy == acc_seconds:
    2057                          return unicode(self.timestamp.strftime("%Y-%m-%d %H:%M:%S"))
    2058
    2059                  if accuracy == acc_subseconds:
    2060                          return unicode(self.timestamp)
    2061
    2062                  raise ValueError, '%s.format_accurately(): <accuracy> (%s) must be between 1 and 7' % (
    2063                          self.__class__.__name__,
    2064                          accuracy
    2065                  )
    2066          #-----------------------------------------------------------------------
    2067 -        def get_mxdt(self):
    2068                  return self.timestamp
    2069          #-----------------------------------------------------------------------
    2070 -        def get_pydt(self):
    2071                  try:
    2072                          gmtoffset = self.timestamp.gmtoffset()
    2073                  except mxDT.Error:
    2074                          # Windows cannot deal with dates < 1970, so
    2075                          # when that happens switch to now()
    2076                          now = mxDT.now()
    2077                          gmtoffset = now.gmtoffset()
    2078                  tz = cFixedOffsetTimezone(gmtoffset.minutes, self.timestamp.tz)
    2079                  secs, msecs = divmod(self.timestamp.second, 1)
    2080                  ts = pyDT.datetime (
    2081                          year = self.timestamp.year,
    2082                          month = self.timestamp.month,
    2083                          day = self.timestamp.day,
    2084                          hour = self.timestamp.hour,
    2085                          minute = self.timestamp.minute,
    2086                          second = int(secs),
    2087                          microsecond = int(msecs * 1000),
    2088                          tzinfo = tz
    2089                  )
    2090                  return ts
    2091  #===========================================================================
    2092  # main
    2093  #---------------------------------------------------------------------------
    2094  if __name__ == '__main__':
    2095
    2096          if len(sys.argv) < 2:
    2097                  sys.exit()
    2098
    2099          if sys.argv[1] != "test":
    2100                  sys.exit()
    2101
    2102          #-----------------------------------------------------------------------
    2103          intervals_as_str = [
    2104                  '7', '12', ' 12', '12 ', ' 12 ', '      12      ', '0', '~12', '~ 12', ' ~ 12', '       ~       12 ',
    2105                  '12a', '12 a', '12      a', '12j', '12J', '12y', '12Y', '       ~       12      a        ', '~0a',
    2106                  '12m', '17 m', '12      m', '17M', '    ~       17      m        ', ' ~ 3       / 12    ', '7/12', '0/12',
    2107                  '12w', '17 w', '12      w', '17W', '    ~       17      w        ', ' ~ 15      / 52', '2/52', '0/52',
    2108                  '12d', '17 d', '12      t', '17D', '    ~       17      T        ', ' ~ 12      / 7', '3/7', '0/7',
    2109                  '12h', '17 h', '12      H', '17H', '    ~       17      h        ', ' ~ 36      / 24', '7/24', '0/24',
    2110                  ' ~ 36  / 60', '7/60', '190/60', '0/60',
    2111                  '12a1m', '12 a 1  M', '12       a17m', '12j             12m', '12J7m', '12y7m', '12Y7M', '      ~       12      a        37 m   ', '~0a0m',
    2112                  '10m1w',
    2113                  'invalid interval input'
    2114          ]
    2115          #-----------------------------------------------------------------------
    2116 -        def test_format_interval():
    2117                  for tmp in intervals_as_str:
    2118                          intv = str2interval(str_interval = tmp)
    2119                          for acc in _accuracy_strings.keys():
    2120                                  print '[%s]: "%s" -> "%s"' % (acc, tmp, format_interval(intv, acc))
    2121          #-----------------------------------------------------------------------
    2122 -        def test_format_interval_medically():
    2123
    2124                  intervals = [
    2125                          pyDT.timedelta(seconds = 1),
    2126                          pyDT.timedelta(seconds = 5),
    2127                          pyDT.timedelta(seconds = 30),
    2128                          pyDT.timedelta(seconds = 60),
    2129                          pyDT.timedelta(seconds = 94),
    2130                          pyDT.timedelta(seconds = 120),
    2131                          pyDT.timedelta(minutes = 5),
    2132                          pyDT.timedelta(minutes = 30),
    2133                          pyDT.timedelta(minutes = 60),
    2134                          pyDT.timedelta(minutes = 90),
    2135                          pyDT.timedelta(minutes = 120),
    2136                          pyDT.timedelta(minutes = 200),
    2137                          pyDT.timedelta(minutes = 400),
    2138                          pyDT.timedelta(minutes = 600),
    2139                          pyDT.timedelta(minutes = 800),
    2140                          pyDT.timedelta(minutes = 1100),
    2141                          pyDT.timedelta(minutes = 2000),
    2142                          pyDT.timedelta(minutes = 3500),
    2143                          pyDT.timedelta(minutes = 4000),
    2144                          pyDT.timedelta(hours = 1),
    2145                          pyDT.timedelta(hours = 2),
    2146                          pyDT.timedelta(hours = 4),
    2147                          pyDT.timedelta(hours = 8),
    2148                          pyDT.timedelta(hours = 12),
    2149                          pyDT.timedelta(hours = 20),
    2150                          pyDT.timedelta(hours = 23),
    2151                          pyDT.timedelta(hours = 24),
    2152                          pyDT.timedelta(hours = 25),
    2153                          pyDT.timedelta(hours = 30),
    2154                          pyDT.timedelta(hours = 48),
    2155                          pyDT.timedelta(hours = 98),
    2156                          pyDT.timedelta(hours = 120),
    2157                          pyDT.timedelta(days = 1),
    2158                          pyDT.timedelta(days = 2),
    2159                          pyDT.timedelta(days = 4),
    2160                          pyDT.timedelta(days = 16),
    2161                          pyDT.timedelta(days = 29),
    2162                          pyDT.timedelta(days = 30),
    2163                          pyDT.timedelta(days = 31),
    2164                          pyDT.timedelta(days = 37),
    2165                          pyDT.timedelta(days = 40),
    2166                          pyDT.timedelta(days = 47),
    2167                          pyDT.timedelta(days = 126),
    2168                          pyDT.timedelta(days = 127),
    2169                          pyDT.timedelta(days = 128),
    2170                          pyDT.timedelta(days = 300),
    2171                          pyDT.timedelta(days = 359),
    2172                          pyDT.timedelta(days = 360),
    2173                          pyDT.timedelta(days = 361),
    2174                          pyDT.timedelta(days = 362),
    2175                          pyDT.timedelta(days = 363),
    2176                          pyDT.timedelta(days = 364),
    2177                          pyDT.timedelta(days = 365),
    2178                          pyDT.timedelta(days = 366),
    2179                          pyDT.timedelta(days = 367),
    2180                          pyDT.timedelta(days = 400),
    2181                          pyDT.timedelta(weeks = 52 * 30),
    2182                          pyDT.timedelta(weeks = 52 * 79, days = 33)
    2183                  ]
    2184
    2185                  idx = 1
    2186                  for intv in intervals:
    2187                          print '%s) %s -> %s' % (idx, intv, format_interval_medically(intv))
    2188                          idx += 1
    2189          #-----------------------------------------------------------------------
    2190 -        def test_str2interval():
    2191                  print "testing str2interval()"
    2192                  print "----------------------"
    2193
    2194                  for interval_as_str in intervals_as_str:
    2195                          print "input: <%s>" % interval_as_str
    2196                          print "  ==>", str2interval(str_interval=interval_as_str)
    2197
    2198                  return True
    2199          #-------------------------------------------------
    2200 -        def test_date_time():
    2201                  print "DST currently in effect:", dst_currently_in_effect
    2202                  print "current UTC offset:", current_local_utc_offset_in_seconds, "seconds"
    2203                  print "current timezone (interval):", current_local_timezone_interval
    2204                  print "current timezone (ISO conformant numeric string):", current_local_iso_numeric_timezone_string
    2205                  print "local timezone class:", cLocalTimezone
    2206                  print ""
    2207                  tz = cLocalTimezone()
    2208                  print "local timezone instance:", tz
    2209                  print " (total) UTC offset:", tz.utcoffset(pyDT.datetime.now())
    2210                  print " DST adjustment:", tz.dst(pyDT.datetime.now())
    2211                  print " timezone name:", tz.tzname(pyDT.datetime.now())
    2212                  print ""
    2213                  print "current local timezone:", gmCurrentLocalTimezone
    2214                  print " (total) UTC offset:", gmCurrentLocalTimezone.utcoffset(pyDT.datetime.now())
    2215                  print " DST adjustment:", gmCurrentLocalTimezone.dst(pyDT.datetime.now())
    2216                  print " timezone name:", gmCurrentLocalTimezone.tzname(pyDT.datetime.now())
    2217                  print ""
    2218                  print "now here:", pydt_now_here()
    2219                  print ""
    2220          #-------------------------------------------------
    2221 -        def test_str2fuzzy_timestamp_matches():
    2222                  print "testing function str2fuzzy_timestamp_matches"
    2223                  print "--------------------------------------------"
    2224
    2225                  val = None
    2226                  while val != 'exit':
    2227                          val = raw_input('Enter date fragment ("exit" quits): ')
    2228                          matches = str2fuzzy_timestamp_matches(str2parse = val)
    2229                          for match in matches:
    2230                                  print 'label shown  :', match['label']
    2231                                  print 'data attached:', match['data'], match['data'].timestamp
    2232                                  print ""
    2233                          print "---------------"
    2234          #-------------------------------------------------
    2235 -        def test_cFuzzyTimeStamp():
    2236                  print "testing fuzzy timestamp class"
    2237                  print "-----------------------------"
    2238
    2239                  ts = mxDT.now()
    2240                  print "mx.DateTime timestamp", type(ts)
    2241                  print "  print ...       :", ts
    2242                  print "  print '%%s' %% ...: %s" % ts
    2243                  print "  str()           :", str(ts)
    2244                  print "  repr()          :", repr(ts)
    2245
    2246                  fts = cFuzzyTimestamp()
    2247                  print "\nfuzzy timestamp <%s '%s'>" % ('class', fts.__class__.__name__)
    2248                  for accuracy in range(1,8):
    2249                          fts.accuracy = accuracy
    2250                          print "  accuracy         : %s (%s)" % (accuracy, _accuracy_strings[accuracy])
    2251                          print "  format_accurately:", fts.format_accurately()
    2252                          print "  strftime()       :", fts.strftime('%Y %b %d  %H:%M:%S')
    2253                          print "  print ...        :", fts
    2254                          print "  print '%%s' %% ... : %s" % fts
    2255                          print "  str()            :", str(fts)
    2256                          print "  repr()           :", repr(fts)
    2257                          raw_input('press ENTER to continue')
    2258          #-------------------------------------------------
    2259 -        def test_get_pydt():
    2260                  print "testing platform for handling dates before 1970"
    2261                  print "-----------------------------------------------"
    2262                  ts = mxDT.DateTime(1935, 4, 2)
    2263                  fts = cFuzzyTimestamp(timestamp=ts)
    2264                  print "fts           :", fts
    2265                  print "fts.get_pydt():", fts.get_pydt()
    2266          #-------------------------------------------------
    2267 -        def test_calculate_apparent_age():
    2268                  # test leap year glitches
    2269                  start = pydt_now_here().replace(year = 2000).replace(month = 2).replace(day = 29)
    2270                  end = pydt_now_here().replace(year = 2012).replace(month = 2).replace(day = 27)
    2271                  print "start is leap year: 29.2.2000"
    2272                  print " ", calculate_apparent_age(start = start, end = end)
    2273                  print " ", format_apparent_age_medically(calculate_apparent_age(start = start))
    2274
    2275                  start = pydt_now_here().replace(month = 10).replace(day = 23).replace(year = 1974)
    2276                  end = pydt_now_here().replace(year = 2012).replace(month = 2).replace(day = 29)
    2277                  print "end is leap year: 29.2.2012"
    2278                  print " ", calculate_apparent_age(start = start, end = end)
    2279                  print " ", format_apparent_age_medically(calculate_apparent_age(start = start))
    2280
    2281                  start = pydt_now_here().replace(year = 2000).replace(month = 2).replace(day = 29)
    2282                  end = pydt_now_here().replace(year = 2012).replace(month = 2).replace(day = 29)
    2283                  print "start is leap year: 29.2.2000"
    2284                  print "end is leap year: 29.2.2012"
    2285                  print " ", calculate_apparent_age(start = start, end = end)
    2286                  print " ", format_apparent_age_medically(calculate_apparent_age(start = start))
    2287
    2288                  print "leap year tests worked"
    2289
    2290                  start = pydt_now_here().replace(month = 10).replace(day = 23).replace(year = 1974)
    2291                  print calculate_apparent_age(start = start)
    2292                  print format_apparent_age_medically(calculate_apparent_age(start = start))
    2293
    2294                  start = pydt_now_here().replace(month = 3).replace(day = 13).replace(year = 1979)
    2295                  print calculate_apparent_age(start = start)
    2296                  print format_apparent_age_medically(calculate_apparent_age(start = start))
    2297
    2298                  start = pydt_now_here().replace(month = 2, day = 2).replace(year = 1979)
    2299                  end = pydt_now_here().replace(month = 3).replace(day = 31).replace(year = 1979)
    2300                  print calculate_apparent_age(start = start, end = end)
    2301
    2302                  start = pydt_now_here().replace(month = 7, day = 21).replace(year = 2009)
    2303                  print format_apparent_age_medically(calculate_apparent_age(start = start))
    2304
    2305                  print "-------"
    2306                  start = pydt_now_here().replace(month = 1).replace(day = 23).replace(hour = 12).replace(minute = 11).replace(year = 2011)
    2307                  print calculate_apparent_age(start = start)
    2308                  print format_apparent_age_medically(calculate_apparent_age(start = start))
    2309          #-------------------------------------------------
    2310 -        def test_str2pydt():
    2311                  print "testing function str2pydt_matches"
    2312                  print "---------------------------------"
    2313
    2314                  val = None
    2315                  while val != 'exit':
    2316                          val = raw_input('Enter date fragment ("exit" quits): ')
    2317                          matches = str2pydt_matches(str2parse = val)
    2318                          for match in matches:
    2319                                  print 'label shown  :', match['label']
    2320                                  print 'data attached:', match['data']
    2321                                  print ""
    2322                          print "---------------"
    2323          #-------------------------------------------------
    2324 -        def test_pydt_strftime():
    2325                  dt = pydt_now_here()
    2326                  print pydt_strftime(dt)
    2327                  print pydt_strftime(dt, accuracy = acc_days)
    2328                  print pydt_strftime(dt, accuracy = acc_minutes)
    2329                  print pydt_strftime(dt, accuracy = acc_seconds)
    2330                  dt = dt.replace(year = 1899)
    2331                  print pydt_strftime(dt)
    2332                  print pydt_strftime(dt, accuracy = acc_days)
    2333                  print pydt_strftime(dt, accuracy = acc_minutes)
    2334                  print pydt_strftime(dt, accuracy = acc_seconds)
    2335          #-------------------------------------------------
    2336 -        def test_is_leap_year():
    2337                  for year in range(1995, 2017):
    2338                          print year, "leaps:", is_leap_year(year)
    2339          #-------------------------------------------------
    2340          # GNUmed libs
    2341          gmI18N.activate_locale()
    2342          gmI18N.install_domain('gnumed')
    2343
    2344          init()
    2345
    2346          #test_date_time()
    2347          #test_str2fuzzy_timestamp_matches()
    2348          #test_cFuzzyTimeStamp()
    2349          #test_get_pydt()
    2350          #test_str2interval()
    2351          #test_format_interval()
    2352          #test_format_interval_medically()
    2353          test_str2pydt()
    2354          #test_pydt_strftime()
    2355          #test_calculate_apparent_age()
    2356          #test_is_leap_year()
    2357
    2358  #===========================================================================
    2359