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