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