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 __author__ = "K. Hilbert <Karsten.Hilbert@gmx.net>"
42 __license__ = "GPL v2 or later (details at http://www.gnu.org)"
43
44
45 import sys, datetime as pyDT, time, os, re as regex, locale, logging
46
47
48
49 import mx.DateTime as mxDT
50 import psycopg2
51
52
53 if __name__ == '__main__':
54 sys.path.insert(0, '../../')
55 from Gnumed.pycommon import gmI18N
56
57
58 _log = logging.getLogger('gm.datetime')
59 _log.info(u'mx.DateTime version: %s', mxDT.__version__)
60
61 dst_locally_in_use = None
62 dst_currently_in_effect = None
63
64 current_local_utc_offset_in_seconds = None
65 current_local_timezone_interval = None
66 current_local_iso_numeric_timezone_string = None
67 current_local_timezone_name = None
68 py_timezone_name = None
69 py_dst_timezone_name = None
70
71 cLocalTimezone = psycopg2.tz.LocalTimezone
72 cFixedOffsetTimezone = psycopg2.tz.FixedOffsetTimezone
73 gmCurrentLocalTimezone = 'gmCurrentLocalTimezone not initialized'
74
75
76 ( acc_years,
77 acc_months,
78 acc_weeks,
79 acc_days,
80 acc_hours,
81 acc_minutes,
82 acc_seconds,
83 acc_subseconds
84 ) = range(1,9)
85
86 _accuracy_strings = {
87 1: 'years',
88 2: 'months',
89 3: 'weeks',
90 4: 'days',
91 5: 'hours',
92 6: 'minutes',
93 7: 'seconds',
94 8: 'subseconds'
95 }
96
97 gregorian_month_length = {
98 1: 31,
99 2: 28,
100 3: 31,
101 4: 30,
102 5: 31,
103 6: 30,
104 7: 31,
105 8: 31,
106 9: 30,
107 10: 31,
108 11: 30,
109 12: 31
110 }
111
112 avg_days_per_gregorian_year = 365
113 avg_days_per_gregorian_month = 30
114 avg_seconds_per_day = 24 * 60 * 60
115 days_per_week = 7
116
117
118
119
121
122 _log.debug('mx.DateTime.now(): [%s]' % mxDT.now())
123 _log.debug('datetime.now() : [%s]' % pyDT.datetime.now())
124 _log.debug('time.localtime() : [%s]' % str(time.localtime()))
125 _log.debug('time.gmtime() : [%s]' % str(time.gmtime()))
126
127 try:
128 _log.debug('$TZ: [%s]' % os.environ['TZ'])
129 except KeyError:
130 _log.debug('$TZ not defined')
131
132 _log.debug('time.daylight: [%s] (whether or not DST is locally used at all)' % time.daylight)
133 _log.debug('time.timezone: [%s] seconds' % time.timezone)
134 _log.debug('time.altzone : [%s] seconds' % time.altzone)
135 _log.debug('time.tzname : [%s / %s] (non-DST / DST)' % time.tzname)
136 _log.debug('mx.DateTime.now().gmtoffset(): [%s]' % mxDT.now().gmtoffset())
137
138 global py_timezone_name
139 py_timezone_name = time.tzname[0].decode(gmI18N.get_encoding(), 'replace')
140
141 global py_dst_timezone_name
142 py_dst_timezone_name = time.tzname[1].decode(gmI18N.get_encoding(), 'replace')
143
144 global dst_locally_in_use
145 dst_locally_in_use = (time.daylight != 0)
146
147 global dst_currently_in_effect
148 dst_currently_in_effect = bool(time.localtime()[8])
149 _log.debug('DST currently in effect: [%s]' % dst_currently_in_effect)
150
151 if (not dst_locally_in_use) and dst_currently_in_effect:
152 _log.error('system inconsistency: DST not in use - but DST currently in effect ?')
153
154 global current_local_utc_offset_in_seconds
155 msg = 'DST currently%sin effect: using UTC offset of [%s] seconds instead of [%s] seconds'
156 if dst_currently_in_effect:
157 current_local_utc_offset_in_seconds = time.altzone * -1
158 _log.debug(msg % (' ', time.altzone * -1, time.timezone * -1))
159 else:
160 current_local_utc_offset_in_seconds = time.timezone * -1
161 _log.debug(msg % (' not ', time.timezone * -1, time.altzone * -1))
162
163 if current_local_utc_offset_in_seconds > 0:
164 _log.debug('UTC offset is positive, assuming EAST of Greenwich (clock is "ahead")')
165 elif current_local_utc_offset_in_seconds < 0:
166 _log.debug('UTC offset is negative, assuming WEST of Greenwich (clock is "behind")')
167 else:
168 _log.debug('UTC offset is ZERO, assuming Greenwich Time')
169
170 global current_local_timezone_interval
171 current_local_timezone_interval = mxDT.now().gmtoffset()
172 _log.debug('ISO timezone: [%s] (taken from mx.DateTime.now().gmtoffset())' % current_local_timezone_interval)
173
174 global current_local_iso_numeric_timezone_string
175 current_local_iso_numeric_timezone_string = str(current_local_timezone_interval).replace(',', '.')
176
177 global current_local_timezone_name
178 try:
179 current_local_timezone_name = os.environ['TZ'].decode(gmI18N.get_encoding(), 'replace')
180 except KeyError:
181 if dst_currently_in_effect:
182 current_local_timezone_name = time.tzname[1].decode(gmI18N.get_encoding(), 'replace')
183 else:
184 current_local_timezone_name = time.tzname[0].decode(gmI18N.get_encoding(), 'replace')
185
186
187
188
189
190
191 global gmCurrentLocalTimezone
192 gmCurrentLocalTimezone = cFixedOffsetTimezone (
193 offset = (current_local_utc_offset_in_seconds / 60),
194 name = current_local_iso_numeric_timezone_string
195 )
196
197
198
200
201 if isinstance(mxDateTime, pyDT.datetime):
202 return mxDateTime
203
204 try:
205 tz_name = str(mxDateTime.gmtoffset()).replace(',', '.')
206 except mxDT.Error:
207 _log.debug('mx.DateTime cannot gmtoffset() this timestamp, assuming local time')
208 tz_name = current_local_iso_numeric_timezone_string
209
210 if dst_currently_in_effect:
211 tz = cFixedOffsetTimezone (
212 offset = ((time.altzone * -1) / 60),
213 name = tz_name
214 )
215 else:
216 tz = cFixedOffsetTimezone (
217 offset = ((time.timezone * -1) / 60),
218 name = tz_name
219 )
220
221 try:
222 return pyDT.datetime (
223 year = mxDateTime.year,
224 month = mxDateTime.month,
225 day = mxDateTime.day,
226 tzinfo = tz
227 )
228 except:
229 _log.debug (u'error converting mx.DateTime.DateTime to Python: %s-%s-%s %s:%s %s.%s',
230 mxDateTime.year,
231 mxDateTime.month,
232 mxDateTime.day,
233 mxDateTime.hour,
234 mxDateTime.minute,
235 mxDateTime.second,
236 mxDateTime.tz
237 )
238 raise
239
251
252 -def pydt_strftime(dt=None, format='%Y %b %d %H:%M.%S', encoding=None, accuracy=None):
253
254 if dt is None:
255 dt = pydt_now_here()
256
257 if encoding is None:
258 encoding = gmI18N.get_encoding()
259
260 try:
261 return dt.strftime(format).decode(encoding, 'replace')
262 except ValueError:
263 _log.exception('Python cannot strftime() this <datetime>')
264
265 if accuracy == acc_days:
266 return u'%04d-%02d-%02d' % (
267 dt.year,
268 dt.month,
269 dt.day
270 )
271
272 if accuracy == acc_minutes:
273 return u'%04d-%02d-%02d %02d:%02d' % (
274 dt.year,
275 dt.month,
276 dt.day,
277 dt.hour,
278 dt.minute
279 )
280
281 return u'%04d-%02d-%02d %02d:%02d:%02d' % (
282 dt.year,
283 dt.month,
284 dt.day,
285 dt.hour,
286 dt.minute,
287 dt.second
288 )
289
293
296
300
301
302
304 if not wxDate.IsValid():
305 raise ValueError (u'invalid wxDate: %s-%s-%s %s:%s %s.%s',
306 wxDate.GetYear(),
307 wxDate.GetMonth(),
308 wxDate.GetDay(),
309 wxDate.GetHour(),
310 wxDate.GetMinute(),
311 wxDate.GetSecond(),
312 wxDate.GetMillisecond()
313 )
314
315 try:
316 return pyDT.datetime (
317 year = wxDate.GetYear(),
318 month = wxDate.GetMonth() + 1,
319 day = wxDate.GetDay(),
320 tzinfo = gmCurrentLocalTimezone
321 )
322 except:
323 _log.debug (u'error converting wxDateTime to Python: %s-%s-%s %s:%s %s.%s',
324 wxDate.GetYear(),
325 wxDate.GetMonth(),
326 wxDate.GetDay(),
327 wxDate.GetHour(),
328 wxDate.GetMinute(),
329 wxDate.GetSecond(),
330 wxDate.GetMillisecond()
331 )
332 raise
333
335 _log.debug(u'setting wx.DateTime from: %s-%s-%s', py_dt.year, py_dt.month, py_dt.day)
336
337
338
339 wxdt = wx.DateTimeFromDMY(py_dt.day, py_dt.month-1, py_dt.year)
340 return wxdt
341
342
343
452
515
517
518 div, remainder = divmod(year, 4)
519
520 if remainder > 0:
521 return False
522
523
524 div, remainder = divmod(year, 100)
525
526 if remainder > 0:
527 return True
528
529
530 div, remainder = divmod(year, 400)
531
532 if remainder == 0:
533 return True
534
535 return False
536
538 """The result of this is a tuple (years, ..., seconds) as one would
539 'expect' an age to look like, that is, simple differences between
540 the fields:
541
542 (years, months, days, hours, minutes, seconds)
543
544 This does not take into account time zones which may
545 shift the result by one day.
546
547 <start> and <end> must by python datetime instances
548 <end> is assumed to be "now" if not given
549 """
550 if end is None:
551 end = pyDT.datetime.now(gmCurrentLocalTimezone)
552
553 if end < start:
554 raise ValueError('calculate_apparent_age(): <end> (%s) before <start> (%s)' % (end, start))
555
556 if end == start:
557 return (0, 0, 0, 0, 0, 0)
558
559
560 if end.month == 2:
561 if end.day == 29:
562 if not is_leap_year(start.year):
563 end = end.replace(day = 28)
564
565
566 years = end.year - start.year
567 end = end.replace(year = start.year)
568 if end < start:
569 years = years - 1
570
571
572 if end.month == start.month:
573 if end < start:
574 months = 11
575 else:
576 months = 0
577 else:
578 months = end.month - start.month
579 if months < 0:
580 months = months + 12
581 if end.day > gregorian_month_length[start.month]:
582 end = end.replace(month = start.month, day = gregorian_month_length[start.month])
583 else:
584 end = end.replace(month = start.month)
585 if end < start:
586 months = months - 1
587
588
589 if end.day == start.day:
590 if end < start:
591 days = gregorian_month_length[start.month] - 1
592 else:
593 days = 0
594 else:
595 days = end.day - start.day
596 if days < 0:
597 days = days + gregorian_month_length[start.month]
598 end = end.replace(day = start.day)
599 if end < start:
600 days = days - 1
601
602
603 if end.hour == start.hour:
604 hours = 0
605 else:
606 hours = end.hour - start.hour
607 if hours < 0:
608 hours = hours + 24
609 end = end.replace(hour = start.hour)
610 if end < start:
611 hours = hours - 1
612
613
614 if end.minute == start.minute:
615 minutes = 0
616 else:
617 minutes = end.minute - start.minute
618 if minutes < 0:
619 minutes = minutes + 60
620 end = end.replace(minute = start.minute)
621 if end < start:
622 minutes = minutes - 1
623
624
625 if end.second == start.second:
626 seconds = 0
627 else:
628 seconds = end.second - start.second
629 if seconds < 0:
630 seconds = seconds + 60
631 end = end.replace(second = start.second)
632 if end < start:
633 seconds = seconds - 1
634
635 return (years, months, days, hours, minutes, seconds)
636
740
742
743 unit_keys = {
744 'year': _('yYaA_keys_year'),
745 'month': _('mM_keys_month'),
746 'week': _('wW_keys_week'),
747 'day': _('dD_keys_day'),
748 'hour': _('hH_keys_hour')
749 }
750
751 str_interval = str_interval.strip()
752
753
754 keys = '|'.join(list(unit_keys['year'].replace('_keys_year', u'')))
755 if regex.match(u'^~*(\s|\t)*\d+(%s)*$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE):
756 return pyDT.timedelta(days = (int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]) * avg_days_per_gregorian_year))
757
758
759 keys = '|'.join(list(unit_keys['month'].replace('_keys_month', u'')))
760 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE):
761 years, months = divmod (
762 int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]),
763 12
764 )
765 return pyDT.timedelta(days = ((years * avg_days_per_gregorian_year) + (months * avg_days_per_gregorian_month)))
766
767
768 keys = '|'.join(list(unit_keys['week'].replace('_keys_week', u'')))
769 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE):
770 return pyDT.timedelta(weeks = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
771
772
773 keys = '|'.join(list(unit_keys['day'].replace('_keys_day', u'')))
774 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE):
775 return pyDT.timedelta(days = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
776
777
778 keys = '|'.join(list(unit_keys['hour'].replace('_keys_hour', u'')))
779 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE):
780 return pyDT.timedelta(hours = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
781
782
783 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*12$', str_interval, flags = regex.LOCALE | regex.UNICODE):
784 years, months = divmod (
785 int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]),
786 12
787 )
788 return pyDT.timedelta(days = ((years * avg_days_per_gregorian_year) + (months * avg_days_per_gregorian_month)))
789
790
791 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*52$', str_interval, flags = regex.LOCALE | regex.UNICODE):
792
793 return pyDT.timedelta(weeks = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
794
795
796 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*7$', str_interval, flags = regex.LOCALE | regex.UNICODE):
797 return pyDT.timedelta(days = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
798
799
800 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*24$', str_interval, flags = regex.LOCALE | regex.UNICODE):
801 return pyDT.timedelta(hours = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
802
803
804 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*60$', str_interval, flags = regex.LOCALE | regex.UNICODE):
805 return pyDT.timedelta(minutes = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
806
807
808 keys_year = '|'.join(list(unit_keys['year'].replace('_keys_year', u'')))
809 keys_month = '|'.join(list(unit_keys['month'].replace('_keys_month', u'')))
810 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):
811 parts = regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)
812 years, months = divmod(int(parts[1]), 12)
813 years += int(parts[0])
814 return pyDT.timedelta(days = ((years * avg_days_per_gregorian_year) + (months * avg_days_per_gregorian_month)))
815
816
817 keys_month = '|'.join(list(unit_keys['month'].replace('_keys_month', u'')))
818 keys_week = '|'.join(list(unit_keys['week'].replace('_keys_week', u'')))
819 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):
820 parts = regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)
821 months, weeks = divmod(int(parts[1]), 4)
822 months += int(parts[0])
823 return pyDT.timedelta(days = ((months * avg_days_per_gregorian_month) + (weeks * days_per_week)))
824
825 return None
826
827
828
830 """This matches on single characters.
831
832 Spaces and tabs are discarded.
833
834 Default is 'ndmy':
835 n - _N_ow
836 d - to_D_ay
837 m - to_M_orrow Someone please suggest a synonym ! ("2" does not cut it ...)
838 y - _Y_esterday
839
840 This also defines the significance of the order of the characters.
841 """
842 if trigger_chars is None:
843 trigger_chars = _('ndmy (single character date triggers)')[:4].lower()
844
845 str2parse = str2parse.strip().lower()
846
847 if len(str2parse) != 1:
848 return []
849
850 if str2parse not in trigger_chars:
851 return []
852
853 now = mxDT.now()
854 enc = gmI18N.get_encoding()
855
856
857
858
859 if str2parse == trigger_chars[0]:
860 return [{
861 'data': mxdt2py_dt(now),
862 'label': _('right now (%s, %s)') % (now.strftime('%A').decode(enc), now)
863 }]
864
865
866 if str2parse == trigger_chars[1]:
867 return [{
868 'data': mxdt2py_dt(now),
869 'label': _('today (%s)') % now.strftime('%A, %Y-%m-%d').decode(enc)
870 }]
871
872
873 if str2parse == trigger_chars[2]:
874 ts = now + mxDT.RelativeDateTime(days = +1)
875 return [{
876 'data': mxdt2py_dt(ts),
877 'label': _('tomorrow (%s)') % ts.strftime('%A, %Y-%m-%d').decode(enc)
878 }]
879
880
881 if str2parse == trigger_chars[3]:
882 ts = now + mxDT.RelativeDateTime(days = -1)
883 return [{
884 'data': mxdt2py_dt(ts),
885 'label': _('yesterday (%s)') % ts.strftime('%A, %Y-%m-%d').decode(enc)
886 }]
887
888 return []
889
891 """Expand fragments containing a single dot.
892
893 Standard colloquial date format in Germany: day.month.year
894
895 "14."
896 - the 14th of the current month
897 - the 14th of next month
898 "-14."
899 - the 14th of last month
900 """
901 str2parse = str2parse.strip()
902
903 if not str2parse.endswith(u'.'):
904 return []
905
906 str2parse = str2parse[:-1]
907 try:
908 day_val = int(str2parse)
909 except ValueError:
910 return []
911
912 if (day_val < -31) or (day_val > 31) or (day_val == 0):
913 return []
914
915 now = mxDT.now()
916 enc = gmI18N.get_encoding()
917 matches = []
918
919
920 if day_val < 0:
921 ts = now + mxDT.RelativeDateTime(day = abs(day_val), months = -1)
922 if abs(day_val) <= gregorian_month_length[ts.month]:
923 matches.append ({
924 'data': mxdt2py_dt(ts),
925 'label': _('%s-%s-%s: a %s last month') % (ts.year, ts.month, ts.day, ts.strftime('%A').decode(enc))
926 })
927
928
929 if day_val > 0:
930 ts = now + mxDT.RelativeDateTime(day = day_val)
931 if day_val <= gregorian_month_length[ts.month]:
932 matches.append ({
933 'data': mxdt2py_dt(ts),
934 'label': _('%s-%s-%s: a %s this month') % (ts.year, ts.month, ts.day, ts.strftime('%A').decode(enc))
935 })
936
937
938 if day_val > 0:
939 ts = now + mxDT.RelativeDateTime(day = day_val, months = +1)
940 if day_val <= gregorian_month_length[ts.month]:
941 matches.append ({
942 'data': mxdt2py_dt(ts),
943 'label': _('%s-%s-%s: a %s next month') % (ts.year, ts.month, ts.day, ts.strftime('%A').decode(enc))
944 })
945
946
947 if day_val > 0:
948 ts = now + mxDT.RelativeDateTime(day = day_val, months = -1)
949 if day_val <= gregorian_month_length[ts.month]:
950 matches.append ({
951 'data': mxdt2py_dt(ts),
952 'label': _('%s-%s-%s: a %s last month') % (ts.year, ts.month, ts.day, ts.strftime('%A').decode(enc))
953 })
954
955 return matches
956
958 """Expand fragments containing a single slash.
959
960 "5/"
961 - 2005/ (2000 - 2025)
962 - 1995/ (1990 - 1999)
963 - Mai/current year
964 - Mai/next year
965 - Mai/last year
966 - Mai/200x
967 - Mai/20xx
968 - Mai/199x
969 - Mai/198x
970 - Mai/197x
971 - Mai/19xx
972
973 5/1999
974 6/2004
975 """
976 str2parse = str2parse.strip()
977
978 now = mxDT.now()
979 enc = gmI18N.get_encoding()
980
981
982 if regex.match(r"^\d{1,2}(\s|\t)*/+(\s|\t)*\d{4}$", str2parse, flags = regex.LOCALE | regex.UNICODE):
983 parts = regex.findall(r'\d+', str2parse, flags = regex.LOCALE | regex.UNICODE)
984 ts = now + mxDT.RelativeDateTime(year = int(parts[1]), month = int(parts[0]))
985 return [{
986 'data': mxdt2py_dt(ts),
987 'label': ts.strftime('%Y-%m-%d').decode(enc)
988 }]
989
990 matches = []
991
992 if regex.match(r"^\d{1,2}(\s|\t)*/+$", str2parse, flags = regex.LOCALE | regex.UNICODE):
993 val = int(str2parse.rstrip(u'/').strip())
994
995
996 if val < 100 and val >= 0:
997 matches.append ({
998 'data': None,
999 'label': '%s-' % (val + 1900)
1000 })
1001
1002
1003 if val < 26 and val >= 0:
1004 matches.append ({
1005 'data': None,
1006 'label': '%s-' % (val + 2000)
1007 })
1008
1009
1010 if val < 10 and val >= 0:
1011 matches.append ({
1012 'data': None,
1013 'label': '%s-' % (val + 1990)
1014 })
1015
1016 if val < 13 and val > 0:
1017
1018 matches.append ({
1019 'data': None,
1020 'label': '%s-%.2d-' % (now.year, val)
1021 })
1022
1023 ts = now + mxDT.RelativeDateTime(years = 1)
1024 matches.append ({
1025 'data': None,
1026 'label': '%s-%.2d-' % (ts.year, val)
1027 })
1028
1029 ts = now + mxDT.RelativeDateTime(years = -1)
1030 matches.append ({
1031 'data': None,
1032 'label': '%s-%.2d-' % (ts.year, val)
1033 })
1034
1035 matches.append ({
1036 'data': None,
1037 'label': '201?-%.2d-' % val
1038 })
1039
1040 matches.append ({
1041 'data': None,
1042 'label': '200?-%.2d-' % val
1043 })
1044
1045 matches.append ({
1046 'data': None,
1047 'label': '20??-%.2d-' % val
1048 })
1049
1050 matches.append ({
1051 'data': None,
1052 'label': '199?-%.2d-' % val
1053 })
1054
1055 matches.append ({
1056 'data': None,
1057 'label': '198?-%.2d-' % val
1058 })
1059
1060 matches.append ({
1061 'data': None,
1062 'label': '197?-%.2d-' % val
1063 })
1064
1065 matches.append ({
1066 'data': None,
1067 'label': '19??-%.2d-' % val
1068 })
1069
1070 return matches
1071
1073 """This matches on single numbers.
1074
1075 Spaces or tabs are discarded.
1076 """
1077 try:
1078 val = int(str2parse.strip())
1079 except ValueError:
1080 return []
1081
1082
1083
1084 enc = gmI18N.get_encoding()
1085 now = mxDT.now()
1086
1087 matches = []
1088
1089
1090 if (1850 < val) and (val < 2100):
1091 ts = now + mxDT.RelativeDateTime(year = val)
1092 matches.append ({
1093 'data': mxdt2py_dt(ts),
1094 'label': ts.strftime('%Y-%m-%d')
1095 })
1096
1097
1098 if (val > 0) and (val <= gregorian_month_length[now.month]):
1099 ts = now + mxDT.RelativeDateTime(day = val)
1100 matches.append ({
1101 'data': mxdt2py_dt(ts),
1102 'label': _('%d. of %s (this month): a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc))
1103 })
1104
1105
1106 if (val > 0) and (val < 32):
1107 ts = now + mxDT.RelativeDateTime(months = 1, day = val)
1108 matches.append ({
1109 'data': mxdt2py_dt(ts),
1110 'label': _('%d. of %s (next month): a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc))
1111 })
1112
1113
1114 if (val > 0) and (val < 32):
1115 ts = now + mxDT.RelativeDateTime(months = -1, day = val)
1116 matches.append ({
1117 'data': mxdt2py_dt(ts),
1118 'label': _('%d. of %s (last month): a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc))
1119 })
1120
1121
1122 if (val > 0) and (val <= 400):
1123 ts = now + mxDT.RelativeDateTime(days = val)
1124 matches.append ({
1125 'data': mxdt2py_dt(ts),
1126 'label': _('in %d day(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1127 })
1128 if (val < 0) and (val >= -400):
1129 ts = now - mxDT.RelativeDateTime(days = abs(val))
1130 matches.append ({
1131 'data': mxdt2py_dt(ts),
1132 'label': _('%d day(s) ago: %s') % (abs(val), ts.strftime('%A, %Y-%m-%d').decode(enc))
1133 })
1134
1135
1136 if (val > 0) and (val <= 50):
1137 ts = now + mxDT.RelativeDateTime(weeks = val)
1138 matches.append ({
1139 'data': mxdt2py_dt(ts),
1140 'label': _('in %d week(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1141 })
1142 if (val < 0) and (val >= -50):
1143 ts = now - mxDT.RelativeDateTime(weeks = abs(val))
1144 matches.append ({
1145 'data': mxdt2py_dt(ts),
1146 'label': _('%d week(s) ago: %s') % (abs(val), ts.strftime('%A, %Y-%m-%d').decode(enc))
1147 })
1148
1149
1150 if (val < 13) and (val > 0):
1151
1152 ts = now + mxDT.RelativeDateTime(month = val)
1153 matches.append ({
1154 'data': mxdt2py_dt(ts),
1155 'label': _('%s (%s this year)') % (ts.strftime('%Y-%m-%d'), ts.strftime('%B').decode(enc))
1156 })
1157
1158
1159 ts = now + mxDT.RelativeDateTime(years = 1, month = val)
1160 matches.append ({
1161 'data': mxdt2py_dt(ts),
1162 'label': _('%s (%s next year)') % (ts.strftime('%Y-%m-%d'), ts.strftime('%B').decode(enc))
1163 })
1164
1165
1166 ts = now + mxDT.RelativeDateTime(years = -1, month = val)
1167 matches.append ({
1168 'data': mxdt2py_dt(ts),
1169 'label': _('%s (%s last year)') % (ts.strftime('%Y-%m-%d'), ts.strftime('%B').decode(enc))
1170 })
1171
1172
1173 matches.append ({
1174 'data': None,
1175 'label': '200?-%s' % val
1176 })
1177 matches.append ({
1178 'data': None,
1179 'label': '199?-%s' % val
1180 })
1181 matches.append ({
1182 'data': None,
1183 'label': '198?-%s' % val
1184 })
1185 matches.append ({
1186 'data': None,
1187 'label': '19??-%s' % val
1188 })
1189
1190
1191 if (val < 8) and (val > 0):
1192
1193 ts = now + mxDT.RelativeDateTime(weekday = (val-1, 0))
1194 matches.append ({
1195 'data': mxdt2py_dt(ts),
1196 'label': _('%s this week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc))
1197 })
1198
1199
1200 ts = now + mxDT.RelativeDateTime(weeks = +1, weekday = (val-1, 0))
1201 matches.append ({
1202 'data': mxdt2py_dt(ts),
1203 'label': _('%s next week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc))
1204 })
1205
1206
1207 ts = now + mxDT.RelativeDateTime(weeks = -1, weekday = (val-1, 0))
1208 matches.append ({
1209 'data': mxdt2py_dt(ts),
1210 'label': _('%s last week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc))
1211 })
1212
1213 if (val < 100) and (val > 0):
1214 matches.append ({
1215 'data': None,
1216 'label': '%s-' % (1900 + val)
1217 })
1218
1219 if val == 201:
1220 tmp = {
1221 'data': mxdt2py_dt(now),
1222 'label': now.strftime('%Y-%m-%d')
1223 }
1224 matches.append(tmp)
1225 matches.append ({
1226 'data': None,
1227 'label': now.strftime('%Y-%m')
1228 })
1229 matches.append ({
1230 'data': None,
1231 'label': now.strftime('%Y')
1232 })
1233 matches.append ({
1234 'data': None,
1235 'label': '%s-' % (now.year + 1)
1236 })
1237 matches.append ({
1238 'data': None,
1239 'label': '%s-' % (now.year - 1)
1240 })
1241
1242 if val < 200 and val >= 190:
1243 for i in range(10):
1244 matches.append ({
1245 'data': None,
1246 'label': '%s%s-' % (val, i)
1247 })
1248
1249 return matches
1250
1252 """
1253 Default is 'hdwmy':
1254 h - hours
1255 d - days
1256 w - weeks
1257 m - months
1258 y - years
1259
1260 This also defines the significance of the order of the characters.
1261 """
1262 if offset_chars is None:
1263 offset_chars = _('hdwmy (single character date offset triggers)')[:5].lower()
1264
1265 str2parse = str2parse.strip()
1266
1267
1268 if not regex.match(r"^(\+|-)?(\s|\t)*\d{1,2}(\s|\t)*[%s]$" % offset_chars, str2parse, flags = regex.LOCALE | regex.UNICODE):
1269 return []
1270
1271
1272 if str2parse.startswith(u'-'):
1273 is_future = False
1274 str2parse = str2parse[1:].strip()
1275 else:
1276 is_future = True
1277 str2parse = str2parse.replace(u'+', u'').strip()
1278
1279 val = int(regex.findall(u'\d{1,2}', str2parse, flags = regex.LOCALE | regex.UNICODE)[0])
1280 offset_char = regex.findall(u'[%s]' % offset_chars, str2parse, flags = regex.LOCALE | regex.UNICODE)[0].lower()
1281
1282 now = mxDT.now()
1283 enc = gmI18N.get_encoding()
1284
1285 ts = None
1286
1287 if offset_char == offset_chars[0]:
1288 if is_future:
1289 ts = now + mxDT.RelativeDateTime(hours = val)
1290 label = _('in %d hour(s): %s') % (val, ts.strftime('%H:%M'))
1291 else:
1292 ts = now - mxDT.RelativeDateTime(hours = val)
1293 label = _('%d hour(s) ago: %s') % (val, ts.strftime('%H:%M'))
1294
1295 elif offset_char == offset_chars[1]:
1296 if is_future:
1297 ts = now + mxDT.RelativeDateTime(days = val)
1298 label = _('in %d day(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1299 else:
1300 ts = now - mxDT.RelativeDateTime(days = val)
1301 label = _('%d day(s) ago: %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1302
1303 elif offset_char == offset_chars[2]:
1304 if is_future:
1305 ts = now + mxDT.RelativeDateTime(weeks = val)
1306 label = _('in %d week(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1307 else:
1308 ts = now - mxDT.RelativeDateTime(weeks = val)
1309 label = _('%d week(s) ago: %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1310
1311 elif offset_char == offset_chars[3]:
1312 if is_future:
1313 ts = now + mxDT.RelativeDateTime(months = val)
1314 label = _('in %d month(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1315 else:
1316 ts = now - mxDT.RelativeDateTime(months = val)
1317 label = _('%d month(s) ago: %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1318
1319 elif offset_char == offset_chars[4]:
1320 if is_future:
1321 ts = now + mxDT.RelativeDateTime(years = val)
1322 label = _('in %d year(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1323 else:
1324 ts = now - mxDT.RelativeDateTime(years = val)
1325 label = _('%d year(s) ago: %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1326
1327 if ts is None:
1328 return []
1329
1330 return [{
1331 'data': mxdt2py_dt(ts),
1332 'label': label
1333 }]
1334
1336 """Turn a string into candidate dates and auto-completions the user is likely to type.
1337
1338 You MUST have called locale.setlocale(locale.LC_ALL, '')
1339 somewhere in your code previously.
1340
1341 @param patterns: list of time.strptime compatible date pattern
1342 @type patterns: list
1343 """
1344 matches = []
1345 matches.extend(__single_dot2py_dt(str2parse))
1346 matches.extend(__numbers_only2py_dt(str2parse))
1347 matches.extend(__single_slash2py_dt(str2parse))
1348 matches.extend(__single_char2py_dt(str2parse))
1349 matches.extend(__explicit_offset2py_dt(str2parse))
1350
1351
1352 try:
1353 date = mxDT.Parser.DateFromString (
1354 text = str2parse,
1355 formats = ('euro', 'iso', 'us', 'altus', 'altiso', 'lit', 'altlit', 'eurlit')
1356 )
1357 matches.append ({
1358 'data': mxdt2py_dt(date),
1359 'label': date.strftime('%Y-%m-%d')
1360 })
1361 except (ValueError, OverflowError, mxDT.RangeError):
1362 pass
1363
1364
1365 if patterns is None:
1366 patterns = []
1367
1368 patterns.append('%Y-%m-%d')
1369 patterns.append('%y-%m-%d')
1370 patterns.append('%Y/%m/%d')
1371 patterns.append('%y/%m/%d')
1372
1373 patterns.append('%d-%m-%Y')
1374 patterns.append('%d-%m-%y')
1375 patterns.append('%d/%m/%Y')
1376 patterns.append('%d/%m/%y')
1377
1378 patterns.append('%m-%d-%Y')
1379 patterns.append('%m-%d-%y')
1380 patterns.append('%m/%d/%Y')
1381 patterns.append('%m/%d/%y')
1382
1383 patterns.append('%Y.%m.%d')
1384 patterns.append('%y.%m.%d')
1385
1386 for pattern in patterns:
1387 try:
1388 date = pyDT.datetime.strptime(str2parse, pattern).replace (
1389 hour = 11,
1390 minute = 11,
1391 second = 11,
1392 tzinfo = gmCurrentLocalTimezone
1393 )
1394 matches.append ({
1395 'data': date,
1396 'label': pydt_strftime(date, format = '%Y-%m-%d', accuracy = acc_days)
1397 })
1398 except AttributeError:
1399
1400 break
1401 except OverflowError:
1402
1403 continue
1404 except ValueError:
1405
1406 continue
1407
1408 return matches
1409
1410
1411
1413 """
1414 Default is 'hdwm':
1415 h - hours
1416 d - days
1417 w - weeks
1418 m - months
1419 y - years
1420
1421 This also defines the significance of the order of the characters.
1422 """
1423 if offset_chars is None:
1424 offset_chars = _('hdwmy (single character date offset triggers)')[:5].lower()
1425
1426
1427 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):
1428 return []
1429 val = int(regex.findall(u'\d{1,2}', str2parse, flags = regex.LOCALE | regex.UNICODE)[0])
1430 offset_char = regex.findall(u'[%s]' % offset_chars, str2parse, flags = regex.LOCALE | regex.UNICODE)[0].lower()
1431
1432 now = mxDT.now()
1433 enc = gmI18N.get_encoding()
1434
1435
1436 is_future = True
1437 if str2parse.find('-') > -1:
1438 is_future = False
1439
1440 ts = None
1441
1442 if offset_char == offset_chars[0]:
1443 if is_future:
1444 ts = now + mxDT.RelativeDateTime(hours = val)
1445 label = _('in %d hour(s) - %s') % (val, ts.strftime('%H:%M'))
1446 else:
1447 ts = now - mxDT.RelativeDateTime(hours = val)
1448 label = _('%d hour(s) ago - %s') % (val, ts.strftime('%H:%M'))
1449 accuracy = acc_subseconds
1450
1451 elif offset_char == offset_chars[1]:
1452 if is_future:
1453 ts = now + mxDT.RelativeDateTime(days = val)
1454 label = _('in %d day(s) - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1455 else:
1456 ts = now - mxDT.RelativeDateTime(days = val)
1457 label = _('%d day(s) ago - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1458 accuracy = acc_days
1459
1460 elif offset_char == offset_chars[2]:
1461 if is_future:
1462 ts = now + mxDT.RelativeDateTime(weeks = val)
1463 label = _('in %d week(s) - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1464 else:
1465 ts = now - mxDT.RelativeDateTime(weeks = val)
1466 label = _('%d week(s) ago - %s)') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1467 accuracy = acc_days
1468
1469 elif offset_char == offset_chars[3]:
1470 if is_future:
1471 ts = now + mxDT.RelativeDateTime(months = val)
1472 label = _('in %d month(s) - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1473 else:
1474 ts = now - mxDT.RelativeDateTime(months = val)
1475 label = _('%d month(s) ago - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1476 accuracy = acc_days
1477
1478 elif offset_char == offset_chars[4]:
1479 if is_future:
1480 ts = now + mxDT.RelativeDateTime(years = val)
1481 label = _('in %d year(s) - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1482 else:
1483 ts = now - mxDT.RelativeDateTime(years = val)
1484 label = _('%d year(s) ago - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1485 accuracy = acc_months
1486
1487 if ts is None:
1488 return []
1489
1490 tmp = {
1491 'data': cFuzzyTimestamp(timestamp = ts, accuracy = accuracy),
1492 'label': label
1493 }
1494 return [tmp]
1495
1497 """Expand fragments containing a single slash.
1498
1499 "5/"
1500 - 2005/ (2000 - 2025)
1501 - 1995/ (1990 - 1999)
1502 - Mai/current year
1503 - Mai/next year
1504 - Mai/last year
1505 - Mai/200x
1506 - Mai/20xx
1507 - Mai/199x
1508 - Mai/198x
1509 - Mai/197x
1510 - Mai/19xx
1511 """
1512 matches = []
1513 now = mxDT.now()
1514 if regex.match(u"^(\s|\t)*\d{1,2}(\s|\t)*/+(\s|\t)*$", str2parse, flags = regex.LOCALE | regex.UNICODE):
1515 val = int(regex.findall(u'\d+', str2parse, flags = regex.LOCALE | regex.UNICODE)[0])
1516
1517 if val < 100 and val >= 0:
1518 matches.append ({
1519 'data': None,
1520 'label': '%s/' % (val + 1900)
1521 })
1522
1523 if val < 26 and val >= 0:
1524 matches.append ({
1525 'data': None,
1526 'label': '%s/' % (val + 2000)
1527 })
1528
1529 if val < 10 and val >= 0:
1530 matches.append ({
1531 'data': None,
1532 'label': '%s/' % (val + 1990)
1533 })
1534
1535 if val < 13 and val > 0:
1536 matches.append ({
1537 'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_months),
1538 'label': '%.2d/%s' % (val, now.year)
1539 })
1540 ts = now + mxDT.RelativeDateTime(years = 1)
1541 matches.append ({
1542 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_months),
1543 'label': '%.2d/%s' % (val, ts.year)
1544 })
1545 ts = now + mxDT.RelativeDateTime(years = -1)
1546 matches.append ({
1547 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_months),
1548 'label': '%.2d/%s' % (val, ts.year)
1549 })
1550 matches.append ({
1551 'data': None,
1552 'label': '%.2d/200' % val
1553 })
1554 matches.append ({
1555 'data': None,
1556 'label': '%.2d/20' % val
1557 })
1558 matches.append ({
1559 'data': None,
1560 'label': '%.2d/199' % val
1561 })
1562 matches.append ({
1563 'data': None,
1564 'label': '%.2d/198' % val
1565 })
1566 matches.append ({
1567 'data': None,
1568 'label': '%.2d/197' % val
1569 })
1570 matches.append ({
1571 'data': None,
1572 'label': '%.2d/19' % val
1573 })
1574
1575 elif regex.match(u"^(\s|\t)*\d{1,2}(\s|\t)*/+(\s|\t)*\d{4}(\s|\t)*$", str2parse, flags = regex.LOCALE | regex.UNICODE):
1576 parts = regex.findall(u'\d+', str2parse, flags = regex.LOCALE | regex.UNICODE)
1577 fts = cFuzzyTimestamp (
1578 timestamp = mxDT.now() + mxDT.RelativeDateTime(year = int(parts[1]), month = int(parts[0])),
1579 accuracy = acc_months
1580 )
1581 matches.append ({
1582 'data': fts,
1583 'label': fts.format_accurately()
1584 })
1585
1586 return matches
1587
1589 """This matches on single numbers.
1590
1591 Spaces or tabs are discarded.
1592 """
1593 if not regex.match(u"^(\s|\t)*\d{1,4}(\s|\t)*$", str2parse, flags = regex.LOCALE | regex.UNICODE):
1594 return []
1595
1596
1597
1598 enc = gmI18N.get_encoding()
1599 now = mxDT.now()
1600 val = int(regex.findall(u'\d{1,4}', str2parse, flags = regex.LOCALE | regex.UNICODE)[0])
1601
1602 matches = []
1603
1604
1605 if (1850 < val) and (val < 2100):
1606 ts = now + mxDT.RelativeDateTime(year = val)
1607 target_date = cFuzzyTimestamp (
1608 timestamp = ts,
1609 accuracy = acc_years
1610 )
1611 tmp = {
1612 'data': target_date,
1613 'label': '%s' % target_date
1614 }
1615 matches.append(tmp)
1616
1617
1618 if val <= gregorian_month_length[now.month]:
1619 ts = now + mxDT.RelativeDateTime(day = val)
1620 target_date = cFuzzyTimestamp (
1621 timestamp = ts,
1622 accuracy = acc_days
1623 )
1624 tmp = {
1625 'data': target_date,
1626 'label': _('%d. of %s (this month) - a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc))
1627 }
1628 matches.append(tmp)
1629
1630
1631 if val <= gregorian_month_length[(now + mxDT.RelativeDateTime(months = 1)).month]:
1632 ts = now + mxDT.RelativeDateTime(months = 1, day = val)
1633 target_date = cFuzzyTimestamp (
1634 timestamp = ts,
1635 accuracy = acc_days
1636 )
1637 tmp = {
1638 'data': target_date,
1639 'label': _('%d. of %s (next month) - a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc))
1640 }
1641 matches.append(tmp)
1642
1643
1644 if val <= gregorian_month_length[(now + mxDT.RelativeDateTime(months = -1)).month]:
1645 ts = now + mxDT.RelativeDateTime(months = -1, day = val)
1646 target_date = cFuzzyTimestamp (
1647 timestamp = ts,
1648 accuracy = acc_days
1649 )
1650 tmp = {
1651 'data': target_date,
1652 'label': _('%d. of %s (last month) - a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc))
1653 }
1654 matches.append(tmp)
1655
1656
1657 if val <= 400:
1658 ts = now + mxDT.RelativeDateTime(days = val)
1659 target_date = cFuzzyTimestamp (
1660 timestamp = ts
1661 )
1662 tmp = {
1663 'data': target_date,
1664 'label': _('in %d day(s) - %s') % (val, target_date.timestamp.strftime('%A, %Y-%m-%d').decode(enc))
1665 }
1666 matches.append(tmp)
1667
1668
1669 if val <= 50:
1670 ts = now + mxDT.RelativeDateTime(weeks = val)
1671 target_date = cFuzzyTimestamp (
1672 timestamp = ts
1673 )
1674 tmp = {
1675 'data': target_date,
1676 'label': _('in %d week(s) - %s') % (val, target_date.timestamp.strftime('%A, %Y-%m-%d').decode(enc))
1677 }
1678 matches.append(tmp)
1679
1680
1681 if val < 13:
1682
1683 ts = now + mxDT.RelativeDateTime(month = val)
1684 target_date = cFuzzyTimestamp (
1685 timestamp = ts,
1686 accuracy = acc_months
1687 )
1688 tmp = {
1689 'data': target_date,
1690 'label': _('%s (%s this year)') % (target_date, ts.strftime('%B').decode(enc))
1691 }
1692 matches.append(tmp)
1693
1694
1695 ts = now + mxDT.RelativeDateTime(years = 1, month = val)
1696 target_date = cFuzzyTimestamp (
1697 timestamp = ts,
1698 accuracy = acc_months
1699 )
1700 tmp = {
1701 'data': target_date,
1702 'label': _('%s (%s next year)') % (target_date, ts.strftime('%B').decode(enc))
1703 }
1704 matches.append(tmp)
1705
1706
1707 ts = now + mxDT.RelativeDateTime(years = -1, month = val)
1708 target_date = cFuzzyTimestamp (
1709 timestamp = ts,
1710 accuracy = acc_months
1711 )
1712 tmp = {
1713 'data': target_date,
1714 'label': _('%s (%s last year)') % (target_date, ts.strftime('%B').decode(enc))
1715 }
1716 matches.append(tmp)
1717
1718
1719 matches.append ({
1720 'data': None,
1721 'label': '%s/200' % val
1722 })
1723 matches.append ({
1724 'data': None,
1725 'label': '%s/199' % val
1726 })
1727 matches.append ({
1728 'data': None,
1729 'label': '%s/198' % val
1730 })
1731 matches.append ({
1732 'data': None,
1733 'label': '%s/19' % val
1734 })
1735
1736
1737 if val < 8:
1738
1739 ts = now + mxDT.RelativeDateTime(weekday = (val-1, 0))
1740 target_date = cFuzzyTimestamp (
1741 timestamp = ts,
1742 accuracy = acc_days
1743 )
1744 tmp = {
1745 'data': target_date,
1746 'label': _('%s this week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc))
1747 }
1748 matches.append(tmp)
1749
1750
1751 ts = now + mxDT.RelativeDateTime(weeks = +1, weekday = (val-1, 0))
1752 target_date = cFuzzyTimestamp (
1753 timestamp = ts,
1754 accuracy = acc_days
1755 )
1756 tmp = {
1757 'data': target_date,
1758 'label': _('%s next week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc))
1759 }
1760 matches.append(tmp)
1761
1762
1763 ts = now + mxDT.RelativeDateTime(weeks = -1, weekday = (val-1, 0))
1764 target_date = cFuzzyTimestamp (
1765 timestamp = ts,
1766 accuracy = acc_days
1767 )
1768 tmp = {
1769 'data': target_date,
1770 'label': _('%s last week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc))
1771 }
1772 matches.append(tmp)
1773
1774 if val < 100:
1775 matches.append ({
1776 'data': None,
1777 'label': '%s/' % (1900 + val)
1778 })
1779
1780 if val == 200:
1781 tmp = {
1782 'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_days),
1783 'label': '%s' % target_date
1784 }
1785 matches.append(tmp)
1786 matches.append ({
1787 'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_months),
1788 'label': '%.2d/%s' % (now.month, now.year)
1789 })
1790 matches.append ({
1791 'data': None,
1792 'label': '%s/' % now.year
1793 })
1794 matches.append ({
1795 'data': None,
1796 'label': '%s/' % (now.year + 1)
1797 })
1798 matches.append ({
1799 'data': None,
1800 'label': '%s/' % (now.year - 1)
1801 })
1802
1803 if val < 200 and val >= 190:
1804 for i in range(10):
1805 matches.append ({
1806 'data': None,
1807 'label': '%s%s/' % (val, i)
1808 })
1809
1810 return matches
1811
1813 """Expand fragments containing a single dot.
1814
1815 Standard colloquial date format in Germany: day.month.year
1816
1817 "14."
1818 - 14th current month this year
1819 - 14th next month this year
1820 """
1821 if not regex.match(u"^(\s|\t)*\d{1,2}\.{1}(\s|\t)*$", str2parse, flags = regex.LOCALE | regex.UNICODE):
1822 return []
1823
1824 val = int(regex.findall(u'\d+', str2parse, flags = regex.LOCALE | regex.UNICODE)[0])
1825 now = mxDT.now()
1826 enc = gmI18N.get_encoding()
1827
1828 matches = []
1829
1830
1831 ts = now + mxDT.RelativeDateTime(day = val)
1832 if val > 0 and val <= gregorian_month_length[ts.month]:
1833 matches.append ({
1834 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_days),
1835 'label': '%s.%s.%s - a %s this month' % (ts.day, ts.month, ts.year, ts.strftime('%A').decode(enc))
1836 })
1837
1838
1839 ts = now + mxDT.RelativeDateTime(day = val, months = +1)
1840 if val > 0 and val <= gregorian_month_length[ts.month]:
1841 matches.append ({
1842 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_days),
1843 'label': '%s.%s.%s - a %s next month' % (ts.day, ts.month, ts.year, ts.strftime('%A').decode(enc))
1844 })
1845
1846
1847 ts = now + mxDT.RelativeDateTime(day = val, months = -1)
1848 if val > 0 and val <= gregorian_month_length[ts.month]:
1849 matches.append ({
1850 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_days),
1851 'label': '%s.%s.%s - a %s last month' % (ts.day, ts.month, ts.year, ts.strftime('%A').decode(enc))
1852 })
1853
1854 return matches
1855
1857 """
1858 Turn a string into candidate fuzzy timestamps and auto-completions the user is likely to type.
1859
1860 You MUST have called locale.setlocale(locale.LC_ALL, '')
1861 somewhere in your code previously.
1862
1863 @param default_time: if you want to force the time part of the time
1864 stamp to a given value and the user doesn't type any time part
1865 this value will be used
1866 @type default_time: an mx.DateTime.DateTimeDelta instance
1867
1868 @param patterns: list of [time.strptime compatible date/time pattern, accuracy]
1869 @type patterns: list
1870 """
1871 matches = __single_dot(str2parse)
1872 matches.extend(__numbers_only(str2parse))
1873 matches.extend(__single_slash(str2parse))
1874 ms = __single_char2py_dt(str2parse)
1875 for m in ms:
1876 matches.append ({
1877 'data': cFuzzyTimestamp (
1878 timestamp = m['data'],
1879 accuracy = acc_days
1880 ),
1881 'label': m['label']
1882 })
1883 matches.extend(__explicit_offset(str2parse))
1884
1885
1886 try:
1887
1888 date_only = mxDT.Parser.DateFromString (
1889 text = str2parse,
1890 formats = ('euro', 'iso', 'us', 'altus', 'altiso', 'lit', 'altlit', 'eurlit')
1891 )
1892
1893 time_part = mxDT.Parser.TimeFromString(text = str2parse)
1894 datetime = date_only + time_part
1895 if datetime == date_only:
1896 accuracy = acc_days
1897 if isinstance(default_time, mxDT.DateTimeDeltaType):
1898 datetime = date_only + default_time
1899 accuracy = acc_minutes
1900 else:
1901 accuracy = acc_subseconds
1902 fts = cFuzzyTimestamp (
1903 timestamp = datetime,
1904 accuracy = accuracy
1905 )
1906 matches.append ({
1907 'data': fts,
1908 'label': fts.format_accurately()
1909 })
1910 except (ValueError, mxDT.RangeError):
1911 pass
1912
1913 if patterns is None:
1914 patterns = []
1915
1916 patterns.append(['%Y-%m-%d', acc_days])
1917 patterns.append(['%y-%m-%d', acc_days])
1918 patterns.append(['%Y/%m/%d', acc_days])
1919 patterns.append(['%y/%m/%d', acc_days])
1920
1921 patterns.append(['%d-%m-%Y', acc_days])
1922 patterns.append(['%d-%m-%y', acc_days])
1923 patterns.append(['%d/%m/%Y', acc_days])
1924 patterns.append(['%d/%m/%y', acc_days])
1925
1926 patterns.append(['%m-%d-%Y', acc_days])
1927 patterns.append(['%m-%d-%y', acc_days])
1928 patterns.append(['%m/%d/%Y', acc_days])
1929 patterns.append(['%m/%d/%y', acc_days])
1930
1931 patterns.append(['%Y.%m.%d', acc_days])
1932 patterns.append(['%y.%m.%d', acc_days])
1933
1934
1935 for pattern in patterns:
1936 try:
1937 fts = cFuzzyTimestamp (
1938 timestamp = pyDT.datetime.fromtimestamp(time.mktime(time.strptime(str2parse, pattern[0]))),
1939 accuracy = pattern[1]
1940 )
1941 matches.append ({
1942 'data': fts,
1943 'label': fts.format_accurately()
1944 })
1945 except AttributeError:
1946
1947 break
1948 except OverflowError:
1949
1950 continue
1951 except ValueError:
1952
1953 continue
1954
1955 return matches
1956
1957
1958
1960
1961
1962
1963 """A timestamp implementation with definable inaccuracy.
1964
1965 This class contains an mxDateTime.DateTime instance to
1966 hold the actual timestamp. It adds an accuracy attribute
1967 to allow the programmer to set the precision of the
1968 timestamp.
1969
1970 The timestamp will have to be initialzed with a fully
1971 precise value (which may, of course, contain partially
1972 fake data to make up for missing values). One can then
1973 set the accuracy value to indicate up to which part of
1974 the timestamp the data is valid. Optionally a modifier
1975 can be set to indicate further specification of the
1976 value (such as "summer", "afternoon", etc).
1977
1978 accuracy values:
1979 1: year only
1980 ...
1981 7: everything including milliseconds value
1982
1983 Unfortunately, one cannot directly derive a class from mx.DateTime.DateTime :-(
1984 """
1985
1987
1988 if timestamp is None:
1989 timestamp = mxDT.now()
1990 accuracy = acc_subseconds
1991 modifier = ''
1992
1993 if (accuracy < 1) or (accuracy > 8):
1994 raise ValueError('%s.__init__(): <accuracy> must be between 1 and 8' % self.__class__.__name__)
1995
1996 if isinstance(timestamp, pyDT.datetime):
1997 timestamp = mxDT.DateTime(timestamp.year, timestamp.month, timestamp.day, timestamp.hour, timestamp.minute, timestamp.second)
1998
1999 if type(timestamp) != mxDT.DateTimeType:
2000 raise TypeError('%s.__init__(): <timestamp> must be of mx.DateTime.DateTime or datetime.datetime type' % self.__class__.__name__)
2001
2002 self.timestamp = timestamp
2003 self.accuracy = accuracy
2004 self.modifier = modifier
2005
2006
2007
2009 """Return string representation meaningful to a user, also for %s formatting."""
2010 return self.format_accurately()
2011
2013 """Return string meaningful to a programmer to aid in debugging."""
2014 tmp = '<[%s]: timestamp [%s], accuracy [%s] (%s), modifier [%s] at %s>' % (
2015 self.__class__.__name__,
2016 repr(self.timestamp),
2017 self.accuracy,
2018 _accuracy_strings[self.accuracy],
2019 self.modifier,
2020 id(self)
2021 )
2022 return tmp
2023
2024
2025
2030
2033
2066
2068 return self.timestamp
2069
2071 try:
2072 gmtoffset = self.timestamp.gmtoffset()
2073 except mxDT.Error:
2074
2075
2076 now = mxDT.now()
2077 gmtoffset = now.gmtoffset()
2078 tz = cFixedOffsetTimezone(gmtoffset.minutes, self.timestamp.tz)
2079 secs, msecs = divmod(self.timestamp.second, 1)
2080 ts = pyDT.datetime (
2081 year = self.timestamp.year,
2082 month = self.timestamp.month,
2083 day = self.timestamp.day,
2084 hour = self.timestamp.hour,
2085 minute = self.timestamp.minute,
2086 second = int(secs),
2087 microsecond = int(msecs * 1000),
2088 tzinfo = tz
2089 )
2090 return ts
2091
2092
2093
2094 if __name__ == '__main__':
2095
2096 if len(sys.argv) < 2:
2097 sys.exit()
2098
2099 if sys.argv[1] != "test":
2100 sys.exit()
2101
2102
2103 intervals_as_str = [
2104 '7', '12', ' 12', '12 ', ' 12 ', ' 12 ', '0', '~12', '~ 12', ' ~ 12', ' ~ 12 ',
2105 '12a', '12 a', '12 a', '12j', '12J', '12y', '12Y', ' ~ 12 a ', '~0a',
2106 '12m', '17 m', '12 m', '17M', ' ~ 17 m ', ' ~ 3 / 12 ', '7/12', '0/12',
2107 '12w', '17 w', '12 w', '17W', ' ~ 17 w ', ' ~ 15 / 52', '2/52', '0/52',
2108 '12d', '17 d', '12 t', '17D', ' ~ 17 T ', ' ~ 12 / 7', '3/7', '0/7',
2109 '12h', '17 h', '12 H', '17H', ' ~ 17 h ', ' ~ 36 / 24', '7/24', '0/24',
2110 ' ~ 36 / 60', '7/60', '190/60', '0/60',
2111 '12a1m', '12 a 1 M', '12 a17m', '12j 12m', '12J7m', '12y7m', '12Y7M', ' ~ 12 a 37 m ', '~0a0m',
2112 '10m1w',
2113 'invalid interval input'
2114 ]
2115
2121
2189
2191 print "testing str2interval()"
2192 print "----------------------"
2193
2194 for interval_as_str in intervals_as_str:
2195 print "input: <%s>" % interval_as_str
2196 print " ==>", str2interval(str_interval=interval_as_str)
2197
2198 return True
2199
2201 print "DST currently in effect:", dst_currently_in_effect
2202 print "current UTC offset:", current_local_utc_offset_in_seconds, "seconds"
2203 print "current timezone (interval):", current_local_timezone_interval
2204 print "current timezone (ISO conformant numeric string):", current_local_iso_numeric_timezone_string
2205 print "local timezone class:", cLocalTimezone
2206 print ""
2207 tz = cLocalTimezone()
2208 print "local timezone instance:", tz
2209 print " (total) UTC offset:", tz.utcoffset(pyDT.datetime.now())
2210 print " DST adjustment:", tz.dst(pyDT.datetime.now())
2211 print " timezone name:", tz.tzname(pyDT.datetime.now())
2212 print ""
2213 print "current local timezone:", gmCurrentLocalTimezone
2214 print " (total) UTC offset:", gmCurrentLocalTimezone.utcoffset(pyDT.datetime.now())
2215 print " DST adjustment:", gmCurrentLocalTimezone.dst(pyDT.datetime.now())
2216 print " timezone name:", gmCurrentLocalTimezone.tzname(pyDT.datetime.now())
2217 print ""
2218 print "now here:", pydt_now_here()
2219 print ""
2220
2222 print "testing function str2fuzzy_timestamp_matches"
2223 print "--------------------------------------------"
2224
2225 val = None
2226 while val != 'exit':
2227 val = raw_input('Enter date fragment ("exit" quits): ')
2228 matches = str2fuzzy_timestamp_matches(str2parse = val)
2229 for match in matches:
2230 print 'label shown :', match['label']
2231 print 'data attached:', match['data'], match['data'].timestamp
2232 print ""
2233 print "---------------"
2234
2236 print "testing fuzzy timestamp class"
2237 print "-----------------------------"
2238
2239 ts = mxDT.now()
2240 print "mx.DateTime timestamp", type(ts)
2241 print " print ... :", ts
2242 print " print '%%s' %% ...: %s" % ts
2243 print " str() :", str(ts)
2244 print " repr() :", repr(ts)
2245
2246 fts = cFuzzyTimestamp()
2247 print "\nfuzzy timestamp <%s '%s'>" % ('class', fts.__class__.__name__)
2248 for accuracy in range(1,8):
2249 fts.accuracy = accuracy
2250 print " accuracy : %s (%s)" % (accuracy, _accuracy_strings[accuracy])
2251 print " format_accurately:", fts.format_accurately()
2252 print " strftime() :", fts.strftime('%Y %b %d %H:%M:%S')
2253 print " print ... :", fts
2254 print " print '%%s' %% ... : %s" % fts
2255 print " str() :", str(fts)
2256 print " repr() :", repr(fts)
2257 raw_input('press ENTER to continue')
2258
2260 print "testing platform for handling dates before 1970"
2261 print "-----------------------------------------------"
2262 ts = mxDT.DateTime(1935, 4, 2)
2263 fts = cFuzzyTimestamp(timestamp=ts)
2264 print "fts :", fts
2265 print "fts.get_pydt():", fts.get_pydt()
2266
2268
2269 start = pydt_now_here().replace(year = 2000).replace(month = 2).replace(day = 29)
2270 end = pydt_now_here().replace(year = 2012).replace(month = 2).replace(day = 27)
2271 print "start is leap year: 29.2.2000"
2272 print " ", calculate_apparent_age(start = start, end = end)
2273 print " ", format_apparent_age_medically(calculate_apparent_age(start = start))
2274
2275 start = pydt_now_here().replace(month = 10).replace(day = 23).replace(year = 1974)
2276 end = pydt_now_here().replace(year = 2012).replace(month = 2).replace(day = 29)
2277 print "end is leap year: 29.2.2012"
2278 print " ", calculate_apparent_age(start = start, end = end)
2279 print " ", format_apparent_age_medically(calculate_apparent_age(start = start))
2280
2281 start = pydt_now_here().replace(year = 2000).replace(month = 2).replace(day = 29)
2282 end = pydt_now_here().replace(year = 2012).replace(month = 2).replace(day = 29)
2283 print "start is leap year: 29.2.2000"
2284 print "end is leap year: 29.2.2012"
2285 print " ", calculate_apparent_age(start = start, end = end)
2286 print " ", format_apparent_age_medically(calculate_apparent_age(start = start))
2287
2288 print "leap year tests worked"
2289
2290 start = pydt_now_here().replace(month = 10).replace(day = 23).replace(year = 1974)
2291 print calculate_apparent_age(start = start)
2292 print format_apparent_age_medically(calculate_apparent_age(start = start))
2293
2294 start = pydt_now_here().replace(month = 3).replace(day = 13).replace(year = 1979)
2295 print calculate_apparent_age(start = start)
2296 print format_apparent_age_medically(calculate_apparent_age(start = start))
2297
2298 start = pydt_now_here().replace(month = 2, day = 2).replace(year = 1979)
2299 end = pydt_now_here().replace(month = 3).replace(day = 31).replace(year = 1979)
2300 print calculate_apparent_age(start = start, end = end)
2301
2302 start = pydt_now_here().replace(month = 7, day = 21).replace(year = 2009)
2303 print format_apparent_age_medically(calculate_apparent_age(start = start))
2304
2305 print "-------"
2306 start = pydt_now_here().replace(month = 1).replace(day = 23).replace(hour = 12).replace(minute = 11).replace(year = 2011)
2307 print calculate_apparent_age(start = start)
2308 print format_apparent_age_medically(calculate_apparent_age(start = start))
2309
2311 print "testing function str2pydt_matches"
2312 print "---------------------------------"
2313
2314 val = None
2315 while val != 'exit':
2316 val = raw_input('Enter date fragment ("exit" quits): ')
2317 matches = str2pydt_matches(str2parse = val)
2318 for match in matches:
2319 print 'label shown :', match['label']
2320 print 'data attached:', match['data']
2321 print ""
2322 print "---------------"
2323
2335
2337 for year in range(1995, 2017):
2338 print year, "leaps:", is_leap_year(year)
2339
2340
2341 gmI18N.activate_locale()
2342 gmI18N.install_domain('gnumed')
2343
2344 init()
2345
2346
2347
2348
2349
2350
2351
2352
2353 test_str2pydt()
2354
2355
2356
2357
2358
2359