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