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