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
38
39 __version__ = "$Revision: 1.34 $"
40 __author__ = "K. Hilbert <Karsten.Hilbert@gmx.net>"
41 __license__ = "GPL (details at http://www.gnu.org)"
42
43
44 import sys, datetime as pyDT, time, os, re as regex, locale, logging
45
46
47
48 import mx.DateTime as mxDT
49 import psycopg2
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
72 cFixedOffsetTimezone = psycopg2.tz.FixedOffsetTimezone
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,
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
119
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
187
188
189
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
198 """Returns NOW @ HERE (IOW, in the local timezone."""
199 return pyDT.datetime.now(gmCurrentLocalTimezone)
200
201
202
204 return pyDT.datetime (
205 year = wxDate.GetYear(),
206 month = wxDate.GetMonth() + 1,
207 day = wxDate.GetDay(),
208 tzinfo = gmCurrentLocalTimezone
209 )
210
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
219
271
335
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
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
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
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
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
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
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
386 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*52$', str_interval, flags = regex.LOCALE | regex.UNICODE):
387
388 return pyDT.timedelta(weeks = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
389
390
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
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
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
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
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
424
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
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
449 is_future = True
450 if str2parse.find('-') > -1:
451 is_future = False
452
453 ts = None
454
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
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
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
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
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
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
533
534
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
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
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
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
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
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
680
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
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
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
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
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
740 if val <= 400:
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
752 if val <= 50:
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
764 if val < 13:
765
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
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
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
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
820 if val < 8:
821
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
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
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
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
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
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
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
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
961 if mxDT is not None:
962 try:
963
964 date_only = mxDT.Parser.DateFromString (
965 text = str2parse,
966 formats = ('euro', 'iso', 'us', 'altus', 'altiso', 'lit', 'altlit', 'eurlit')
967 )
968
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
1007 break
1008 except OverflowError:
1009
1010 continue
1011 except ValueError:
1012
1013 continue
1014
1015 return matches
1016
1017
1018
1020
1021
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
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
1069
1071 """Return string representation meaningful to a user, also for %s formatting."""
1072 return self.format_accurately()
1073
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
1087
1092
1095
1122
1124 return self.timestamp
1125
1127 try:
1128 gmtoffset = self.timestamp.gmtoffset()
1129 except mxDT.Error:
1130
1131
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
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
1170
1236
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
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
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
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
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
1317 gmI18N.activate_locale()
1318 gmI18N.install_domain('gnumed')
1319
1320 init()
1321
1322
1323
1324
1325
1326
1327
1328 test_format_interval_medically()
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495