1 __doc__ = """
2 GNUmed date/time handling.
3
4 This modules provides access to date/time handling
5 and offers an fuzzy timestamp implementation
6
7 It utilizes
8
9 - Python time
10 - Python datetime
11 - mxDateTime
12
13 Note that if you want locale-aware formatting you need to call
14
15 locale.setlocale(locale.LC_ALL, '')
16
17 somewhere before importing this script.
18
19 Note regarding UTC offsets
20 --------------------------
21
22 Looking from Greenwich:
23 WEST (IOW "behind"): negative values
24 EAST (IOW "ahead"): positive values
25
26 This is in compliance with what datetime.tzinfo.utcoffset()
27 does but NOT what time.altzone/time.timezone do !
28
29 This module also implements a class which allows the
30 programmer to define the degree of fuzziness, uncertainty
31 or imprecision of the timestamp contained within.
32
33 This is useful in fields such as medicine where only partial
34 timestamps may be known for certain events.
35 """
36
37 __version__ = "$Revision: 1.34 $"
38 __author__ = "K. Hilbert <Karsten.Hilbert@gmx.net>"
39 __license__ = "GPL (details at http://www.gnu.org)"
40
41
42 import sys, datetime as pyDT, time, os, re as regex, locale, logging
43
44
45
46 import mx.DateTime as mxDT
47 import psycopg2
48
49
50 if __name__ == '__main__':
51 sys.path.insert(0, '../../')
52 from Gnumed.pycommon import gmI18N
53
54
55 _log = logging.getLogger('gm.datetime')
56 _log.info(__version__)
57 _log.info(u'mx.DateTime version: %s', mxDT.__version__)
58
59 dst_locally_in_use = None
60 dst_currently_in_effect = None
61
62 current_local_utc_offset_in_seconds = None
63 current_local_timezone_interval = None
64 current_local_iso_numeric_timezone_string = None
65 current_local_timezone_name = None
66 py_timezone_name = None
67 py_dst_timezone_name = None
68
69 cLocalTimezone = psycopg2.tz.LocalTimezone
70 cFixedOffsetTimezone = psycopg2.tz.FixedOffsetTimezone
71 gmCurrentLocalTimezone = 'gmCurrentLocalTimezone not initialized'
72
73
74 ( acc_years,
75 acc_months,
76 acc_weeks,
77 acc_days,
78 acc_hours,
79 acc_minutes,
80 acc_seconds,
81 acc_subseconds
82 ) = range(1,9)
83
84 _accuracy_strings = {
85 1: 'years',
86 2: 'months',
87 3: 'weeks',
88 4: 'days',
89 5: 'hours',
90 6: 'minutes',
91 7: 'seconds',
92 8: 'subseconds'
93 }
94
95 gregorian_month_length = {
96 1: 31,
97 2: 28,
98 3: 31,
99 4: 30,
100 5: 31,
101 6: 30,
102 7: 31,
103 8: 31,
104 9: 30,
105 10: 31,
106 11: 30,
107 12: 31
108 }
109
110 avg_days_per_gregorian_year = 365
111 avg_days_per_gregorian_month = 30
112 avg_seconds_per_day = 24 * 60 * 60
113 days_per_week = 7
114
115
116
117
119
120 _log.debug('mx.DateTime.now(): [%s]' % mxDT.now())
121 _log.debug('datetime.now() : [%s]' % pyDT.datetime.now())
122 _log.debug('time.localtime() : [%s]' % str(time.localtime()))
123 _log.debug('time.gmtime() : [%s]' % str(time.gmtime()))
124
125 try:
126 _log.debug('$TZ: [%s]' % os.environ['TZ'])
127 except KeyError:
128 _log.debug('$TZ not defined')
129
130 _log.debug('time.daylight: [%s] (whether or not DST is locally used at all)' % time.daylight)
131 _log.debug('time.timezone: [%s] seconds' % time.timezone)
132 _log.debug('time.altzone : [%s] seconds' % time.altzone)
133 _log.debug('time.tzname : [%s / %s] (non-DST / DST)' % time.tzname)
134 _log.debug('mx.DateTime.now().gmtoffset(): [%s]' % mxDT.now().gmtoffset())
135
136 global py_timezone_name
137 py_timezone_name = time.tzname[0].decode(gmI18N.get_encoding(), 'replace')
138
139 global py_dst_timezone_name
140 py_dst_timezone_name = time.tzname[1].decode(gmI18N.get_encoding(), 'replace')
141
142 global dst_locally_in_use
143 dst_locally_in_use = (time.daylight != 0)
144
145 global dst_currently_in_effect
146 dst_currently_in_effect = bool(time.localtime()[8])
147 _log.debug('DST currently in effect: [%s]' % dst_currently_in_effect)
148
149 if (not dst_locally_in_use) and dst_currently_in_effect:
150 _log.error('system inconsistency: DST not in use - but DST currently in effect ?')
151
152 global current_local_utc_offset_in_seconds
153 msg = 'DST currently%sin effect: using UTC offset of [%s] seconds instead of [%s] seconds'
154 if dst_currently_in_effect:
155 current_local_utc_offset_in_seconds = time.altzone * -1
156 _log.debug(msg % (' ', time.altzone * -1, time.timezone * -1))
157 else:
158 current_local_utc_offset_in_seconds = time.timezone * -1
159 _log.debug(msg % (' not ', time.timezone * -1, time.altzone * -1))
160
161 if current_local_utc_offset_in_seconds > 0:
162 _log.debug('UTC offset is positive, assuming EAST of Greenwich (clock is "ahead")')
163 elif current_local_utc_offset_in_seconds < 0:
164 _log.debug('UTC offset is negative, assuming WEST of Greenwich (clock is "behind")')
165 else:
166 _log.debug('UTC offset is ZERO, assuming Greenwich Time')
167
168 global current_local_timezone_interval
169 current_local_timezone_interval = mxDT.now().gmtoffset()
170 _log.debug('ISO timezone: [%s] (taken from mx.DateTime.now().gmtoffset())' % current_local_timezone_interval)
171
172 global current_local_iso_numeric_timezone_string
173 current_local_iso_numeric_timezone_string = str(current_local_timezone_interval).replace(',', '.')
174
175 global current_local_timezone_name
176 try:
177 current_local_timezone_name = os.environ['TZ'].decode(gmI18N.get_encoding(), 'replace')
178 except KeyError:
179 if dst_currently_in_effect:
180 current_local_timezone_name = time.tzname[1].decode(gmI18N.get_encoding(), 'replace')
181 else:
182 current_local_timezone_name = time.tzname[0].decode(gmI18N.get_encoding(), 'replace')
183
184
185
186
187
188
189 global gmCurrentLocalTimezone
190 gmCurrentLocalTimezone = cFixedOffsetTimezone (
191 offset = (current_local_utc_offset_in_seconds / 60),
192 name = current_local_iso_numeric_timezone_string
193 )
194
195
196
198
199 if isinstance(mxDateTime, pyDT.datetime):
200 return mxDateTime
201
202 if dst_currently_in_effect:
203 tz = cFixedOffsetTimezone (
204 offset = ((time.altzone * -1) / 60),
205 name = str(mxDateTime.gmtoffset()).replace(',', '.')
206 )
207 else:
208 tz = cFixedOffsetTimezone (
209 offset = ((time.timezone * -1) / 60),
210 name = str(mxDateTime.gmtoffset()).replace(',', '.')
211 )
212
213 try:
214 return pyDT.datetime (
215 year = mxDateTime.year,
216 month = mxDateTime.month,
217 day = mxDateTime.day,
218 tzinfo = tz
219 )
220 except:
221 _log.debug (u'error converting mx.DateTime.DateTime to Python: %s-%s-%s %s:%s %s.%s',
222 mxDateTime.year,
223 mxDateTime.month,
224 mxDateTime.day,
225 mxDateTime.hour,
226 mxDateTime.minute,
227 mxDateTime.second,
228 mxDateTime.tz
229 )
230 raise
231
233 """Returns NOW @ HERE (IOW, in the local timezone."""
234 return pyDT.datetime.now(gmCurrentLocalTimezone)
235
238
242
243
244
246 if not wxDate.IsValid():
247 raise ValueError (u'invalid wxDate: %s-%s-%s %s:%s %s.%s',
248 wxDate.GetYear(),
249 wxDate.GetMonth(),
250 wxDate.GetDay(),
251 wxDate.GetHour(),
252 wxDate.GetMinute(),
253 wxDate.GetSecond(),
254 wxDate.GetMillisecond()
255 )
256
257 try:
258 return pyDT.datetime (
259 year = wxDate.GetYear(),
260 month = wxDate.GetMonth() + 1,
261 day = wxDate.GetDay(),
262 tzinfo = gmCurrentLocalTimezone
263 )
264 except:
265 _log.debug (u'error converting wxDateTime to Python: %s-%s-%s %s:%s %s.%s',
266 wxDate.GetYear(),
267 wxDate.GetMonth(),
268 wxDate.GetDay(),
269 wxDate.GetHour(),
270 wxDate.GetMinute(),
271 wxDate.GetSecond(),
272 wxDate.GetMillisecond()
273 )
274 raise
275
277 _log.debug(u'setting wx.DateTime from: %s-%s-%s', py_dt.year, py_dt.month, py_dt.day)
278
279
280
281 wxdt = wx.DateTimeFromDMY(py_dt.day, py_dt.month-1, py_dt.year)
282 return wxdt
283
284
285
337
402
404 """The result of this is a tuple (years, ..., seconds) as one would
405 'expect' a date to look like, that is, simple differences between
406 the fields.
407
408 No need for 100/400 years leap days rule because 2000 WAS a leap year.
409
410 This does not take into account time zones which may
411 shift the result by one day.
412
413 <start> and <end> must by python datetime instances
414 <end> is assumed to be "now" if not given
415 """
416 if end is None:
417 end = pyDT.datetime.now(gmCurrentLocalTimezone)
418
419 if end < start:
420 raise ValueError('calculate_apparent_age(): <end> (%s) before <start> %s' % (end, start))
421
422 if end == start:
423 years = months = days = hours = minutes = seconds = 0
424 return (years, months, days, hours, minutes, seconds)
425
426
427 years = end.year - start.year
428 end = end.replace(year = start.year)
429 if end < start:
430 years = years - 1
431
432
433 if end.month == start.month:
434 months = 0
435 else:
436 months = end.month - start.month
437 if months < 0:
438 months = months + 12
439 if end.day > gregorian_month_length[start.month]:
440 end = end.replace(month = start.month, day = gregorian_month_length[start.month])
441 else:
442 end = end.replace(month = start.month)
443 if end < start:
444 months = months - 1
445
446
447 if end.day == start.day:
448 days = 0
449 else:
450 days = end.day - start.day
451 if days < 0:
452 days = days + gregorian_month_length[start.month]
453 end = end.replace(day = start.day)
454 if end < start:
455 days = days - 1
456
457
458 if end.hour == start.hour:
459 hours = 0
460 else:
461 hours = end.hour - start.hour
462 if hours < 0:
463 hours = hours + 24
464 end = end.replace(hour = start.hour)
465 if end < start:
466 hours = hours - 1
467
468
469 if end.minute == start.minute:
470 minutes = 0
471 else:
472 minutes = end.minute - start.minute
473 if minutes < 0:
474 minutes = minutes + 60
475 end = end.replace(minute = start.minute)
476 if end < start:
477 minutes = minutes - 1
478
479
480 if end.second == start.second:
481 seconds = 0
482 else:
483 seconds = end.second - start.second
484 if seconds < 0:
485 seconds = seconds + 60
486 end = end.replace(second = start.second)
487 if end < start:
488 seconds = seconds - 1
489
490 return (years, months, days, hours, minutes, seconds)
491
595
597
598 unit_keys = {
599 'year': _('yYaA_keys_year'),
600 'month': _('mM_keys_month'),
601 'week': _('wW_keys_week'),
602 'day': _('dD_keys_day'),
603 'hour': _('hH_keys_hour')
604 }
605
606 str_interval = str_interval.strip()
607
608
609 keys = '|'.join(list(unit_keys['year'].replace('_keys_year', u'')))
610 if regex.match(u'^~*(\s|\t)*\d+(%s)*$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE):
611 return pyDT.timedelta(days = (int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]) * avg_days_per_gregorian_year))
612
613
614 keys = '|'.join(list(unit_keys['month'].replace('_keys_month', u'')))
615 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE):
616 years, months = divmod (
617 int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]),
618 12
619 )
620 return pyDT.timedelta(days = ((years * avg_days_per_gregorian_year) + (months * avg_days_per_gregorian_month)))
621
622
623 keys = '|'.join(list(unit_keys['week'].replace('_keys_week', u'')))
624 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE):
625 return pyDT.timedelta(weeks = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
626
627
628 keys = '|'.join(list(unit_keys['day'].replace('_keys_day', u'')))
629 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE):
630 return pyDT.timedelta(days = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
631
632
633 keys = '|'.join(list(unit_keys['hour'].replace('_keys_hour', u'')))
634 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE):
635 return pyDT.timedelta(hours = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
636
637
638 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*12$', str_interval, flags = regex.LOCALE | regex.UNICODE):
639 years, months = divmod (
640 int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]),
641 12
642 )
643 return pyDT.timedelta(days = ((years * avg_days_per_gregorian_year) + (months * avg_days_per_gregorian_month)))
644
645
646 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*52$', str_interval, flags = regex.LOCALE | regex.UNICODE):
647
648 return pyDT.timedelta(weeks = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
649
650
651 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*7$', str_interval, flags = regex.LOCALE | regex.UNICODE):
652 return pyDT.timedelta(days = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
653
654
655 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*24$', str_interval, flags = regex.LOCALE | regex.UNICODE):
656 return pyDT.timedelta(hours = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
657
658
659 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*60$', str_interval, flags = regex.LOCALE | regex.UNICODE):
660 return pyDT.timedelta(minutes = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
661
662
663 keys_year = '|'.join(list(unit_keys['year'].replace('_keys_year', u'')))
664 keys_month = '|'.join(list(unit_keys['month'].replace('_keys_month', u'')))
665 if regex.match(u'^~*(\s|\t)*\d+(%s|\s|\t)+\d+(\s|\t)*(%s)+$' % (keys_year, keys_month), str_interval, flags = regex.LOCALE | regex.UNICODE):
666 parts = regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)
667 years, months = divmod(int(parts[1]), 12)
668 years += int(parts[0])
669 return pyDT.timedelta(days = ((years * avg_days_per_gregorian_year) + (months * avg_days_per_gregorian_month)))
670
671
672 keys_month = '|'.join(list(unit_keys['month'].replace('_keys_month', u'')))
673 keys_week = '|'.join(list(unit_keys['week'].replace('_keys_week', u'')))
674 if regex.match(u'^~*(\s|\t)*\d+(%s|\s|\t)+\d+(\s|\t)*(%s)+$' % (keys_month, keys_week), str_interval, flags = regex.LOCALE | regex.UNICODE):
675 parts = regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)
676 months, weeks = divmod(int(parts[1]), 4)
677 months += int(parts[0])
678 return pyDT.timedelta(days = ((months * avg_days_per_gregorian_month) + (weeks * days_per_week)))
679
680 return None
681
682
683
685 """This matches on single characters.
686
687 Spaces and tabs are discarded.
688
689 Default is 'dmy':
690 d - toDay
691 m - toMorrow Someone please suggest a synonym !
692 y - Yesterday
693
694 This also defines the significance of the order of the characters.
695 """
696 if trigger_chars is None:
697 trigger_chars = _('dmy (single character date triggers)')[:3].lower()
698
699 str2parse = str2parse.strip().lower()
700
701 if len(str2parse) != 1:
702 return []
703
704 if str2parse not in trigger_chars:
705 return []
706
707 now = mxDT.now()
708 enc = gmI18N.get_encoding()
709
710
711 if str2parse == trigger_chars[0]:
712 return [{
713 'data': mxdt2py_dt(now),
714 'label': _('today (%s)') % now.strftime('%A, %Y-%m-%d').decode(enc)
715 }]
716
717
718 if str2parse == trigger_chars[1]:
719 ts = now + mxDT.RelativeDateTime(days = +1)
720 return [{
721 'data': mxdt2py_dt(ts),
722 'label': _('tomorrow (%s)') % ts.strftime('%A, %Y-%m-%d').decode(enc)
723 }]
724
725
726 if str2parse == trigger_chars[2]:
727 ts = now + mxDT.RelativeDateTime(days = -1)
728 return [{
729 'data': mxdt2py_dt(ts),
730 'label': _('yesterday (%s)') % ts.strftime('%A, %Y-%m-%d').decode(enc)
731 }]
732
733 return []
734
736 """Expand fragments containing a single dot.
737
738 Standard colloquial date format in Germany: day.month.year
739
740 "14."
741 - the 14th of the current month
742 - the 14th of next month
743 "-14."
744 - the 14th of last month
745 """
746 str2parse = str2parse.strip()
747
748 if not str2parse.endswith(u'.'):
749 return []
750
751 str2parse = str2parse[:-1]
752 try:
753 day_val = int(str2parse)
754 except ValueError:
755 return []
756
757 if (day_val < -31) or (day_val > 31) or (day_val == 0):
758 return []
759
760 now = mxDT.now()
761 enc = gmI18N.get_encoding()
762 matches = []
763
764
765 if day_val < 0:
766 ts = now + mxDT.RelativeDateTime(day = abs(day_val), months = -1)
767 if abs(day_val) <= gregorian_month_length[ts.month]:
768 matches.append ({
769 'data': mxdt2py_dt(ts),
770 'label': _('%s-%s-%s: a %s last month') % (ts.year, ts.month, ts.day, ts.strftime('%A').decode(enc))
771 })
772
773
774 if day_val > 0:
775 ts = now + mxDT.RelativeDateTime(day = day_val)
776 if day_val <= gregorian_month_length[ts.month]:
777 matches.append ({
778 'data': mxdt2py_dt(ts),
779 'label': _('%s-%s-%s: a %s this month') % (ts.year, ts.month, ts.day, ts.strftime('%A').decode(enc))
780 })
781
782
783 if day_val > 0:
784 ts = now + mxDT.RelativeDateTime(day = day_val, months = +1)
785 if day_val <= gregorian_month_length[ts.month]:
786 matches.append ({
787 'data': mxdt2py_dt(ts),
788 'label': _('%s-%s-%s: a %s next month') % (ts.year, ts.month, ts.day, ts.strftime('%A').decode(enc))
789 })
790
791
792 if day_val > 0:
793 ts = now + mxDT.RelativeDateTime(day = day_val, months = -1)
794 if day_val <= gregorian_month_length[ts.month]:
795 matches.append ({
796 'data': mxdt2py_dt(ts),
797 'label': _('%s-%s-%s: a %s last month') % (ts.year, ts.month, ts.day, ts.strftime('%A').decode(enc))
798 })
799
800 return matches
801
803 """Expand fragments containing a single slash.
804
805 "5/"
806 - 2005/ (2000 - 2025)
807 - 1995/ (1990 - 1999)
808 - Mai/current year
809 - Mai/next year
810 - Mai/last year
811 - Mai/200x
812 - Mai/20xx
813 - Mai/199x
814 - Mai/198x
815 - Mai/197x
816 - Mai/19xx
817
818 5/1999
819 6/2004
820 """
821 str2parse = str2parse.strip()
822
823 now = mxDT.now()
824
825
826 if regex.match(r"^\d{1,2}(\s|\t)*/+(\s|\t)*\d{4}$", str2parse, flags = regex.LOCALE | regex.UNICODE):
827 parts = regex.findall(r'\d+', str2parse, flags = regex.LOCALE | regex.UNICODE)
828 ts = now + mxDT.RelativeDateTime(year = int(parts[1]), month = int(parts[0]))
829 return [{
830 'data': mxdt2py_dt(ts),
831 'label': ts.strftime('%Y-%m-%d').decode(enc)
832 }]
833
834 matches = []
835
836 if regex.match(r"^\d{1,2}(\s|\t)*/+$", str2parse, flags = regex.LOCALE | regex.UNICODE):
837 val = int(str2parse[:-1].strip())
838
839
840 if val < 100 and val >= 0:
841 matches.append ({
842 'data': None,
843 'label': '%s-' % (val + 1900)
844 })
845
846
847 if val < 26 and val >= 0:
848 matches.append ({
849 'data': None,
850 'label': '%s-' % (val + 2000)
851 })
852
853
854 if val < 10 and val >= 0:
855 matches.append ({
856 'data': None,
857 'label': '%s-' % (val + 1990)
858 })
859
860 if val < 13 and val > 0:
861
862 matches.append ({
863 'data': None,
864 'label': '%s-%.2d-' % (now.year, val)
865 })
866
867 ts = now + mxDT.RelativeDateTime(years = 1)
868 matches.append ({
869 'data': None,
870 'label': '%s-%.2d-' % (ts.year, val)
871 })
872
873 ts = now + mxDT.RelativeDateTime(years = -1)
874 matches.append ({
875 'data': None,
876 'label': '%s-%.2d-' % (ts.year, val)
877 })
878
879 matches.append ({
880 'data': None,
881 'label': '201?-%.2d-' % val
882 })
883
884 matches.append ({
885 'data': None,
886 'label': '200?-%.2d-' % val
887 })
888
889 matches.append ({
890 'data': None,
891 'label': '20??-%.2d-' % val
892 })
893
894 matches.append ({
895 'data': None,
896 'label': '199?-%.2d-' % val
897 })
898
899 matches.append ({
900 'data': None,
901 'label': '198?-%.2d-' % val
902 })
903
904 matches.append ({
905 'data': None,
906 'label': '197?-%.2d-' % val
907 })
908
909 matches.append ({
910 'data': None,
911 'label': '19??-%.2d-' % val
912 })
913
914 return matches
915
917 """This matches on single numbers.
918
919 Spaces or tabs are discarded.
920 """
921 try:
922 val = int(str2parse.strip())
923 except ValueError:
924 return []
925
926
927
928 enc = gmI18N.get_encoding()
929 now = mxDT.now()
930
931 matches = []
932
933
934 if (1850 < val) and (val < 2100):
935 ts = now + mxDT.RelativeDateTime(year = val)
936 matches.append ({
937 'data': mxdt2py_dt(ts),
938 'label': ts.strftime('%Y-%m-%d')
939 })
940
941
942 if (val > 0) and (val <= gregorian_month_length[now.month]):
943 ts = now + mxDT.RelativeDateTime(day = val)
944 matches.append ({
945 'data': mxdt2py_dt(ts),
946 'label': _('%d. of %s (this month): a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc))
947 })
948
949
950 ts = now + mxDT.RelativeDateTime(months = 1, day = val)
951 if (val > 0) and (val <= gregorian_month_length[ts.month]):
952 matches.append ({
953 'data': mxdt2py_dt(ts),
954 'label': _('%d. of %s (next month): a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc))
955 })
956
957
958 ts = now + mxDT.RelativeDateTime(months = -1, day = val)
959 if (val > 0) and (val <= gregorian_month_length[ts.month]):
960 matches.append ({
961 'data': mxdt2py_dt(ts),
962 'label': _('%d. of %s (last month): a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc))
963 })
964
965
966 if (val > 0) and (val <= 400):
967 ts = now + mxDT.RelativeDateTime(days = val)
968 matches.append ({
969 'data': mxdt2py_dt(ts),
970 'label': _('in %d day(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
971 })
972 if (val < 0) and (val >= -400):
973 ts = now - mxDT.RelativeDateTime(days = abs(val))
974 matches.append ({
975 'data': mxdt2py_dt(ts),
976 'label': _('%d day(s) ago: %s') % (abs(val), ts.strftime('%A, %Y-%m-%d').decode(enc))
977 })
978
979
980 if (val > 0) and (val <= 50):
981 ts = now + mxDT.RelativeDateTime(weeks = val)
982 matches.append ({
983 'data': mxdt2py_dt(ts),
984 'label': _('in %d week(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
985 })
986 if (val < 0) and (val >= -50):
987 ts = now - mxDT.RelativeDateTime(weeks = abs(val))
988 matches.append ({
989 'data': mxdt2py_dt(ts),
990 'label': _('%d week(s) ago: %s') % (abs(val), ts.strftime('%A, %Y-%m-%d').decode(enc))
991 })
992
993
994 if (val < 13) and (val > 0):
995
996 ts = now + mxDT.RelativeDateTime(month = val)
997 matches.append ({
998 'data': mxdt2py_dt(ts),
999 'label': _('%s (%s this year)') % (ts.strftime('%Y-%m-%d'), ts.strftime('%B').decode(enc))
1000 })
1001
1002
1003 ts = now + mxDT.RelativeDateTime(years = 1, month = val)
1004 matches.append ({
1005 'data': mxdt2py_dt(ts),
1006 'label': _('%s (%s next year)') % (ts.strftime('%Y-%m-%d'), ts.strftime('%B').decode(enc))
1007 })
1008
1009
1010 ts = now + mxDT.RelativeDateTime(years = -1, month = val)
1011 matches.append ({
1012 'data': mxdt2py_dt(ts),
1013 'label': _('%s (%s last year)') % (ts.strftime('%Y-%m-%d'), ts.strftime('%B').decode(enc))
1014 })
1015
1016
1017 matches.append ({
1018 'data': None,
1019 'label': '200?-%s' % val
1020 })
1021 matches.append ({
1022 'data': None,
1023 'label': '199?-%s' % val
1024 })
1025 matches.append ({
1026 'data': None,
1027 'label': '198?-%s' % val
1028 })
1029 matches.append ({
1030 'data': None,
1031 'label': '19??-%s' % val
1032 })
1033
1034
1035 if (val < 8) and (val > 0):
1036
1037 ts = now + mxDT.RelativeDateTime(weekday = (val-1, 0))
1038 matches.append ({
1039 'data': mxdt2py_dt(ts),
1040 'label': _('%s this week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc))
1041 })
1042
1043
1044 ts = now + mxDT.RelativeDateTime(weeks = +1, weekday = (val-1, 0))
1045 matches.append ({
1046 'data': mxdt2py_dt(ts),
1047 'label': _('%s next week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc))
1048 })
1049
1050
1051 ts = now + mxDT.RelativeDateTime(weeks = -1, weekday = (val-1, 0))
1052 matches.append ({
1053 'data': mxdt2py_dt(ts),
1054 'label': _('%s last week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc))
1055 })
1056
1057 if (val < 100) and (val > 0):
1058 matches.append ({
1059 'data': None,
1060 'label': '%s-' % (1900 + val)
1061 })
1062
1063 if val == 201:
1064 tmp = {
1065 'data': mxdt2py_dt(now),
1066 'label': now.strftime('%Y-%m-%d')
1067 }
1068 matches.append(tmp)
1069 matches.append ({
1070 'data': None,
1071 'label': now.strftime('%Y-%m')
1072 })
1073 matches.append ({
1074 'data': None,
1075 'label': now.strftime('%Y')
1076 })
1077 matches.append ({
1078 'data': None,
1079 'label': '%s-' % (now.year + 1)
1080 })
1081 matches.append ({
1082 'data': None,
1083 'label': '%s-' % (now.year - 1)
1084 })
1085
1086 if val < 200 and val >= 190:
1087 for i in range(10):
1088 matches.append ({
1089 'data': None,
1090 'label': '%s%s-' % (val, i)
1091 })
1092
1093 return matches
1094
1096 """
1097 Default is 'hdwmy':
1098 h - hours
1099 d - days
1100 w - weeks
1101 m - months
1102 y - years
1103
1104 This also defines the significance of the order of the characters.
1105 """
1106 if offset_chars is None:
1107 offset_chars = _('hdwmy (single character date offset triggers)')[:5].lower()
1108
1109 str2parse = str2parse.strip()
1110
1111
1112 if not regex.match(r"^(\+|-)?(\s|\t)*\d{1,2}(\s|\t)*[%s]$" % offset_chars, str2parse, flags = regex.LOCALE | regex.UNICODE):
1113 return []
1114
1115
1116 if str2parse.startswith(u'-'):
1117 is_future = False
1118 str2parse = str2parse[1:].strip()
1119 else:
1120 is_future = True
1121 str2parse = str2parse.replace(u'+', u'').strip()
1122
1123 val = int(regex.findall(u'\d{1,2}', str2parse, flags = regex.LOCALE | regex.UNICODE)[0])
1124 offset_char = regex.findall(u'[%s]' % offset_chars, str2parse, flags = regex.LOCALE | regex.UNICODE)[0].lower()
1125
1126 now = mxDT.now()
1127 enc = gmI18N.get_encoding()
1128
1129 ts = None
1130
1131 if offset_char == offset_chars[0]:
1132 if is_future:
1133 ts = now + mxDT.RelativeDateTime(hours = val)
1134 label = _('in %d hour(s): %s') % (val, ts.strftime('%H:%M'))
1135 else:
1136 ts = now - mxDT.RelativeDateTime(hours = val)
1137 label = _('%d hour(s) ago: %s') % (val, ts.strftime('%H:%M'))
1138
1139 elif offset_char == offset_chars[1]:
1140 if is_future:
1141 ts = now + mxDT.RelativeDateTime(days = val)
1142 label = _('in %d day(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1143 else:
1144 ts = now - mxDT.RelativeDateTime(days = val)
1145 label = _('%d day(s) ago: %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1146
1147 elif offset_char == offset_chars[2]:
1148 if is_future:
1149 ts = now + mxDT.RelativeDateTime(weeks = val)
1150 label = _('in %d week(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1151 else:
1152 ts = now - mxDT.RelativeDateTime(weeks = val)
1153 label = _('%d week(s) ago: %s)') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1154
1155 elif offset_char == offset_chars[3]:
1156 if is_future:
1157 ts = now + mxDT.RelativeDateTime(months = val)
1158 label = _('in %d month(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1159 else:
1160 ts = now - mxDT.RelativeDateTime(months = val)
1161 label = _('%d month(s) ago: %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1162
1163 elif offset_char == offset_chars[4]:
1164 if is_future:
1165 ts = now + mxDT.RelativeDateTime(years = val)
1166 label = _('in %d year(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1167 else:
1168 ts = now - mxDT.RelativeDateTime(years = val)
1169 label = _('%d year(s) ago: %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1170
1171 if ts is None:
1172 return []
1173
1174 return [{
1175 'data': mxdt2py_dt(ts),
1176 'label': label
1177 }]
1178
1180 """Turn a string into candidate dates and auto-completions the user is likely to type.
1181
1182 You MUST have called locale.setlocale(locale.LC_ALL, '')
1183 somewhere in your code previously.
1184
1185 @param patterns: list of time.strptime compatible date pattern
1186 @type patterns: list
1187 """
1188 matches = []
1189 matches.extend(__single_dot2py_dt(str2parse))
1190 matches.extend(__numbers_only2py_dt(str2parse))
1191 matches.extend(__single_slash2py_dt(str2parse))
1192 matches.extend(__single_char2py_dt(str2parse))
1193 matches.extend(__explicit_offset2py_dt(str2parse))
1194
1195
1196 try:
1197 date = mxDT.Parser.DateFromString (
1198 text = str2parse,
1199 formats = ('euro', 'iso', 'us', 'altus', 'altiso', 'lit', 'altlit', 'eurlit')
1200 )
1201
1202 matches.append ({
1203 'data': mxdt2py_dt(date),
1204 'label': date.strftime('%Y-%m-%d')
1205 })
1206 except (ValueError, mxDT.RangeError):
1207 pass
1208
1209
1210 if patterns is None:
1211 patterns = []
1212
1213 patterns.append('%Y.%m.%d')
1214 patterns.append('%Y/%m/%d')
1215 patterns.append('%Y-%m-%d')
1216
1217 for pattern in patterns:
1218 try:
1219 date = pyDT.datetime.strptime(str2parse, pattern)
1220 matches.append ({
1221 'data': mxdt2py_dt(date),
1222 'label': date.strftime('%Y-%m-%d')
1223 })
1224 except AttributeError:
1225
1226 break
1227 except OverflowError:
1228
1229 continue
1230 except ValueError:
1231
1232 continue
1233
1234 return matches
1235
1236
1237
1239 """
1240 Default is 'hdwm':
1241 h - hours
1242 d - days
1243 w - weeks
1244 m - months
1245 y - years
1246
1247 This also defines the significance of the order of the characters.
1248 """
1249 if offset_chars is None:
1250 offset_chars = _('hdwmy (single character date offset triggers)')[:5].lower()
1251
1252
1253 if not regex.match(u"^(\s|\t)*(\+|-)?(\s|\t)*\d{1,2}(\s|\t)*[%s](\s|\t)*$" % offset_chars, str2parse, flags = regex.LOCALE | regex.UNICODE):
1254 return []
1255 val = int(regex.findall(u'\d{1,2}', str2parse, flags = regex.LOCALE | regex.UNICODE)[0])
1256 offset_char = regex.findall(u'[%s]' % offset_chars, str2parse, flags = regex.LOCALE | regex.UNICODE)[0].lower()
1257
1258 now = mxDT.now()
1259 enc = gmI18N.get_encoding()
1260
1261
1262 is_future = True
1263 if str2parse.find('-') > -1:
1264 is_future = False
1265
1266 ts = None
1267
1268 if offset_char == offset_chars[0]:
1269 if is_future:
1270 ts = now + mxDT.RelativeDateTime(hours = val)
1271 label = _('in %d hour(s) - %s') % (val, ts.strftime('%H:%M'))
1272 else:
1273 ts = now - mxDT.RelativeDateTime(hours = val)
1274 label = _('%d hour(s) ago - %s') % (val, ts.strftime('%H:%M'))
1275 accuracy = acc_subseconds
1276
1277 elif offset_char == offset_chars[1]:
1278 if is_future:
1279 ts = now + mxDT.RelativeDateTime(days = val)
1280 label = _('in %d day(s) - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1281 else:
1282 ts = now - mxDT.RelativeDateTime(days = val)
1283 label = _('%d day(s) ago - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1284 accuracy = acc_days
1285
1286 elif offset_char == offset_chars[2]:
1287 if is_future:
1288 ts = now + mxDT.RelativeDateTime(weeks = val)
1289 label = _('in %d week(s) - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1290 else:
1291 ts = now - mxDT.RelativeDateTime(weeks = val)
1292 label = _('%d week(s) ago - %s)') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1293 accuracy = acc_days
1294
1295 elif offset_char == offset_chars[3]:
1296 if is_future:
1297 ts = now + mxDT.RelativeDateTime(months = val)
1298 label = _('in %d month(s) - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1299 else:
1300 ts = now - mxDT.RelativeDateTime(months = val)
1301 label = _('%d month(s) ago - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1302 accuracy = acc_days
1303
1304 elif offset_char == offset_chars[4]:
1305 if is_future:
1306 ts = now + mxDT.RelativeDateTime(years = val)
1307 label = _('in %d year(s) - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1308 else:
1309 ts = now - mxDT.RelativeDateTime(years = val)
1310 label = _('%d year(s) ago - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1311 accuracy = acc_months
1312
1313 if ts is None:
1314 return []
1315
1316 tmp = {
1317 'data': cFuzzyTimestamp(timestamp = ts, accuracy = accuracy),
1318 'label': label
1319 }
1320 return [tmp]
1321
1323 """This matches on single characters.
1324
1325 Spaces and tabs are discarded.
1326
1327 Default is 'ndmy':
1328 n - now
1329 d - toDay
1330 m - toMorrow Someone please suggest a synonym !
1331 y - yesterday
1332
1333 This also defines the significance of the order of the characters.
1334 """
1335 if trigger_chars is None:
1336 trigger_chars = _('ndmy (single character date triggers)')[:4].lower()
1337
1338 if not regex.match(u'^(\s|\t)*[%s]{1}(\s|\t)*$' % trigger_chars, str2parse, flags = regex.LOCALE | regex.UNICODE):
1339 return []
1340 val = str2parse.strip().lower()
1341
1342 now = mxDT.now()
1343 enc = gmI18N.get_encoding()
1344
1345
1346
1347
1348 if val == trigger_chars[0]:
1349 ts = now
1350 return [{
1351 'data': cFuzzyTimestamp (
1352 timestamp = ts,
1353 accuracy = acc_subseconds
1354 ),
1355 'label': _('right now (%s, %s)') % (ts.strftime('%A').decode(enc), ts)
1356 }]
1357
1358
1359 if val == trigger_chars[1]:
1360 return [{
1361 'data': cFuzzyTimestamp (
1362 timestamp = now,
1363 accuracy = acc_days
1364 ),
1365 'label': _('today (%s)') % now.strftime('%A, %Y-%m-%d').decode(enc)
1366 }]
1367
1368
1369 if val == trigger_chars[2]:
1370 ts = now + mxDT.RelativeDateTime(days = +1)
1371 return [{
1372 'data': cFuzzyTimestamp (
1373 timestamp = ts,
1374 accuracy = acc_days
1375 ),
1376 'label': _('tomorrow (%s)') % ts.strftime('%A, %Y-%m-%d').decode(enc)
1377 }]
1378
1379
1380 if val == trigger_chars[3]:
1381 ts = now + mxDT.RelativeDateTime(days = -1)
1382 return [{
1383 'data': cFuzzyTimestamp (
1384 timestamp = ts,
1385 accuracy = acc_days
1386 ),
1387 'label': _('yesterday (%s)') % ts.strftime('%A, %Y-%m-%d').decode(enc)
1388 }]
1389
1390 return []
1391
1393 """Expand fragments containing a single slash.
1394
1395 "5/"
1396 - 2005/ (2000 - 2025)
1397 - 1995/ (1990 - 1999)
1398 - Mai/current year
1399 - Mai/next year
1400 - Mai/last year
1401 - Mai/200x
1402 - Mai/20xx
1403 - Mai/199x
1404 - Mai/198x
1405 - Mai/197x
1406 - Mai/19xx
1407 """
1408 matches = []
1409 now = mxDT.now()
1410 if regex.match(u"^(\s|\t)*\d{1,2}(\s|\t)*/+(\s|\t)*$", str2parse, flags = regex.LOCALE | regex.UNICODE):
1411 val = int(regex.findall(u'\d+', str2parse, flags = regex.LOCALE | regex.UNICODE)[0])
1412
1413 if val < 100 and val >= 0:
1414 matches.append ({
1415 'data': None,
1416 'label': '%s/' % (val + 1900)
1417 })
1418
1419 if val < 26 and val >= 0:
1420 matches.append ({
1421 'data': None,
1422 'label': '%s/' % (val + 2000)
1423 })
1424
1425 if val < 10 and val >= 0:
1426 matches.append ({
1427 'data': None,
1428 'label': '%s/' % (val + 1990)
1429 })
1430
1431 if val < 13 and val > 0:
1432 matches.append ({
1433 'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_months),
1434 'label': '%.2d/%s' % (val, now.year)
1435 })
1436 ts = now + mxDT.RelativeDateTime(years = 1)
1437 matches.append ({
1438 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_months),
1439 'label': '%.2d/%s' % (val, ts.year)
1440 })
1441 ts = now + mxDT.RelativeDateTime(years = -1)
1442 matches.append ({
1443 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_months),
1444 'label': '%.2d/%s' % (val, ts.year)
1445 })
1446 matches.append ({
1447 'data': None,
1448 'label': '%.2d/200' % val
1449 })
1450 matches.append ({
1451 'data': None,
1452 'label': '%.2d/20' % val
1453 })
1454 matches.append ({
1455 'data': None,
1456 'label': '%.2d/199' % val
1457 })
1458 matches.append ({
1459 'data': None,
1460 'label': '%.2d/198' % val
1461 })
1462 matches.append ({
1463 'data': None,
1464 'label': '%.2d/197' % val
1465 })
1466 matches.append ({
1467 'data': None,
1468 'label': '%.2d/19' % val
1469 })
1470
1471 elif regex.match(u"^(\s|\t)*\d{1,2}(\s|\t)*/+(\s|\t)*\d{4}(\s|\t)*$", str2parse, flags = regex.LOCALE | regex.UNICODE):
1472 parts = regex.findall(u'\d+', str2parse, flags = regex.LOCALE | regex.UNICODE)
1473 fts = cFuzzyTimestamp (
1474 timestamp = mxDT.now() + mxDT.RelativeDateTime(year = int(parts[1]), month = int(parts[0])),
1475 accuracy = acc_months
1476 )
1477 matches.append ({
1478 'data': fts,
1479 'label': fts.format_accurately()
1480 })
1481
1482 return matches
1483
1485 """This matches on single numbers.
1486
1487 Spaces or tabs are discarded.
1488 """
1489 if not regex.match(u"^(\s|\t)*\d{1,4}(\s|\t)*$", str2parse, flags = regex.LOCALE | regex.UNICODE):
1490 return []
1491
1492
1493
1494 enc = gmI18N.get_encoding()
1495 now = mxDT.now()
1496 val = int(regex.findall(u'\d{1,4}', str2parse, flags = regex.LOCALE | regex.UNICODE)[0])
1497
1498 matches = []
1499
1500
1501 if (1850 < val) and (val < 2100):
1502 ts = now + mxDT.RelativeDateTime(year = val)
1503 target_date = cFuzzyTimestamp (
1504 timestamp = ts,
1505 accuracy = acc_years
1506 )
1507 tmp = {
1508 'data': target_date,
1509 'label': '%s' % target_date
1510 }
1511 matches.append(tmp)
1512
1513
1514 if val <= gregorian_month_length[now.month]:
1515 ts = now + mxDT.RelativeDateTime(day = val)
1516 target_date = cFuzzyTimestamp (
1517 timestamp = ts,
1518 accuracy = acc_days
1519 )
1520 tmp = {
1521 'data': target_date,
1522 'label': _('%d. of %s (this month) - a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc))
1523 }
1524 matches.append(tmp)
1525
1526
1527 if val <= gregorian_month_length[(now + mxDT.RelativeDateTime(months = 1)).month]:
1528 ts = now + mxDT.RelativeDateTime(months = 1, day = val)
1529 target_date = cFuzzyTimestamp (
1530 timestamp = ts,
1531 accuracy = acc_days
1532 )
1533 tmp = {
1534 'data': target_date,
1535 'label': _('%d. of %s (next month) - a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc))
1536 }
1537 matches.append(tmp)
1538
1539
1540 if val <= gregorian_month_length[(now + mxDT.RelativeDateTime(months = -1)).month]:
1541 ts = now + mxDT.RelativeDateTime(months = -1, day = val)
1542 target_date = cFuzzyTimestamp (
1543 timestamp = ts,
1544 accuracy = acc_days
1545 )
1546 tmp = {
1547 'data': target_date,
1548 'label': _('%d. of %s (last month) - a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc))
1549 }
1550 matches.append(tmp)
1551
1552
1553 if val <= 400:
1554 ts = now + mxDT.RelativeDateTime(days = val)
1555 target_date = cFuzzyTimestamp (
1556 timestamp = ts
1557 )
1558 tmp = {
1559 'data': target_date,
1560 'label': _('in %d day(s) - %s') % (val, target_date.timestamp.strftime('%A, %Y-%m-%d').decode(enc))
1561 }
1562 matches.append(tmp)
1563
1564
1565 if val <= 50:
1566 ts = now + mxDT.RelativeDateTime(weeks = val)
1567 target_date = cFuzzyTimestamp (
1568 timestamp = ts
1569 )
1570 tmp = {
1571 'data': target_date,
1572 'label': _('in %d week(s) - %s') % (val, target_date.timestamp.strftime('%A, %Y-%m-%d').decode(enc))
1573 }
1574 matches.append(tmp)
1575
1576
1577 if val < 13:
1578
1579 ts = now + mxDT.RelativeDateTime(month = val)
1580 target_date = cFuzzyTimestamp (
1581 timestamp = ts,
1582 accuracy = acc_months
1583 )
1584 tmp = {
1585 'data': target_date,
1586 'label': _('%s (%s this year)') % (target_date, ts.strftime('%B').decode(enc))
1587 }
1588 matches.append(tmp)
1589
1590
1591 ts = now + mxDT.RelativeDateTime(years = 1, month = val)
1592 target_date = cFuzzyTimestamp (
1593 timestamp = ts,
1594 accuracy = acc_months
1595 )
1596 tmp = {
1597 'data': target_date,
1598 'label': _('%s (%s next year)') % (target_date, ts.strftime('%B').decode(enc))
1599 }
1600 matches.append(tmp)
1601
1602
1603 ts = now + mxDT.RelativeDateTime(years = -1, month = val)
1604 target_date = cFuzzyTimestamp (
1605 timestamp = ts,
1606 accuracy = acc_months
1607 )
1608 tmp = {
1609 'data': target_date,
1610 'label': _('%s (%s last year)') % (target_date, ts.strftime('%B').decode(enc))
1611 }
1612 matches.append(tmp)
1613
1614
1615 matches.append ({
1616 'data': None,
1617 'label': '%s/200' % val
1618 })
1619 matches.append ({
1620 'data': None,
1621 'label': '%s/199' % val
1622 })
1623 matches.append ({
1624 'data': None,
1625 'label': '%s/198' % val
1626 })
1627 matches.append ({
1628 'data': None,
1629 'label': '%s/19' % val
1630 })
1631
1632
1633 if val < 8:
1634
1635 ts = now + mxDT.RelativeDateTime(weekday = (val-1, 0))
1636 target_date = cFuzzyTimestamp (
1637 timestamp = ts,
1638 accuracy = acc_days
1639 )
1640 tmp = {
1641 'data': target_date,
1642 'label': _('%s this week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc))
1643 }
1644 matches.append(tmp)
1645
1646
1647 ts = now + mxDT.RelativeDateTime(weeks = +1, weekday = (val-1, 0))
1648 target_date = cFuzzyTimestamp (
1649 timestamp = ts,
1650 accuracy = acc_days
1651 )
1652 tmp = {
1653 'data': target_date,
1654 'label': _('%s next week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc))
1655 }
1656 matches.append(tmp)
1657
1658
1659 ts = now + mxDT.RelativeDateTime(weeks = -1, weekday = (val-1, 0))
1660 target_date = cFuzzyTimestamp (
1661 timestamp = ts,
1662 accuracy = acc_days
1663 )
1664 tmp = {
1665 'data': target_date,
1666 'label': _('%s last week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc))
1667 }
1668 matches.append(tmp)
1669
1670 if val < 100:
1671 matches.append ({
1672 'data': None,
1673 'label': '%s/' % (1900 + val)
1674 })
1675
1676 if val == 200:
1677 tmp = {
1678 'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_days),
1679 'label': '%s' % target_date
1680 }
1681 matches.append(tmp)
1682 matches.append ({
1683 'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_months),
1684 'label': '%.2d/%s' % (now.month, now.year)
1685 })
1686 matches.append ({
1687 'data': None,
1688 'label': '%s/' % now.year
1689 })
1690 matches.append ({
1691 'data': None,
1692 'label': '%s/' % (now.year + 1)
1693 })
1694 matches.append ({
1695 'data': None,
1696 'label': '%s/' % (now.year - 1)
1697 })
1698
1699 if val < 200 and val >= 190:
1700 for i in range(10):
1701 matches.append ({
1702 'data': None,
1703 'label': '%s%s/' % (val, i)
1704 })
1705
1706 return matches
1707
1709 """Expand fragments containing a single dot.
1710
1711 Standard colloquial date format in Germany: day.month.year
1712
1713 "14."
1714 - 14th current month this year
1715 - 14th next month this year
1716 """
1717 if not regex.match(u"^(\s|\t)*\d{1,2}\.{1}(\s|\t)*$", str2parse, flags = regex.LOCALE | regex.UNICODE):
1718 return []
1719
1720 val = int(regex.findall(u'\d+', str2parse, flags = regex.LOCALE | regex.UNICODE)[0])
1721 now = mxDT.now()
1722 enc = gmI18N.get_encoding()
1723
1724 matches = []
1725
1726
1727 ts = now + mxDT.RelativeDateTime(day = val)
1728 if val > 0 and val <= gregorian_month_length[ts.month]:
1729 matches.append ({
1730 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_days),
1731 'label': '%s.%s.%s - a %s this month' % (ts.day, ts.month, ts.year, ts.strftime('%A').decode(enc))
1732 })
1733
1734
1735 ts = now + mxDT.RelativeDateTime(day = val, months = +1)
1736 if val > 0 and val <= gregorian_month_length[ts.month]:
1737 matches.append ({
1738 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_days),
1739 'label': '%s.%s.%s - a %s next month' % (ts.day, ts.month, ts.year, ts.strftime('%A').decode(enc))
1740 })
1741
1742
1743 ts = now + mxDT.RelativeDateTime(day = val, months = -1)
1744 if val > 0 and val <= gregorian_month_length[ts.month]:
1745 matches.append ({
1746 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_days),
1747 'label': '%s.%s.%s - a %s last month' % (ts.day, ts.month, ts.year, ts.strftime('%A').decode(enc))
1748 })
1749
1750 return matches
1751
1753 """
1754 Turn a string into candidate fuzzy timestamps and auto-completions the user is likely to type.
1755
1756 You MUST have called locale.setlocale(locale.LC_ALL, '')
1757 somewhere in your code previously.
1758
1759 @param default_time: if you want to force the time part of the time
1760 stamp to a given value and the user doesn't type any time part
1761 this value will be used
1762 @type default_time: an mx.DateTime.DateTimeDelta instance
1763
1764 @param patterns: list of [time.strptime compatible date/time pattern, accuracy]
1765 @type patterns: list
1766 """
1767 matches = __single_dot(str2parse)
1768 matches.extend(__numbers_only(str2parse))
1769 matches.extend(__single_slash(str2parse))
1770 matches.extend(__single_char(str2parse))
1771 matches.extend(__explicit_offset(str2parse))
1772
1773
1774 if mxDT is not None:
1775 try:
1776
1777 date_only = mxDT.Parser.DateFromString (
1778 text = str2parse,
1779 formats = ('euro', 'iso', 'us', 'altus', 'altiso', 'lit', 'altlit', 'eurlit')
1780 )
1781
1782 time_part = mxDT.Parser.TimeFromString(text = str2parse)
1783 datetime = date_only + time_part
1784 if datetime == date_only:
1785 accuracy = acc_days
1786 if isinstance(default_time, mxDT.DateTimeDeltaType):
1787 datetime = date_only + default_time
1788 accuracy = acc_minutes
1789 else:
1790 accuracy = acc_subseconds
1791 fts = cFuzzyTimestamp (
1792 timestamp = datetime,
1793 accuracy = accuracy
1794 )
1795 matches.append ({
1796 'data': fts,
1797 'label': fts.format_accurately()
1798 })
1799 except (ValueError, mxDT.RangeError):
1800 pass
1801
1802 if patterns is None:
1803 patterns = []
1804
1805 patterns.append(['%Y.%m.%d', acc_days])
1806 patterns.append(['%Y/%m/%d', acc_days])
1807
1808 for pattern in patterns:
1809 try:
1810 fts = cFuzzyTimestamp (
1811 timestamp = pyDT.datetime.fromtimestamp(time.mktime(time.strptime(str2parse, pattern[0]))),
1812 accuracy = pattern[1]
1813 )
1814 matches.append ({
1815 'data': fts,
1816 'label': fts.format_accurately()
1817 })
1818 except AttributeError:
1819
1820 break
1821 except OverflowError:
1822
1823 continue
1824 except ValueError:
1825
1826 continue
1827
1828 return matches
1829
1830
1831
1833
1834
1835
1836 """A timestamp implementation with definable inaccuracy.
1837
1838 This class contains an mxDateTime.DateTime instance to
1839 hold the actual timestamp. It adds an accuracy attribute
1840 to allow the programmer to set the precision of the
1841 timestamp.
1842
1843 The timestamp will have to be initialzed with a fully
1844 precise value (which may, of course, contain partially
1845 fake data to make up for missing values). One can then
1846 set the accuracy value to indicate up to which part of
1847 the timestamp the data is valid. Optionally a modifier
1848 can be set to indicate further specification of the
1849 value (such as "summer", "afternoon", etc).
1850
1851 accuracy values:
1852 1: year only
1853 ...
1854 7: everything including milliseconds value
1855
1856 Unfortunately, one cannot directly derive a class from mx.DateTime.DateTime :-(
1857 """
1858
1860
1861 if timestamp is None:
1862 timestamp = mxDT.now()
1863 accuracy = acc_subseconds
1864 modifier = ''
1865
1866 if isinstance(timestamp, pyDT.datetime):
1867 timestamp = mxDT.DateTime(timestamp.year, timestamp.month, timestamp.day, timestamp.hour, timestamp.minute, timestamp.second)
1868
1869 if type(timestamp) != mxDT.DateTimeType:
1870 raise TypeError, '%s.__init__(): <timestamp> must be of mx.DateTime.DateTime or datetime.datetime type' % self.__class__.__name__
1871
1872 self.timestamp = timestamp
1873
1874 if (accuracy < 1) or (accuracy > 8):
1875 raise ValueError, '%s.__init__(): <accuracy> must be between 1 and 7' % self.__class__.__name__
1876 self.accuracy = accuracy
1877
1878 self.modifier = modifier
1879
1880
1881
1882
1884 """Return string representation meaningful to a user, also for %s formatting."""
1885 return self.format_accurately()
1886
1888 """Return string meaningful to a programmer to aid in debugging."""
1889 tmp = '<[%s]: timestamp [%s], accuracy [%s] (%s), modifier [%s] at %s>' % (
1890 self.__class__.__name__,
1891 repr(self.timestamp),
1892 self.accuracy,
1893 _accuracy_strings[self.accuracy],
1894 self.modifier,
1895 id(self)
1896 )
1897 return tmp
1898
1899
1900
1905
1908
1935
1937 return self.timestamp
1938
1940 try:
1941 gmtoffset = self.timestamp.gmtoffset()
1942 except mxDT.Error:
1943
1944
1945 now = mxDT.now()
1946 gmtoffset = now.gmtoffset()
1947 tz = cFixedOffsetTimezone(gmtoffset.minutes, self.timestamp.tz)
1948 secs, msecs = divmod(self.timestamp.second, 1)
1949 ts = pyDT.datetime (
1950 year = self.timestamp.year,
1951 month = self.timestamp.month,
1952 day = self.timestamp.day,
1953 hour = self.timestamp.hour,
1954 minute = self.timestamp.minute,
1955 second = secs,
1956 microsecond = msecs,
1957 tzinfo = tz
1958 )
1959 return ts
1960
1961
1962
1963 if __name__ == '__main__':
1964
1965 if len(sys.argv) < 2:
1966 sys.exit()
1967
1968 if sys.argv[1] != "test":
1969 sys.exit()
1970
1971
1972 intervals_as_str = [
1973 '7', '12', ' 12', '12 ', ' 12 ', ' 12 ', '0', '~12', '~ 12', ' ~ 12', ' ~ 12 ',
1974 '12a', '12 a', '12 a', '12j', '12J', '12y', '12Y', ' ~ 12 a ', '~0a',
1975 '12m', '17 m', '12 m', '17M', ' ~ 17 m ', ' ~ 3 / 12 ', '7/12', '0/12',
1976 '12w', '17 w', '12 w', '17W', ' ~ 17 w ', ' ~ 15 / 52', '2/52', '0/52',
1977 '12d', '17 d', '12 t', '17D', ' ~ 17 T ', ' ~ 12 / 7', '3/7', '0/7',
1978 '12h', '17 h', '12 H', '17H', ' ~ 17 h ', ' ~ 36 / 24', '7/24', '0/24',
1979 ' ~ 36 / 60', '7/60', '190/60', '0/60',
1980 '12a1m', '12 a 1 M', '12 a17m', '12j 12m', '12J7m', '12y7m', '12Y7M', ' ~ 12 a 37 m ', '~0a0m',
1981 '10m1w',
1982 'invalid interval input'
1983 ]
1984
1990
2058
2060 print "testing str2interval()"
2061 print "----------------------"
2062
2063 for interval_as_str in intervals_as_str:
2064 print "input: <%s>" % interval_as_str
2065 print " ==>", str2interval(str_interval=interval_as_str)
2066
2067 return True
2068
2070 print "DST currently in effect:", dst_currently_in_effect
2071 print "current UTC offset:", current_local_utc_offset_in_seconds, "seconds"
2072 print "current timezone (interval):", current_local_timezone_interval
2073 print "current timezone (ISO conformant numeric string):", current_local_iso_numeric_timezone_string
2074 print "local timezone class:", cLocalTimezone
2075 print ""
2076 tz = cLocalTimezone()
2077 print "local timezone instance:", tz
2078 print " (total) UTC offset:", tz.utcoffset(pyDT.datetime.now())
2079 print " DST adjustment:", tz.dst(pyDT.datetime.now())
2080 print " timezone name:", tz.tzname(pyDT.datetime.now())
2081 print ""
2082 print "current local timezone:", gmCurrentLocalTimezone
2083 print " (total) UTC offset:", gmCurrentLocalTimezone.utcoffset(pyDT.datetime.now())
2084 print " DST adjustment:", gmCurrentLocalTimezone.dst(pyDT.datetime.now())
2085 print " timezone name:", gmCurrentLocalTimezone.tzname(pyDT.datetime.now())
2086 print ""
2087 print "now here:", pydt_now_here()
2088 print ""
2089
2091 print "testing function str2fuzzy_timestamp_matches"
2092 print "--------------------------------------------"
2093
2094 val = None
2095 while val != 'exit':
2096 val = raw_input('Enter date fragment ("exit" quits): ')
2097 matches = str2fuzzy_timestamp_matches(str2parse = val)
2098 for match in matches:
2099 print 'label shown :', match['label']
2100 print 'data attached:', match['data']
2101 print ""
2102 print "---------------"
2103
2105 print "testing fuzzy timestamp class"
2106 print "-----------------------------"
2107
2108 ts = mxDT.now()
2109 print "mx.DateTime timestamp", type(ts)
2110 print " print ... :", ts
2111 print " print '%%s' %% ...: %s" % ts
2112 print " str() :", str(ts)
2113 print " repr() :", repr(ts)
2114
2115 fts = cFuzzyTimestamp()
2116 print "\nfuzzy timestamp <%s '%s'>" % ('class', fts.__class__.__name__)
2117 for accuracy in range(1,8):
2118 fts.accuracy = accuracy
2119 print " accuracy : %s (%s)" % (accuracy, _accuracy_strings[accuracy])
2120 print " format_accurately:", fts.format_accurately()
2121 print " strftime() :", fts.strftime('%c')
2122 print " print ... :", fts
2123 print " print '%%s' %% ... : %s" % fts
2124 print " str() :", str(fts)
2125 print " repr() :", repr(fts)
2126 raw_input('press ENTER to continue')
2127
2129 print "testing platform for handling dates before 1970"
2130 print "-----------------------------------------------"
2131 ts = mxDT.DateTime(1935, 4, 2)
2132 fts = cFuzzyTimestamp(timestamp=ts)
2133 print "fts :", fts
2134 print "fts.get_pydt():", fts.get_pydt()
2135
2147
2149 print "testing function str2pydt_matches"
2150 print "---------------------------------"
2151
2152 val = None
2153 while val != 'exit':
2154 val = raw_input('Enter date fragment ("exit" quits): ')
2155 matches = str2pydt_matches(str2parse = val)
2156 for match in matches:
2157 print 'label shown :', match['label']
2158 print 'data attached:', match['data']
2159 print ""
2160 print "---------------"
2161
2162
2163 gmI18N.activate_locale()
2164 gmI18N.install_domain('gnumed')
2165
2166 init()
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176 test_str2pydt()
2177
2178
2179