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