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, 'replace')
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 'ndmy':
750 n - Now
751 d - toDay
752 m - toMorrow Someone please suggest a synonym !
753 y - Yesterday
754
755 This also defines the significance of the order of the characters.
756 """
757 if trigger_chars is None:
758 trigger_chars = _('ndmy (single character date triggers)')[:4].lower()
759
760 str2parse = str2parse.strip().lower()
761
762 if len(str2parse) != 1:
763 return []
764
765 if str2parse not in trigger_chars:
766 return []
767
768 now = mxDT.now()
769 enc = gmI18N.get_encoding()
770
771
772
773
774 if str2parse == trigger_chars[0]:
775 return [{
776 'data': mxdt2py_dt(now),
777 'label': _('right now (%s, %s)') % (now.strftime('%A').decode(enc), now)
778 }]
779
780
781 if str2parse == trigger_chars[1]:
782 return [{
783 'data': mxdt2py_dt(now),
784 'label': _('today (%s)') % now.strftime('%A, %Y-%m-%d').decode(enc)
785 }]
786
787
788 if str2parse == trigger_chars[2]:
789 ts = now + mxDT.RelativeDateTime(days = +1)
790 return [{
791 'data': mxdt2py_dt(ts),
792 'label': _('tomorrow (%s)') % ts.strftime('%A, %Y-%m-%d').decode(enc)
793 }]
794
795
796 if str2parse == trigger_chars[3]:
797 ts = now + mxDT.RelativeDateTime(days = -1)
798 return [{
799 'data': mxdt2py_dt(ts),
800 'label': _('yesterday (%s)') % ts.strftime('%A, %Y-%m-%d').decode(enc)
801 }]
802
803 return []
804
806 """Expand fragments containing a single dot.
807
808 Standard colloquial date format in Germany: day.month.year
809
810 "14."
811 - the 14th of the current month
812 - the 14th of next month
813 "-14."
814 - the 14th of last month
815 """
816 str2parse = str2parse.strip()
817
818 if not str2parse.endswith(u'.'):
819 return []
820
821 str2parse = str2parse[:-1]
822 try:
823 day_val = int(str2parse)
824 except ValueError:
825 return []
826
827 if (day_val < -31) or (day_val > 31) or (day_val == 0):
828 return []
829
830 now = mxDT.now()
831 enc = gmI18N.get_encoding()
832 matches = []
833
834
835 if day_val < 0:
836 ts = now + mxDT.RelativeDateTime(day = abs(day_val), months = -1)
837 if abs(day_val) <= gregorian_month_length[ts.month]:
838 matches.append ({
839 'data': mxdt2py_dt(ts),
840 'label': _('%s-%s-%s: a %s last month') % (ts.year, ts.month, ts.day, ts.strftime('%A').decode(enc))
841 })
842
843
844 if day_val > 0:
845 ts = now + mxDT.RelativeDateTime(day = day_val)
846 if day_val <= gregorian_month_length[ts.month]:
847 matches.append ({
848 'data': mxdt2py_dt(ts),
849 'label': _('%s-%s-%s: a %s this month') % (ts.year, ts.month, ts.day, ts.strftime('%A').decode(enc))
850 })
851
852
853 if day_val > 0:
854 ts = now + mxDT.RelativeDateTime(day = day_val, months = +1)
855 if day_val <= gregorian_month_length[ts.month]:
856 matches.append ({
857 'data': mxdt2py_dt(ts),
858 'label': _('%s-%s-%s: a %s next month') % (ts.year, ts.month, ts.day, ts.strftime('%A').decode(enc))
859 })
860
861
862 if day_val > 0:
863 ts = now + mxDT.RelativeDateTime(day = day_val, months = -1)
864 if day_val <= gregorian_month_length[ts.month]:
865 matches.append ({
866 'data': mxdt2py_dt(ts),
867 'label': _('%s-%s-%s: a %s last month') % (ts.year, ts.month, ts.day, ts.strftime('%A').decode(enc))
868 })
869
870 return matches
871
873 """Expand fragments containing a single slash.
874
875 "5/"
876 - 2005/ (2000 - 2025)
877 - 1995/ (1990 - 1999)
878 - Mai/current year
879 - Mai/next year
880 - Mai/last year
881 - Mai/200x
882 - Mai/20xx
883 - Mai/199x
884 - Mai/198x
885 - Mai/197x
886 - Mai/19xx
887
888 5/1999
889 6/2004
890 """
891 str2parse = str2parse.strip()
892
893 now = mxDT.now()
894
895
896 if regex.match(r"^\d{1,2}(\s|\t)*/+(\s|\t)*\d{4}$", str2parse, flags = regex.LOCALE | regex.UNICODE):
897 parts = regex.findall(r'\d+', str2parse, flags = regex.LOCALE | regex.UNICODE)
898 ts = now + mxDT.RelativeDateTime(year = int(parts[1]), month = int(parts[0]))
899 return [{
900 'data': mxdt2py_dt(ts),
901 'label': ts.strftime('%Y-%m-%d').decode(enc)
902 }]
903
904 matches = []
905
906 if regex.match(r"^\d{1,2}(\s|\t)*/+$", str2parse, flags = regex.LOCALE | regex.UNICODE):
907 val = int(str2parse[:-1].strip())
908
909
910 if val < 100 and val >= 0:
911 matches.append ({
912 'data': None,
913 'label': '%s-' % (val + 1900)
914 })
915
916
917 if val < 26 and val >= 0:
918 matches.append ({
919 'data': None,
920 'label': '%s-' % (val + 2000)
921 })
922
923
924 if val < 10 and val >= 0:
925 matches.append ({
926 'data': None,
927 'label': '%s-' % (val + 1990)
928 })
929
930 if val < 13 and val > 0:
931
932 matches.append ({
933 'data': None,
934 'label': '%s-%.2d-' % (now.year, val)
935 })
936
937 ts = now + mxDT.RelativeDateTime(years = 1)
938 matches.append ({
939 'data': None,
940 'label': '%s-%.2d-' % (ts.year, val)
941 })
942
943 ts = now + mxDT.RelativeDateTime(years = -1)
944 matches.append ({
945 'data': None,
946 'label': '%s-%.2d-' % (ts.year, val)
947 })
948
949 matches.append ({
950 'data': None,
951 'label': '201?-%.2d-' % val
952 })
953
954 matches.append ({
955 'data': None,
956 'label': '200?-%.2d-' % val
957 })
958
959 matches.append ({
960 'data': None,
961 'label': '20??-%.2d-' % val
962 })
963
964 matches.append ({
965 'data': None,
966 'label': '199?-%.2d-' % val
967 })
968
969 matches.append ({
970 'data': None,
971 'label': '198?-%.2d-' % val
972 })
973
974 matches.append ({
975 'data': None,
976 'label': '197?-%.2d-' % val
977 })
978
979 matches.append ({
980 'data': None,
981 'label': '19??-%.2d-' % val
982 })
983
984 return matches
985
987 """This matches on single numbers.
988
989 Spaces or tabs are discarded.
990 """
991 try:
992 val = int(str2parse.strip())
993 except ValueError:
994 return []
995
996
997
998 enc = gmI18N.get_encoding()
999 now = mxDT.now()
1000
1001 matches = []
1002
1003
1004 if (1850 < val) and (val < 2100):
1005 ts = now + mxDT.RelativeDateTime(year = val)
1006 matches.append ({
1007 'data': mxdt2py_dt(ts),
1008 'label': ts.strftime('%Y-%m-%d')
1009 })
1010
1011
1012 if (val > 0) and (val <= gregorian_month_length[now.month]):
1013 ts = now + mxDT.RelativeDateTime(day = val)
1014 matches.append ({
1015 'data': mxdt2py_dt(ts),
1016 'label': _('%d. of %s (this month): a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc))
1017 })
1018
1019
1020 if (val > 0) and (val < 32):
1021 ts = now + mxDT.RelativeDateTime(months = 1, day = val)
1022 matches.append ({
1023 'data': mxdt2py_dt(ts),
1024 'label': _('%d. of %s (next month): a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc))
1025 })
1026
1027
1028 if (val > 0) and (val < 32):
1029 ts = now + mxDT.RelativeDateTime(months = -1, day = val)
1030 matches.append ({
1031 'data': mxdt2py_dt(ts),
1032 'label': _('%d. of %s (last month): a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc))
1033 })
1034
1035
1036 if (val > 0) and (val <= 400):
1037 ts = now + mxDT.RelativeDateTime(days = val)
1038 matches.append ({
1039 'data': mxdt2py_dt(ts),
1040 'label': _('in %d day(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1041 })
1042 if (val < 0) and (val >= -400):
1043 ts = now - mxDT.RelativeDateTime(days = abs(val))
1044 matches.append ({
1045 'data': mxdt2py_dt(ts),
1046 'label': _('%d day(s) ago: %s') % (abs(val), ts.strftime('%A, %Y-%m-%d').decode(enc))
1047 })
1048
1049
1050 if (val > 0) and (val <= 50):
1051 ts = now + mxDT.RelativeDateTime(weeks = val)
1052 matches.append ({
1053 'data': mxdt2py_dt(ts),
1054 'label': _('in %d week(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1055 })
1056 if (val < 0) and (val >= -50):
1057 ts = now - mxDT.RelativeDateTime(weeks = abs(val))
1058 matches.append ({
1059 'data': mxdt2py_dt(ts),
1060 'label': _('%d week(s) ago: %s') % (abs(val), ts.strftime('%A, %Y-%m-%d').decode(enc))
1061 })
1062
1063
1064 if (val < 13) and (val > 0):
1065
1066 ts = now + mxDT.RelativeDateTime(month = val)
1067 matches.append ({
1068 'data': mxdt2py_dt(ts),
1069 'label': _('%s (%s this year)') % (ts.strftime('%Y-%m-%d'), ts.strftime('%B').decode(enc))
1070 })
1071
1072
1073 ts = now + mxDT.RelativeDateTime(years = 1, month = val)
1074 matches.append ({
1075 'data': mxdt2py_dt(ts),
1076 'label': _('%s (%s next year)') % (ts.strftime('%Y-%m-%d'), ts.strftime('%B').decode(enc))
1077 })
1078
1079
1080 ts = now + mxDT.RelativeDateTime(years = -1, month = val)
1081 matches.append ({
1082 'data': mxdt2py_dt(ts),
1083 'label': _('%s (%s last year)') % (ts.strftime('%Y-%m-%d'), ts.strftime('%B').decode(enc))
1084 })
1085
1086
1087 matches.append ({
1088 'data': None,
1089 'label': '200?-%s' % val
1090 })
1091 matches.append ({
1092 'data': None,
1093 'label': '199?-%s' % val
1094 })
1095 matches.append ({
1096 'data': None,
1097 'label': '198?-%s' % val
1098 })
1099 matches.append ({
1100 'data': None,
1101 'label': '19??-%s' % val
1102 })
1103
1104
1105 if (val < 8) and (val > 0):
1106
1107 ts = now + mxDT.RelativeDateTime(weekday = (val-1, 0))
1108 matches.append ({
1109 'data': mxdt2py_dt(ts),
1110 'label': _('%s this week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc))
1111 })
1112
1113
1114 ts = now + mxDT.RelativeDateTime(weeks = +1, weekday = (val-1, 0))
1115 matches.append ({
1116 'data': mxdt2py_dt(ts),
1117 'label': _('%s next week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc))
1118 })
1119
1120
1121 ts = now + mxDT.RelativeDateTime(weeks = -1, weekday = (val-1, 0))
1122 matches.append ({
1123 'data': mxdt2py_dt(ts),
1124 'label': _('%s last week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc))
1125 })
1126
1127 if (val < 100) and (val > 0):
1128 matches.append ({
1129 'data': None,
1130 'label': '%s-' % (1900 + val)
1131 })
1132
1133 if val == 201:
1134 tmp = {
1135 'data': mxdt2py_dt(now),
1136 'label': now.strftime('%Y-%m-%d')
1137 }
1138 matches.append(tmp)
1139 matches.append ({
1140 'data': None,
1141 'label': now.strftime('%Y-%m')
1142 })
1143 matches.append ({
1144 'data': None,
1145 'label': now.strftime('%Y')
1146 })
1147 matches.append ({
1148 'data': None,
1149 'label': '%s-' % (now.year + 1)
1150 })
1151 matches.append ({
1152 'data': None,
1153 'label': '%s-' % (now.year - 1)
1154 })
1155
1156 if val < 200 and val >= 190:
1157 for i in range(10):
1158 matches.append ({
1159 'data': None,
1160 'label': '%s%s-' % (val, i)
1161 })
1162
1163 return matches
1164
1166 """
1167 Default is 'hdwmy':
1168 h - hours
1169 d - days
1170 w - weeks
1171 m - months
1172 y - years
1173
1174 This also defines the significance of the order of the characters.
1175 """
1176 if offset_chars is None:
1177 offset_chars = _('hdwmy (single character date offset triggers)')[:5].lower()
1178
1179 str2parse = str2parse.strip()
1180
1181
1182 if not regex.match(r"^(\+|-)?(\s|\t)*\d{1,2}(\s|\t)*[%s]$" % offset_chars, str2parse, flags = regex.LOCALE | regex.UNICODE):
1183 return []
1184
1185
1186 if str2parse.startswith(u'-'):
1187 is_future = False
1188 str2parse = str2parse[1:].strip()
1189 else:
1190 is_future = True
1191 str2parse = str2parse.replace(u'+', u'').strip()
1192
1193 val = int(regex.findall(u'\d{1,2}', str2parse, flags = regex.LOCALE | regex.UNICODE)[0])
1194 offset_char = regex.findall(u'[%s]' % offset_chars, str2parse, flags = regex.LOCALE | regex.UNICODE)[0].lower()
1195
1196 now = mxDT.now()
1197 enc = gmI18N.get_encoding()
1198
1199 ts = None
1200
1201 if offset_char == offset_chars[0]:
1202 if is_future:
1203 ts = now + mxDT.RelativeDateTime(hours = val)
1204 label = _('in %d hour(s): %s') % (val, ts.strftime('%H:%M'))
1205 else:
1206 ts = now - mxDT.RelativeDateTime(hours = val)
1207 label = _('%d hour(s) ago: %s') % (val, ts.strftime('%H:%M'))
1208
1209 elif offset_char == offset_chars[1]:
1210 if is_future:
1211 ts = now + mxDT.RelativeDateTime(days = val)
1212 label = _('in %d day(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1213 else:
1214 ts = now - mxDT.RelativeDateTime(days = val)
1215 label = _('%d day(s) ago: %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1216
1217 elif offset_char == offset_chars[2]:
1218 if is_future:
1219 ts = now + mxDT.RelativeDateTime(weeks = val)
1220 label = _('in %d week(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1221 else:
1222 ts = now - mxDT.RelativeDateTime(weeks = val)
1223 label = _('%d week(s) ago: %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1224
1225 elif offset_char == offset_chars[3]:
1226 if is_future:
1227 ts = now + mxDT.RelativeDateTime(months = val)
1228 label = _('in %d month(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1229 else:
1230 ts = now - mxDT.RelativeDateTime(months = val)
1231 label = _('%d month(s) ago: %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1232
1233 elif offset_char == offset_chars[4]:
1234 if is_future:
1235 ts = now + mxDT.RelativeDateTime(years = val)
1236 label = _('in %d year(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1237 else:
1238 ts = now - mxDT.RelativeDateTime(years = val)
1239 label = _('%d year(s) ago: %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1240
1241 if ts is None:
1242 return []
1243
1244 return [{
1245 'data': mxdt2py_dt(ts),
1246 'label': label
1247 }]
1248
1250 """Turn a string into candidate dates and auto-completions the user is likely to type.
1251
1252 You MUST have called locale.setlocale(locale.LC_ALL, '')
1253 somewhere in your code previously.
1254
1255 @param patterns: list of time.strptime compatible date pattern
1256 @type patterns: list
1257 """
1258 matches = []
1259 matches.extend(__single_dot2py_dt(str2parse))
1260 matches.extend(__numbers_only2py_dt(str2parse))
1261 matches.extend(__single_slash2py_dt(str2parse))
1262 matches.extend(__single_char2py_dt(str2parse))
1263 matches.extend(__explicit_offset2py_dt(str2parse))
1264
1265
1266 try:
1267 date = mxDT.Parser.DateFromString (
1268 text = str2parse,
1269 formats = ('euro', 'iso', 'us', 'altus', 'altiso', 'lit', 'altlit', 'eurlit')
1270 )
1271 matches.append ({
1272 'data': mxdt2py_dt(date),
1273 'label': date.strftime('%Y-%m-%d')
1274 })
1275 except (ValueError, OverflowError, mxDT.RangeError):
1276 pass
1277
1278
1279 if patterns is None:
1280 patterns = []
1281
1282 patterns.append('%Y-%m-%d')
1283 patterns.append('%y-%m-%d')
1284 patterns.append('%Y/%m/%d')
1285 patterns.append('%y/%m/%d')
1286
1287 patterns.append('%d-%m-%Y')
1288 patterns.append('%d-%m-%y')
1289 patterns.append('%d/%m/%Y')
1290 patterns.append('%d/%m/%y')
1291
1292 patterns.append('%m-%d-%Y')
1293 patterns.append('%m-%d-%y')
1294 patterns.append('%m/%d/%Y')
1295 patterns.append('%m/%d/%y')
1296
1297 patterns.append('%Y.%m.%d')
1298 patterns.append('%y.%m.%d')
1299
1300 for pattern in patterns:
1301 try:
1302 date = pyDT.datetime.strptime(str2parse, pattern).replace (
1303 hour = 11,
1304 minute = 11,
1305 second = 11,
1306 tzinfo = gmCurrentLocalTimezone
1307 )
1308 matches.append ({
1309 'data': date,
1310 'label': pydt_strftime(date, format = '%Y-%m-%d', accuracy = acc_days)
1311 })
1312 except AttributeError:
1313
1314 break
1315 except OverflowError:
1316
1317 continue
1318 except ValueError:
1319
1320 continue
1321
1322 return matches
1323
1324
1325
1327 """
1328 Default is 'hdwm':
1329 h - hours
1330 d - days
1331 w - weeks
1332 m - months
1333 y - years
1334
1335 This also defines the significance of the order of the characters.
1336 """
1337 if offset_chars is None:
1338 offset_chars = _('hdwmy (single character date offset triggers)')[:5].lower()
1339
1340
1341 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):
1342 return []
1343 val = int(regex.findall(u'\d{1,2}', str2parse, flags = regex.LOCALE | regex.UNICODE)[0])
1344 offset_char = regex.findall(u'[%s]' % offset_chars, str2parse, flags = regex.LOCALE | regex.UNICODE)[0].lower()
1345
1346 now = mxDT.now()
1347 enc = gmI18N.get_encoding()
1348
1349
1350 is_future = True
1351 if str2parse.find('-') > -1:
1352 is_future = False
1353
1354 ts = None
1355
1356 if offset_char == offset_chars[0]:
1357 if is_future:
1358 ts = now + mxDT.RelativeDateTime(hours = val)
1359 label = _('in %d hour(s) - %s') % (val, ts.strftime('%H:%M'))
1360 else:
1361 ts = now - mxDT.RelativeDateTime(hours = val)
1362 label = _('%d hour(s) ago - %s') % (val, ts.strftime('%H:%M'))
1363 accuracy = acc_subseconds
1364
1365 elif offset_char == offset_chars[1]:
1366 if is_future:
1367 ts = now + mxDT.RelativeDateTime(days = val)
1368 label = _('in %d day(s) - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1369 else:
1370 ts = now - mxDT.RelativeDateTime(days = val)
1371 label = _('%d day(s) ago - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1372 accuracy = acc_days
1373
1374 elif offset_char == offset_chars[2]:
1375 if is_future:
1376 ts = now + mxDT.RelativeDateTime(weeks = val)
1377 label = _('in %d week(s) - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1378 else:
1379 ts = now - mxDT.RelativeDateTime(weeks = val)
1380 label = _('%d week(s) ago - %s)') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1381 accuracy = acc_days
1382
1383 elif offset_char == offset_chars[3]:
1384 if is_future:
1385 ts = now + mxDT.RelativeDateTime(months = val)
1386 label = _('in %d month(s) - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1387 else:
1388 ts = now - mxDT.RelativeDateTime(months = val)
1389 label = _('%d month(s) ago - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1390 accuracy = acc_days
1391
1392 elif offset_char == offset_chars[4]:
1393 if is_future:
1394 ts = now + mxDT.RelativeDateTime(years = val)
1395 label = _('in %d year(s) - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1396 else:
1397 ts = now - mxDT.RelativeDateTime(years = val)
1398 label = _('%d year(s) ago - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1399 accuracy = acc_months
1400
1401 if ts is None:
1402 return []
1403
1404 tmp = {
1405 'data': cFuzzyTimestamp(timestamp = ts, accuracy = accuracy),
1406 'label': label
1407 }
1408 return [tmp]
1409
1411 """Expand fragments containing a single slash.
1412
1413 "5/"
1414 - 2005/ (2000 - 2025)
1415 - 1995/ (1990 - 1999)
1416 - Mai/current year
1417 - Mai/next year
1418 - Mai/last year
1419 - Mai/200x
1420 - Mai/20xx
1421 - Mai/199x
1422 - Mai/198x
1423 - Mai/197x
1424 - Mai/19xx
1425 """
1426 matches = []
1427 now = mxDT.now()
1428 if regex.match(u"^(\s|\t)*\d{1,2}(\s|\t)*/+(\s|\t)*$", str2parse, flags = regex.LOCALE | regex.UNICODE):
1429 val = int(regex.findall(u'\d+', str2parse, flags = regex.LOCALE | regex.UNICODE)[0])
1430
1431 if val < 100 and val >= 0:
1432 matches.append ({
1433 'data': None,
1434 'label': '%s/' % (val + 1900)
1435 })
1436
1437 if val < 26 and val >= 0:
1438 matches.append ({
1439 'data': None,
1440 'label': '%s/' % (val + 2000)
1441 })
1442
1443 if val < 10 and val >= 0:
1444 matches.append ({
1445 'data': None,
1446 'label': '%s/' % (val + 1990)
1447 })
1448
1449 if val < 13 and val > 0:
1450 matches.append ({
1451 'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_months),
1452 'label': '%.2d/%s' % (val, now.year)
1453 })
1454 ts = now + mxDT.RelativeDateTime(years = 1)
1455 matches.append ({
1456 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_months),
1457 'label': '%.2d/%s' % (val, ts.year)
1458 })
1459 ts = now + mxDT.RelativeDateTime(years = -1)
1460 matches.append ({
1461 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_months),
1462 'label': '%.2d/%s' % (val, ts.year)
1463 })
1464 matches.append ({
1465 'data': None,
1466 'label': '%.2d/200' % val
1467 })
1468 matches.append ({
1469 'data': None,
1470 'label': '%.2d/20' % val
1471 })
1472 matches.append ({
1473 'data': None,
1474 'label': '%.2d/199' % val
1475 })
1476 matches.append ({
1477 'data': None,
1478 'label': '%.2d/198' % val
1479 })
1480 matches.append ({
1481 'data': None,
1482 'label': '%.2d/197' % val
1483 })
1484 matches.append ({
1485 'data': None,
1486 'label': '%.2d/19' % val
1487 })
1488
1489 elif regex.match(u"^(\s|\t)*\d{1,2}(\s|\t)*/+(\s|\t)*\d{4}(\s|\t)*$", str2parse, flags = regex.LOCALE | regex.UNICODE):
1490 parts = regex.findall(u'\d+', str2parse, flags = regex.LOCALE | regex.UNICODE)
1491 fts = cFuzzyTimestamp (
1492 timestamp = mxDT.now() + mxDT.RelativeDateTime(year = int(parts[1]), month = int(parts[0])),
1493 accuracy = acc_months
1494 )
1495 matches.append ({
1496 'data': fts,
1497 'label': fts.format_accurately()
1498 })
1499
1500 return matches
1501
1503 """This matches on single numbers.
1504
1505 Spaces or tabs are discarded.
1506 """
1507 if not regex.match(u"^(\s|\t)*\d{1,4}(\s|\t)*$", str2parse, flags = regex.LOCALE | regex.UNICODE):
1508 return []
1509
1510
1511
1512 enc = gmI18N.get_encoding()
1513 now = mxDT.now()
1514 val = int(regex.findall(u'\d{1,4}', str2parse, flags = regex.LOCALE | regex.UNICODE)[0])
1515
1516 matches = []
1517
1518
1519 if (1850 < val) and (val < 2100):
1520 ts = now + mxDT.RelativeDateTime(year = val)
1521 target_date = cFuzzyTimestamp (
1522 timestamp = ts,
1523 accuracy = acc_years
1524 )
1525 tmp = {
1526 'data': target_date,
1527 'label': '%s' % target_date
1528 }
1529 matches.append(tmp)
1530
1531
1532 if val <= gregorian_month_length[now.month]:
1533 ts = now + mxDT.RelativeDateTime(day = val)
1534 target_date = cFuzzyTimestamp (
1535 timestamp = ts,
1536 accuracy = acc_days
1537 )
1538 tmp = {
1539 'data': target_date,
1540 'label': _('%d. of %s (this month) - a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc))
1541 }
1542 matches.append(tmp)
1543
1544
1545 if val <= gregorian_month_length[(now + mxDT.RelativeDateTime(months = 1)).month]:
1546 ts = now + mxDT.RelativeDateTime(months = 1, day = val)
1547 target_date = cFuzzyTimestamp (
1548 timestamp = ts,
1549 accuracy = acc_days
1550 )
1551 tmp = {
1552 'data': target_date,
1553 'label': _('%d. of %s (next month) - a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc))
1554 }
1555 matches.append(tmp)
1556
1557
1558 if val <= gregorian_month_length[(now + mxDT.RelativeDateTime(months = -1)).month]:
1559 ts = now + mxDT.RelativeDateTime(months = -1, day = val)
1560 target_date = cFuzzyTimestamp (
1561 timestamp = ts,
1562 accuracy = acc_days
1563 )
1564 tmp = {
1565 'data': target_date,
1566 'label': _('%d. of %s (last month) - a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc))
1567 }
1568 matches.append(tmp)
1569
1570
1571 if val <= 400:
1572 ts = now + mxDT.RelativeDateTime(days = val)
1573 target_date = cFuzzyTimestamp (
1574 timestamp = ts
1575 )
1576 tmp = {
1577 'data': target_date,
1578 'label': _('in %d day(s) - %s') % (val, target_date.timestamp.strftime('%A, %Y-%m-%d').decode(enc))
1579 }
1580 matches.append(tmp)
1581
1582
1583 if val <= 50:
1584 ts = now + mxDT.RelativeDateTime(weeks = val)
1585 target_date = cFuzzyTimestamp (
1586 timestamp = ts
1587 )
1588 tmp = {
1589 'data': target_date,
1590 'label': _('in %d week(s) - %s') % (val, target_date.timestamp.strftime('%A, %Y-%m-%d').decode(enc))
1591 }
1592 matches.append(tmp)
1593
1594
1595 if val < 13:
1596
1597 ts = now + mxDT.RelativeDateTime(month = val)
1598 target_date = cFuzzyTimestamp (
1599 timestamp = ts,
1600 accuracy = acc_months
1601 )
1602 tmp = {
1603 'data': target_date,
1604 'label': _('%s (%s this year)') % (target_date, ts.strftime('%B').decode(enc))
1605 }
1606 matches.append(tmp)
1607
1608
1609 ts = now + mxDT.RelativeDateTime(years = 1, month = val)
1610 target_date = cFuzzyTimestamp (
1611 timestamp = ts,
1612 accuracy = acc_months
1613 )
1614 tmp = {
1615 'data': target_date,
1616 'label': _('%s (%s next year)') % (target_date, ts.strftime('%B').decode(enc))
1617 }
1618 matches.append(tmp)
1619
1620
1621 ts = now + mxDT.RelativeDateTime(years = -1, month = val)
1622 target_date = cFuzzyTimestamp (
1623 timestamp = ts,
1624 accuracy = acc_months
1625 )
1626 tmp = {
1627 'data': target_date,
1628 'label': _('%s (%s last year)') % (target_date, ts.strftime('%B').decode(enc))
1629 }
1630 matches.append(tmp)
1631
1632
1633 matches.append ({
1634 'data': None,
1635 'label': '%s/200' % val
1636 })
1637 matches.append ({
1638 'data': None,
1639 'label': '%s/199' % val
1640 })
1641 matches.append ({
1642 'data': None,
1643 'label': '%s/198' % val
1644 })
1645 matches.append ({
1646 'data': None,
1647 'label': '%s/19' % val
1648 })
1649
1650
1651 if val < 8:
1652
1653 ts = now + mxDT.RelativeDateTime(weekday = (val-1, 0))
1654 target_date = cFuzzyTimestamp (
1655 timestamp = ts,
1656 accuracy = acc_days
1657 )
1658 tmp = {
1659 'data': target_date,
1660 'label': _('%s this week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc))
1661 }
1662 matches.append(tmp)
1663
1664
1665 ts = now + mxDT.RelativeDateTime(weeks = +1, weekday = (val-1, 0))
1666 target_date = cFuzzyTimestamp (
1667 timestamp = ts,
1668 accuracy = acc_days
1669 )
1670 tmp = {
1671 'data': target_date,
1672 'label': _('%s next week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc))
1673 }
1674 matches.append(tmp)
1675
1676
1677 ts = now + mxDT.RelativeDateTime(weeks = -1, weekday = (val-1, 0))
1678 target_date = cFuzzyTimestamp (
1679 timestamp = ts,
1680 accuracy = acc_days
1681 )
1682 tmp = {
1683 'data': target_date,
1684 'label': _('%s last week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc))
1685 }
1686 matches.append(tmp)
1687
1688 if val < 100:
1689 matches.append ({
1690 'data': None,
1691 'label': '%s/' % (1900 + val)
1692 })
1693
1694 if val == 200:
1695 tmp = {
1696 'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_days),
1697 'label': '%s' % target_date
1698 }
1699 matches.append(tmp)
1700 matches.append ({
1701 'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_months),
1702 'label': '%.2d/%s' % (now.month, now.year)
1703 })
1704 matches.append ({
1705 'data': None,
1706 'label': '%s/' % now.year
1707 })
1708 matches.append ({
1709 'data': None,
1710 'label': '%s/' % (now.year + 1)
1711 })
1712 matches.append ({
1713 'data': None,
1714 'label': '%s/' % (now.year - 1)
1715 })
1716
1717 if val < 200 and val >= 190:
1718 for i in range(10):
1719 matches.append ({
1720 'data': None,
1721 'label': '%s%s/' % (val, i)
1722 })
1723
1724 return matches
1725
1727 """Expand fragments containing a single dot.
1728
1729 Standard colloquial date format in Germany: day.month.year
1730
1731 "14."
1732 - 14th current month this year
1733 - 14th next month this year
1734 """
1735 if not regex.match(u"^(\s|\t)*\d{1,2}\.{1}(\s|\t)*$", str2parse, flags = regex.LOCALE | regex.UNICODE):
1736 return []
1737
1738 val = int(regex.findall(u'\d+', str2parse, flags = regex.LOCALE | regex.UNICODE)[0])
1739 now = mxDT.now()
1740 enc = gmI18N.get_encoding()
1741
1742 matches = []
1743
1744
1745 ts = now + mxDT.RelativeDateTime(day = val)
1746 if val > 0 and val <= gregorian_month_length[ts.month]:
1747 matches.append ({
1748 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_days),
1749 'label': '%s.%s.%s - a %s this month' % (ts.day, ts.month, ts.year, ts.strftime('%A').decode(enc))
1750 })
1751
1752
1753 ts = now + mxDT.RelativeDateTime(day = val, months = +1)
1754 if val > 0 and val <= gregorian_month_length[ts.month]:
1755 matches.append ({
1756 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_days),
1757 'label': '%s.%s.%s - a %s next month' % (ts.day, ts.month, ts.year, ts.strftime('%A').decode(enc))
1758 })
1759
1760
1761 ts = now + mxDT.RelativeDateTime(day = val, months = -1)
1762 if val > 0 and val <= gregorian_month_length[ts.month]:
1763 matches.append ({
1764 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_days),
1765 'label': '%s.%s.%s - a %s last month' % (ts.day, ts.month, ts.year, ts.strftime('%A').decode(enc))
1766 })
1767
1768 return matches
1769
1771 """
1772 Turn a string into candidate fuzzy timestamps and auto-completions the user is likely to type.
1773
1774 You MUST have called locale.setlocale(locale.LC_ALL, '')
1775 somewhere in your code previously.
1776
1777 @param default_time: if you want to force the time part of the time
1778 stamp to a given value and the user doesn't type any time part
1779 this value will be used
1780 @type default_time: an mx.DateTime.DateTimeDelta instance
1781
1782 @param patterns: list of [time.strptime compatible date/time pattern, accuracy]
1783 @type patterns: list
1784 """
1785 matches = __single_dot(str2parse)
1786 matches.extend(__numbers_only(str2parse))
1787 matches.extend(__single_slash(str2parse))
1788 ms = __single_char2py_dt(str2parse)
1789 for m in ms:
1790 matches.append ({
1791 'data': cFuzzyTimestamp (
1792 timestamp = m['data'],
1793 accuracy = acc_days
1794 ),
1795 'label': m['label']
1796 })
1797 matches.extend(__explicit_offset(str2parse))
1798
1799
1800 try:
1801
1802 date_only = mxDT.Parser.DateFromString (
1803 text = str2parse,
1804 formats = ('euro', 'iso', 'us', 'altus', 'altiso', 'lit', 'altlit', 'eurlit')
1805 )
1806
1807 time_part = mxDT.Parser.TimeFromString(text = str2parse)
1808 datetime = date_only + time_part
1809 if datetime == date_only:
1810 accuracy = acc_days
1811 if isinstance(default_time, mxDT.DateTimeDeltaType):
1812 datetime = date_only + default_time
1813 accuracy = acc_minutes
1814 else:
1815 accuracy = acc_subseconds
1816 fts = cFuzzyTimestamp (
1817 timestamp = datetime,
1818 accuracy = accuracy
1819 )
1820 matches.append ({
1821 'data': fts,
1822 'label': fts.format_accurately()
1823 })
1824 except (ValueError, mxDT.RangeError):
1825 pass
1826
1827 if patterns is None:
1828 patterns = []
1829
1830 patterns.append(['%Y-%m-%d', acc_days])
1831 patterns.append(['%y-%m-%d', acc_days])
1832 patterns.append(['%Y/%m/%d', acc_days])
1833 patterns.append(['%y/%m/%d', acc_days])
1834
1835 patterns.append(['%d-%m-%Y', acc_days])
1836 patterns.append(['%d-%m-%y', acc_days])
1837 patterns.append(['%d/%m/%Y', acc_days])
1838 patterns.append(['%d/%m/%y', acc_days])
1839
1840 patterns.append(['%m-%d-%Y', acc_days])
1841 patterns.append(['%m-%d-%y', acc_days])
1842 patterns.append(['%m/%d/%Y', acc_days])
1843 patterns.append(['%m/%d/%y', acc_days])
1844
1845 patterns.append(['%Y.%m.%d', acc_days])
1846 patterns.append(['%y.%m.%d', acc_days])
1847
1848
1849 for pattern in patterns:
1850 try:
1851 fts = cFuzzyTimestamp (
1852 timestamp = pyDT.datetime.fromtimestamp(time.mktime(time.strptime(str2parse, pattern[0]))),
1853 accuracy = pattern[1]
1854 )
1855 matches.append ({
1856 'data': fts,
1857 'label': fts.format_accurately()
1858 })
1859 except AttributeError:
1860
1861 break
1862 except OverflowError:
1863
1864 continue
1865 except ValueError:
1866
1867 continue
1868
1869 return matches
1870
1871
1872
1874
1875
1876
1877 """A timestamp implementation with definable inaccuracy.
1878
1879 This class contains an mxDateTime.DateTime instance to
1880 hold the actual timestamp. It adds an accuracy attribute
1881 to allow the programmer to set the precision of the
1882 timestamp.
1883
1884 The timestamp will have to be initialzed with a fully
1885 precise value (which may, of course, contain partially
1886 fake data to make up for missing values). One can then
1887 set the accuracy value to indicate up to which part of
1888 the timestamp the data is valid. Optionally a modifier
1889 can be set to indicate further specification of the
1890 value (such as "summer", "afternoon", etc).
1891
1892 accuracy values:
1893 1: year only
1894 ...
1895 7: everything including milliseconds value
1896
1897 Unfortunately, one cannot directly derive a class from mx.DateTime.DateTime :-(
1898 """
1899
1901
1902 if timestamp is None:
1903 timestamp = mxDT.now()
1904 accuracy = acc_subseconds
1905 modifier = ''
1906
1907 if (accuracy < 1) or (accuracy > 8):
1908 raise ValueError('%s.__init__(): <accuracy> must be between 1 and 8' % self.__class__.__name__)
1909
1910 if isinstance(timestamp, pyDT.datetime):
1911 timestamp = mxDT.DateTime(timestamp.year, timestamp.month, timestamp.day, timestamp.hour, timestamp.minute, timestamp.second)
1912
1913 if type(timestamp) != mxDT.DateTimeType:
1914 raise TypeError('%s.__init__(): <timestamp> must be of mx.DateTime.DateTime or datetime.datetime type' % self.__class__.__name__)
1915
1916 self.timestamp = timestamp
1917 self.accuracy = accuracy
1918 self.modifier = modifier
1919
1920
1921
1923 """Return string representation meaningful to a user, also for %s formatting."""
1924 return self.format_accurately()
1925
1927 """Return string meaningful to a programmer to aid in debugging."""
1928 tmp = '<[%s]: timestamp [%s], accuracy [%s] (%s), modifier [%s] at %s>' % (
1929 self.__class__.__name__,
1930 repr(self.timestamp),
1931 self.accuracy,
1932 _accuracy_strings[self.accuracy],
1933 self.modifier,
1934 id(self)
1935 )
1936 return tmp
1937
1938
1939
1944
1947
1980
1982 return self.timestamp
1983
1985 try:
1986 gmtoffset = self.timestamp.gmtoffset()
1987 except mxDT.Error:
1988
1989
1990 now = mxDT.now()
1991 gmtoffset = now.gmtoffset()
1992 tz = cFixedOffsetTimezone(gmtoffset.minutes, self.timestamp.tz)
1993 secs, msecs = divmod(self.timestamp.second, 1)
1994 ts = pyDT.datetime (
1995 year = self.timestamp.year,
1996 month = self.timestamp.month,
1997 day = self.timestamp.day,
1998 hour = self.timestamp.hour,
1999 minute = self.timestamp.minute,
2000 second = int(secs),
2001 microsecond = int(msecs * 1000),
2002 tzinfo = tz
2003 )
2004 return ts
2005
2006
2007
2008 if __name__ == '__main__':
2009
2010 if len(sys.argv) < 2:
2011 sys.exit()
2012
2013 if sys.argv[1] != "test":
2014 sys.exit()
2015
2016
2017 intervals_as_str = [
2018 '7', '12', ' 12', '12 ', ' 12 ', ' 12 ', '0', '~12', '~ 12', ' ~ 12', ' ~ 12 ',
2019 '12a', '12 a', '12 a', '12j', '12J', '12y', '12Y', ' ~ 12 a ', '~0a',
2020 '12m', '17 m', '12 m', '17M', ' ~ 17 m ', ' ~ 3 / 12 ', '7/12', '0/12',
2021 '12w', '17 w', '12 w', '17W', ' ~ 17 w ', ' ~ 15 / 52', '2/52', '0/52',
2022 '12d', '17 d', '12 t', '17D', ' ~ 17 T ', ' ~ 12 / 7', '3/7', '0/7',
2023 '12h', '17 h', '12 H', '17H', ' ~ 17 h ', ' ~ 36 / 24', '7/24', '0/24',
2024 ' ~ 36 / 60', '7/60', '190/60', '0/60',
2025 '12a1m', '12 a 1 M', '12 a17m', '12j 12m', '12J7m', '12y7m', '12Y7M', ' ~ 12 a 37 m ', '~0a0m',
2026 '10m1w',
2027 'invalid interval input'
2028 ]
2029
2035
2103
2105 print "testing str2interval()"
2106 print "----------------------"
2107
2108 for interval_as_str in intervals_as_str:
2109 print "input: <%s>" % interval_as_str
2110 print " ==>", str2interval(str_interval=interval_as_str)
2111
2112 return True
2113
2115 print "DST currently in effect:", dst_currently_in_effect
2116 print "current UTC offset:", current_local_utc_offset_in_seconds, "seconds"
2117 print "current timezone (interval):", current_local_timezone_interval
2118 print "current timezone (ISO conformant numeric string):", current_local_iso_numeric_timezone_string
2119 print "local timezone class:", cLocalTimezone
2120 print ""
2121 tz = cLocalTimezone()
2122 print "local timezone instance:", tz
2123 print " (total) UTC offset:", tz.utcoffset(pyDT.datetime.now())
2124 print " DST adjustment:", tz.dst(pyDT.datetime.now())
2125 print " timezone name:", tz.tzname(pyDT.datetime.now())
2126 print ""
2127 print "current local timezone:", gmCurrentLocalTimezone
2128 print " (total) UTC offset:", gmCurrentLocalTimezone.utcoffset(pyDT.datetime.now())
2129 print " DST adjustment:", gmCurrentLocalTimezone.dst(pyDT.datetime.now())
2130 print " timezone name:", gmCurrentLocalTimezone.tzname(pyDT.datetime.now())
2131 print ""
2132 print "now here:", pydt_now_here()
2133 print ""
2134
2136 print "testing function str2fuzzy_timestamp_matches"
2137 print "--------------------------------------------"
2138
2139 val = None
2140 while val != 'exit':
2141 val = raw_input('Enter date fragment ("exit" quits): ')
2142 matches = str2fuzzy_timestamp_matches(str2parse = val)
2143 for match in matches:
2144 print 'label shown :', match['label']
2145 print 'data attached:', match['data'], match['data'].timestamp
2146 print ""
2147 print "---------------"
2148
2150 print "testing fuzzy timestamp class"
2151 print "-----------------------------"
2152
2153 ts = mxDT.now()
2154 print "mx.DateTime timestamp", type(ts)
2155 print " print ... :", ts
2156 print " print '%%s' %% ...: %s" % ts
2157 print " str() :", str(ts)
2158 print " repr() :", repr(ts)
2159
2160 fts = cFuzzyTimestamp()
2161 print "\nfuzzy timestamp <%s '%s'>" % ('class', fts.__class__.__name__)
2162 for accuracy in range(1,8):
2163 fts.accuracy = accuracy
2164 print " accuracy : %s (%s)" % (accuracy, _accuracy_strings[accuracy])
2165 print " format_accurately:", fts.format_accurately()
2166 print " strftime() :", fts.strftime('%c')
2167 print " print ... :", fts
2168 print " print '%%s' %% ... : %s" % fts
2169 print " str() :", str(fts)
2170 print " repr() :", repr(fts)
2171 raw_input('press ENTER to continue')
2172
2174 print "testing platform for handling dates before 1970"
2175 print "-----------------------------------------------"
2176 ts = mxDT.DateTime(1935, 4, 2)
2177 fts = cFuzzyTimestamp(timestamp=ts)
2178 print "fts :", fts
2179 print "fts.get_pydt():", fts.get_pydt()
2180
2196
2198 print "testing function str2pydt_matches"
2199 print "---------------------------------"
2200
2201 val = None
2202 while val != 'exit':
2203 val = raw_input('Enter date fragment ("exit" quits): ')
2204 matches = str2pydt_matches(str2parse = val)
2205 for match in matches:
2206 print 'label shown :', match['label']
2207 print 'data attached:', match['data']
2208 print ""
2209 print "---------------"
2210
2222
2223
2224 gmI18N.activate_locale()
2225 gmI18N.install_domain('gnumed')
2226
2227 init()
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237 test_str2pydt()
2238
2239
2240
2241