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

Source Code for Module Gnumed.pycommon.gmDateTime

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