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