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
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
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
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
137
138 global py_timezone_name
139 py_timezone_name = time.tzname[0]
140
141 global py_dst_timezone_name
142 py_dst_timezone_name = time.tzname[1]
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
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']
180 except KeyError:
181 if dst_currently_in_effect:
182 current_local_timezone_name = time.tzname[1]
183 else:
184 current_local_timezone_name = time.tzname[0]
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
199
201 next_month = dt.month + 1
202 if next_month == 13:
203 return 1
204 return next_month
205
206
208 last_month = dt.month - 1
209 if last_month == 0:
210 return 12
211 return last_month
212
213
214
215
217
218 if isinstance(mxDateTime, pyDT.datetime):
219 return mxDateTime
220
221 try:
222 tz_name = str(mxDateTime.gmtoffset()).replace(',', '.')
223 except mxDT.Error:
224 _log.debug('mx.DateTime cannot gmtoffset() this timestamp, assuming local time')
225 tz_name = current_local_iso_numeric_timezone_string
226
227 if dst_currently_in_effect:
228 tz = cFixedOffsetTimezone (
229 offset = ((time.altzone * -1) // 60),
230 name = tz_name
231 )
232 else:
233 tz = cFixedOffsetTimezone (
234 offset = ((time.timezone * -1) // 60),
235 name = tz_name
236 )
237
238 try:
239 return pyDT.datetime (
240 year = mxDateTime.year,
241 month = mxDateTime.month,
242 day = mxDateTime.day,
243 tzinfo = tz
244 )
245 except:
246 _log.debug ('error converting mx.DateTime.DateTime to Python: %s-%s-%s %s:%s %s.%s',
247 mxDateTime.year,
248 mxDateTime.month,
249 mxDateTime.day,
250 mxDateTime.hour,
251 mxDateTime.minute,
252 mxDateTime.second,
253 mxDateTime.tz
254 )
255 raise
256
257
269
270
271 -def pydt_strftime(dt=None, format='%Y %b %d %H:%M.%S', accuracy=None, none_str=None):
272
273 if dt is None:
274 if none_str is not None:
275 return none_str
276 raise ValueError('must provide <none_str> if <dt>=None is to be dealt with')
277
278 try:
279 return dt.strftime(format)
280 except ValueError:
281 _log.exception('Python cannot strftime() this <datetime>, trying ourselves')
282
283 if isinstance(dt, pyDT.date):
284 accuracy = acc_days
285
286 if accuracy == acc_days:
287 return '%04d-%02d-%02d' % (
288 dt.year,
289 dt.month,
290 dt.day
291 )
292
293 if accuracy == acc_minutes:
294 return '%04d-%02d-%02d %02d:%02d' % (
295 dt.year,
296 dt.month,
297 dt.day,
298 dt.hour,
299 dt.minute
300 )
301
302 return '%04d-%02d-%02d %02d:%02d:%02d' % (
303 dt.year,
304 dt.month,
305 dt.day,
306 dt.hour,
307 dt.minute,
308 dt.second
309 )
310
311
312 -def pydt_add(dt, years=0, months=0, weeks=0, days=0, hours=0, minutes=0, seconds=0, milliseconds=0, microseconds=0):
313 if months > 11 or months < -11:
314 raise ValueError('pydt_add(): months must be within [-11..11]')
315
316 dt = dt + pyDT.timedelta (
317 weeks = weeks,
318 days = days,
319 hours = hours,
320 minutes = minutes,
321 seconds = seconds,
322 milliseconds = milliseconds,
323 microseconds = microseconds
324 )
325 if (years == 0) and (months == 0):
326 return dt
327 target_year = dt.year + years
328 target_month = dt.month + months
329 if target_month > 12:
330 target_year += 1
331 target_month -= 12
332 elif target_month < 1:
333 target_year -= 1
334 target_month += 12
335 return pydt_replace(dt, year = target_year, month = target_month, strict = False)
336
337
338 -def pydt_replace(dt, strict=True, year=None, month=None, day=None, hour=None, minute=None, second=None, microsecond=None, tzinfo=None):
339
340
341 if year is None:
342 year = dt.year
343 if month is None:
344 month = dt.month
345 if day is None:
346 day = dt.day
347 if hour is None:
348 hour = dt.hour
349 if minute is None:
350 minute = dt.minute
351 if second is None:
352 second = dt.second
353 if microsecond is None:
354 microsecond = dt.microsecond
355 if tzinfo is None:
356 tzinfo = dt.tzinfo
357
358 if strict:
359 return dt.replace(year = year, month = month, day = day, hour = hour, minute = minute, second = second, microsecond = microsecond, tzinfo = tzinfo)
360
361 try:
362 return dt.replace(year = year, month = month, day = day, hour = hour, minute = minute, second = second, microsecond = microsecond, tzinfo = tzinfo)
363 except ValueError:
364 _log.debug('error replacing datetime member(s): %s', locals())
365
366
367 if month == 2:
368 if day > 28:
369 if is_leap_year(year):
370 day = 29
371 else:
372 day = 28
373 else:
374 if day == 31:
375 day = 30
376
377 return dt.replace(year = year, month = month, day = day, hour = hour, minute = minute, second = second, microsecond = microsecond, tzinfo = tzinfo)
378
379
389
390
394
395
398
399
400
401
403 if not wxDate.IsValid():
404 raise ValueError ('invalid wxDate: %s-%s-%s %s:%s %s.%s',
405 wxDate.GetYear(),
406 wxDate.GetMonth(),
407 wxDate.GetDay(),
408 wxDate.GetHour(),
409 wxDate.GetMinute(),
410 wxDate.GetSecond(),
411 wxDate.GetMillisecond()
412 )
413
414 try:
415 return pyDT.datetime (
416 year = wxDate.GetYear(),
417 month = wxDate.GetMonth() + 1,
418 day = wxDate.GetDay(),
419 tzinfo = gmCurrentLocalTimezone
420 )
421 except:
422 _log.debug ('error converting wxDateTime to Python: %s-%s-%s %s:%s %s.%s',
423 wxDate.GetYear(),
424 wxDate.GetMonth(),
425 wxDate.GetDay(),
426 wxDate.GetHour(),
427 wxDate.GetMinute(),
428 wxDate.GetSecond(),
429 wxDate.GetMillisecond()
430 )
431 raise
432
433
434
435
572
573
636
637
646
647
654
655
657
658 div, remainder = divmod(year, 4)
659
660 if remainder > 0:
661 return False
662
663
664 div, remainder = divmod(year, 100)
665
666 if remainder > 0:
667 return True
668
669
670 div, remainder = divmod(year, 400)
671
672 if remainder == 0:
673 return True
674
675 return False
676
677
679 """The result of this is a tuple (years, ..., seconds) as one would
680 'expect' an age to look like, that is, simple differences between
681 the fields:
682
683 (years, months, days, hours, minutes, seconds)
684
685 This does not take into account time zones which may
686 shift the result by one day.
687
688 <start> and <end> must by python datetime instances
689 <end> is assumed to be "now" if not given
690 """
691 if end is None:
692 end = pyDT.datetime.now(gmCurrentLocalTimezone)
693
694 if end < start:
695 raise ValueError('calculate_apparent_age(): <end> (%s) before <start> (%s)' % (end, start))
696
697 if end == start:
698 return (0, 0, 0, 0, 0, 0)
699
700
701 if end.month == 2:
702 if end.day == 29:
703 if not is_leap_year(start.year):
704 end = end.replace(day = 28)
705
706
707 years = end.year - start.year
708 end = end.replace(year = start.year)
709 if end < start:
710 years = years - 1
711
712
713 if end.month == start.month:
714 if end < start:
715 months = 11
716 else:
717 months = 0
718 else:
719 months = end.month - start.month
720 if months < 0:
721 months = months + 12
722 if end.day > gregorian_month_length[start.month]:
723 end = end.replace(month = start.month, day = gregorian_month_length[start.month])
724 else:
725 end = end.replace(month = start.month)
726 if end < start:
727 months = months - 1
728
729
730 if end.day == start.day:
731 if end < start:
732 days = gregorian_month_length[start.month] - 1
733 else:
734 days = 0
735 else:
736 days = end.day - start.day
737 if days < 0:
738 days = days + gregorian_month_length[start.month]
739 end = end.replace(day = start.day)
740 if end < start:
741 days = days - 1
742
743
744 if end.hour == start.hour:
745 hours = 0
746 else:
747 hours = end.hour - start.hour
748 if hours < 0:
749 hours = hours + 24
750 end = end.replace(hour = start.hour)
751 if end < start:
752 hours = hours - 1
753
754
755 if end.minute == start.minute:
756 minutes = 0
757 else:
758 minutes = end.minute - start.minute
759 if minutes < 0:
760 minutes = minutes + 60
761 end = end.replace(minute = start.minute)
762 if end < start:
763 minutes = minutes - 1
764
765
766 if end.second == start.second:
767 seconds = 0
768 else:
769 seconds = end.second - start.second
770 if seconds < 0:
771 seconds = seconds + 60
772 end = end.replace(second = start.second)
773 if end < start:
774 seconds = seconds - 1
775
776 return (years, months, days, hours, minutes, seconds)
777
778
882
884
885 unit_keys = {
886 'year': _('yYaA_keys_year'),
887 'month': _('mM_keys_month'),
888 'week': _('wW_keys_week'),
889 'day': _('dD_keys_day'),
890 'hour': _('hH_keys_hour')
891 }
892
893 str_interval = str_interval.strip()
894
895
896 keys = '|'.join(list(unit_keys['year'].replace('_keys_year', '')))
897 if regex.match('^~*(\s|\t)*\d+(%s)*$' % keys, str_interval, flags = regex.UNICODE):
898 return pyDT.timedelta(days = (int(regex.findall('\d+', str_interval, flags = regex.UNICODE)[0]) * avg_days_per_gregorian_year))
899
900
901 keys = '|'.join(list(unit_keys['month'].replace('_keys_month', '')))
902 if regex.match('^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.UNICODE):
903 years, months = divmod (
904 int(regex.findall('\d+', str_interval, flags = regex.UNICODE)[0]),
905 12
906 )
907 return pyDT.timedelta(days = ((years * avg_days_per_gregorian_year) + (months * avg_days_per_gregorian_month)))
908
909
910 keys = '|'.join(list(unit_keys['week'].replace('_keys_week', '')))
911 if regex.match('^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.UNICODE):
912 return pyDT.timedelta(weeks = int(regex.findall('\d+', str_interval, flags = regex.UNICODE)[0]))
913
914
915 keys = '|'.join(list(unit_keys['day'].replace('_keys_day', '')))
916 if regex.match('^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.UNICODE):
917 return pyDT.timedelta(days = int(regex.findall('\d+', str_interval, flags = regex.UNICODE)[0]))
918
919
920 keys = '|'.join(list(unit_keys['hour'].replace('_keys_hour', '')))
921 if regex.match('^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.UNICODE):
922 return pyDT.timedelta(hours = int(regex.findall('\d+', str_interval, flags = regex.UNICODE)[0]))
923
924
925 if regex.match('^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*12$', str_interval, flags = regex.UNICODE):
926 years, months = divmod (
927 int(regex.findall('\d+', str_interval, flags = regex.UNICODE)[0]),
928 12
929 )
930 return pyDT.timedelta(days = ((years * avg_days_per_gregorian_year) + (months * avg_days_per_gregorian_month)))
931
932
933 if regex.match('^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*52$', str_interval, flags = regex.UNICODE):
934 return pyDT.timedelta(weeks = int(regex.findall('\d+', str_interval, flags = regex.UNICODE)[0]))
935
936
937 if regex.match('^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*7$', str_interval, flags = regex.UNICODE):
938 return pyDT.timedelta(days = int(regex.findall('\d+', str_interval, flags = regex.UNICODE)[0]))
939
940
941 if regex.match('^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*24$', str_interval, flags = regex.UNICODE):
942 return pyDT.timedelta(hours = int(regex.findall('\d+', str_interval, flags = regex.UNICODE)[0]))
943
944
945 if regex.match('^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*60$', str_interval, flags = regex.UNICODE):
946 return pyDT.timedelta(minutes = int(regex.findall('\d+', str_interval, flags = regex.UNICODE)[0]))
947
948
949 keys_year = '|'.join(list(unit_keys['year'].replace('_keys_year', '')))
950 keys_month = '|'.join(list(unit_keys['month'].replace('_keys_month', '')))
951 if regex.match('^~*(\s|\t)*\d+(%s|\s|\t)+\d+(\s|\t)*(%s)+$' % (keys_year, keys_month), str_interval, flags = regex.UNICODE):
952 parts = regex.findall('\d+', str_interval, flags = regex.UNICODE)
953 years, months = divmod(int(parts[1]), 12)
954 years += int(parts[0])
955 return pyDT.timedelta(days = ((years * avg_days_per_gregorian_year) + (months * avg_days_per_gregorian_month)))
956
957
958 keys_month = '|'.join(list(unit_keys['month'].replace('_keys_month', '')))
959 keys_week = '|'.join(list(unit_keys['week'].replace('_keys_week', '')))
960 if regex.match('^~*(\s|\t)*\d+(%s|\s|\t)+\d+(\s|\t)*(%s)+$' % (keys_month, keys_week), str_interval, flags = regex.UNICODE):
961 parts = regex.findall('\d+', str_interval, flags = regex.UNICODE)
962 months, weeks = divmod(int(parts[1]), 4)
963 months += int(parts[0])
964 return pyDT.timedelta(days = ((months * avg_days_per_gregorian_month) + (weeks * days_per_week)))
965
966 return None
967
968
969
970
972 """This matches on single characters.
973
974 Spaces and tabs are discarded.
975
976 Default is 'ndmy':
977 n - _N_ow
978 d - to_D_ay
979 m - to_M_orrow Someone please suggest a synonym ! ("2" does not cut it ...)
980 y - _Y_esterday
981
982 This also defines the significance of the order of the characters.
983 """
984 str2parse = str2parse.strip().lower()
985 if len(str2parse) != 1:
986 return []
987
988 if trigger_chars is None:
989 trigger_chars = _('ndmy (single character date triggers)')[:4].lower()
990
991 if str2parse not in trigger_chars:
992 return []
993
994 now = pydt_now_here()
995
996
997
998
999 if str2parse == trigger_chars[0]:
1000 return [{
1001 'data': now,
1002 'label': _('right now (%s, %s)') % (now.strftime('%A'), now)
1003 }]
1004
1005 if str2parse == trigger_chars[1]:
1006 return [{
1007 'data': now,
1008 'label': _('today (%s)') % now.strftime('%A, %Y-%m-%d')
1009 }]
1010
1011 if str2parse == trigger_chars[2]:
1012 ts = pydt_add(now, days = 1)
1013 return [{
1014 'data': ts,
1015 'label': _('tomorrow (%s)') % ts.strftime('%A, %Y-%m-%d')
1016 }]
1017
1018 if str2parse == trigger_chars[3]:
1019 ts = pydt_add(now, days = -1)
1020 return [{
1021 'data': ts,
1022 'label': _('yesterday (%s)') % ts.strftime('%A, %Y-%m-%d')
1023 }]
1024 return []
1025
1026
1028 """Expand fragments containing a single dot.
1029
1030 Standard colloquial date format in Germany: day.month.year
1031
1032 "14."
1033 - the 14th of the current month
1034 - the 14th of next month
1035 "-14."
1036 - the 14th of last month
1037 """
1038 str2parse = str2parse.replace(' ', '').replace('\t', '')
1039
1040 if not str2parse.endswith('.'):
1041 return []
1042 try:
1043 day_val = int(str2parse[:-1])
1044 except ValueError:
1045 return []
1046 if (day_val < -31) or (day_val > 31) or (day_val == 0):
1047 return []
1048
1049 now = pydt_now_here()
1050 matches = []
1051
1052
1053 if day_val < 0:
1054 ts = pydt_replace(pydt_add(now, months = -1), day = abs(day_val))
1055 if ts.day == day_val:
1056 matches.append ({
1057 'data': ts,
1058 'label': _('%s-%s-%s: a %s last month') % (ts.year, ts.month, ts.day, ts.strftime('%A'))
1059 })
1060
1061
1062 if day_val > 0:
1063
1064 ts = pydt_replace(now, day = day_val)
1065 matches.append ({
1066 'data': ts,
1067 'label': _('%s-%s-%s: a %s this month') % (ts.year, ts.month, ts.day, ts.strftime('%A'))
1068 })
1069
1070 ts = pydt_replace(pydt_add(now, months = 1), day = day_val)
1071 if ts.day == day_val:
1072 matches.append ({
1073 'data': ts,
1074 'label': _('%s-%s-%s: a %s next month') % (ts.year, ts.month, ts.day, ts.strftime('%A'))
1075 })
1076
1077 ts = pydt_replace(pydt_add(now, months = -1), day = day_val)
1078 if ts.day == day_val:
1079 matches.append ({
1080 'data': ts,
1081 'label': _('%s-%s-%s: a %s last month') % (ts.year, ts.month, ts.day, ts.strftime('%A'))
1082 })
1083
1084 return matches
1085
1086
1088 """Expand fragments containing a single slash.
1089
1090 "5/"
1091 - 2005/ (2000 - 2025)
1092 - 1995/ (1990 - 1999)
1093 - Mai/current year
1094 - Mai/next year
1095 - Mai/last year
1096 - Mai/200x
1097 - Mai/20xx
1098 - Mai/199x
1099 - Mai/198x
1100 - Mai/197x
1101 - Mai/19xx
1102
1103 5/1999
1104 6/2004
1105 """
1106 str2parse = str2parse.strip()
1107
1108 now = pydt_now_here()
1109
1110
1111 if regex.match(r"^\d{1,2}(\s|\t)*/+(\s|\t)*\d{4}$", str2parse, flags = regex.UNICODE):
1112 month, year = regex.findall(r'\d+', str2parse, flags = regex.UNICODE)
1113 ts = pydt_replace(now, year = int(year), month = int(month))
1114 return [{
1115 'data': ts,
1116 'label': ts.strftime('%Y-%m-%d')
1117 }]
1118
1119 matches = []
1120
1121 if regex.match(r"^\d{1,2}(\s|\t)*/+$", str2parse, flags = regex.UNICODE):
1122 val = int(str2parse.rstrip('/').strip())
1123
1124
1125 if val < 100 and val >= 0:
1126 matches.append ({
1127 'data': None,
1128 'label': '%s-' % (val + 1900)
1129 })
1130
1131 if val < 26 and val >= 0:
1132 matches.append ({
1133 'data': None,
1134 'label': '%s-' % (val + 2000)
1135 })
1136
1137 if val < 10 and val >= 0:
1138 matches.append ({
1139 'data': None,
1140 'label': '%s-' % (val + 1990)
1141 })
1142 if val < 13 and val > 0:
1143
1144 matches.append ({
1145 'data': None,
1146 'label': '%s-%.2d-' % (now.year, val)
1147 })
1148
1149 ts = pydt_add(now, years = 1)
1150 matches.append ({
1151 'data': None,
1152 'label': '%s-%.2d-' % (ts.year, val)
1153 })
1154
1155 ts = pydt_add(now, years = -1)
1156 matches.append ({
1157 'data': None,
1158 'label': '%s-%.2d-' % (ts.year, val)
1159 })
1160
1161 matches.append ({
1162 'data': None,
1163 'label': '201?-%.2d-' % val
1164 })
1165
1166 matches.append ({
1167 'data': None,
1168 'label': '200?-%.2d-' % val
1169 })
1170
1171 matches.append ({
1172 'data': None,
1173 'label': '20??-%.2d-' % val
1174 })
1175
1176 matches.append ({
1177 'data': None,
1178 'label': '199?-%.2d-' % val
1179 })
1180
1181 matches.append ({
1182 'data': None,
1183 'label': '198?-%.2d-' % val
1184 })
1185
1186 matches.append ({
1187 'data': None,
1188 'label': '197?-%.2d-' % val
1189 })
1190
1191 matches.append ({
1192 'data': None,
1193 'label': '19??-%.2d-' % val
1194 })
1195
1196 return matches
1197
1198
1200 """This matches on single numbers.
1201
1202 Spaces or tabs are discarded.
1203 """
1204 try:
1205 val = int(str2parse.strip())
1206 except ValueError:
1207 return []
1208
1209 now = pydt_now_here()
1210
1211 matches = []
1212
1213
1214 if (1850 < val) and (val < 2100):
1215 ts = pydt_replace(now, year = val)
1216 matches.append ({
1217 'data': ts,
1218 'label': ts.strftime('%Y-%m-%d')
1219 })
1220
1221 if (val > 0) and (val <= gregorian_month_length[now.month]):
1222 ts = pydt_replace(now, day = val)
1223 matches.append ({
1224 'data': ts,
1225 'label': _('%d. of %s (this month): a %s') % (val, ts.strftime('%B'), ts.strftime('%A'))
1226 })
1227
1228 if (val > 0) and (val < 32):
1229
1230 ts = pydt_replace(pydt_add(now, months = 1), day = val)
1231 matches.append ({
1232 'data': ts,
1233 'label': _('%d. of %s (next month): a %s') % (val, ts.strftime('%B'), ts.strftime('%A'))
1234 })
1235
1236 ts = pydt_replace(pydt_add(now, months = -1), day = val)
1237 matches.append ({
1238 'data': ts,
1239 'label': _('%d. of %s (last month): a %s') % (val, ts.strftime('%B'), ts.strftime('%A'))
1240 })
1241
1242 if (val > 0) and (val <= 400):
1243 ts = pydt_add(now, days = val)
1244 matches.append ({
1245 'data': ts,
1246 'label': _('in %d day(s): %s') % (val, ts.strftime('%A, %Y-%m-%d'))
1247 })
1248 if (val < 0) and (val >= -400):
1249 ts = pydt_add(now, days = val)
1250 matches.append ({
1251 'data': ts,
1252 'label': _('%d day(s) ago: %s') % (abs(val), ts.strftime('%A, %Y-%m-%d'))
1253 })
1254
1255 if (val > 0) and (val <= 50):
1256 ts = pydt_add(now, weeks = val)
1257 matches.append ({
1258 'data': ts,
1259 'label': _('in %d week(s): %s') % (val, ts.strftime('%A, %Y-%m-%d'))
1260 })
1261 if (val < 0) and (val >= -50):
1262 ts = pydt_add(now, weeks = val)
1263 matches.append ({
1264 'data': ts,
1265 'label': _('%d week(s) ago: %s') % (abs(val), ts.strftime('%A, %Y-%m-%d'))
1266 })
1267
1268
1269 if (val < 13) and (val > 0):
1270
1271 ts = pydt_replace(now, month = val)
1272 matches.append ({
1273 'data': ts,
1274 'label': _('%s (%s this year)') % (ts.strftime('%Y-%m-%d'), ts.strftime('%B'))
1275 })
1276
1277 ts = pydt_replace(pydt_add(now, years = 1), month = val)
1278 matches.append ({
1279 'data': ts,
1280 'label': _('%s (%s next year)') % (ts.strftime('%Y-%m-%d'), ts.strftime('%B'))
1281 })
1282
1283 ts = pydt_replace(pydt_add(now, years = -1), month = val)
1284 matches.append ({
1285 'data': ts,
1286 'label': _('%s (%s last year)') % (ts.strftime('%Y-%m-%d'), ts.strftime('%B'))
1287 })
1288
1289 matches.append ({
1290 'data': None,
1291 'label': '200?-%s' % val
1292 })
1293 matches.append ({
1294 'data': None,
1295 'label': '199?-%s' % val
1296 })
1297 matches.append ({
1298 'data': None,
1299 'label': '198?-%s' % val
1300 })
1301 matches.append ({
1302 'data': None,
1303 'label': '19??-%s' % val
1304 })
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328 if (val < 100) and (val > 0):
1329 matches.append ({
1330 'data': None,
1331 'label': '%s-' % (1900 + val)
1332 })
1333
1334 if val == 201:
1335 matches.append ({
1336 'data': now,
1337 'label': now.strftime('%Y-%m-%d')
1338 })
1339 matches.append ({
1340 'data': None,
1341 'label': now.strftime('%Y-%m')
1342 })
1343 matches.append ({
1344 'data': None,
1345 'label': now.strftime('%Y')
1346 })
1347 matches.append ({
1348 'data': None,
1349 'label': '%s-' % (now.year + 1)
1350 })
1351 matches.append ({
1352 'data': None,
1353 'label': '%s-' % (now.year - 1)
1354 })
1355
1356 if val < 200 and val >= 190:
1357 for i in range(10):
1358 matches.append ({
1359 'data': None,
1360 'label': '%s%s-' % (val, i)
1361 })
1362
1363 return matches
1364
1365
1367 """Default is 'hdwmy':
1368 h - hours
1369 d - days
1370 w - weeks
1371 m - months
1372 y - years
1373
1374 This also defines the significance of the order of the characters.
1375 """
1376 if offset_chars is None:
1377 offset_chars = _('hdwmy (single character date offset triggers)')[:5].lower()
1378
1379 str2parse = str2parse.replace(' ', '').replace('\t', '')
1380
1381 if regex.fullmatch(r"(\+|-){,1}\d{1,3}[%s]" % offset_chars, str2parse) is None:
1382 return []
1383
1384 offset_val = int(str2parse[:-1])
1385 offset_char = str2parse[-1:]
1386 is_past = str2parse.startswith('-')
1387 now = pydt_now_here()
1388 ts = None
1389
1390
1391 if offset_char == offset_chars[0]:
1392 ts = pydt_add(now, hours = offset_val)
1393 if is_past:
1394 label = _('%d hour(s) ago: %s') % (abs(offset_val), ts.strftime('%H:%M'))
1395 else:
1396 label = _('in %d hour(s): %s') % (offset_val, ts.strftime('%H:%M'))
1397
1398 elif offset_char == offset_chars[1]:
1399 ts = pydt_add(now, days = offset_val)
1400 if is_past:
1401 label = _('%d day(s) ago: %s') % (abs(offset_val), ts.strftime('%A, %Y-%m-%d'))
1402 else:
1403 label = _('in %d day(s): %s') % (offset_val, ts.strftime('%A, %Y-%m-%d'))
1404
1405 elif offset_char == offset_chars[2]:
1406 ts = pydt_add(now, weeks = offset_val)
1407 if is_past:
1408 label = _('%d week(s) ago: %s') % (abs(offset_val), ts.strftime('%A, %Y-%m-%d'))
1409 else:
1410 label = _('in %d week(s): %s') % (offset_val, ts.strftime('%A, %Y-%m-%d'))
1411
1412 elif offset_char == offset_chars[3]:
1413 ts = pydt_add(now, months = offset_val)
1414 if is_past:
1415 label = _('%d month(s) ago: %s') % (abs(offset_val), ts.strftime('%A, %Y-%m-%d'))
1416 else:
1417 label = _('in %d month(s): %s') % (offset_val, ts.strftime('%A, %Y-%m-%d'))
1418
1419 elif offset_char == offset_chars[4]:
1420 ts = pydt_add(now, years = offset_val)
1421 if is_past:
1422 label = _('%d year(s) ago: %s') % (abs(offset_val), ts.strftime('%A, %Y-%m-%d'))
1423 else:
1424 label = _('in %d year(s): %s') % (offset_val, ts.strftime('%A, %Y-%m-%d'))
1425
1426 if ts is None:
1427 return []
1428
1429 return [{'data': ts, 'label': label}]
1430
1431
1433 """Turn a string into candidate dates and auto-completions the user is likely to type.
1434
1435 You MUST have called locale.setlocale(locale.LC_ALL, '')
1436 somewhere in your code previously.
1437
1438 @param patterns: list of time.strptime compatible date pattern
1439 @type patterns: list
1440 """
1441 matches = []
1442 matches.extend(__single_dot2py_dt(str2parse))
1443 matches.extend(__numbers_only2py_dt(str2parse))
1444 matches.extend(__single_slash2py_dt(str2parse))
1445 matches.extend(__single_char2py_dt(str2parse))
1446 matches.extend(__explicit_offset2py_dt(str2parse))
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465 if patterns is None:
1466 patterns = []
1467
1468 patterns.append('%Y-%m-%d')
1469 patterns.append('%y-%m-%d')
1470 patterns.append('%Y/%m/%d')
1471 patterns.append('%y/%m/%d')
1472
1473 patterns.append('%d-%m-%Y')
1474 patterns.append('%d-%m-%y')
1475 patterns.append('%d/%m/%Y')
1476 patterns.append('%d/%m/%y')
1477 patterns.append('%d.%m.%Y')
1478
1479 patterns.append('%m-%d-%Y')
1480 patterns.append('%m-%d-%y')
1481 patterns.append('%m/%d/%Y')
1482 patterns.append('%m/%d/%y')
1483
1484 patterns.append('%Y.%m.%d')
1485
1486 for pattern in patterns:
1487 try:
1488 date = pyDT.datetime.strptime(str2parse, pattern).replace (
1489 hour = 11,
1490 minute = 11,
1491 second = 11,
1492 tzinfo = gmCurrentLocalTimezone
1493 )
1494 matches.append ({
1495 'data': date,
1496 'label': pydt_strftime(date, format = '%Y-%m-%d', accuracy = acc_days)
1497 })
1498 except ValueError:
1499
1500 continue
1501
1502 return matches
1503
1504
1505
1506
1508 """Expand fragments containing a single slash.
1509
1510 "5/"
1511 - 2005/ (2000 - 2025)
1512 - 1995/ (1990 - 1999)
1513 - Mai/current year
1514 - Mai/next year
1515 - Mai/last year
1516 - Mai/200x
1517 - Mai/20xx
1518 - Mai/199x
1519 - Mai/198x
1520 - Mai/197x
1521 - Mai/19xx
1522 """
1523 matches = []
1524 now = pydt_now_here()
1525
1526 if regex.match("^(\s|\t)*\d{1,2}(\s|\t)*/+(\s|\t)*\d{4}(\s|\t)*$", str2parse, flags = regex.UNICODE):
1527 parts = regex.findall('\d+', str2parse, flags = regex.UNICODE)
1528 month = int(parts[0])
1529 if month in range(1, 13):
1530 fts = cFuzzyTimestamp (
1531 timestamp = now.replace(year = int(parts[1], month = month)),
1532 accuracy = acc_months
1533 )
1534 matches.append ({
1535 'data': fts,
1536 'label': fts.format_accurately()
1537 })
1538
1539 elif regex.match("^(\s|\t)*\d{1,2}(\s|\t)*/+(\s|\t)*$", str2parse, flags = regex.UNICODE):
1540 val = int(regex.findall('\d+', str2parse, flags = regex.UNICODE)[0])
1541
1542 if val < 100 and val >= 0:
1543 matches.append ({
1544 'data': None,
1545 'label': '%s/' % (val + 1900)
1546 })
1547
1548 if val < 26 and val >= 0:
1549 matches.append ({
1550 'data': None,
1551 'label': '%s/' % (val + 2000)
1552 })
1553
1554 if val < 10 and val >= 0:
1555 matches.append ({
1556 'data': None,
1557 'label': '%s/' % (val + 1990)
1558 })
1559
1560 if val < 13 and val > 0:
1561 matches.append ({
1562 'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_months),
1563 'label': '%.2d/%s' % (val, now.year)
1564 })
1565 ts = now.replace(year = now.year + 1)
1566 matches.append ({
1567 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_months),
1568 'label': '%.2d/%s' % (val, ts.year)
1569 })
1570 ts = now.replace(year = now.year - 1)
1571 matches.append ({
1572 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_months),
1573 'label': '%.2d/%s' % (val, ts.year)
1574 })
1575 matches.append ({
1576 'data': None,
1577 'label': '%.2d/200' % val
1578 })
1579 matches.append ({
1580 'data': None,
1581 'label': '%.2d/20' % val
1582 })
1583 matches.append ({
1584 'data': None,
1585 'label': '%.2d/199' % val
1586 })
1587 matches.append ({
1588 'data': None,
1589 'label': '%.2d/198' % val
1590 })
1591 matches.append ({
1592 'data': None,
1593 'label': '%.2d/197' % val
1594 })
1595 matches.append ({
1596 'data': None,
1597 'label': '%.2d/19' % val
1598 })
1599
1600 return matches
1601
1602
1604 """This matches on single numbers.
1605
1606 Spaces or tabs are discarded.
1607 """
1608 if not regex.match("^(\s|\t)*\d{1,4}(\s|\t)*$", str2parse, flags = regex.UNICODE):
1609 return []
1610
1611 now = pydt_now_here()
1612 val = int(regex.findall('\d{1,4}', str2parse, flags = regex.UNICODE)[0])
1613
1614 matches = []
1615
1616
1617 if (1850 < val) and (val < 2100):
1618 target_date = cFuzzyTimestamp (
1619 timestamp = now.replace(year = val),
1620 accuracy = acc_years
1621 )
1622 tmp = {
1623 'data': target_date,
1624 'label': '%s' % target_date
1625 }
1626 matches.append(tmp)
1627
1628
1629 if val <= gregorian_month_length[now.month]:
1630 ts = now.replace(day = val)
1631 target_date = cFuzzyTimestamp (
1632 timestamp = ts,
1633 accuracy = acc_days
1634 )
1635 tmp = {
1636 'data': target_date,
1637 'label': _('%d. of %s (this month) - a %s') % (val, ts.strftime('%B'), ts.strftime('%A'))
1638 }
1639 matches.append(tmp)
1640
1641
1642 next_month = get_next_month(now)
1643 if val <= gregorian_month_length[next_month]:
1644 ts = now.replace(day = val, month = next_month)
1645 target_date = cFuzzyTimestamp (
1646 timestamp = ts,
1647 accuracy = acc_days
1648 )
1649 tmp = {
1650 'data': target_date,
1651 'label': _('%d. of %s (next month) - a %s') % (val, ts.strftime('%B'), ts.strftime('%A'))
1652 }
1653 matches.append(tmp)
1654
1655
1656 last_month = get_last_month(now)
1657 if val <= gregorian_month_length[last_month]:
1658 ts = now.replace(day = val, month = last_month)
1659 target_date = cFuzzyTimestamp (
1660 timestamp = ts,
1661 accuracy = acc_days
1662 )
1663 tmp = {
1664 'data': target_date,
1665 'label': _('%d. of %s (last month) - a %s') % (val, ts.strftime('%B'), ts.strftime('%A'))
1666 }
1667 matches.append(tmp)
1668
1669
1670 if val <= 400:
1671 target_date = cFuzzyTimestamp(timestamp = now + pyDT.timedelta(days = val))
1672 tmp = {
1673 'data': target_date,
1674 'label': _('in %d day(s) - %s') % (val, target_date.timestamp.strftime('%A, %Y-%m-%d'))
1675 }
1676 matches.append(tmp)
1677
1678
1679 if val <= 50:
1680 target_date = cFuzzyTimestamp(timestamp = now + pyDT.timedelta(weeks = val))
1681 tmp = {
1682 'data': target_date,
1683 'label': _('in %d week(s) - %s') % (val, target_date.timestamp.strftime('%A, %Y-%m-%d'))
1684 }
1685 matches.append(tmp)
1686
1687
1688 if val < 13:
1689
1690 target_date = cFuzzyTimestamp (
1691 timestamp = pydt_replace(now, month = val),
1692 accuracy = acc_months
1693 )
1694 tmp = {
1695 'data': target_date,
1696 'label': _('%s (%s this year)') % (target_date, ts.strftime('%B'))
1697 }
1698 matches.append(tmp)
1699
1700
1701 target_date = cFuzzyTimestamp (
1702 timestamp = pydt_add(pydt_replace(now, month = val), years = 1),
1703 accuracy = acc_months
1704 )
1705 tmp = {
1706 'data': target_date,
1707 'label': _('%s (%s next year)') % (target_date, ts.strftime('%B'))
1708 }
1709 matches.append(tmp)
1710
1711
1712 target_date = cFuzzyTimestamp (
1713 timestamp = pydt_add(pydt_replace(now, month = val), years = -1),
1714 accuracy = acc_months
1715 )
1716 tmp = {
1717 'data': target_date,
1718 'label': _('%s (%s last year)') % (target_date, ts.strftime('%B'))
1719 }
1720 matches.append(tmp)
1721
1722
1723 matches.append ({
1724 'data': None,
1725 'label': '%s/200' % val
1726 })
1727 matches.append ({
1728 'data': None,
1729 'label': '%s/199' % val
1730 })
1731 matches.append ({
1732 'data': None,
1733 'label': '%s/198' % val
1734 })
1735 matches.append ({
1736 'data': None,
1737 'label': '%s/19' % val
1738 })
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779 if val < 100:
1780 matches.append ({
1781 'data': None,
1782 'label': '%s/' % (1900 + val)
1783 })
1784
1785
1786 if val == 200:
1787 tmp = {
1788 'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_days),
1789 'label': '%s' % target_date
1790 }
1791 matches.append(tmp)
1792 matches.append ({
1793 'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_months),
1794 'label': '%.2d/%s' % (now.month, now.year)
1795 })
1796 matches.append ({
1797 'data': None,
1798 'label': '%s/' % now.year
1799 })
1800 matches.append ({
1801 'data': None,
1802 'label': '%s/' % (now.year + 1)
1803 })
1804 matches.append ({
1805 'data': None,
1806 'label': '%s/' % (now.year - 1)
1807 })
1808
1809 if val < 200 and val >= 190:
1810 for i in range(10):
1811 matches.append ({
1812 'data': None,
1813 'label': '%s%s/' % (val, i)
1814 })
1815
1816 return matches
1817
1818
1820 """
1821 Turn a string into candidate fuzzy timestamps and auto-completions the user is likely to type.
1822
1823 You MUST have called locale.setlocale(locale.LC_ALL, '')
1824 somewhere in your code previously.
1825
1826 @param default_time: if you want to force the time part of the time
1827 stamp to a given value and the user doesn't type any time part
1828 this value will be used
1829 @type default_time: an mx.DateTime.DateTimeDelta instance
1830
1831 @param patterns: list of [time.strptime compatible date/time pattern, accuracy]
1832 @type patterns: list
1833 """
1834 matches = []
1835
1836 matches.extend(__numbers_only(str2parse))
1837 matches.extend(__single_slash(str2parse))
1838
1839 matches.extend ([
1840 { 'data': cFuzzyTimestamp(timestamp = m['data'], accuracy = acc_days),
1841 'label': m['label']
1842 } for m in __single_dot2py_dt(str2parse)
1843 ])
1844 matches.extend ([
1845 { 'data': cFuzzyTimestamp(timestamp = m['data'], accuracy = acc_days),
1846 'label': m['label']
1847 } for m in __single_char2py_dt(str2parse)
1848 ])
1849 matches.extend ([
1850 { 'data': cFuzzyTimestamp(timestamp = m['data'], accuracy = acc_days),
1851 'label': m['label']
1852 } for m in __explicit_offset2py_dt(str2parse)
1853 ])
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886 if patterns is None:
1887 patterns = []
1888 patterns.extend([
1889 ['%Y-%m-%d', acc_days],
1890 ['%y-%m-%d', acc_days],
1891 ['%Y/%m/%d', acc_days],
1892 ['%y/%m/%d', acc_days],
1893
1894 ['%d-%m-%Y', acc_days],
1895 ['%d-%m-%y', acc_days],
1896 ['%d/%m/%Y', acc_days],
1897 ['%d/%m/%y', acc_days],
1898 ['%d.%m.%Y', acc_days],
1899
1900 ['%m-%d-%Y', acc_days],
1901 ['%m-%d-%y', acc_days],
1902 ['%m/%d/%Y', acc_days],
1903 ['%m/%d/%y', acc_days]
1904 ])
1905 for pattern in patterns:
1906 try:
1907 ts = pyDT.datetime.strptime(str2parse, pattern[0]).replace (
1908 hour = 11,
1909 minute = 11,
1910 second = 11,
1911 tzinfo = gmCurrentLocalTimezone
1912 )
1913 fts = cFuzzyTimestamp(timestamp = ts, accuracy = pattern[1])
1914 matches.append ({
1915 'data': fts,
1916 'label': fts.format_accurately()
1917 })
1918 except ValueError:
1919
1920 continue
1921
1922 return matches
1923
1924
1925
1926
1928
1929
1930
1931 """A timestamp implementation with definable inaccuracy.
1932
1933 This class contains an datetime.datetime instance to
1934 hold the actual timestamp. It adds an accuracy attribute
1935 to allow the programmer to set the precision of the
1936 timestamp.
1937
1938 The timestamp will have to be initialzed with a fully
1939 precise value (which may, of course, contain partially
1940 fake data to make up for missing values). One can then
1941 set the accuracy value to indicate up to which part of
1942 the timestamp the data is valid. Optionally a modifier
1943 can be set to indicate further specification of the
1944 value (such as "summer", "afternoon", etc).
1945
1946 accuracy values:
1947 1: year only
1948 ...
1949 7: everything including milliseconds value
1950
1951 Unfortunately, one cannot directly derive a class from mx.DateTime.DateTime :-(
1952 """
1953
1955
1956 if timestamp is None:
1957 timestamp = pydt_now_here()
1958 accuracy = acc_subseconds
1959 modifier = ''
1960
1961 if (accuracy < 1) or (accuracy > 8):
1962 raise ValueError('%s.__init__(): <accuracy> must be between 1 and 8' % self.__class__.__name__)
1963
1964 if not isinstance(timestamp, pyDT.datetime):
1965 raise TypeError('%s.__init__(): <timestamp> must be of datetime.datetime type, but is %s' % self.__class__.__name__, type(timestamp))
1966
1967 if timestamp.tzinfo is None:
1968 raise ValueError('%s.__init__(): <tzinfo> must be defined' % self.__class__.__name__)
1969
1970 self.timestamp = timestamp
1971 self.accuracy = accuracy
1972 self.modifier = modifier
1973
1974
1975
1976
1978 """Return string representation meaningful to a user, also for %s formatting."""
1979 return self.format_accurately()
1980
1981
1983 """Return string meaningful to a programmer to aid in debugging."""
1984 tmp = '<[%s]: timestamp [%s], accuracy [%s] (%s), modifier [%s] at %s>' % (
1985 self.__class__.__name__,
1986 repr(self.timestamp),
1987 self.accuracy,
1988 _accuracy_strings[self.accuracy],
1989 self.modifier,
1990 id(self)
1991 )
1992 return tmp
1993
1994
1995
1996
2001
2002
2005
2006
2039
2040
2042 return self.timestamp
2043
2044
2045
2046
2047 if __name__ == '__main__':
2048
2049 if len(sys.argv) < 2:
2050 sys.exit()
2051
2052 if sys.argv[1] != "test":
2053 sys.exit()
2054
2055
2056 intervals_as_str = [
2057 '7', '12', ' 12', '12 ', ' 12 ', ' 12 ', '0', '~12', '~ 12', ' ~ 12', ' ~ 12 ',
2058 '12a', '12 a', '12 a', '12j', '12J', '12y', '12Y', ' ~ 12 a ', '~0a',
2059 '12m', '17 m', '12 m', '17M', ' ~ 17 m ', ' ~ 3 / 12 ', '7/12', '0/12',
2060 '12w', '17 w', '12 w', '17W', ' ~ 17 w ', ' ~ 15 / 52', '2/52', '0/52',
2061 '12d', '17 d', '12 t', '17D', ' ~ 17 T ', ' ~ 12 / 7', '3/7', '0/7',
2062 '12h', '17 h', '12 H', '17H', ' ~ 17 h ', ' ~ 36 / 24', '7/24', '0/24',
2063 ' ~ 36 / 60', '7/60', '190/60', '0/60',
2064 '12a1m', '12 a 1 M', '12 a17m', '12j 12m', '12J7m', '12y7m', '12Y7M', ' ~ 12 a 37 m ', '~0a0m',
2065 '10m1w',
2066 'invalid interval input'
2067 ]
2068
2082
2083
2151
2153 print ("testing str2interval()")
2154 print ("----------------------")
2155
2156 for interval_as_str in intervals_as_str:
2157 print ("input: <%s>" % interval_as_str)
2158 print (" ==>", str2interval(str_interval=interval_as_str))
2159
2160 return True
2161
2163 print ("DST currently in effect:", dst_currently_in_effect)
2164 print ("current UTC offset:", current_local_utc_offset_in_seconds, "seconds")
2165 print ("current timezone (interval):", current_local_timezone_interval)
2166 print ("current timezone (ISO conformant numeric string):", current_local_iso_numeric_timezone_string)
2167 print ("local timezone class:", cLocalTimezone)
2168 print ("")
2169 tz = cLocalTimezone()
2170 print ("local timezone instance:", tz)
2171 print (" (total) UTC offset:", tz.utcoffset(pyDT.datetime.now()))
2172 print (" DST adjustment:", tz.dst(pyDT.datetime.now()))
2173 print (" timezone name:", tz.tzname(pyDT.datetime.now()))
2174 print ("")
2175 print ("current local timezone:", gmCurrentLocalTimezone)
2176 print (" (total) UTC offset:", gmCurrentLocalTimezone.utcoffset(pyDT.datetime.now()))
2177 print (" DST adjustment:", gmCurrentLocalTimezone.dst(pyDT.datetime.now()))
2178 print (" timezone name:", gmCurrentLocalTimezone.tzname(pyDT.datetime.now()))
2179 print ("")
2180 print ("now here:", pydt_now_here())
2181 print ("")
2182
2183
2185 print ("testing function str2fuzzy_timestamp_matches")
2186 print ("--------------------------------------------")
2187
2188 val = None
2189 while val != 'exit':
2190 val = input('Enter date fragment ("exit" quits): ')
2191 matches = str2fuzzy_timestamp_matches(str2parse = val)
2192 for match in matches:
2193 print ('label shown :', match['label'])
2194 print ('data attached:', match['data'], match['data'].timestamp)
2195 print ("")
2196 print ("---------------")
2197
2198
2200 print ("testing fuzzy timestamp class")
2201 print ("-----------------------------")
2202
2203 fts = cFuzzyTimestamp()
2204 print ("\nfuzzy timestamp <%s '%s'>" % ('class', fts.__class__.__name__))
2205 for accuracy in range(1,8):
2206 fts.accuracy = accuracy
2207 print (" accuracy : %s (%s)" % (accuracy, _accuracy_strings[accuracy]))
2208 print (" format_accurately:", fts.format_accurately())
2209 print (" strftime() :", fts.strftime('%Y %b %d %H:%M:%S'))
2210 print (" print ... :", fts)
2211 print (" print '%%s' %% ... : %s" % fts)
2212 print (" str() :", str(fts))
2213 print (" repr() :", repr(fts))
2214 input('press ENTER to continue')
2215
2216
2218 print ("testing platform for handling dates before 1970")
2219 print ("-----------------------------------------------")
2220 ts = mxDT.DateTime(1935, 4, 2)
2221 fts = cFuzzyTimestamp(timestamp=ts)
2222 print ("fts :", fts)
2223 print ("fts.get_pydt():", fts.get_pydt())
2224
2226
2227 start = pydt_now_here().replace(year = 2000).replace(month = 2).replace(day = 29)
2228 end = pydt_now_here().replace(year = 2012).replace(month = 2).replace(day = 27)
2229 print ("start is leap year: 29.2.2000")
2230 print (" ", calculate_apparent_age(start = start, end = end))
2231 print (" ", format_apparent_age_medically(calculate_apparent_age(start = start)))
2232
2233 start = pydt_now_here().replace(month = 10).replace(day = 23).replace(year = 1974)
2234 end = pydt_now_here().replace(year = 2012).replace(month = 2).replace(day = 29)
2235 print ("end is leap year: 29.2.2012")
2236 print (" ", calculate_apparent_age(start = start, end = end))
2237 print (" ", format_apparent_age_medically(calculate_apparent_age(start = start)))
2238
2239 start = pydt_now_here().replace(year = 2000).replace(month = 2).replace(day = 29)
2240 end = pydt_now_here().replace(year = 2012).replace(month = 2).replace(day = 29)
2241 print ("start is leap year: 29.2.2000")
2242 print ("end is leap year: 29.2.2012")
2243 print (" ", calculate_apparent_age(start = start, end = end))
2244 print (" ", format_apparent_age_medically(calculate_apparent_age(start = start)))
2245
2246 print ("leap year tests worked")
2247
2248 start = pydt_now_here().replace(month = 10).replace(day = 23).replace(year = 1974)
2249 print (calculate_apparent_age(start = start))
2250 print (format_apparent_age_medically(calculate_apparent_age(start = start)))
2251
2252 start = pydt_now_here().replace(month = 3).replace(day = 13).replace(year = 1979)
2253 print (calculate_apparent_age(start = start))
2254 print (format_apparent_age_medically(calculate_apparent_age(start = start)))
2255
2256 start = pydt_now_here().replace(month = 2, day = 2).replace(year = 1979)
2257 end = pydt_now_here().replace(month = 3).replace(day = 31).replace(year = 1979)
2258 print (calculate_apparent_age(start = start, end = end))
2259
2260 start = pydt_now_here().replace(month = 7, day = 21).replace(year = 2009)
2261 print (format_apparent_age_medically(calculate_apparent_age(start = start)))
2262
2263 print ("-------")
2264 start = pydt_now_here().replace(month = 1).replace(day = 23).replace(hour = 12).replace(minute = 11).replace(year = 2011)
2265 print (calculate_apparent_age(start = start))
2266 print (format_apparent_age_medically(calculate_apparent_age(start = start)))
2267
2269 print ("testing function str2pydt_matches")
2270 print ("---------------------------------")
2271
2272 val = None
2273 while val != 'exit':
2274 val = input('Enter date fragment ("exit" quits): ')
2275 matches = str2pydt_matches(str2parse = val)
2276 for match in matches:
2277 print ('label shown :', match['label'])
2278 print ('data attached:', match['data'])
2279 print ("")
2280 print ("---------------")
2281
2282
2295
2297 for year in range(1995, 2017):
2298 print (year, "leaps:", is_leap_year(year))
2299
2300
2301
2302 gmI18N.activate_locale()
2303 gmI18N.install_domain('gnumed')
2304
2305 init()
2306
2307
2308 test_str2fuzzy_timestamp_matches()
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320