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 -def pydt_now_here():
196 """Returns NOW @ HERE (IOW, in the local timezone.""" 197 return pyDT.datetime.now(gmCurrentLocalTimezone)
198 #---------------------------------------------------------------------------
199 -def wx_now_here(wx=None):
200 """Returns NOW @ HERE (IOW, in the local timezone.""" 201 return py_dt2wxDate(py_dt = pydt_now_here(), wx = wx)
202 #=========================================================================== 203 # wxPython conversions 204 #---------------------------------------------------------------------------
205 -def wxDate2py_dt(wxDate=None):
206 if not wxDate.IsValid(): 207 raise ArgumentError (u'invalid wxDate: %s-%s-%s %s:%s %s.%s', 208 wxDate.GetYear(), 209 wxDate.GetMonth(), 210 wxDate.GetDay(), 211 wxDate.GetHour(), 212 wxDate.GetMinute(), 213 wxDate.GetSecond(), 214 wxDate.GetMillisecond() 215 ) 216 217 try: 218 return pyDT.datetime ( 219 year = wxDate.GetYear(), 220 month = wxDate.GetMonth() + 1, 221 day = wxDate.GetDay(), 222 tzinfo = gmCurrentLocalTimezone 223 ) 224 except: 225 _log.debug (u'error converting wxDateTime to Python: %s-%s-%s %s:%s %s.%s', 226 wxDate.GetYear(), 227 wxDate.GetMonth(), 228 wxDate.GetDay(), 229 wxDate.GetHour(), 230 wxDate.GetMinute(), 231 wxDate.GetSecond(), 232 wxDate.GetMillisecond() 233 ) 234 raise
235 #---------------------------------------------------------------------------
236 -def py_dt2wxDate(py_dt=None, wx=None):
237 wxdt = wx.DateTime() 238 wxdt.SetYear(py_dt.year) 239 wxdt.SetMonth(py_dt.month-1) 240 wxdt.SetDay(py_dt.day) 241 return wxdt
242 #=========================================================================== 243 # interval related 244 #---------------------------------------------------------------------------
245 -def format_interval(interval=None, accuracy_wanted=acc_seconds):
246 247 years, days = divmod(interval.days, avg_days_per_gregorian_year) 248 months, days = divmod(days, avg_days_per_gregorian_month) 249 weeks, days = divmod(days, days_per_week) 250 days, secs = divmod((days * avg_seconds_per_day) + interval.seconds, avg_seconds_per_day) 251 hours, secs = divmod(secs, 3600) 252 mins, secs = divmod(secs, 60) 253 254 tmp = u'' 255 256 if years > 0: 257 tmp += u'%s%s' % (int(years), _('interval_format_tag::years::y')[-1:]) 258 259 if accuracy_wanted < acc_months: 260 return tmp.strip() 261 262 if months > 0: 263 tmp += u' %s%s' % (int(months), _('interval_format_tag::months::m')[-1:]) 264 265 if accuracy_wanted < acc_weeks: 266 return tmp.strip() 267 268 if weeks > 0: 269 tmp += u' %s%s' % (int(weeks), _('interval_format_tag::weeks::w')[-1:]) 270 271 if accuracy_wanted < acc_days: 272 return tmp.strip() 273 274 if days > 0: 275 tmp += u' %s%s' % (int(days), _('interval_format_tag::days::d')[-1:]) 276 277 if accuracy_wanted < acc_hours: 278 return tmp.strip() 279 280 if hours > 0: 281 tmp += u' %s/24' % int(hours) 282 283 if accuracy_wanted < acc_minutes: 284 return tmp.strip() 285 286 if mins > 0: 287 tmp += u' %s/60' % int(mins) 288 289 if accuracy_wanted < acc_seconds: 290 return tmp.strip() 291 292 if secs > 0: 293 tmp += u' %s/60' % int(secs) 294 295 return tmp.strip()
296 #---------------------------------------------------------------------------
297 -def format_interval_medically(interval=None):
298 """Formats an interval. 299 300 This isn't mathematically correct but close enough for display. 301 """ 302 # FIXME: i18n for abbrevs 303 304 # more than 1 year ? 305 if interval.days > 363: 306 years, days = divmod(interval.days, 364) 307 leap_days, tmp = divmod(years, 4) 308 months, day = divmod((days + leap_days), 30.33) 309 if int(months) == 0: 310 return "%sy" % int(years) 311 return "%sy %sm" % (int(years), int(months)) 312 313 # more than 30 days / 1 month ? 314 if interval.days > 30: 315 months, days = divmod(interval.days, 30.33) 316 weeks, days = divmod(days, 7) 317 if int(weeks + days) == 0: 318 result = '%smo' % int(months) 319 else: 320 result = '%sm' % int(months) 321 if int(weeks) != 0: 322 result += ' %sw' % int(weeks) 323 if int(days) != 0: 324 result += ' %sd' % int(days) 325 return result 326 327 # between 7 and 30 days ? 328 if interval.days > 7: 329 return "%sd" % interval.days 330 331 # between 1 and 7 days ? 332 if interval.days > 0: 333 hours, seconds = divmod(interval.seconds, 3600) 334 if hours == 0: 335 return '%sd' % interval.days 336 return "%sd (%sh)" % (interval.days, int(hours)) 337 338 # between 5 hours and 1 day 339 if interval.seconds > (5*3600): 340 return "%sh" % int(interval.seconds // 3600) 341 342 # between 1 and 5 hours 343 if interval.seconds > 3600: 344 hours, seconds = divmod(interval.seconds, 3600) 345 minutes = seconds // 60 346 if minutes == 0: 347 return '%sh' % int(hours) 348 return "%s:%02d" % (int(hours), int(minutes)) 349 350 # minutes only 351 if interval.seconds > (5*60): 352 return "0:%02d" % (int(interval.seconds // 60)) 353 354 # seconds 355 minutes, seconds = divmod(interval.seconds, 60) 356 if minutes == 0: 357 return '%ss' % int(seconds) 358 if seconds == 0: 359 return '0:%02d' % int(minutes) 360 return "%s.%ss" % (int(minutes), int(seconds))
361 #---------------------------------------------------------------------------
362 -def calculate_apparent_age(start=None, end=None):
363 """The result of this is a tuple (years, ..., seconds) as one would 364 'expect' a date to look like, that is, simple differences between 365 the fields. 366 367 No need for 100/400 years leap days rule because 2000 WAS a leap year. 368 369 This does not take into account time zones which may 370 shift the result by one day. 371 372 <start> and <end> must by python datetime instances 373 <end> is assumed to be "now" if not given 374 """ 375 if end is None: 376 end = pyDT.datetime.now(gmCurrentLocalTimezone) 377 378 if end < start: 379 raise ValueError('calculate_apparent_age(): <end> (%s) before <start> %s' % (end, start)) 380 381 if end == start: 382 years = months = days = hours = minutes = seconds = 0 383 return (years, months, days, hours, minutes, seconds) 384 385 # years 386 years = end.year - start.year 387 end = end.replace(year = start.year) 388 if end < start: 389 years = years - 1 390 391 # months 392 if end.month == start.month: 393 months = 0 394 else: 395 months = end.month - start.month 396 if months < 0: 397 months = months + 12 398 end = end.replace(month = start.month) 399 if end < start: 400 months = months - 1 401 402 # days 403 if end.day == start.day: 404 days = 0 405 else: 406 days = end.day - start.day 407 if days < 0: 408 days = days + gregorian_month_length[start.month] 409 end = end.replace(day = start.day) 410 if end < start: 411 days = days - 1 412 413 # hours 414 if end.hour == start.hour: 415 hours = 0 416 else: 417 hours = end.hour - start.hour 418 if hours < 0: 419 hours = hours + 24 420 end = end.replace(hour = start.hour) 421 if end < start: 422 hours = hours - 1 423 424 # minutes 425 if end.minute == start.minute: 426 minutes = 0 427 else: 428 minutes = end.minute - start.minute 429 if minutes < 0: 430 minutes = minutes + 60 431 end = end.replace(minute = start.minute) 432 if end < start: 433 minutes = minutes - 1 434 435 # seconds 436 if end.second == start.second: 437 seconds = 0 438 else: 439 seconds = end.second - start.second 440 if seconds < 0: 441 seconds = seconds + 60 442 end = end.replace(second = start.second) 443 if end < start: 444 seconds = seconds - 1 445 446 return (years, months, days, hours, minutes, seconds)
447 #---------------------------------------------------------------------------
448 -def format_apparent_age_medically(age=None):
449 """<age> must be a tuple as created by calculate_apparent_age()""" 450 451 (years, months, days, hours, minutes, seconds) = age 452 453 # more than 1 year ? 454 if years > 1: 455 if months == 0: 456 return u'%s%s' % ( 457 years, 458 _('y::year_abbreviation').replace('::year_abbreviation', u'') 459 ) 460 return u'%s%s %s%s' % ( 461 years, 462 _('y::year_abbreviation').replace('::year_abbreviation', u''), 463 months, 464 _('m::month_abbreviation').replace('::month_abbreviation', u'') 465 ) 466 467 # more than 1 month ? 468 if months > 1: 469 if days == 0: 470 return u'%s%s' % ( 471 months, 472 _('mo::month_only_abbreviation').replace('::month_only_abbreviation', u'') 473 ) 474 475 result = u'%s%s' % ( 476 months, 477 _('m::month_abbreviation').replace('::month_abbreviation', u'') 478 ) 479 480 weeks, days = divmod(days, 7) 481 if int(weeks) != 0: 482 result += u'%s%s' % ( 483 int(weeks), 484 _('w::week_abbreviation').replace('::week_abbreviation', u'') 485 ) 486 if int(days) != 0: 487 result += u'%s%s' % ( 488 int(days), 489 _('d::day_abbreviation').replace('::day_abbreviation', u'') 490 ) 491 492 return result 493 494 # between 7 days and 1 month 495 if days > 7: 496 return u"%s%s" % ( 497 days, 498 _('d::day_abbreviation').replace('::day_abbreviation', u'') 499 ) 500 501 # between 1 and 7 days ? 502 if days > 0: 503 if hours == 0: 504 return u'%s%s' % ( 505 days, 506 _('d::day_abbreviation').replace('::day_abbreviation', u'') 507 ) 508 return u'%s%s (%s%s)' % ( 509 days, 510 _('d::day_abbreviation').replace('::day_abbreviation', u''), 511 hours, 512 _('h::hour_abbreviation').replace('::hour_abbreviation', u'') 513 ) 514 515 # between 5 hours and 1 day 516 if hours > 5: 517 return u'%s%s' % ( 518 hours, 519 _('h::hour_abbreviation').replace('::hour_abbreviation', u'') 520 ) 521 522 # between 1 and 5 hours 523 if hours > 1: 524 if minutes == 0: 525 return u'%s%s' % ( 526 hours, 527 _('h::hour_abbreviation').replace('::hour_abbreviation', u'') 528 ) 529 return u'%s:%02d' % ( 530 hours, 531 minutes 532 ) 533 534 # between 5 and 60 minutes 535 if minutes > 5: 536 return u"0:%02d" % minutes 537 538 # less than 5 minutes 539 if minutes == 0: 540 return u'%s%s' % ( 541 seconds, 542 _('s::second_abbreviation').replace('::second_abbreviation', u'') 543 ) 544 if seconds == 0: 545 return u"0:%02d" % minutes 546 return "%s.%s%s" % ( 547 minutes, 548 seconds, 549 _('s::second_abbreviation').replace('::second_abbreviation', u'') 550 )
551 #---------------------------------------------------------------------------
552 -def str2interval(str_interval=None):
553 554 unit_keys = { 555 'year': _('yYaA_keys_year'), 556 'month': _('mM_keys_month'), 557 'week': _('wW_keys_week'), 558 'day': _('dD_keys_day'), 559 'hour': _('hH_keys_hour') 560 } 561 562 str_interval = str_interval.strip() 563 564 # "(~)35(yY)" - at age 35 years 565 keys = '|'.join(list(unit_keys['year'].replace('_keys_year', u''))) 566 if regex.match(u'^~*(\s|\t)*\d+(%s)*$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE): 567 return pyDT.timedelta(days = (int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]) * avg_days_per_gregorian_year)) 568 569 # "(~)12mM" - at age 12 months 570 keys = '|'.join(list(unit_keys['month'].replace('_keys_month', u''))) 571 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE): 572 years, months = divmod ( 573 int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]), 574 12 575 ) 576 return pyDT.timedelta(days = ((years * avg_days_per_gregorian_year) + (months * avg_days_per_gregorian_month))) 577 578 # weeks 579 keys = '|'.join(list(unit_keys['week'].replace('_keys_week', u''))) 580 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE): 581 return pyDT.timedelta(weeks = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0])) 582 583 # days 584 keys = '|'.join(list(unit_keys['day'].replace('_keys_day', u''))) 585 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE): 586 return pyDT.timedelta(days = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0])) 587 588 # hours 589 keys = '|'.join(list(unit_keys['hour'].replace('_keys_hour', u''))) 590 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE): 591 return pyDT.timedelta(hours = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0])) 592 593 # x/12 - months 594 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*12$', str_interval, flags = regex.LOCALE | regex.UNICODE): 595 years, months = divmod ( 596 int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]), 597 12 598 ) 599 return pyDT.timedelta(days = ((years * avg_days_per_gregorian_year) + (months * avg_days_per_gregorian_month))) 600 601 # x/52 - weeks 602 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*52$', str_interval, flags = regex.LOCALE | regex.UNICODE): 603 # return pyDT.timedelta(days = (int(regex.findall('\d+', str_interval)[0]) * days_per_week)) 604 return pyDT.timedelta(weeks = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0])) 605 606 # x/7 - days 607 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*7$', str_interval, flags = regex.LOCALE | regex.UNICODE): 608 return pyDT.timedelta(days = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0])) 609 610 # x/24 - hours 611 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*24$', str_interval, flags = regex.LOCALE | regex.UNICODE): 612 return pyDT.timedelta(hours = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0])) 613 614 # x/60 - minutes 615 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*60$', str_interval, flags = regex.LOCALE | regex.UNICODE): 616 return pyDT.timedelta(minutes = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0])) 617 618 # nYnM - years, months 619 keys_year = '|'.join(list(unit_keys['year'].replace('_keys_year', u''))) 620 keys_month = '|'.join(list(unit_keys['month'].replace('_keys_month', u''))) 621 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): 622 parts = regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE) 623 years, months = divmod(int(parts[1]), 12) 624 years += int(parts[0]) 625 return pyDT.timedelta(days = ((years * avg_days_per_gregorian_year) + (months * avg_days_per_gregorian_month))) 626 627 # nMnW - months, weeks 628 keys_month = '|'.join(list(unit_keys['month'].replace('_keys_month', u''))) 629 keys_week = '|'.join(list(unit_keys['week'].replace('_keys_week', u''))) 630 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): 631 parts = regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE) 632 months, weeks = divmod(int(parts[1]), 4) 633 months += int(parts[0]) 634 return pyDT.timedelta(days = ((months * avg_days_per_gregorian_month) + (weeks * days_per_week))) 635 636 return None
637 638 #=========================================================================== 639 # string -> timestamp parsers 640 #---------------------------------------------------------------------------
641 -def __explicit_offset(str2parse, offset_chars=None):
642 """ 643 Default is 'hdwm': 644 h - hours 645 d - days 646 w - weeks 647 m - months 648 y - years 649 650 This also defines the significance of the order of the characters. 651 """ 652 if offset_chars is None: 653 offset_chars = _('hdwmy (single character date offset triggers)')[:5].lower() 654 655 # "+/-XXd/w/m/t" 656 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): 657 return [] 658 val = int(regex.findall(u'\d{1,2}', str2parse, flags = regex.LOCALE | regex.UNICODE)[0]) 659 offset_char = regex.findall(u'[%s]' % offset_chars, str2parse, flags = regex.LOCALE | regex.UNICODE)[0].lower() 660 661 now = mxDT.now() 662 enc = gmI18N.get_encoding() 663 664 # allow past ? 665 is_future = True 666 if str2parse.find('-') > -1: 667 is_future = False 668 669 ts = None 670 # hours 671 if offset_char == offset_chars[0]: 672 if is_future: 673 ts = now + mxDT.RelativeDateTime(hours = val) 674 label = _('in %d hour(s) - %s') % (val, ts.strftime('%H:%M')) 675 else: 676 ts = now - mxDT.RelativeDateTime(hours = val) 677 label = _('%d hour(s) ago - %s') % (val, ts.strftime('%H:%M')) 678 accuracy = acc_subseconds 679 # days 680 elif offset_char == offset_chars[1]: 681 if is_future: 682 ts = now + mxDT.RelativeDateTime(days = val) 683 label = _('in %d day(s) - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc)) 684 else: 685 ts = now - mxDT.RelativeDateTime(days = val) 686 label = _('%d day(s) ago - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc)) 687 accuracy = acc_days 688 # weeks 689 elif offset_char == offset_chars[2]: 690 if is_future: 691 ts = now + mxDT.RelativeDateTime(weeks = val) 692 label = _('in %d week(s) - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc)) 693 else: 694 ts = now - mxDT.RelativeDateTime(weeks = val) 695 label = _('%d week(s) ago - %s)') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc)) 696 accuracy = acc_days 697 # months 698 elif offset_char == offset_chars[3]: 699 if is_future: 700 ts = now + mxDT.RelativeDateTime(months = val) 701 label = _('in %d month(s) - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc)) 702 else: 703 ts = now - mxDT.RelativeDateTime(months = val) 704 label = _('%d month(s) ago - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc)) 705 accuracy = acc_days 706 # years 707 elif offset_char == offset_chars[4]: 708 if is_future: 709 ts = now + mxDT.RelativeDateTime(years = val) 710 label = _('in %d year(s) - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc)) 711 else: 712 ts = now - mxDT.RelativeDateTime(years = val) 713 label = _('%d year(s) ago - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc)) 714 accuracy = acc_months 715 716 if ts is None: 717 return [] 718 719 tmp = { 720 'data': cFuzzyTimestamp(timestamp = ts, accuracy = accuracy), 721 'label': label 722 } 723 return [tmp]
724 #---------------------------------------------------------------------------
725 -def __single_char(str2parse, trigger_chars=None):
726 """This matches on single characters. 727 728 Spaces and tabs are discarded. 729 730 Default is 'ndmy': 731 n - now 732 d - toDay 733 m - toMorrow Someone please suggest a synonym ! 734 y - yesterday 735 736 This also defines the significance of the order of the characters. 737 """ 738 if trigger_chars is None: 739 trigger_chars = _('ndmy (single character date triggers)')[:4].lower() 740 741 if not regex.match(u'^(\s|\t)*[%s]{1}(\s|\t)*$' % trigger_chars, str2parse, flags = regex.LOCALE | regex.UNICODE): 742 return [] 743 val = str2parse.strip().lower() 744 745 now = mxDT.now() 746 enc = gmI18N.get_encoding() 747 748 # FIXME: handle uebermorgen/vorgestern ? 749 750 # right now 751 if val == trigger_chars[0]: 752 ts = now 753 return [{ 754 'data': cFuzzyTimestamp ( 755 timestamp = ts, 756 accuracy = acc_subseconds 757 ), 758 'label': _('right now (%s, %s)') % (ts.strftime('%A').decode(enc), ts) 759 }] 760 761 # today 762 if val == trigger_chars[1]: 763 return [{ 764 'data': cFuzzyTimestamp ( 765 timestamp = now, 766 accuracy = acc_days 767 ), 768 'label': _('today (%s)') % now.strftime('%A, %Y-%m-%d').decode(enc) 769 }] 770 771 # tomorrow 772 if val == trigger_chars[2]: 773 ts = now + mxDT.RelativeDateTime(days = +1) 774 return [{ 775 'data': cFuzzyTimestamp ( 776 timestamp = ts, 777 accuracy = acc_days 778 ), 779 'label': _('tomorrow (%s)') % ts.strftime('%A, %Y-%m-%d').decode(enc) 780 }] 781 782 # yesterday 783 if val == trigger_chars[3]: 784 ts = now + mxDT.RelativeDateTime(days = -1) 785 return [{ 786 'data': cFuzzyTimestamp ( 787 timestamp = ts, 788 accuracy = acc_days 789 ), 790 'label': _('yesterday (%s)') % ts.strftime('%A, %Y-%m-%d').decode(enc) 791 }] 792 793 return []
794 #---------------------------------------------------------------------------
795 -def __single_slash(str2parse):
796 """Expand fragments containing a single slash. 797 798 "5/" 799 - 2005/ (2000 - 2025) 800 - 1995/ (1990 - 1999) 801 - Mai/current year 802 - Mai/next year 803 - Mai/last year 804 - Mai/200x 805 - Mai/20xx 806 - Mai/199x 807 - Mai/198x 808 - Mai/197x 809 - Mai/19xx 810 """ 811 matches = [] 812 now = mxDT.now() 813 if regex.match(u"^(\s|\t)*\d{1,2}(\s|\t)*/+(\s|\t)*$", str2parse, flags = regex.LOCALE | regex.UNICODE): 814 val = int(regex.findall(u'\d+', str2parse, flags = regex.LOCALE | regex.UNICODE)[0]) 815 816 if val < 100 and val >= 0: 817 matches.append ({ 818 'data': None, 819 'label': '%s/' % (val + 1900) 820 }) 821 822 if val < 26 and val >= 0: 823 matches.append ({ 824 'data': None, 825 'label': '%s/' % (val + 2000) 826 }) 827 828 if val < 10 and val >= 0: 829 matches.append ({ 830 'data': None, 831 'label': '%s/' % (val + 1990) 832 }) 833 834 if val < 13 and val > 0: 835 matches.append ({ 836 'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_months), 837 'label': '%.2d/%s' % (val, now.year) 838 }) 839 ts = now + mxDT.RelativeDateTime(years = 1) 840 matches.append ({ 841 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_months), 842 'label': '%.2d/%s' % (val, ts.year) 843 }) 844 ts = now + mxDT.RelativeDateTime(years = -1) 845 matches.append ({ 846 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_months), 847 'label': '%.2d/%s' % (val, ts.year) 848 }) 849 matches.append ({ 850 'data': None, 851 'label': '%.2d/200' % val 852 }) 853 matches.append ({ 854 'data': None, 855 'label': '%.2d/20' % val 856 }) 857 matches.append ({ 858 'data': None, 859 'label': '%.2d/199' % val 860 }) 861 matches.append ({ 862 'data': None, 863 'label': '%.2d/198' % val 864 }) 865 matches.append ({ 866 'data': None, 867 'label': '%.2d/197' % val 868 }) 869 matches.append ({ 870 'data': None, 871 'label': '%.2d/19' % val 872 }) 873 874 elif regex.match(u"^(\s|\t)*\d{1,2}(\s|\t)*/+(\s|\t)*\d{4}(\s|\t)*$", str2parse, flags = regex.LOCALE | regex.UNICODE): 875 parts = regex.findall(u'\d+', str2parse, flags = regex.LOCALE | regex.UNICODE) 876 fts = cFuzzyTimestamp ( 877 timestamp = mxDT.now() + mxDT.RelativeDateTime(year = int(parts[1]), month = int(parts[0])), 878 accuracy = acc_months 879 ) 880 matches.append ({ 881 'data': fts, 882 'label': fts.format_accurately() 883 }) 884 885 return matches
886 #---------------------------------------------------------------------------
887 -def __numbers_only(str2parse):
888 """This matches on single numbers. 889 890 Spaces or tabs are discarded. 891 """ 892 if not regex.match(u"^(\s|\t)*\d{1,4}(\s|\t)*$", str2parse, flags = regex.LOCALE | regex.UNICODE): 893 return [] 894 895 # strftime() returns str but in the localized encoding, 896 # so we may need to decode that to unicode 897 enc = gmI18N.get_encoding() 898 now = mxDT.now() 899 val = int(regex.findall(u'\d{1,4}', str2parse, flags = regex.LOCALE | regex.UNICODE)[0]) 900 901 matches = [] 902 903 # that year 904 if (1850 < val) and (val < 2100): 905 ts = now + mxDT.RelativeDateTime(year = val) 906 target_date = cFuzzyTimestamp ( 907 timestamp = ts, 908 accuracy = acc_years 909 ) 910 tmp = { 911 'data': target_date, 912 'label': '%s' % target_date 913 } 914 matches.append(tmp) 915 916 # day X of this month 917 if val <= gregorian_month_length[now.month]: 918 ts = now + mxDT.RelativeDateTime(day = val) 919 target_date = cFuzzyTimestamp ( 920 timestamp = ts, 921 accuracy = acc_days 922 ) 923 tmp = { 924 'data': target_date, 925 'label': _('%d. of %s (this month) - a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc)) 926 } 927 matches.append(tmp) 928 929 # day X of next month 930 if val <= gregorian_month_length[(now + mxDT.RelativeDateTime(months = 1)).month]: 931 ts = now + mxDT.RelativeDateTime(months = 1, day = val) 932 target_date = cFuzzyTimestamp ( 933 timestamp = ts, 934 accuracy = acc_days 935 ) 936 tmp = { 937 'data': target_date, 938 'label': _('%d. of %s (next month) - a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc)) 939 } 940 matches.append(tmp) 941 942 # day X of last month 943 if val <= gregorian_month_length[(now + mxDT.RelativeDateTime(months = -1)).month]: 944 ts = now + mxDT.RelativeDateTime(months = -1, day = val) 945 target_date = cFuzzyTimestamp ( 946 timestamp = ts, 947 accuracy = acc_days 948 ) 949 tmp = { 950 'data': target_date, 951 'label': _('%d. of %s (last month) - a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc)) 952 } 953 matches.append(tmp) 954 955 # X days from now 956 if val <= 400: # more than a year ahead in days ?? nah ! 957 ts = now + mxDT.RelativeDateTime(days = val) 958 target_date = cFuzzyTimestamp ( 959 timestamp = ts 960 ) 961 tmp = { 962 'data': target_date, 963 'label': _('in %d day(s) - %s') % (val, target_date.timestamp.strftime('%A, %Y-%m-%d').decode(enc)) 964 } 965 matches.append(tmp) 966 967 # X weeks from now 968 if val <= 50: # pregnancy takes about 40 weeks :-) 969 ts = now + mxDT.RelativeDateTime(weeks = val) 970 target_date = cFuzzyTimestamp ( 971 timestamp = ts 972 ) 973 tmp = { 974 'data': target_date, 975 'label': _('in %d week(s) - %s') % (val, target_date.timestamp.strftime('%A, %Y-%m-%d').decode(enc)) 976 } 977 matches.append(tmp) 978 979 # month X of ... 980 if val < 13: 981 # ... this year 982 ts = now + mxDT.RelativeDateTime(month = val) 983 target_date = cFuzzyTimestamp ( 984 timestamp = ts, 985 accuracy = acc_months 986 ) 987 tmp = { 988 'data': target_date, 989 'label': _('%s (%s this year)') % (target_date, ts.strftime('%B').decode(enc)) 990 } 991 matches.append(tmp) 992 993 # ... next year 994 ts = now + mxDT.RelativeDateTime(years = 1, month = val) 995 target_date = cFuzzyTimestamp ( 996 timestamp = ts, 997 accuracy = acc_months 998 ) 999 tmp = { 1000 'data': target_date, 1001 'label': _('%s (%s next year)') % (target_date, ts.strftime('%B').decode(enc)) 1002 } 1003 matches.append(tmp) 1004 1005 # ... last year 1006 ts = now + mxDT.RelativeDateTime(years = -1, month = val) 1007 target_date = cFuzzyTimestamp ( 1008 timestamp = ts, 1009 accuracy = acc_months 1010 ) 1011 tmp = { 1012 'data': target_date, 1013 'label': _('%s (%s last year)') % (target_date, ts.strftime('%B').decode(enc)) 1014 } 1015 matches.append(tmp) 1016 1017 # fragment expansion 1018 matches.append ({ 1019 'data': None, 1020 'label': '%s/200' % val 1021 }) 1022 matches.append ({ 1023 'data': None, 1024 'label': '%s/199' % val 1025 }) 1026 matches.append ({ 1027 'data': None, 1028 'label': '%s/198' % val 1029 }) 1030 matches.append ({ 1031 'data': None, 1032 'label': '%s/19' % val 1033 }) 1034 1035 # day X of ... 1036 if val < 8: 1037 # ... this week 1038 ts = now + mxDT.RelativeDateTime(weekday = (val-1, 0)) 1039 target_date = cFuzzyTimestamp ( 1040 timestamp = ts, 1041 accuracy = acc_days 1042 ) 1043 tmp = { 1044 'data': target_date, 1045 'label': _('%s this week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc)) 1046 } 1047 matches.append(tmp) 1048 1049 # ... next week 1050 ts = now + mxDT.RelativeDateTime(weeks = +1, weekday = (val-1, 0)) 1051 target_date = cFuzzyTimestamp ( 1052 timestamp = ts, 1053 accuracy = acc_days 1054 ) 1055 tmp = { 1056 'data': target_date, 1057 'label': _('%s next week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc)) 1058 } 1059 matches.append(tmp) 1060 1061 # ... last week 1062 ts = now + mxDT.RelativeDateTime(weeks = -1, weekday = (val-1, 0)) 1063 target_date = cFuzzyTimestamp ( 1064 timestamp = ts, 1065 accuracy = acc_days 1066 ) 1067 tmp = { 1068 'data': target_date, 1069 'label': _('%s last week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc)) 1070 } 1071 matches.append(tmp) 1072 1073 if val < 100: 1074 matches.append ({ 1075 'data': None, 1076 'label': '%s/' % (1900 + val) 1077 }) 1078 1079 if val == 200: 1080 tmp = { 1081 'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_days), 1082 'label': '%s' % target_date 1083 } 1084 matches.append(tmp) 1085 matches.append ({ 1086 'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_months), 1087 'label': '%.2d/%s' % (now.month, now.year) 1088 }) 1089 matches.append ({ 1090 'data': None, 1091 'label': '%s/' % now.year 1092 }) 1093 matches.append ({ 1094 'data': None, 1095 'label': '%s/' % (now.year + 1) 1096 }) 1097 matches.append ({ 1098 'data': None, 1099 'label': '%s/' % (now.year - 1) 1100 }) 1101 1102 if val < 200 and val >= 190: 1103 for i in range(10): 1104 matches.append ({ 1105 'data': None, 1106 'label': '%s%s/' % (val, i) 1107 }) 1108 1109 return matches
1110 #---------------------------------------------------------------------------
1111 -def __single_dot(str2parse):
1112 """Expand fragments containing a single dot. 1113 1114 Standard colloquial date format in Germany: day.month.year 1115 1116 "14." 1117 - 14th current month this year 1118 - 14th next month this year 1119 """ 1120 if not regex.match(u"^(\s|\t)*\d{1,2}\.{1}(\s|\t)*$", str2parse, flags = regex.LOCALE | regex.UNICODE): 1121 return [] 1122 1123 val = int(regex.findall(u'\d+', str2parse, flags = regex.LOCALE | regex.UNICODE)[0]) 1124 now = mxDT.now() 1125 enc = gmI18N.get_encoding() 1126 1127 matches = [] 1128 1129 # day X of this month 1130 ts = now + mxDT.RelativeDateTime(day = val) 1131 if val > 0 and val <= gregorian_month_length[ts.month]: 1132 matches.append ({ 1133 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_days), 1134 'label': '%s.%s.%s - a %s this month' % (ts.day, ts.month, ts.year, ts.strftime('%A').decode(enc)) 1135 }) 1136 1137 # day X of next month 1138 ts = now + mxDT.RelativeDateTime(day = val, months = +1) 1139 if val > 0 and val <= gregorian_month_length[ts.month]: 1140 matches.append ({ 1141 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_days), 1142 'label': '%s.%s.%s - a %s next month' % (ts.day, ts.month, ts.year, ts.strftime('%A').decode(enc)) 1143 }) 1144 1145 # day X of last month 1146 ts = now + mxDT.RelativeDateTime(day = val, months = -1) 1147 if val > 0 and val <= gregorian_month_length[ts.month]: 1148 matches.append ({ 1149 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_days), 1150 'label': '%s.%s.%s - a %s last month' % (ts.day, ts.month, ts.year, ts.strftime('%A').decode(enc)) 1151 }) 1152 1153 return matches
1154 #---------------------------------------------------------------------------
1155 -def str2fuzzy_timestamp_matches(str2parse=None, default_time=None, patterns=None):
1156 """ 1157 Turn a string into candidate fuzzy timestamps and auto-completions the user is likely to type. 1158 1159 You MUST have called locale.setlocale(locale.LC_ALL, '') 1160 somewhere in your code previously. 1161 1162 @param default_time: if you want to force the time part of the time 1163 stamp to a given value and the user doesn't type any time part 1164 this value will be used 1165 @type default_time: an mx.DateTime.DateTimeDelta instance 1166 1167 @param patterns: list of [time.strptime compatible date/time pattern, accuracy] 1168 @type patterns: list 1169 """ 1170 matches = __single_dot(str2parse) 1171 matches.extend(__numbers_only(str2parse)) 1172 matches.extend(__single_slash(str2parse)) 1173 matches.extend(__single_char(str2parse)) 1174 matches.extend(__explicit_offset(str2parse)) 1175 1176 # try mxDT parsers 1177 if mxDT is not None: 1178 try: 1179 # date ? 1180 date_only = mxDT.Parser.DateFromString ( 1181 text = str2parse, 1182 formats = ('euro', 'iso', 'us', 'altus', 'altiso', 'lit', 'altlit', 'eurlit') 1183 ) 1184 # time, too ? 1185 time_part = mxDT.Parser.TimeFromString(text = str2parse) 1186 datetime = date_only + time_part 1187 if datetime == date_only: 1188 accuracy = acc_days 1189 if isinstance(default_time, mxDT.DateTimeDeltaType): 1190 datetime = date_only + default_time 1191 accuracy = acc_minutes 1192 else: 1193 accuracy = acc_subseconds 1194 fts = cFuzzyTimestamp ( 1195 timestamp = datetime, 1196 accuracy = accuracy 1197 ) 1198 matches.append ({ 1199 'data': fts, 1200 'label': fts.format_accurately() 1201 }) 1202 except (ValueError, mxDT.RangeError): 1203 pass 1204 1205 if patterns is None: 1206 patterns = [] 1207 1208 patterns.append(['%Y.%m.%d', acc_days]) 1209 patterns.append(['%Y/%m/%d', acc_days]) 1210 1211 for pattern in patterns: 1212 try: 1213 fts = cFuzzyTimestamp ( 1214 timestamp = pyDT.datetime.fromtimestamp(time.mktime(time.strptime(str2parse, pattern[0]))), 1215 accuracy = pattern[1] 1216 ) 1217 matches.append ({ 1218 'data': fts, 1219 'label': fts.format_accurately() 1220 }) 1221 except AttributeError: 1222 # strptime() only available starting with Python 2.5 1223 break 1224 except OverflowError: 1225 # time.mktime() cannot handle dates older than a platform-dependant limit :-( 1226 continue 1227 except ValueError: 1228 # C-level overflow 1229 continue 1230 1231 return matches
1232 #=========================================================================== 1233 # fuzzy timestamp class 1234 #---------------------------------------------------------------------------
1235 -class cFuzzyTimestamp:
1236 1237 # FIXME: add properties for year, month, ... 1238 1239 """A timestamp implementation with definable inaccuracy. 1240 1241 This class contains an mxDateTime.DateTime instance to 1242 hold the actual timestamp. It adds an accuracy attribute 1243 to allow the programmer to set the precision of the 1244 timestamp. 1245 1246 The timestamp will have to be initialzed with a fully 1247 precise value (which may, of course, contain partially 1248 fake data to make up for missing values). One can then 1249 set the accuracy value to indicate up to which part of 1250 the timestamp the data is valid. Optionally a modifier 1251 can be set to indicate further specification of the 1252 value (such as "summer", "afternoon", etc). 1253 1254 accuracy values: 1255 1: year only 1256 ... 1257 7: everything including milliseconds value 1258 1259 Unfortunately, one cannot directly derive a class from mx.DateTime.DateTime :-( 1260 """ 1261 #-----------------------------------------------------------------------
1262 - def __init__(self, timestamp=None, accuracy=acc_subseconds, modifier=''):
1263 1264 if timestamp is None: 1265 timestamp = mxDT.now() 1266 accuracy = acc_subseconds 1267 modifier = '' 1268 1269 if isinstance(timestamp, pyDT.datetime): 1270 timestamp = mxDT.DateTime(timestamp.year, timestamp.month, timestamp.day, timestamp.hour, timestamp.minute, timestamp.second) 1271 1272 if type(timestamp) != mxDT.DateTimeType: 1273 raise TypeError, '%s.__init__(): <timestamp> must be of mx.DateTime.DateTime or datetime.datetime type' % self.__class__.__name__ 1274 1275 self.timestamp = timestamp 1276 1277 if (accuracy < 1) or (accuracy > 8): 1278 raise ValueError, '%s.__init__(): <accuracy> must be between 1 and 7' % self.__class__.__name__ 1279 self.accuracy = accuracy 1280 1281 self.modifier = modifier
1282 1283 #----------------------------------------------------------------------- 1284 # magic API 1285 #-----------------------------------------------------------------------
1286 - def __str__(self):
1287 """Return string representation meaningful to a user, also for %s formatting.""" 1288 return self.format_accurately()
1289 #-----------------------------------------------------------------------
1290 - def __repr__(self):
1291 """Return string meaningful to a programmer to aid in debugging.""" 1292 tmp = '<[%s]: timestamp [%s], accuracy [%s] (%s), modifier [%s] at %s>' % ( 1293 self.__class__.__name__, 1294 repr(self.timestamp), 1295 self.accuracy, 1296 _accuracy_strings[self.accuracy], 1297 self.modifier, 1298 id(self) 1299 ) 1300 return tmp
1301 #----------------------------------------------------------------------- 1302 # external API 1303 #-----------------------------------------------------------------------
1304 - def strftime(self, format_string):
1305 if self.accuracy == 7: 1306 return self.timestamp.strftime(format_string) 1307 return self.format_accurately()
1308 #-----------------------------------------------------------------------
1309 - def Format(self, format_string):
1310 return self.strftime(format_string)
1311 #-----------------------------------------------------------------------
1312 - def format_accurately(self):
1313 if self.accuracy == acc_years: 1314 return unicode(self.timestamp.year) 1315 1316 if self.accuracy == acc_months: 1317 return unicode(self.timestamp.strftime('%m/%Y')) # FIXME: use 3-letter month ? 1318 1319 if self.accuracy == acc_days: 1320 return unicode(self.timestamp.strftime('%Y-%m-%d')) 1321 1322 if self.accuracy == acc_hours: 1323 return unicode(self.timestamp.strftime("%Y-%m-%d %I%p")) 1324 1325 if self.accuracy == acc_minutes: 1326 return unicode(self.timestamp.strftime("%Y-%m-%d %H:%M")) 1327 1328 if self.accuracy == acc_seconds: 1329 return unicode(self.timestamp.strftime("%Y-%m-%d %H:%M:%S")) 1330 1331 if self.accuracy == acc_subseconds: 1332 return unicode(self.timestamp) 1333 1334 raise ValueError, '%s.format_accurately(): <accuracy> (%s) must be between 1 and 7' % ( 1335 self.__class__.__name__, 1336 self.accuracy 1337 )
1338 #-----------------------------------------------------------------------
1339 - def get_mxdt(self):
1340 return self.timestamp
1341 #-----------------------------------------------------------------------
1342 - def get_pydt(self):
1343 try: 1344 gmtoffset = self.timestamp.gmtoffset() 1345 except mxDT.Error: 1346 # Windows cannot deal with dates < 1970, so 1347 # when that happens switch to now() 1348 now = mxDT.now() 1349 gmtoffset = now.gmtoffset() 1350 tz = cFixedOffsetTimezone(gmtoffset.minutes, self.timestamp.tz) 1351 secs, msecs = divmod(self.timestamp.second, 1) 1352 ts = pyDT.datetime ( 1353 year = self.timestamp.year, 1354 month = self.timestamp.month, 1355 day = self.timestamp.day, 1356 hour = self.timestamp.hour, 1357 minute = self.timestamp.minute, 1358 second = secs, 1359 microsecond = msecs, 1360 tzinfo = tz 1361 ) 1362 return ts
1363 #=========================================================================== 1364 # main 1365 #--------------------------------------------------------------------------- 1366 if __name__ == '__main__': 1367 1368 intervals_as_str = [ 1369 '7', '12', ' 12', '12 ', ' 12 ', ' 12 ', '0', '~12', '~ 12', ' ~ 12', ' ~ 12 ', 1370 '12a', '12 a', '12 a', '12j', '12J', '12y', '12Y', ' ~ 12 a ', '~0a', 1371 '12m', '17 m', '12 m', '17M', ' ~ 17 m ', ' ~ 3 / 12 ', '7/12', '0/12', 1372 '12w', '17 w', '12 w', '17W', ' ~ 17 w ', ' ~ 15 / 52', '2/52', '0/52', 1373 '12d', '17 d', '12 t', '17D', ' ~ 17 T ', ' ~ 12 / 7', '3/7', '0/7', 1374 '12h', '17 h', '12 H', '17H', ' ~ 17 h ', ' ~ 36 / 24', '7/24', '0/24', 1375 ' ~ 36 / 60', '7/60', '190/60', '0/60', 1376 '12a1m', '12 a 1 M', '12 a17m', '12j 12m', '12J7m', '12y7m', '12Y7M', ' ~ 12 a 37 m ', '~0a0m', 1377 '10m1w', 1378 'invalid interval input' 1379 ] 1380 #-----------------------------------------------------------------------
1381 - def test_format_interval():
1382 for tmp in intervals_as_str: 1383 intv = str2interval(str_interval = tmp) 1384 for acc in _accuracy_strings.keys(): 1385 print '[%s]: "%s" -> "%s"' % (acc, tmp, format_interval(intv, acc))
1386 #-----------------------------------------------------------------------
1387 - def test_format_interval_medically():
1388 1389 intervals = [ 1390 pyDT.timedelta(seconds = 1), 1391 pyDT.timedelta(seconds = 5), 1392 pyDT.timedelta(seconds = 30), 1393 pyDT.timedelta(seconds = 60), 1394 pyDT.timedelta(seconds = 94), 1395 pyDT.timedelta(seconds = 120), 1396 pyDT.timedelta(minutes = 5), 1397 pyDT.timedelta(minutes = 30), 1398 pyDT.timedelta(minutes = 60), 1399 pyDT.timedelta(minutes = 90), 1400 pyDT.timedelta(minutes = 120), 1401 pyDT.timedelta(minutes = 200), 1402 pyDT.timedelta(minutes = 400), 1403 pyDT.timedelta(minutes = 600), 1404 pyDT.timedelta(minutes = 800), 1405 pyDT.timedelta(minutes = 1100), 1406 pyDT.timedelta(minutes = 2000), 1407 pyDT.timedelta(minutes = 3500), 1408 pyDT.timedelta(minutes = 4000), 1409 pyDT.timedelta(hours = 1), 1410 pyDT.timedelta(hours = 2), 1411 pyDT.timedelta(hours = 4), 1412 pyDT.timedelta(hours = 8), 1413 pyDT.timedelta(hours = 12), 1414 pyDT.timedelta(hours = 20), 1415 pyDT.timedelta(hours = 23), 1416 pyDT.timedelta(hours = 24), 1417 pyDT.timedelta(hours = 25), 1418 pyDT.timedelta(hours = 30), 1419 pyDT.timedelta(hours = 48), 1420 pyDT.timedelta(hours = 98), 1421 pyDT.timedelta(hours = 120), 1422 pyDT.timedelta(days = 1), 1423 pyDT.timedelta(days = 2), 1424 pyDT.timedelta(days = 4), 1425 pyDT.timedelta(days = 16), 1426 pyDT.timedelta(days = 29), 1427 pyDT.timedelta(days = 30), 1428 pyDT.timedelta(days = 31), 1429 pyDT.timedelta(days = 37), 1430 pyDT.timedelta(days = 40), 1431 pyDT.timedelta(days = 47), 1432 pyDT.timedelta(days = 126), 1433 pyDT.timedelta(days = 127), 1434 pyDT.timedelta(days = 128), 1435 pyDT.timedelta(days = 300), 1436 pyDT.timedelta(days = 359), 1437 pyDT.timedelta(days = 360), 1438 pyDT.timedelta(days = 361), 1439 pyDT.timedelta(days = 362), 1440 pyDT.timedelta(days = 363), 1441 pyDT.timedelta(days = 364), 1442 pyDT.timedelta(days = 365), 1443 pyDT.timedelta(days = 366), 1444 pyDT.timedelta(days = 367), 1445 pyDT.timedelta(days = 400), 1446 pyDT.timedelta(weeks = 52 * 30), 1447 pyDT.timedelta(weeks = 52 * 79, days = 33) 1448 ] 1449 1450 idx = 1 1451 for intv in intervals: 1452 print '%s) %s -> %s' % (idx, intv, format_interval_medically(intv)) 1453 idx += 1
1454 #-----------------------------------------------------------------------
1455 - def test_str2interval():
1456 print "testing str2interval()" 1457 print "----------------------" 1458 1459 for interval_as_str in intervals_as_str: 1460 print "input: <%s>" % interval_as_str 1461 print " ==>", str2interval(str_interval=interval_as_str) 1462 1463 return True
1464 #-------------------------------------------------
1465 - def test_date_time():
1466 print "DST currently in effect:", dst_currently_in_effect 1467 print "current UTC offset:", current_local_utc_offset_in_seconds, "seconds" 1468 print "current timezone (interval):", current_local_timezone_interval 1469 print "current timezone (ISO conformant numeric string):", current_local_iso_numeric_timezone_string 1470 print "local timezone class:", cLocalTimezone 1471 print "" 1472 tz = cLocalTimezone() 1473 print "local timezone instance:", tz 1474 print " (total) UTC offset:", tz.utcoffset(pyDT.datetime.now()) 1475 print " DST adjustment:", tz.dst(pyDT.datetime.now()) 1476 print " timezone name:", tz.tzname(pyDT.datetime.now()) 1477 print "" 1478 print "current local timezone:", gmCurrentLocalTimezone 1479 print " (total) UTC offset:", gmCurrentLocalTimezone.utcoffset(pyDT.datetime.now()) 1480 print " DST adjustment:", gmCurrentLocalTimezone.dst(pyDT.datetime.now()) 1481 print " timezone name:", gmCurrentLocalTimezone.tzname(pyDT.datetime.now()) 1482 print "" 1483 print "now here:", pydt_now_here() 1484 print ""
1485 #-------------------------------------------------
1486 - def test_str2fuzzy_timestamp_matches():
1487 print "testing function str2fuzzy_timestamp_matches" 1488 print "--------------------------------------------" 1489 1490 val = None 1491 while val != 'exit': 1492 val = raw_input('Enter date fragment ("exit" quits): ') 1493 matches = str2fuzzy_timestamp_matches(str2parse = val) 1494 for match in matches: 1495 print 'label shown :', match['label'] 1496 print 'data attached:', match['data'] 1497 print "" 1498 print "---------------"
1499 #-------------------------------------------------
1500 - def test_cFuzzyTimeStamp():
1501 print "testing fuzzy timestamp class" 1502 print "-----------------------------" 1503 1504 ts = mxDT.now() 1505 print "mx.DateTime timestamp", type(ts) 1506 print " print ... :", ts 1507 print " print '%%s' %% ...: %s" % ts 1508 print " str() :", str(ts) 1509 print " repr() :", repr(ts) 1510 1511 fts = cFuzzyTimestamp() 1512 print "\nfuzzy timestamp <%s '%s'>" % ('class', fts.__class__.__name__) 1513 for accuracy in range(1,8): 1514 fts.accuracy = accuracy 1515 print " accuracy : %s (%s)" % (accuracy, _accuracy_strings[accuracy]) 1516 print " format_accurately:", fts.format_accurately() 1517 print " strftime() :", fts.strftime('%c') 1518 print " print ... :", fts 1519 print " print '%%s' %% ... : %s" % fts 1520 print " str() :", str(fts) 1521 print " repr() :", repr(fts) 1522 raw_input('press ENTER to continue')
1523 #-------------------------------------------------
1524 - def test_get_pydt():
1525 print "testing platform for handling dates before 1970" 1526 print "-----------------------------------------------" 1527 ts = mxDT.DateTime(1935, 4, 2) 1528 fts = cFuzzyTimestamp(timestamp=ts) 1529 print "fts :", fts 1530 print "fts.get_pydt():", fts.get_pydt()
1531 #-------------------------------------------------
1532 - def test_calculate_apparent_age():
1533 start = pydt_now_here().replace(year = 1974).replace(month = 10).replace(day = 23) 1534 print calculate_apparent_age(start = start) 1535 print format_apparent_age_medically(calculate_apparent_age(start = start)) 1536 start = pydt_now_here().replace(year = 1979).replace(month = 3).replace(day = 13) 1537 print calculate_apparent_age(start = start) 1538 print format_apparent_age_medically(calculate_apparent_age(start = start))
1539 #------------------------------------------------- 1540 if len(sys.argv) > 1 and sys.argv[1] == "test": 1541 1542 # GNUmed libs 1543 gmI18N.activate_locale() 1544 gmI18N.install_domain('gnumed') 1545 1546 init() 1547 1548 #test_date_time() 1549 #test_str2fuzzy_timestamp_matches() 1550 #test_cFuzzyTimeStamp() 1551 #test_get_pydt() 1552 #test_str2interval() 1553 #test_format_interval() 1554 #test_format_interval_medically() 1555 test_calculate_apparent_age() 1556 1557 #=========================================================================== 1558