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

Source Code for Module Gnumed.pycommon.gmDateTime

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