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