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