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