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 years = months = days = hours = minutes = seconds = 0 484 return (years, months, days, hours, minutes, seconds) 485 486 # years 487 years = end.year - start.year 488 end = end.replace(year = start.year) 489 if end < start: 490 years = years - 1 491 492 # months 493 if end.month == start.month: 494 months = 0 495 else: 496 months = end.month - start.month 497 if months < 0: 498 months = months + 12 499 if end.day > gregorian_month_length[start.month]: 500 end = end.replace(month = start.month, day = gregorian_month_length[start.month]) 501 else: 502 end = end.replace(month = start.month) 503 if end < start: 504 months = months - 1 505 506 # days 507 if end.day == start.day: 508 days = 0 509 else: 510 days = end.day - start.day 511 if days < 0: 512 days = days + gregorian_month_length[start.month] 513 end = end.replace(day = start.day) 514 if end < start: 515 days = days - 1 516 517 # hours 518 if end.hour == start.hour: 519 hours = 0 520 else: 521 hours = end.hour - start.hour 522 if hours < 0: 523 hours = hours + 24 524 end = end.replace(hour = start.hour) 525 if end < start: 526 hours = hours - 1 527 528 # minutes 529 if end.minute == start.minute: 530 minutes = 0 531 else: 532 minutes = end.minute - start.minute 533 if minutes < 0: 534 minutes = minutes + 60 535 end = end.replace(minute = start.minute) 536 if end < start: 537 minutes = minutes - 1 538 539 # seconds 540 if end.second == start.second: 541 seconds = 0 542 else: 543 seconds = end.second - start.second 544 if seconds < 0: 545 seconds = seconds + 60 546 end = end.replace(second = start.second) 547 if end < start: 548 seconds = seconds - 1 549 550 return (years, months, days, hours, minutes, seconds)
551 #---------------------------------------------------------------------------
552 -def format_apparent_age_medically(age=None):
553 """<age> must be a tuple as created by calculate_apparent_age()""" 554 555 (years, months, days, hours, minutes, seconds) = age 556 557 # at least 1 year ? 558 if years > 0: 559 if months == 0: 560 return u'%s%s' % ( 561 years, 562 _('y::year_abbreviation').replace('::year_abbreviation', u'') 563 ) 564 return u'%s%s %s%s' % ( 565 years, 566 _('y::year_abbreviation').replace('::year_abbreviation', u''), 567 months, 568 _('m::month_abbreviation').replace('::month_abbreviation', u'') 569 ) 570 571 # more than 1 month ? 572 if months > 1: 573 if days == 0: 574 return u'%s%s' % ( 575 months, 576 _('mo::month_only_abbreviation').replace('::month_only_abbreviation', u'') 577 ) 578 579 result = u'%s%s' % ( 580 months, 581 _('m::month_abbreviation').replace('::month_abbreviation', u'') 582 ) 583 584 weeks, days = divmod(days, 7) 585 if int(weeks) != 0: 586 result += u'%s%s' % ( 587 int(weeks), 588 _('w::week_abbreviation').replace('::week_abbreviation', u'') 589 ) 590 if int(days) != 0: 591 result += u'%s%s' % ( 592 int(days), 593 _('d::day_abbreviation').replace('::day_abbreviation', u'') 594 ) 595 596 return result 597 598 # between 7 days and 1 month 599 if days > 7: 600 return u"%s%s" % ( 601 days, 602 _('d::day_abbreviation').replace('::day_abbreviation', u'') 603 ) 604 605 # between 1 and 7 days ? 606 if days > 0: 607 if hours == 0: 608 return u'%s%s' % ( 609 days, 610 _('d::day_abbreviation').replace('::day_abbreviation', u'') 611 ) 612 return u'%s%s (%s%s)' % ( 613 days, 614 _('d::day_abbreviation').replace('::day_abbreviation', u''), 615 hours, 616 _('h::hour_abbreviation').replace('::hour_abbreviation', u'') 617 ) 618 619 # between 5 hours and 1 day 620 if hours > 5: 621 return u'%s%s' % ( 622 hours, 623 _('h::hour_abbreviation').replace('::hour_abbreviation', u'') 624 ) 625 626 # between 1 and 5 hours 627 if hours > 1: 628 if minutes == 0: 629 return u'%s%s' % ( 630 hours, 631 _('h::hour_abbreviation').replace('::hour_abbreviation', u'') 632 ) 633 return u'%s:%02d' % ( 634 hours, 635 minutes 636 ) 637 638 # between 5 and 60 minutes 639 if minutes > 5: 640 return u"0:%02d" % minutes 641 642 # less than 5 minutes 643 if minutes == 0: 644 return u'%s%s' % ( 645 seconds, 646 _('s::second_abbreviation').replace('::second_abbreviation', u'') 647 ) 648 if seconds == 0: 649 return u"0:%02d" % minutes 650 return "%s.%s%s" % ( 651 minutes, 652 seconds, 653 _('s::second_abbreviation').replace('::second_abbreviation', u'') 654 )
655 #---------------------------------------------------------------------------
656 -def str2interval(str_interval=None):
657 658 unit_keys = { 659 'year': _('yYaA_keys_year'), 660 'month': _('mM_keys_month'), 661 'week': _('wW_keys_week'), 662 'day': _('dD_keys_day'), 663 'hour': _('hH_keys_hour') 664 } 665 666 str_interval = str_interval.strip() 667 668 # "(~)35(yY)" - at age 35 years 669 keys = '|'.join(list(unit_keys['year'].replace('_keys_year', u''))) 670 if regex.match(u'^~*(\s|\t)*\d+(%s)*$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE): 671 return pyDT.timedelta(days = (int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]) * avg_days_per_gregorian_year)) 672 673 # "(~)12mM" - at age 12 months 674 keys = '|'.join(list(unit_keys['month'].replace('_keys_month', u''))) 675 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE): 676 years, months = divmod ( 677 int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]), 678 12 679 ) 680 return pyDT.timedelta(days = ((years * avg_days_per_gregorian_year) + (months * avg_days_per_gregorian_month))) 681 682 # weeks 683 keys = '|'.join(list(unit_keys['week'].replace('_keys_week', u''))) 684 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE): 685 return pyDT.timedelta(weeks = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0])) 686 687 # days 688 keys = '|'.join(list(unit_keys['day'].replace('_keys_day', u''))) 689 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE): 690 return pyDT.timedelta(days = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0])) 691 692 # hours 693 keys = '|'.join(list(unit_keys['hour'].replace('_keys_hour', u''))) 694 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE): 695 return pyDT.timedelta(hours = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0])) 696 697 # x/12 - months 698 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*12$', str_interval, flags = regex.LOCALE | regex.UNICODE): 699 years, months = divmod ( 700 int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]), 701 12 702 ) 703 return pyDT.timedelta(days = ((years * avg_days_per_gregorian_year) + (months * avg_days_per_gregorian_month))) 704 705 # x/52 - weeks 706 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*52$', str_interval, flags = regex.LOCALE | regex.UNICODE): 707 # return pyDT.timedelta(days = (int(regex.findall('\d+', str_interval)[0]) * days_per_week)) 708 return pyDT.timedelta(weeks = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0])) 709 710 # x/7 - days 711 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*7$', str_interval, flags = regex.LOCALE | regex.UNICODE): 712 return pyDT.timedelta(days = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0])) 713 714 # x/24 - hours 715 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*24$', str_interval, flags = regex.LOCALE | regex.UNICODE): 716 return pyDT.timedelta(hours = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0])) 717 718 # x/60 - minutes 719 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*60$', str_interval, flags = regex.LOCALE | regex.UNICODE): 720 return pyDT.timedelta(minutes = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0])) 721 722 # nYnM - years, months 723 keys_year = '|'.join(list(unit_keys['year'].replace('_keys_year', u''))) 724 keys_month = '|'.join(list(unit_keys['month'].replace('_keys_month', u''))) 725 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): 726 parts = regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE) 727 years, months = divmod(int(parts[1]), 12) 728 years += int(parts[0]) 729 return pyDT.timedelta(days = ((years * avg_days_per_gregorian_year) + (months * avg_days_per_gregorian_month))) 730 731 # nMnW - months, weeks 732 keys_month = '|'.join(list(unit_keys['month'].replace('_keys_month', u''))) 733 keys_week = '|'.join(list(unit_keys['week'].replace('_keys_week', u''))) 734 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): 735 parts = regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE) 736 months, weeks = divmod(int(parts[1]), 4) 737 months += int(parts[0]) 738 return pyDT.timedelta(days = ((months * avg_days_per_gregorian_month) + (weeks * days_per_week))) 739 740 return None
741 #=========================================================================== 742 # string -> date parser 743 #---------------------------------------------------------------------------
744 -def __single_char2py_dt(str2parse, trigger_chars=None):
745 """This matches on single characters. 746 747 Spaces and tabs are discarded. 748 749 Default is 'ndmy': 750 n - Now 751 d - toDay 752 m - toMorrow Someone please suggest a synonym ! 753 y - Yesterday 754 755 This also defines the significance of the order of the characters. 756 """ 757 if trigger_chars is None: 758 trigger_chars = _('ndmy (single character date triggers)')[:4].lower() 759 760 str2parse = str2parse.strip().lower() 761 762 if len(str2parse) != 1: 763 return [] 764 765 if str2parse not in trigger_chars: 766 return [] 767 768 now = mxDT.now() 769 enc = gmI18N.get_encoding() 770 771 # FIXME: handle uebermorgen/vorgestern ? 772 773 # right now 774 if str2parse == trigger_chars[0]: 775 return [{ 776 'data': mxdt2py_dt(now), 777 'label': _('right now (%s, %s)') % (now.strftime('%A').decode(enc), now) 778 }] 779 780 # today 781 if str2parse == trigger_chars[1]: 782 return [{ 783 'data': mxdt2py_dt(now), 784 'label': _('today (%s)') % now.strftime('%A, %Y-%m-%d').decode(enc) 785 }] 786 787 # tomorrow 788 if str2parse == trigger_chars[2]: 789 ts = now + mxDT.RelativeDateTime(days = +1) 790 return [{ 791 'data': mxdt2py_dt(ts), 792 'label': _('tomorrow (%s)') % ts.strftime('%A, %Y-%m-%d').decode(enc) 793 }] 794 795 # yesterday 796 if str2parse == trigger_chars[3]: 797 ts = now + mxDT.RelativeDateTime(days = -1) 798 return [{ 799 'data': mxdt2py_dt(ts), 800 'label': _('yesterday (%s)') % ts.strftime('%A, %Y-%m-%d').decode(enc) 801 }] 802 803 return []
804 #---------------------------------------------------------------------------
805 -def __single_dot2py_dt(str2parse):
806 """Expand fragments containing a single dot. 807 808 Standard colloquial date format in Germany: day.month.year 809 810 "14." 811 - the 14th of the current month 812 - the 14th of next month 813 "-14." 814 - the 14th of last month 815 """ 816 str2parse = str2parse.strip() 817 818 if not str2parse.endswith(u'.'): 819 return [] 820 821 str2parse = str2parse[:-1] 822 try: 823 day_val = int(str2parse) 824 except ValueError: 825 return [] 826 827 if (day_val < -31) or (day_val > 31) or (day_val == 0): 828 return [] 829 830 now = mxDT.now() 831 enc = gmI18N.get_encoding() 832 matches = [] 833 834 # day X of last month only 835 if day_val < 0: 836 ts = now + mxDT.RelativeDateTime(day = abs(day_val), months = -1) 837 if abs(day_val) <= gregorian_month_length[ts.month]: 838 matches.append ({ 839 'data': mxdt2py_dt(ts), 840 'label': _('%s-%s-%s: a %s last month') % (ts.year, ts.month, ts.day, ts.strftime('%A').decode(enc)) 841 }) 842 843 # day X of this month 844 if day_val > 0: 845 ts = now + mxDT.RelativeDateTime(day = day_val) 846 if day_val <= gregorian_month_length[ts.month]: 847 matches.append ({ 848 'data': mxdt2py_dt(ts), 849 'label': _('%s-%s-%s: a %s this month') % (ts.year, ts.month, ts.day, ts.strftime('%A').decode(enc)) 850 }) 851 852 # day X of next month 853 if day_val > 0: 854 ts = now + mxDT.RelativeDateTime(day = day_val, months = +1) 855 if day_val <= gregorian_month_length[ts.month]: 856 matches.append ({ 857 'data': mxdt2py_dt(ts), 858 'label': _('%s-%s-%s: a %s next month') % (ts.year, ts.month, ts.day, ts.strftime('%A').decode(enc)) 859 }) 860 861 # day X of last month 862 if day_val > 0: 863 ts = now + mxDT.RelativeDateTime(day = day_val, months = -1) 864 if day_val <= gregorian_month_length[ts.month]: 865 matches.append ({ 866 'data': mxdt2py_dt(ts), 867 'label': _('%s-%s-%s: a %s last month') % (ts.year, ts.month, ts.day, ts.strftime('%A').decode(enc)) 868 }) 869 870 return matches
871 #---------------------------------------------------------------------------
872 -def __single_slash2py_dt(str2parse):
873 """Expand fragments containing a single slash. 874 875 "5/" 876 - 2005/ (2000 - 2025) 877 - 1995/ (1990 - 1999) 878 - Mai/current year 879 - Mai/next year 880 - Mai/last year 881 - Mai/200x 882 - Mai/20xx 883 - Mai/199x 884 - Mai/198x 885 - Mai/197x 886 - Mai/19xx 887 888 5/1999 889 6/2004 890 """ 891 str2parse = str2parse.strip() 892 893 now = mxDT.now() 894 895 # 5/1999 896 if regex.match(r"^\d{1,2}(\s|\t)*/+(\s|\t)*\d{4}$", str2parse, flags = regex.LOCALE | regex.UNICODE): 897 parts = regex.findall(r'\d+', str2parse, flags = regex.LOCALE | regex.UNICODE) 898 ts = now + mxDT.RelativeDateTime(year = int(parts[1]), month = int(parts[0])) 899 return [{ 900 'data': mxdt2py_dt(ts), 901 'label': ts.strftime('%Y-%m-%d').decode(enc) 902 }] 903 904 matches = [] 905 # 5/ 906 if regex.match(r"^\d{1,2}(\s|\t)*/+$", str2parse, flags = regex.LOCALE | regex.UNICODE): 907 val = int(str2parse[:-1].strip()) 908 909 # "55/" -> "1955" 910 if val < 100 and val >= 0: 911 matches.append ({ 912 'data': None, 913 'label': '%s-' % (val + 1900) 914 }) 915 916 # "11/" -> "2011" 917 if val < 26 and val >= 0: 918 matches.append ({ 919 'data': None, 920 'label': '%s-' % (val + 2000) 921 }) 922 923 # "5/" -> "1995" 924 if val < 10 and val >= 0: 925 matches.append ({ 926 'data': None, 927 'label': '%s-' % (val + 1990) 928 }) 929 930 if val < 13 and val > 0: 931 # "11/" -> "11/this year" 932 matches.append ({ 933 'data': None, 934 'label': '%s-%.2d-' % (now.year, val) 935 }) 936 # "11/" -> "11/next year" 937 ts = now + mxDT.RelativeDateTime(years = 1) 938 matches.append ({ 939 'data': None, 940 'label': '%s-%.2d-' % (ts.year, val) 941 }) 942 # "11/" -> "11/last year" 943 ts = now + mxDT.RelativeDateTime(years = -1) 944 matches.append ({ 945 'data': None, 946 'label': '%s-%.2d-' % (ts.year, val) 947 }) 948 # "11/" -> "201?-11-" 949 matches.append ({ 950 'data': None, 951 'label': '201?-%.2d-' % val 952 }) 953 # "11/" -> "200?-11-" 954 matches.append ({ 955 'data': None, 956 'label': '200?-%.2d-' % val 957 }) 958 # "11/" -> "20??-11-" 959 matches.append ({ 960 'data': None, 961 'label': '20??-%.2d-' % val 962 }) 963 # "11/" -> "199?-11-" 964 matches.append ({ 965 'data': None, 966 'label': '199?-%.2d-' % val 967 }) 968 # "11/" -> "198?-11-" 969 matches.append ({ 970 'data': None, 971 'label': '198?-%.2d-' % val 972 }) 973 # "11/" -> "198?-11-" 974 matches.append ({ 975 'data': None, 976 'label': '197?-%.2d-' % val 977 }) 978 # "11/" -> "19??-11-" 979 matches.append ({ 980 'data': None, 981 'label': '19??-%.2d-' % val 982 }) 983 984 return matches
985 #---------------------------------------------------------------------------
986 -def __numbers_only2py_dt(str2parse):
987 """This matches on single numbers. 988 989 Spaces or tabs are discarded. 990 """ 991 try: 992 val = int(str2parse.strip()) 993 except ValueError: 994 return [] 995 996 # strftime() returns str but in the localized encoding, 997 # so we may need to decode that to unicode 998 enc = gmI18N.get_encoding() 999 now = mxDT.now() 1000 1001 matches = [] 1002 1003 # that year 1004 if (1850 < val) and (val < 2100): 1005 ts = now + mxDT.RelativeDateTime(year = val) 1006 matches.append ({ 1007 'data': mxdt2py_dt(ts), 1008 'label': ts.strftime('%Y-%m-%d') 1009 }) 1010 1011 # day X of this month 1012 if (val > 0) and (val <= gregorian_month_length[now.month]): 1013 ts = now + mxDT.RelativeDateTime(day = val) 1014 matches.append ({ 1015 'data': mxdt2py_dt(ts), 1016 'label': _('%d. of %s (this month): a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc)) 1017 }) 1018 1019 # day X of next month 1020 if (val > 0) and (val < 32): 1021 ts = now + mxDT.RelativeDateTime(months = 1, day = val) 1022 matches.append ({ 1023 'data': mxdt2py_dt(ts), 1024 'label': _('%d. of %s (next month): a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc)) 1025 }) 1026 1027 # day X of last month 1028 if (val > 0) and (val < 32): 1029 ts = now + mxDT.RelativeDateTime(months = -1, day = val) 1030 matches.append ({ 1031 'data': mxdt2py_dt(ts), 1032 'label': _('%d. of %s (last month): a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc)) 1033 }) 1034 1035 # X days from now 1036 if (val > 0) and (val <= 400): # more than a year ahead in days ?? nah ! 1037 ts = now + mxDT.RelativeDateTime(days = val) 1038 matches.append ({ 1039 'data': mxdt2py_dt(ts), 1040 'label': _('in %d day(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc)) 1041 }) 1042 if (val < 0) and (val >= -400): # more than a year back in days ?? nah ! 1043 ts = now - mxDT.RelativeDateTime(days = abs(val)) 1044 matches.append ({ 1045 'data': mxdt2py_dt(ts), 1046 'label': _('%d day(s) ago: %s') % (abs(val), ts.strftime('%A, %Y-%m-%d').decode(enc)) 1047 }) 1048 1049 # X weeks from now 1050 if (val > 0) and (val <= 50): # pregnancy takes about 40 weeks :-) 1051 ts = now + mxDT.RelativeDateTime(weeks = val) 1052 matches.append ({ 1053 'data': mxdt2py_dt(ts), 1054 'label': _('in %d week(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc)) 1055 }) 1056 if (val < 0) and (val >= -50): # pregnancy takes about 40 weeks :-) 1057 ts = now - mxDT.RelativeDateTime(weeks = abs(val)) 1058 matches.append ({ 1059 'data': mxdt2py_dt(ts), 1060 'label': _('%d week(s) ago: %s') % (abs(val), ts.strftime('%A, %Y-%m-%d').decode(enc)) 1061 }) 1062 1063 # month X of ... 1064 if (val < 13) and (val > 0): 1065 # ... this year 1066 ts = now + mxDT.RelativeDateTime(month = val) 1067 matches.append ({ 1068 'data': mxdt2py_dt(ts), 1069 'label': _('%s (%s this year)') % (ts.strftime('%Y-%m-%d'), ts.strftime('%B').decode(enc)) 1070 }) 1071 1072 # ... next year 1073 ts = now + mxDT.RelativeDateTime(years = 1, month = val) 1074 matches.append ({ 1075 'data': mxdt2py_dt(ts), 1076 'label': _('%s (%s next year)') % (ts.strftime('%Y-%m-%d'), ts.strftime('%B').decode(enc)) 1077 }) 1078 1079 # ... last year 1080 ts = now + mxDT.RelativeDateTime(years = -1, month = val) 1081 matches.append ({ 1082 'data': mxdt2py_dt(ts), 1083 'label': _('%s (%s last year)') % (ts.strftime('%Y-%m-%d'), ts.strftime('%B').decode(enc)) 1084 }) 1085 1086 # fragment expansion 1087 matches.append ({ 1088 'data': None, 1089 'label': '200?-%s' % val 1090 }) 1091 matches.append ({ 1092 'data': None, 1093 'label': '199?-%s' % val 1094 }) 1095 matches.append ({ 1096 'data': None, 1097 'label': '198?-%s' % val 1098 }) 1099 matches.append ({ 1100 'data': None, 1101 'label': '19??-%s' % val 1102 }) 1103 1104 # day X of ... 1105 if (val < 8) and (val > 0): 1106 # ... this week 1107 ts = now + mxDT.RelativeDateTime(weekday = (val-1, 0)) 1108 matches.append ({ 1109 'data': mxdt2py_dt(ts), 1110 'label': _('%s this week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc)) 1111 }) 1112 1113 # ... next week 1114 ts = now + mxDT.RelativeDateTime(weeks = +1, weekday = (val-1, 0)) 1115 matches.append ({ 1116 'data': mxdt2py_dt(ts), 1117 'label': _('%s next week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc)) 1118 }) 1119 1120 # ... last week 1121 ts = now + mxDT.RelativeDateTime(weeks = -1, weekday = (val-1, 0)) 1122 matches.append ({ 1123 'data': mxdt2py_dt(ts), 1124 'label': _('%s last week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc)) 1125 }) 1126 1127 if (val < 100) and (val > 0): 1128 matches.append ({ 1129 'data': None, 1130 'label': '%s-' % (1900 + val) 1131 }) 1132 1133 if val == 201: 1134 tmp = { 1135 'data': mxdt2py_dt(now), 1136 'label': now.strftime('%Y-%m-%d') 1137 } 1138 matches.append(tmp) 1139 matches.append ({ 1140 'data': None, 1141 'label': now.strftime('%Y-%m') 1142 }) 1143 matches.append ({ 1144 'data': None, 1145 'label': now.strftime('%Y') 1146 }) 1147 matches.append ({ 1148 'data': None, 1149 'label': '%s-' % (now.year + 1) 1150 }) 1151 matches.append ({ 1152 'data': None, 1153 'label': '%s-' % (now.year - 1) 1154 }) 1155 1156 if val < 200 and val >= 190: 1157 for i in range(10): 1158 matches.append ({ 1159 'data': None, 1160 'label': '%s%s-' % (val, i) 1161 }) 1162 1163 return matches
1164 #---------------------------------------------------------------------------
1165 -def __explicit_offset2py_dt(str2parse, offset_chars=None):
1166 """ 1167 Default is 'hdwmy': 1168 h - hours 1169 d - days 1170 w - weeks 1171 m - months 1172 y - years 1173 1174 This also defines the significance of the order of the characters. 1175 """ 1176 if offset_chars is None: 1177 offset_chars = _('hdwmy (single character date offset triggers)')[:5].lower() 1178 1179 str2parse = str2parse.strip() 1180 1181 # "+/-XXd/w/m/t" 1182 if not regex.match(r"^(\+|-)?(\s|\t)*\d{1,2}(\s|\t)*[%s]$" % offset_chars, str2parse, flags = regex.LOCALE | regex.UNICODE): 1183 return [] 1184 1185 # into the past ? 1186 if str2parse.startswith(u'-'): 1187 is_future = False 1188 str2parse = str2parse[1:].strip() 1189 else: 1190 is_future = True 1191 str2parse = str2parse.replace(u'+', u'').strip() 1192 1193 val = int(regex.findall(u'\d{1,2}', str2parse, flags = regex.LOCALE | regex.UNICODE)[0]) 1194 offset_char = regex.findall(u'[%s]' % offset_chars, str2parse, flags = regex.LOCALE | regex.UNICODE)[0].lower() 1195 1196 now = mxDT.now() 1197 enc = gmI18N.get_encoding() 1198 1199 ts = None 1200 # hours 1201 if offset_char == offset_chars[0]: 1202 if is_future: 1203 ts = now + mxDT.RelativeDateTime(hours = val) 1204 label = _('in %d hour(s): %s') % (val, ts.strftime('%H:%M')) 1205 else: 1206 ts = now - mxDT.RelativeDateTime(hours = val) 1207 label = _('%d hour(s) ago: %s') % (val, ts.strftime('%H:%M')) 1208 # days 1209 elif offset_char == offset_chars[1]: 1210 if is_future: 1211 ts = now + mxDT.RelativeDateTime(days = val) 1212 label = _('in %d day(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc)) 1213 else: 1214 ts = now - mxDT.RelativeDateTime(days = val) 1215 label = _('%d day(s) ago: %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc)) 1216 # weeks 1217 elif offset_char == offset_chars[2]: 1218 if is_future: 1219 ts = now + mxDT.RelativeDateTime(weeks = val) 1220 label = _('in %d week(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc)) 1221 else: 1222 ts = now - mxDT.RelativeDateTime(weeks = val) 1223 label = _('%d week(s) ago: %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc)) 1224 # months 1225 elif offset_char == offset_chars[3]: 1226 if is_future: 1227 ts = now + mxDT.RelativeDateTime(months = val) 1228 label = _('in %d month(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc)) 1229 else: 1230 ts = now - mxDT.RelativeDateTime(months = val) 1231 label = _('%d month(s) ago: %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc)) 1232 # years 1233 elif offset_char == offset_chars[4]: 1234 if is_future: 1235 ts = now + mxDT.RelativeDateTime(years = val) 1236 label = _('in %d year(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc)) 1237 else: 1238 ts = now - mxDT.RelativeDateTime(years = val) 1239 label = _('%d year(s) ago: %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc)) 1240 1241 if ts is None: 1242 return [] 1243 1244 return [{ 1245 'data': mxdt2py_dt(ts), 1246 'label': label 1247 }]
1248 #---------------------------------------------------------------------------
1249 -def str2pydt_matches(str2parse=None, patterns=None):
1250 """Turn a string into candidate dates and auto-completions the user is likely to type. 1251 1252 You MUST have called locale.setlocale(locale.LC_ALL, '') 1253 somewhere in your code previously. 1254 1255 @param patterns: list of time.strptime compatible date pattern 1256 @type patterns: list 1257 """ 1258 matches = [] 1259 matches.extend(__single_dot2py_dt(str2parse)) 1260 matches.extend(__numbers_only2py_dt(str2parse)) 1261 matches.extend(__single_slash2py_dt(str2parse)) 1262 matches.extend(__single_char2py_dt(str2parse)) 1263 matches.extend(__explicit_offset2py_dt(str2parse)) 1264 1265 # try mxDT parsers 1266 try: 1267 date = mxDT.Parser.DateFromString ( 1268 text = str2parse, 1269 formats = ('euro', 'iso', 'us', 'altus', 'altiso', 'lit', 'altlit', 'eurlit') 1270 ) 1271 matches.append ({ 1272 'data': mxdt2py_dt(date), 1273 'label': date.strftime('%Y-%m-%d') 1274 }) 1275 except (ValueError, OverflowError, mxDT.RangeError): 1276 pass 1277 1278 # apply explicit patterns 1279 if patterns is None: 1280 patterns = [] 1281 1282 patterns.append('%Y-%m-%d') 1283 patterns.append('%y-%m-%d') 1284 patterns.append('%Y/%m/%d') 1285 patterns.append('%y/%m/%d') 1286 1287 patterns.append('%d-%m-%Y') 1288 patterns.append('%d-%m-%y') 1289 patterns.append('%d/%m/%Y') 1290 patterns.append('%d/%m/%y') 1291 1292 patterns.append('%m-%d-%Y') 1293 patterns.append('%m-%d-%y') 1294 patterns.append('%m/%d/%Y') 1295 patterns.append('%m/%d/%y') 1296 1297 patterns.append('%Y.%m.%d') 1298 patterns.append('%y.%m.%d') 1299 1300 for pattern in patterns: 1301 try: 1302 date = pyDT.datetime.strptime(str2parse, pattern).replace ( 1303 hour = 11, 1304 minute = 11, 1305 second = 11, 1306 tzinfo = gmCurrentLocalTimezone 1307 ) 1308 matches.append ({ 1309 'data': date, 1310 'label': pydt_strftime(date, format = '%Y-%m-%d', accuracy = acc_days) 1311 }) 1312 except AttributeError: 1313 # strptime() only available starting with Python 2.5 1314 break 1315 except OverflowError: 1316 # time.mktime() cannot handle dates older than a platform-dependant limit :-( 1317 continue 1318 except ValueError: 1319 # C-level overflow 1320 continue 1321 1322 return matches
1323 #=========================================================================== 1324 # string -> fuzzy timestamp parser 1325 #---------------------------------------------------------------------------
1326 -def __explicit_offset(str2parse, offset_chars=None):
1327 """ 1328 Default is 'hdwm': 1329 h - hours 1330 d - days 1331 w - weeks 1332 m - months 1333 y - years 1334 1335 This also defines the significance of the order of the characters. 1336 """ 1337 if offset_chars is None: 1338 offset_chars = _('hdwmy (single character date offset triggers)')[:5].lower() 1339 1340 # "+/-XXd/w/m/t" 1341 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): 1342 return [] 1343 val = int(regex.findall(u'\d{1,2}', str2parse, flags = regex.LOCALE | regex.UNICODE)[0]) 1344 offset_char = regex.findall(u'[%s]' % offset_chars, str2parse, flags = regex.LOCALE | regex.UNICODE)[0].lower() 1345 1346 now = mxDT.now() 1347 enc = gmI18N.get_encoding() 1348 1349 # allow past ? 1350 is_future = True 1351 if str2parse.find('-') > -1: 1352 is_future = False 1353 1354 ts = None 1355 # hours 1356 if offset_char == offset_chars[0]: 1357 if is_future: 1358 ts = now + mxDT.RelativeDateTime(hours = val) 1359 label = _('in %d hour(s) - %s') % (val, ts.strftime('%H:%M')) 1360 else: 1361 ts = now - mxDT.RelativeDateTime(hours = val) 1362 label = _('%d hour(s) ago - %s') % (val, ts.strftime('%H:%M')) 1363 accuracy = acc_subseconds 1364 # days 1365 elif offset_char == offset_chars[1]: 1366 if is_future: 1367 ts = now + mxDT.RelativeDateTime(days = val) 1368 label = _('in %d day(s) - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc)) 1369 else: 1370 ts = now - mxDT.RelativeDateTime(days = val) 1371 label = _('%d day(s) ago - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc)) 1372 accuracy = acc_days 1373 # weeks 1374 elif offset_char == offset_chars[2]: 1375 if is_future: 1376 ts = now + mxDT.RelativeDateTime(weeks = val) 1377 label = _('in %d week(s) - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc)) 1378 else: 1379 ts = now - mxDT.RelativeDateTime(weeks = val) 1380 label = _('%d week(s) ago - %s)') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc)) 1381 accuracy = acc_days 1382 # months 1383 elif offset_char == offset_chars[3]: 1384 if is_future: 1385 ts = now + mxDT.RelativeDateTime(months = val) 1386 label = _('in %d month(s) - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc)) 1387 else: 1388 ts = now - mxDT.RelativeDateTime(months = val) 1389 label = _('%d month(s) ago - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc)) 1390 accuracy = acc_days 1391 # years 1392 elif offset_char == offset_chars[4]: 1393 if is_future: 1394 ts = now + mxDT.RelativeDateTime(years = val) 1395 label = _('in %d year(s) - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc)) 1396 else: 1397 ts = now - mxDT.RelativeDateTime(years = val) 1398 label = _('%d year(s) ago - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc)) 1399 accuracy = acc_months 1400 1401 if ts is None: 1402 return [] 1403 1404 tmp = { 1405 'data': cFuzzyTimestamp(timestamp = ts, accuracy = accuracy), 1406 'label': label 1407 } 1408 return [tmp]
1409 #---------------------------------------------------------------------------
1410 -def __single_slash(str2parse):
1411 """Expand fragments containing a single slash. 1412 1413 "5/" 1414 - 2005/ (2000 - 2025) 1415 - 1995/ (1990 - 1999) 1416 - Mai/current year 1417 - Mai/next year 1418 - Mai/last year 1419 - Mai/200x 1420 - Mai/20xx 1421 - Mai/199x 1422 - Mai/198x 1423 - Mai/197x 1424 - Mai/19xx 1425 """ 1426 matches = [] 1427 now = mxDT.now() 1428 if regex.match(u"^(\s|\t)*\d{1,2}(\s|\t)*/+(\s|\t)*$", str2parse, flags = regex.LOCALE | regex.UNICODE): 1429 val = int(regex.findall(u'\d+', str2parse, flags = regex.LOCALE | regex.UNICODE)[0]) 1430 1431 if val < 100 and val >= 0: 1432 matches.append ({ 1433 'data': None, 1434 'label': '%s/' % (val + 1900) 1435 }) 1436 1437 if val < 26 and val >= 0: 1438 matches.append ({ 1439 'data': None, 1440 'label': '%s/' % (val + 2000) 1441 }) 1442 1443 if val < 10 and val >= 0: 1444 matches.append ({ 1445 'data': None, 1446 'label': '%s/' % (val + 1990) 1447 }) 1448 1449 if val < 13 and val > 0: 1450 matches.append ({ 1451 'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_months), 1452 'label': '%.2d/%s' % (val, now.year) 1453 }) 1454 ts = now + mxDT.RelativeDateTime(years = 1) 1455 matches.append ({ 1456 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_months), 1457 'label': '%.2d/%s' % (val, ts.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 matches.append ({ 1465 'data': None, 1466 'label': '%.2d/200' % val 1467 }) 1468 matches.append ({ 1469 'data': None, 1470 'label': '%.2d/20' % val 1471 }) 1472 matches.append ({ 1473 'data': None, 1474 'label': '%.2d/199' % val 1475 }) 1476 matches.append ({ 1477 'data': None, 1478 'label': '%.2d/198' % val 1479 }) 1480 matches.append ({ 1481 'data': None, 1482 'label': '%.2d/197' % val 1483 }) 1484 matches.append ({ 1485 'data': None, 1486 'label': '%.2d/19' % val 1487 }) 1488 1489 elif regex.match(u"^(\s|\t)*\d{1,2}(\s|\t)*/+(\s|\t)*\d{4}(\s|\t)*$", str2parse, flags = regex.LOCALE | regex.UNICODE): 1490 parts = regex.findall(u'\d+', str2parse, flags = regex.LOCALE | regex.UNICODE) 1491 fts = cFuzzyTimestamp ( 1492 timestamp = mxDT.now() + mxDT.RelativeDateTime(year = int(parts[1]), month = int(parts[0])), 1493 accuracy = acc_months 1494 ) 1495 matches.append ({ 1496 'data': fts, 1497 'label': fts.format_accurately() 1498 }) 1499 1500 return matches
1501 #---------------------------------------------------------------------------
1502 -def __numbers_only(str2parse):
1503 """This matches on single numbers. 1504 1505 Spaces or tabs are discarded. 1506 """ 1507 if not regex.match(u"^(\s|\t)*\d{1,4}(\s|\t)*$", str2parse, flags = regex.LOCALE | regex.UNICODE): 1508 return [] 1509 1510 # strftime() returns str but in the localized encoding, 1511 # so we may need to decode that to unicode 1512 enc = gmI18N.get_encoding() 1513 now = mxDT.now() 1514 val = int(regex.findall(u'\d{1,4}', str2parse, flags = regex.LOCALE | regex.UNICODE)[0]) 1515 1516 matches = [] 1517 1518 # that year 1519 if (1850 < val) and (val < 2100): 1520 ts = now + mxDT.RelativeDateTime(year = val) 1521 target_date = cFuzzyTimestamp ( 1522 timestamp = ts, 1523 accuracy = acc_years 1524 ) 1525 tmp = { 1526 'data': target_date, 1527 'label': '%s' % target_date 1528 } 1529 matches.append(tmp) 1530 1531 # day X of this month 1532 if val <= gregorian_month_length[now.month]: 1533 ts = now + mxDT.RelativeDateTime(day = val) 1534 target_date = cFuzzyTimestamp ( 1535 timestamp = ts, 1536 accuracy = acc_days 1537 ) 1538 tmp = { 1539 'data': target_date, 1540 'label': _('%d. of %s (this month) - a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc)) 1541 } 1542 matches.append(tmp) 1543 1544 # day X of next month 1545 if val <= gregorian_month_length[(now + mxDT.RelativeDateTime(months = 1)).month]: 1546 ts = now + mxDT.RelativeDateTime(months = 1, day = val) 1547 target_date = cFuzzyTimestamp ( 1548 timestamp = ts, 1549 accuracy = acc_days 1550 ) 1551 tmp = { 1552 'data': target_date, 1553 'label': _('%d. of %s (next month) - a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc)) 1554 } 1555 matches.append(tmp) 1556 1557 # day X of last month 1558 if val <= gregorian_month_length[(now + mxDT.RelativeDateTime(months = -1)).month]: 1559 ts = now + mxDT.RelativeDateTime(months = -1, day = val) 1560 target_date = cFuzzyTimestamp ( 1561 timestamp = ts, 1562 accuracy = acc_days 1563 ) 1564 tmp = { 1565 'data': target_date, 1566 'label': _('%d. of %s (last month) - a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc)) 1567 } 1568 matches.append(tmp) 1569 1570 # X days from now 1571 if val <= 400: # more than a year ahead in days ?? nah ! 1572 ts = now + mxDT.RelativeDateTime(days = val) 1573 target_date = cFuzzyTimestamp ( 1574 timestamp = ts 1575 ) 1576 tmp = { 1577 'data': target_date, 1578 'label': _('in %d day(s) - %s') % (val, target_date.timestamp.strftime('%A, %Y-%m-%d').decode(enc)) 1579 } 1580 matches.append(tmp) 1581 1582 # X weeks from now 1583 if val <= 50: # pregnancy takes about 40 weeks :-) 1584 ts = now + mxDT.RelativeDateTime(weeks = val) 1585 target_date = cFuzzyTimestamp ( 1586 timestamp = ts 1587 ) 1588 tmp = { 1589 'data': target_date, 1590 'label': _('in %d week(s) - %s') % (val, target_date.timestamp.strftime('%A, %Y-%m-%d').decode(enc)) 1591 } 1592 matches.append(tmp) 1593 1594 # month X of ... 1595 if val < 13: 1596 # ... this year 1597 ts = now + mxDT.RelativeDateTime(month = val) 1598 target_date = cFuzzyTimestamp ( 1599 timestamp = ts, 1600 accuracy = acc_months 1601 ) 1602 tmp = { 1603 'data': target_date, 1604 'label': _('%s (%s this year)') % (target_date, ts.strftime('%B').decode(enc)) 1605 } 1606 matches.append(tmp) 1607 1608 # ... next year 1609 ts = now + mxDT.RelativeDateTime(years = 1, month = val) 1610 target_date = cFuzzyTimestamp ( 1611 timestamp = ts, 1612 accuracy = acc_months 1613 ) 1614 tmp = { 1615 'data': target_date, 1616 'label': _('%s (%s next year)') % (target_date, ts.strftime('%B').decode(enc)) 1617 } 1618 matches.append(tmp) 1619 1620 # ... last year 1621 ts = now + mxDT.RelativeDateTime(years = -1, month = val) 1622 target_date = cFuzzyTimestamp ( 1623 timestamp = ts, 1624 accuracy = acc_months 1625 ) 1626 tmp = { 1627 'data': target_date, 1628 'label': _('%s (%s last year)') % (target_date, ts.strftime('%B').decode(enc)) 1629 } 1630 matches.append(tmp) 1631 1632 # fragment expansion 1633 matches.append ({ 1634 'data': None, 1635 'label': '%s/200' % val 1636 }) 1637 matches.append ({ 1638 'data': None, 1639 'label': '%s/199' % val 1640 }) 1641 matches.append ({ 1642 'data': None, 1643 'label': '%s/198' % val 1644 }) 1645 matches.append ({ 1646 'data': None, 1647 'label': '%s/19' % val 1648 }) 1649 1650 # day X of ... 1651 if val < 8: 1652 # ... this week 1653 ts = now + mxDT.RelativeDateTime(weekday = (val-1, 0)) 1654 target_date = cFuzzyTimestamp ( 1655 timestamp = ts, 1656 accuracy = acc_days 1657 ) 1658 tmp = { 1659 'data': target_date, 1660 'label': _('%s this week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc)) 1661 } 1662 matches.append(tmp) 1663 1664 # ... next week 1665 ts = now + mxDT.RelativeDateTime(weeks = +1, weekday = (val-1, 0)) 1666 target_date = cFuzzyTimestamp ( 1667 timestamp = ts, 1668 accuracy = acc_days 1669 ) 1670 tmp = { 1671 'data': target_date, 1672 'label': _('%s next week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc)) 1673 } 1674 matches.append(tmp) 1675 1676 # ... last week 1677 ts = now + mxDT.RelativeDateTime(weeks = -1, weekday = (val-1, 0)) 1678 target_date = cFuzzyTimestamp ( 1679 timestamp = ts, 1680 accuracy = acc_days 1681 ) 1682 tmp = { 1683 'data': target_date, 1684 'label': _('%s last week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc)) 1685 } 1686 matches.append(tmp) 1687 1688 if val < 100: 1689 matches.append ({ 1690 'data': None, 1691 'label': '%s/' % (1900 + val) 1692 }) 1693 1694 if val == 200: 1695 tmp = { 1696 'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_days), 1697 'label': '%s' % target_date 1698 } 1699 matches.append(tmp) 1700 matches.append ({ 1701 'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_months), 1702 'label': '%.2d/%s' % (now.month, now.year) 1703 }) 1704 matches.append ({ 1705 'data': None, 1706 'label': '%s/' % now.year 1707 }) 1708 matches.append ({ 1709 'data': None, 1710 'label': '%s/' % (now.year + 1) 1711 }) 1712 matches.append ({ 1713 'data': None, 1714 'label': '%s/' % (now.year - 1) 1715 }) 1716 1717 if val < 200 and val >= 190: 1718 for i in range(10): 1719 matches.append ({ 1720 'data': None, 1721 'label': '%s%s/' % (val, i) 1722 }) 1723 1724 return matches
1725 #---------------------------------------------------------------------------
1726 -def __single_dot(str2parse):
1727 """Expand fragments containing a single dot. 1728 1729 Standard colloquial date format in Germany: day.month.year 1730 1731 "14." 1732 - 14th current month this year 1733 - 14th next month this year 1734 """ 1735 if not regex.match(u"^(\s|\t)*\d{1,2}\.{1}(\s|\t)*$", str2parse, flags = regex.LOCALE | regex.UNICODE): 1736 return [] 1737 1738 val = int(regex.findall(u'\d+', str2parse, flags = regex.LOCALE | regex.UNICODE)[0]) 1739 now = mxDT.now() 1740 enc = gmI18N.get_encoding() 1741 1742 matches = [] 1743 1744 # day X of this month 1745 ts = now + mxDT.RelativeDateTime(day = val) 1746 if val > 0 and val <= gregorian_month_length[ts.month]: 1747 matches.append ({ 1748 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_days), 1749 'label': '%s.%s.%s - a %s this month' % (ts.day, ts.month, ts.year, ts.strftime('%A').decode(enc)) 1750 }) 1751 1752 # day X of next month 1753 ts = now + mxDT.RelativeDateTime(day = val, months = +1) 1754 if val > 0 and val <= gregorian_month_length[ts.month]: 1755 matches.append ({ 1756 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_days), 1757 'label': '%s.%s.%s - a %s next month' % (ts.day, ts.month, ts.year, ts.strftime('%A').decode(enc)) 1758 }) 1759 1760 # day X of last month 1761 ts = now + mxDT.RelativeDateTime(day = val, months = -1) 1762 if val > 0 and val <= gregorian_month_length[ts.month]: 1763 matches.append ({ 1764 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_days), 1765 'label': '%s.%s.%s - a %s last month' % (ts.day, ts.month, ts.year, ts.strftime('%A').decode(enc)) 1766 }) 1767 1768 return matches
1769 #---------------------------------------------------------------------------
1770 -def str2fuzzy_timestamp_matches(str2parse=None, default_time=None, patterns=None):
1771 """ 1772 Turn a string into candidate fuzzy timestamps and auto-completions the user is likely to type. 1773 1774 You MUST have called locale.setlocale(locale.LC_ALL, '') 1775 somewhere in your code previously. 1776 1777 @param default_time: if you want to force the time part of the time 1778 stamp to a given value and the user doesn't type any time part 1779 this value will be used 1780 @type default_time: an mx.DateTime.DateTimeDelta instance 1781 1782 @param patterns: list of [time.strptime compatible date/time pattern, accuracy] 1783 @type patterns: list 1784 """ 1785 matches = __single_dot(str2parse) 1786 matches.extend(__numbers_only(str2parse)) 1787 matches.extend(__single_slash(str2parse)) 1788 ms = __single_char2py_dt(str2parse) 1789 for m in ms: 1790 matches.append ({ 1791 'data': cFuzzyTimestamp ( 1792 timestamp = m['data'], 1793 accuracy = acc_days 1794 ), 1795 'label': m['label'] 1796 }) 1797 matches.extend(__explicit_offset(str2parse)) 1798 1799 # try mxDT parsers 1800 try: 1801 # date ? 1802 date_only = mxDT.Parser.DateFromString ( 1803 text = str2parse, 1804 formats = ('euro', 'iso', 'us', 'altus', 'altiso', 'lit', 'altlit', 'eurlit') 1805 ) 1806 # time, too ? 1807 time_part = mxDT.Parser.TimeFromString(text = str2parse) 1808 datetime = date_only + time_part 1809 if datetime == date_only: 1810 accuracy = acc_days 1811 if isinstance(default_time, mxDT.DateTimeDeltaType): 1812 datetime = date_only + default_time 1813 accuracy = acc_minutes 1814 else: 1815 accuracy = acc_subseconds 1816 fts = cFuzzyTimestamp ( 1817 timestamp = datetime, 1818 accuracy = accuracy 1819 ) 1820 matches.append ({ 1821 'data': fts, 1822 'label': fts.format_accurately() 1823 }) 1824 except (ValueError, mxDT.RangeError): 1825 pass 1826 1827 if patterns is None: 1828 patterns = [] 1829 1830 patterns.append(['%Y-%m-%d', acc_days]) 1831 patterns.append(['%y-%m-%d', acc_days]) 1832 patterns.append(['%Y/%m/%d', acc_days]) 1833 patterns.append(['%y/%m/%d', acc_days]) 1834 1835 patterns.append(['%d-%m-%Y', acc_days]) 1836 patterns.append(['%d-%m-%y', acc_days]) 1837 patterns.append(['%d/%m/%Y', acc_days]) 1838 patterns.append(['%d/%m/%y', acc_days]) 1839 1840 patterns.append(['%m-%d-%Y', acc_days]) 1841 patterns.append(['%m-%d-%y', acc_days]) 1842 patterns.append(['%m/%d/%Y', acc_days]) 1843 patterns.append(['%m/%d/%y', acc_days]) 1844 1845 patterns.append(['%Y.%m.%d', acc_days]) 1846 patterns.append(['%y.%m.%d', acc_days]) 1847 1848 1849 for pattern in patterns: 1850 try: 1851 fts = cFuzzyTimestamp ( 1852 timestamp = pyDT.datetime.fromtimestamp(time.mktime(time.strptime(str2parse, pattern[0]))), 1853 accuracy = pattern[1] 1854 ) 1855 matches.append ({ 1856 'data': fts, 1857 'label': fts.format_accurately() 1858 }) 1859 except AttributeError: 1860 # strptime() only available starting with Python 2.5 1861 break 1862 except OverflowError: 1863 # time.mktime() cannot handle dates older than a platform-dependant limit :-( 1864 continue 1865 except ValueError: 1866 # C-level overflow 1867 continue 1868 1869 return matches
1870 #=========================================================================== 1871 # fuzzy timestamp class 1872 #---------------------------------------------------------------------------
1873 -class cFuzzyTimestamp:
1874 1875 # FIXME: add properties for year, month, ... 1876 1877 """A timestamp implementation with definable inaccuracy. 1878 1879 This class contains an mxDateTime.DateTime instance to 1880 hold the actual timestamp. It adds an accuracy attribute 1881 to allow the programmer to set the precision of the 1882 timestamp. 1883 1884 The timestamp will have to be initialzed with a fully 1885 precise value (which may, of course, contain partially 1886 fake data to make up for missing values). One can then 1887 set the accuracy value to indicate up to which part of 1888 the timestamp the data is valid. Optionally a modifier 1889 can be set to indicate further specification of the 1890 value (such as "summer", "afternoon", etc). 1891 1892 accuracy values: 1893 1: year only 1894 ... 1895 7: everything including milliseconds value 1896 1897 Unfortunately, one cannot directly derive a class from mx.DateTime.DateTime :-( 1898 """ 1899 #-----------------------------------------------------------------------
1900 - def __init__(self, timestamp=None, accuracy=acc_subseconds, modifier=''):
1901 1902 if timestamp is None: 1903 timestamp = mxDT.now() 1904 accuracy = acc_subseconds 1905 modifier = '' 1906 1907 if (accuracy < 1) or (accuracy > 8): 1908 raise ValueError('%s.__init__(): <accuracy> must be between 1 and 8' % self.__class__.__name__) 1909 1910 if isinstance(timestamp, pyDT.datetime): 1911 timestamp = mxDT.DateTime(timestamp.year, timestamp.month, timestamp.day, timestamp.hour, timestamp.minute, timestamp.second) 1912 1913 if type(timestamp) != mxDT.DateTimeType: 1914 raise TypeError('%s.__init__(): <timestamp> must be of mx.DateTime.DateTime or datetime.datetime type' % self.__class__.__name__) 1915 1916 self.timestamp = timestamp 1917 self.accuracy = accuracy 1918 self.modifier = modifier
1919 #----------------------------------------------------------------------- 1920 # magic API 1921 #-----------------------------------------------------------------------
1922 - def __str__(self):
1923 """Return string representation meaningful to a user, also for %s formatting.""" 1924 return self.format_accurately()
1925 #-----------------------------------------------------------------------
1926 - def __repr__(self):
1927 """Return string meaningful to a programmer to aid in debugging.""" 1928 tmp = '<[%s]: timestamp [%s], accuracy [%s] (%s), modifier [%s] at %s>' % ( 1929 self.__class__.__name__, 1930 repr(self.timestamp), 1931 self.accuracy, 1932 _accuracy_strings[self.accuracy], 1933 self.modifier, 1934 id(self) 1935 ) 1936 return tmp
1937 #----------------------------------------------------------------------- 1938 # external API 1939 #-----------------------------------------------------------------------
1940 - def strftime(self, format_string):
1941 if self.accuracy == 7: 1942 return self.timestamp.strftime(format_string) 1943 return self.format_accurately()
1944 #-----------------------------------------------------------------------
1945 - def Format(self, format_string):
1946 return self.strftime(format_string)
1947 #-----------------------------------------------------------------------
1948 - def format_accurately(self, accuracy=None):
1949 if accuracy is None: 1950 accuracy = self.accuracy 1951 1952 if accuracy == acc_years: 1953 return unicode(self.timestamp.year) 1954 1955 if accuracy == acc_months: 1956 return unicode(self.timestamp.strftime('%m/%Y')) # FIXME: use 3-letter month ? 1957 1958 if accuracy == acc_weeks: 1959 return unicode(self.timestamp.strftime('%m/%Y')) # FIXME: use 3-letter month ? 1960 1961 if accuracy == acc_days: 1962 return unicode(self.timestamp.strftime('%Y-%m-%d')) 1963 1964 if accuracy == acc_hours: 1965 return unicode(self.timestamp.strftime("%Y-%m-%d %I%p")) 1966 1967 if accuracy == acc_minutes: 1968 return unicode(self.timestamp.strftime("%Y-%m-%d %H:%M")) 1969 1970 if accuracy == acc_seconds: 1971 return unicode(self.timestamp.strftime("%Y-%m-%d %H:%M:%S")) 1972 1973 if accuracy == acc_subseconds: 1974 return unicode(self.timestamp) 1975 1976 raise ValueError, '%s.format_accurately(): <accuracy> (%s) must be between 1 and 7' % ( 1977 self.__class__.__name__, 1978 accuracy 1979 )
1980 #-----------------------------------------------------------------------
1981 - def get_mxdt(self):
1982 return self.timestamp
1983 #-----------------------------------------------------------------------
1984 - def get_pydt(self):
1985 try: 1986 gmtoffset = self.timestamp.gmtoffset() 1987 except mxDT.Error: 1988 # Windows cannot deal with dates < 1970, so 1989 # when that happens switch to now() 1990 now = mxDT.now() 1991 gmtoffset = now.gmtoffset() 1992 tz = cFixedOffsetTimezone(gmtoffset.minutes, self.timestamp.tz) 1993 secs, msecs = divmod(self.timestamp.second, 1) 1994 ts = pyDT.datetime ( 1995 year = self.timestamp.year, 1996 month = self.timestamp.month, 1997 day = self.timestamp.day, 1998 hour = self.timestamp.hour, 1999 minute = self.timestamp.minute, 2000 second = int(secs), 2001 microsecond = int(msecs * 1000), 2002 tzinfo = tz 2003 ) 2004 return ts
2005 #=========================================================================== 2006 # main 2007 #--------------------------------------------------------------------------- 2008 if __name__ == '__main__': 2009 2010 if len(sys.argv) < 2: 2011 sys.exit() 2012 2013 if sys.argv[1] != "test": 2014 sys.exit() 2015 2016 #----------------------------------------------------------------------- 2017 intervals_as_str = [ 2018 '7', '12', ' 12', '12 ', ' 12 ', ' 12 ', '0', '~12', '~ 12', ' ~ 12', ' ~ 12 ', 2019 '12a', '12 a', '12 a', '12j', '12J', '12y', '12Y', ' ~ 12 a ', '~0a', 2020 '12m', '17 m', '12 m', '17M', ' ~ 17 m ', ' ~ 3 / 12 ', '7/12', '0/12', 2021 '12w', '17 w', '12 w', '17W', ' ~ 17 w ', ' ~ 15 / 52', '2/52', '0/52', 2022 '12d', '17 d', '12 t', '17D', ' ~ 17 T ', ' ~ 12 / 7', '3/7', '0/7', 2023 '12h', '17 h', '12 H', '17H', ' ~ 17 h ', ' ~ 36 / 24', '7/24', '0/24', 2024 ' ~ 36 / 60', '7/60', '190/60', '0/60', 2025 '12a1m', '12 a 1 M', '12 a17m', '12j 12m', '12J7m', '12y7m', '12Y7M', ' ~ 12 a 37 m ', '~0a0m', 2026 '10m1w', 2027 'invalid interval input' 2028 ] 2029 #-----------------------------------------------------------------------
2030 - def test_format_interval():
2031 for tmp in intervals_as_str: 2032 intv = str2interval(str_interval = tmp) 2033 for acc in _accuracy_strings.keys(): 2034 print '[%s]: "%s" -> "%s"' % (acc, tmp, format_interval(intv, acc))
2035 #-----------------------------------------------------------------------
2036 - def test_format_interval_medically():
2037 2038 intervals = [ 2039 pyDT.timedelta(seconds = 1), 2040 pyDT.timedelta(seconds = 5), 2041 pyDT.timedelta(seconds = 30), 2042 pyDT.timedelta(seconds = 60), 2043 pyDT.timedelta(seconds = 94), 2044 pyDT.timedelta(seconds = 120), 2045 pyDT.timedelta(minutes = 5), 2046 pyDT.timedelta(minutes = 30), 2047 pyDT.timedelta(minutes = 60), 2048 pyDT.timedelta(minutes = 90), 2049 pyDT.timedelta(minutes = 120), 2050 pyDT.timedelta(minutes = 200), 2051 pyDT.timedelta(minutes = 400), 2052 pyDT.timedelta(minutes = 600), 2053 pyDT.timedelta(minutes = 800), 2054 pyDT.timedelta(minutes = 1100), 2055 pyDT.timedelta(minutes = 2000), 2056 pyDT.timedelta(minutes = 3500), 2057 pyDT.timedelta(minutes = 4000), 2058 pyDT.timedelta(hours = 1), 2059 pyDT.timedelta(hours = 2), 2060 pyDT.timedelta(hours = 4), 2061 pyDT.timedelta(hours = 8), 2062 pyDT.timedelta(hours = 12), 2063 pyDT.timedelta(hours = 20), 2064 pyDT.timedelta(hours = 23), 2065 pyDT.timedelta(hours = 24), 2066 pyDT.timedelta(hours = 25), 2067 pyDT.timedelta(hours = 30), 2068 pyDT.timedelta(hours = 48), 2069 pyDT.timedelta(hours = 98), 2070 pyDT.timedelta(hours = 120), 2071 pyDT.timedelta(days = 1), 2072 pyDT.timedelta(days = 2), 2073 pyDT.timedelta(days = 4), 2074 pyDT.timedelta(days = 16), 2075 pyDT.timedelta(days = 29), 2076 pyDT.timedelta(days = 30), 2077 pyDT.timedelta(days = 31), 2078 pyDT.timedelta(days = 37), 2079 pyDT.timedelta(days = 40), 2080 pyDT.timedelta(days = 47), 2081 pyDT.timedelta(days = 126), 2082 pyDT.timedelta(days = 127), 2083 pyDT.timedelta(days = 128), 2084 pyDT.timedelta(days = 300), 2085 pyDT.timedelta(days = 359), 2086 pyDT.timedelta(days = 360), 2087 pyDT.timedelta(days = 361), 2088 pyDT.timedelta(days = 362), 2089 pyDT.timedelta(days = 363), 2090 pyDT.timedelta(days = 364), 2091 pyDT.timedelta(days = 365), 2092 pyDT.timedelta(days = 366), 2093 pyDT.timedelta(days = 367), 2094 pyDT.timedelta(days = 400), 2095 pyDT.timedelta(weeks = 52 * 30), 2096 pyDT.timedelta(weeks = 52 * 79, days = 33) 2097 ] 2098 2099 idx = 1 2100 for intv in intervals: 2101 print '%s) %s -> %s' % (idx, intv, format_interval_medically(intv)) 2102 idx += 1
2103 #-----------------------------------------------------------------------
2104 - def test_str2interval():
2105 print "testing str2interval()" 2106 print "----------------------" 2107 2108 for interval_as_str in intervals_as_str: 2109 print "input: <%s>" % interval_as_str 2110 print " ==>", str2interval(str_interval=interval_as_str) 2111 2112 return True
2113 #-------------------------------------------------
2114 - def test_date_time():
2115 print "DST currently in effect:", dst_currently_in_effect 2116 print "current UTC offset:", current_local_utc_offset_in_seconds, "seconds" 2117 print "current timezone (interval):", current_local_timezone_interval 2118 print "current timezone (ISO conformant numeric string):", current_local_iso_numeric_timezone_string 2119 print "local timezone class:", cLocalTimezone 2120 print "" 2121 tz = cLocalTimezone() 2122 print "local timezone instance:", tz 2123 print " (total) UTC offset:", tz.utcoffset(pyDT.datetime.now()) 2124 print " DST adjustment:", tz.dst(pyDT.datetime.now()) 2125 print " timezone name:", tz.tzname(pyDT.datetime.now()) 2126 print "" 2127 print "current local timezone:", gmCurrentLocalTimezone 2128 print " (total) UTC offset:", gmCurrentLocalTimezone.utcoffset(pyDT.datetime.now()) 2129 print " DST adjustment:", gmCurrentLocalTimezone.dst(pyDT.datetime.now()) 2130 print " timezone name:", gmCurrentLocalTimezone.tzname(pyDT.datetime.now()) 2131 print "" 2132 print "now here:", pydt_now_here() 2133 print ""
2134 #-------------------------------------------------
2135 - def test_str2fuzzy_timestamp_matches():
2136 print "testing function str2fuzzy_timestamp_matches" 2137 print "--------------------------------------------" 2138 2139 val = None 2140 while val != 'exit': 2141 val = raw_input('Enter date fragment ("exit" quits): ') 2142 matches = str2fuzzy_timestamp_matches(str2parse = val) 2143 for match in matches: 2144 print 'label shown :', match['label'] 2145 print 'data attached:', match['data'], match['data'].timestamp 2146 print "" 2147 print "---------------"
2148 #-------------------------------------------------
2149 - def test_cFuzzyTimeStamp():
2150 print "testing fuzzy timestamp class" 2151 print "-----------------------------" 2152 2153 ts = mxDT.now() 2154 print "mx.DateTime timestamp", type(ts) 2155 print " print ... :", ts 2156 print " print '%%s' %% ...: %s" % ts 2157 print " str() :", str(ts) 2158 print " repr() :", repr(ts) 2159 2160 fts = cFuzzyTimestamp() 2161 print "\nfuzzy timestamp <%s '%s'>" % ('class', fts.__class__.__name__) 2162 for accuracy in range(1,8): 2163 fts.accuracy = accuracy 2164 print " accuracy : %s (%s)" % (accuracy, _accuracy_strings[accuracy]) 2165 print " format_accurately:", fts.format_accurately() 2166 print " strftime() :", fts.strftime('%c') 2167 print " print ... :", fts 2168 print " print '%%s' %% ... : %s" % fts 2169 print " str() :", str(fts) 2170 print " repr() :", repr(fts) 2171 raw_input('press ENTER to continue')
2172 #-------------------------------------------------
2173 - def test_get_pydt():
2174 print "testing platform for handling dates before 1970" 2175 print "-----------------------------------------------" 2176 ts = mxDT.DateTime(1935, 4, 2) 2177 fts = cFuzzyTimestamp(timestamp=ts) 2178 print "fts :", fts 2179 print "fts.get_pydt():", fts.get_pydt()
2180 #-------------------------------------------------
2181 - def test_calculate_apparent_age():
2182 start = pydt_now_here().replace(year = 1974).replace(month = 10).replace(day = 23) 2183 print calculate_apparent_age(start = start) 2184 print format_apparent_age_medically(calculate_apparent_age(start = start)) 2185 2186 start = pydt_now_here().replace(year = 1979).replace(month = 3).replace(day = 13) 2187 print calculate_apparent_age(start = start) 2188 print format_apparent_age_medically(calculate_apparent_age(start = start)) 2189 2190 start = pydt_now_here().replace(year = 1979).replace(month = 2, day = 2) 2191 end = pydt_now_here().replace(year = 1979).replace(month = 3).replace(day = 31) 2192 print calculate_apparent_age(start = start, end = end) 2193 2194 start = pydt_now_here().replace(year = 2009).replace(month = 7, day = 21) 2195 print format_apparent_age_medically(calculate_apparent_age(start = start))
2196 #-------------------------------------------------
2197 - def test_str2pydt():
2198 print "testing function str2pydt_matches" 2199 print "---------------------------------" 2200 2201 val = None 2202 while val != 'exit': 2203 val = raw_input('Enter date fragment ("exit" quits): ') 2204 matches = str2pydt_matches(str2parse = val) 2205 for match in matches: 2206 print 'label shown :', match['label'] 2207 print 'data attached:', match['data'] 2208 print "" 2209 print "---------------"
2210 #-------------------------------------------------
2211 - def test_pydt_strftime():
2212 dt = pydt_now_here() 2213 print pydt_strftime(dt) 2214 print pydt_strftime(dt, accuracy = acc_days) 2215 print pydt_strftime(dt, accuracy = acc_minutes) 2216 print pydt_strftime(dt, accuracy = acc_seconds) 2217 dt = dt.replace(year = 1899) 2218 print pydt_strftime(dt) 2219 print pydt_strftime(dt, accuracy = acc_days) 2220 print pydt_strftime(dt, accuracy = acc_minutes) 2221 print pydt_strftime(dt, accuracy = acc_seconds)
2222 #------------------------------------------------- 2223 # GNUmed libs 2224 gmI18N.activate_locale() 2225 gmI18N.install_domain('gnumed') 2226 2227 init() 2228 2229 #test_date_time() 2230 #test_str2fuzzy_timestamp_matches() 2231 #test_cFuzzyTimeStamp() 2232 #test_get_pydt() 2233 #test_str2interval() 2234 #test_format_interval() 2235 #test_format_interval_medically() 2236 #test_calculate_apparent_age() 2237 test_str2pydt() 2238 #test_pydt_strftime() 2239 2240 #=========================================================================== 2241