1
2
3 __doc__ = """
4 GNUmed date/time handling.
5
6 This modules provides access to date/time handling
7 and offers an fuzzy timestamp implementation
8
9 It utilizes
10
11 - Python time
12 - Python datetime
13 - mxDateTime
14
15 Note that if you want locale-aware formatting you need to call
16
17 locale.setlocale(locale.LC_ALL, '')
18
19 somewhere before importing this script.
20
21 Note regarding UTC offsets
22 --------------------------
23
24 Looking from Greenwich:
25 WEST (IOW "behind"): negative values
26 EAST (IOW "ahead"): positive values
27
28 This is in compliance with what datetime.tzinfo.utcoffset()
29 does but NOT what time.altzone/time.timezone do !
30
31 This module also implements a class which allows the
32 programmer to define the degree of fuzziness, uncertainty
33 or imprecision of the timestamp contained within.
34
35 This is useful in fields such as medicine where only partial
36 timestamps may be known for certain events.
37
38 Other useful links:
39
40 http://joda-time.sourceforge.net/key_instant.html
41 """
42
43 __author__ = "K. Hilbert <Karsten.Hilbert@gmx.net>"
44 __license__ = "GPL v2 or later (details at http://www.gnu.org)"
45
46
47 import sys, datetime as pyDT, time, os, re as regex, locale, logging
48
49
50
51
52
53
54 if __name__ == '__main__':
55 sys.path.insert(0, '../../')
56
57
58
59 _log = logging.getLogger('gm.datetime')
60
61
62 dst_locally_in_use = None
63 dst_currently_in_effect = None
64
65 current_local_utc_offset_in_seconds = None
66 current_local_timezone_interval = None
67 current_local_iso_numeric_timezone_string = None
68 current_local_timezone_name = None
69 py_timezone_name = None
70 py_dst_timezone_name = None
71
72 gmCurrentLocalTimezone = 'gmCurrentLocalTimezone not initialized'
73
74
75 ( acc_years,
76 acc_months,
77 acc_weeks,
78 acc_days,
79 acc_hours,
80 acc_minutes,
81 acc_seconds,
82 acc_subseconds
83 ) = range(1,9)
84
85 _accuracy_strings = {
86 1: 'years',
87 2: 'months',
88 3: 'weeks',
89 4: 'days',
90 5: 'hours',
91 6: 'minutes',
92 7: 'seconds',
93 8: 'subseconds'
94 }
95
96 gregorian_month_length = {
97 1: 31,
98 2: 28,
99 3: 31,
100 4: 30,
101 5: 31,
102 6: 30,
103 7: 31,
104 8: 31,
105 9: 30,
106 10: 31,
107 11: 30,
108 12: 31
109 }
110
111 avg_days_per_gregorian_year = 365
112 avg_days_per_gregorian_month = 30
113 avg_seconds_per_day = 24 * 60 * 60
114 days_per_week = 7
115
116
117
118
120
121
122 _log.debug('datetime.now() : [%s]' % pyDT.datetime.now())
123 _log.debug('time.localtime() : [%s]' % str(time.localtime()))
124 _log.debug('time.gmtime() : [%s]' % str(time.gmtime()))
125
126 try:
127 _log.debug('$TZ: [%s]' % os.environ['TZ'])
128 except KeyError:
129 _log.debug('$TZ not defined')
130
131 _log.debug('time.daylight: [%s] (whether or not DST is locally used at all)' % time.daylight)
132 _log.debug('time.timezone: [%s] seconds' % time.timezone)
133 _log.debug('time.altzone : [%s] seconds' % time.altzone)
134 _log.debug('time.tzname : [%s / %s] (non-DST / DST)' % time.tzname)
135
136
137 global py_timezone_name
138 py_timezone_name = time.tzname[0]
139
140 global py_dst_timezone_name
141 py_dst_timezone_name = time.tzname[1]
142
143 global dst_locally_in_use
144 dst_locally_in_use = (time.daylight != 0)
145
146 global dst_currently_in_effect
147 dst_currently_in_effect = bool(time.localtime()[8])
148 _log.debug('DST currently in effect: [%s]' % dst_currently_in_effect)
149
150 if (not dst_locally_in_use) and dst_currently_in_effect:
151 _log.error('system inconsistency: DST not in use - but DST currently in effect ?')
152
153 global current_local_utc_offset_in_seconds
154 msg = 'DST currently%sin effect: using UTC offset of [%s] seconds instead of [%s] seconds'
155 if dst_currently_in_effect:
156 current_local_utc_offset_in_seconds = time.altzone * -1
157 _log.debug(msg % (' ', time.altzone * -1, time.timezone * -1))
158 else:
159 current_local_utc_offset_in_seconds = time.timezone * -1
160 _log.debug(msg % (' not ', time.timezone * -1, time.altzone * -1))
161
162 if current_local_utc_offset_in_seconds > 0:
163 _log.debug('UTC offset is positive, assuming EAST of Greenwich (clock is "ahead")')
164 elif current_local_utc_offset_in_seconds < 0:
165 _log.debug('UTC offset is negative, assuming WEST of Greenwich (clock is "behind")')
166 else:
167 _log.debug('UTC offset is ZERO, assuming Greenwich Time')
168
169 global current_local_timezone_interval
170
171 _log.debug('ISO timezone: [%s] (taken from mx.DateTime.now().gmtoffset())' % current_local_timezone_interval)
172
173 global current_local_iso_numeric_timezone_string
174 current_local_iso_numeric_timezone_string = str(current_local_timezone_interval).replace(',', '.')
175
176 global current_local_timezone_name
177 try:
178 current_local_timezone_name = os.environ['TZ']
179 except KeyError:
180 if dst_currently_in_effect:
181 current_local_timezone_name = time.tzname[1]
182 else:
183 current_local_timezone_name = time.tzname[0]
184
185
186
187
188
189
190 global gmCurrentLocalTimezone
191 gmCurrentLocalTimezone = cPlatformLocalTimezone()
192 _log.debug('local-timezone class: %s', cPlatformLocalTimezone)
193 _log.debug('local-timezone instance: %s', gmCurrentLocalTimezone)
194
195
196
197
198
199
200
201
202
203
204
205
206
252
253
254
255
257 next_month = dt.month + 1
258 if next_month == 13:
259 return 1
260 return next_month
261
262
264 last_month = dt.month - 1
265 if last_month == 0:
266 return 12
267 return last_month
268
269
271
272
273
274 if weekday not in [0,1,2,3,4,5,6,7]:
275 raise ValueError('weekday must be in 0 (Sunday) to 7 (Sunday, again)')
276 if base_dt is None:
277 base_dt = pydt_now_here()
278 dt_weekday = base_dt.isoweekday()
279 day_diff = dt_weekday - weekday
280 days2add = (-1 * day_diff)
281 return pydt_add(base_dt, days = days2add)
282
283
285
286
287
288 if weekday not in [0,1,2,3,4,5,6,7]:
289 raise ValueError('weekday must be in 0 (Sunday) to 7 (Sunday, again)')
290 if weekday == 0:
291 weekday = 7
292 if base_dt is None:
293 base_dt = pydt_now_here()
294 dt_weekday = base_dt.isoweekday()
295 days2add = weekday - dt_weekday
296 if days2add == 0:
297 days2add = 7
298 elif days2add < 0:
299 days2add += 7
300 return pydt_add(base_dt, days = days2add)
301
302
303
304
306
307 if isinstance(mxDateTime, pyDT.datetime):
308 return mxDateTime
309
310 try:
311 tz_name = str(mxDateTime.gmtoffset()).replace(',', '.')
312 except mxDT.Error:
313 _log.debug('mx.DateTime cannot gmtoffset() this timestamp, assuming local time')
314 tz_name = current_local_iso_numeric_timezone_string
315
316 if dst_currently_in_effect:
317
318 tz = cFixedOffsetTimezone (
319 offset = ((time.altzone * -1) // 60),
320 name = tz_name
321 )
322 else:
323
324 tz = cFixedOffsetTimezone (
325 offset = ((time.timezone * -1) // 60),
326 name = tz_name
327 )
328
329 try:
330 return pyDT.datetime (
331 year = mxDateTime.year,
332 month = mxDateTime.month,
333 day = mxDateTime.day,
334 tzinfo = tz
335 )
336 except Exception:
337 _log.debug ('error converting mx.DateTime.DateTime to Python: %s-%s-%s %s:%s %s.%s',
338 mxDateTime.year,
339 mxDateTime.month,
340 mxDateTime.day,
341 mxDateTime.hour,
342 mxDateTime.minute,
343 mxDateTime.second,
344 mxDateTime.tz
345 )
346 raise
347
348
360
361
362 -def pydt_strftime(dt=None, format='%Y %b %d %H:%M.%S', accuracy=None, none_str=None):
363
364 if dt is None:
365 if none_str is not None:
366 return none_str
367 raise ValueError('must provide <none_str> if <dt>=None is to be dealt with')
368
369 try:
370 return dt.strftime(format)
371 except ValueError:
372 _log.exception()
373 return 'strftime() error'
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405 -def pydt_add(dt, years=0, months=0, weeks=0, days=0, hours=0, minutes=0, seconds=0, milliseconds=0, microseconds=0):
406 if months > 11 or months < -11:
407 raise ValueError('pydt_add(): months must be within [-11..11]')
408
409 dt = dt + pyDT.timedelta (
410 weeks = weeks,
411 days = days,
412 hours = hours,
413 minutes = minutes,
414 seconds = seconds,
415 milliseconds = milliseconds,
416 microseconds = microseconds
417 )
418 if (years == 0) and (months == 0):
419 return dt
420 target_year = dt.year + years
421 target_month = dt.month + months
422 if target_month > 12:
423 target_year += 1
424 target_month -= 12
425 elif target_month < 1:
426 target_year -= 1
427 target_month += 12
428 return pydt_replace(dt, year = target_year, month = target_month, strict = False)
429
430
431 -def pydt_replace(dt, strict=True, year=None, month=None, day=None, hour=None, minute=None, second=None, microsecond=None, tzinfo=None):
432
433
434 if year is None:
435 year = dt.year
436 if month is None:
437 month = dt.month
438 if day is None:
439 day = dt.day
440 if hour is None:
441 hour = dt.hour
442 if minute is None:
443 minute = dt.minute
444 if second is None:
445 second = dt.second
446 if microsecond is None:
447 microsecond = dt.microsecond
448 if tzinfo is None:
449 tzinfo = dt.tzinfo
450
451 if strict:
452 return dt.replace(year = year, month = month, day = day, hour = hour, minute = minute, second = second, microsecond = microsecond, tzinfo = tzinfo)
453
454 try:
455 return dt.replace(year = year, month = month, day = day, hour = hour, minute = minute, second = second, microsecond = microsecond, tzinfo = tzinfo)
456 except ValueError:
457 _log.debug('error replacing datetime member(s): %s', locals())
458
459
460 if month == 2:
461 if day > 28:
462 if is_leap_year(year):
463 day = 29
464 else:
465 day = 28
466 else:
467 if day == 31:
468 day = 30
469
470 return dt.replace(year = year, month = month, day = day, hour = hour, minute = minute, second = second, microsecond = microsecond, tzinfo = tzinfo)
471
472
482
483
487
488
491
492
493
494
496 if not wxDate.IsValid():
497 raise ValueError ('invalid wxDate: %s-%s-%s %s:%s %s.%s',
498 wxDate.GetYear(),
499 wxDate.GetMonth(),
500 wxDate.GetDay(),
501 wxDate.GetHour(),
502 wxDate.GetMinute(),
503 wxDate.GetSecond(),
504 wxDate.GetMillisecond()
505 )
506
507 try:
508 return pyDT.datetime (
509 year = wxDate.GetYear(),
510 month = wxDate.GetMonth() + 1,
511 day = wxDate.GetDay(),
512 tzinfo = gmCurrentLocalTimezone
513 )
514 except Exception:
515 _log.debug ('error converting wxDateTime to Python: %s-%s-%s %s:%s %s.%s',
516 wxDate.GetYear(),
517 wxDate.GetMonth(),
518 wxDate.GetDay(),
519 wxDate.GetHour(),
520 wxDate.GetMinute(),
521 wxDate.GetSecond(),
522 wxDate.GetMillisecond()
523 )
524 raise
525
526
527
528
665
666
729
730
739
740
747
748
750
751 div, remainder = divmod(year, 4)
752
753 if remainder > 0:
754 return False
755
756
757 div, remainder = divmod(year, 100)
758
759 if remainder > 0:
760 return True
761
762
763 div, remainder = divmod(year, 400)
764
765 if remainder == 0:
766 return True
767
768 return False
769
770
772 """The result of this is a tuple (years, ..., seconds) as one would
773 'expect' an age to look like, that is, simple differences between
774 the fields:
775
776 (years, months, days, hours, minutes, seconds)
777
778 This does not take into account time zones which may
779 shift the result by one day.
780
781 <start> and <end> must by python datetime instances
782 <end> is assumed to be "now" if not given
783 """
784 if end is None:
785 end = pyDT.datetime.now(gmCurrentLocalTimezone)
786
787 if end < start:
788 raise ValueError('calculate_apparent_age(): <end> (%s) before <start> (%s)' % (end, start))
789
790 if end == start:
791 return (0, 0, 0, 0, 0, 0)
792
793
794 if end.month == 2:
795 if end.day == 29:
796 if not is_leap_year(start.year):
797 end = end.replace(day = 28)
798
799
800 years = end.year - start.year
801 end = end.replace(year = start.year)
802 if end < start:
803 years = years - 1
804
805
806 if end.month == start.month:
807 if end < start:
808 months = 11
809 else:
810 months = 0
811 else:
812 months = end.month - start.month
813 if months < 0:
814 months = months + 12
815 if end.day > gregorian_month_length[start.month]:
816 end = end.replace(month = start.month, day = gregorian_month_length[start.month])
817 else:
818 end = end.replace(month = start.month)
819 if end < start:
820 months = months - 1
821
822
823 if end.day == start.day:
824 if end < start:
825 days = gregorian_month_length[start.month] - 1
826 else:
827 days = 0
828 else:
829 days = end.day - start.day
830 if days < 0:
831 days = days + gregorian_month_length[start.month]
832 end = end.replace(day = start.day)
833 if end < start:
834 days = days - 1
835
836
837 if end.hour == start.hour:
838 hours = 0
839 else:
840 hours = end.hour - start.hour
841 if hours < 0:
842 hours = hours + 24
843 end = end.replace(hour = start.hour)
844 if end < start:
845 hours = hours - 1
846
847
848 if end.minute == start.minute:
849 minutes = 0
850 else:
851 minutes = end.minute - start.minute
852 if minutes < 0:
853 minutes = minutes + 60
854 end = end.replace(minute = start.minute)
855 if end < start:
856 minutes = minutes - 1
857
858
859 if end.second == start.second:
860 seconds = 0
861 else:
862 seconds = end.second - start.second
863 if seconds < 0:
864 seconds = seconds + 60
865 end = end.replace(second = start.second)
866 if end < start:
867 seconds = seconds - 1
868
869 return (years, months, days, hours, minutes, seconds)
870
871
975
977
978 unit_keys = {
979 'year': _('yYaA_keys_year'),
980 'month': _('mM_keys_month'),
981 'week': _('wW_keys_week'),
982 'day': _('dD_keys_day'),
983 'hour': _('hH_keys_hour')
984 }
985
986 str_interval = str_interval.strip()
987
988
989 keys = '|'.join(list(unit_keys['year'].replace('_keys_year', '')))
990 if regex.match('^~*(\s|\t)*\d+(%s)*$' % keys, str_interval, flags = regex.UNICODE):
991 return pyDT.timedelta(days = (int(regex.findall('\d+', str_interval, flags = regex.UNICODE)[0]) * avg_days_per_gregorian_year))
992
993
994 keys = '|'.join(list(unit_keys['month'].replace('_keys_month', '')))
995 if regex.match('^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.UNICODE):
996 years, months = divmod (
997 int(regex.findall('\d+', str_interval, flags = regex.UNICODE)[0]),
998 12
999 )
1000 return pyDT.timedelta(days = ((years * avg_days_per_gregorian_year) + (months * avg_days_per_gregorian_month)))
1001
1002
1003 keys = '|'.join(list(unit_keys['week'].replace('_keys_week', '')))
1004 if regex.match('^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.UNICODE):
1005 return pyDT.timedelta(weeks = int(regex.findall('\d+', str_interval, flags = regex.UNICODE)[0]))
1006
1007
1008 keys = '|'.join(list(unit_keys['day'].replace('_keys_day', '')))
1009 if regex.match('^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.UNICODE):
1010 return pyDT.timedelta(days = int(regex.findall('\d+', str_interval, flags = regex.UNICODE)[0]))
1011
1012
1013 keys = '|'.join(list(unit_keys['hour'].replace('_keys_hour', '')))
1014 if regex.match('^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.UNICODE):
1015 return pyDT.timedelta(hours = int(regex.findall('\d+', str_interval, flags = regex.UNICODE)[0]))
1016
1017
1018 if regex.match('^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*12$', str_interval, flags = regex.UNICODE):
1019 years, months = divmod (
1020 int(regex.findall('\d+', str_interval, flags = regex.UNICODE)[0]),
1021 12
1022 )
1023 return pyDT.timedelta(days = ((years * avg_days_per_gregorian_year) + (months * avg_days_per_gregorian_month)))
1024
1025
1026 if regex.match('^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*52$', str_interval, flags = regex.UNICODE):
1027 return pyDT.timedelta(weeks = int(regex.findall('\d+', str_interval, flags = regex.UNICODE)[0]))
1028
1029
1030 if regex.match('^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*7$', str_interval, flags = regex.UNICODE):
1031 return pyDT.timedelta(days = int(regex.findall('\d+', str_interval, flags = regex.UNICODE)[0]))
1032
1033
1034 if regex.match('^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*24$', str_interval, flags = regex.UNICODE):
1035 return pyDT.timedelta(hours = int(regex.findall('\d+', str_interval, flags = regex.UNICODE)[0]))
1036
1037
1038 if regex.match('^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*60$', str_interval, flags = regex.UNICODE):
1039 return pyDT.timedelta(minutes = int(regex.findall('\d+', str_interval, flags = regex.UNICODE)[0]))
1040
1041
1042 keys_year = '|'.join(list(unit_keys['year'].replace('_keys_year', '')))
1043 keys_month = '|'.join(list(unit_keys['month'].replace('_keys_month', '')))
1044 if regex.match('^~*(\s|\t)*\d+(%s|\s|\t)+\d+(\s|\t)*(%s)+$' % (keys_year, keys_month), str_interval, flags = regex.UNICODE):
1045 parts = regex.findall('\d+', str_interval, flags = regex.UNICODE)
1046 years, months = divmod(int(parts[1]), 12)
1047 years += int(parts[0])
1048 return pyDT.timedelta(days = ((years * avg_days_per_gregorian_year) + (months * avg_days_per_gregorian_month)))
1049
1050
1051 keys_month = '|'.join(list(unit_keys['month'].replace('_keys_month', '')))
1052 keys_week = '|'.join(list(unit_keys['week'].replace('_keys_week', '')))
1053 if regex.match('^~*(\s|\t)*\d+(%s|\s|\t)+\d+(\s|\t)*(%s)+$' % (keys_month, keys_week), str_interval, flags = regex.UNICODE):
1054 parts = regex.findall('\d+', str_interval, flags = regex.UNICODE)
1055 months, weeks = divmod(int(parts[1]), 4)
1056 months += int(parts[0])
1057 return pyDT.timedelta(days = ((months * avg_days_per_gregorian_month) + (weeks * days_per_week)))
1058
1059 return None
1060
1061
1062
1063
1065 """This matches on single characters.
1066
1067 Spaces and tabs are discarded.
1068
1069 Default is 'ndmy':
1070 n - _N_ow
1071 d - to_D_ay
1072 m - to_M_orrow Someone please suggest a synonym ! ("2" does not cut it ...)
1073 y - _Y_esterday
1074
1075 This also defines the significance of the order of the characters.
1076 """
1077 str2parse = str2parse.strip().lower()
1078 if len(str2parse) != 1:
1079 return []
1080
1081 if trigger_chars is None:
1082 trigger_chars = _('ndmy (single character date triggers)')[:4].lower()
1083
1084 if str2parse not in trigger_chars:
1085 return []
1086
1087 now = pydt_now_here()
1088
1089
1090
1091
1092 if str2parse == trigger_chars[0]:
1093 return [{
1094 'data': now,
1095 'label': _('right now (%s, %s)') % (now.strftime('%A'), now)
1096 }]
1097
1098 if str2parse == trigger_chars[1]:
1099 return [{
1100 'data': now,
1101 'label': _('today (%s)') % now.strftime('%A, %Y-%m-%d')
1102 }]
1103
1104 if str2parse == trigger_chars[2]:
1105 ts = pydt_add(now, days = 1)
1106 return [{
1107 'data': ts,
1108 'label': _('tomorrow (%s)') % ts.strftime('%A, %Y-%m-%d')
1109 }]
1110
1111 if str2parse == trigger_chars[3]:
1112 ts = pydt_add(now, days = -1)
1113 return [{
1114 'data': ts,
1115 'label': _('yesterday (%s)') % ts.strftime('%A, %Y-%m-%d')
1116 }]
1117 return []
1118
1119
1121 """Expand fragments containing a single dot.
1122
1123 Standard colloquial date format in Germany: day.month.year
1124
1125 "14."
1126 - the 14th of the current month
1127 - the 14th of next month
1128 "-14."
1129 - the 14th of last month
1130 """
1131 str2parse = str2parse.replace(' ', '').replace('\t', '')
1132
1133 if not str2parse.endswith('.'):
1134 return []
1135 try:
1136 day_val = int(str2parse[:-1])
1137 except ValueError:
1138 return []
1139 if (day_val < -31) or (day_val > 31) or (day_val == 0):
1140 return []
1141
1142 now = pydt_now_here()
1143 matches = []
1144
1145
1146 if day_val < 0:
1147 ts = pydt_replace(pydt_add(now, months = -1), day = abs(day_val), strict = False)
1148 if ts.day == day_val:
1149 matches.append ({
1150 'data': ts,
1151 'label': _('%s-%s-%s: a %s last month') % (ts.year, ts.month, ts.day, ts.strftime('%A'))
1152 })
1153
1154
1155 if day_val > 0:
1156
1157 try:
1158 ts = pydt_replace(now, day = day_val, strict = False)
1159 matches.append ({
1160 'data': ts,
1161 'label': _('%s-%s-%s: a %s this month') % (ts.year, ts.month, ts.day, ts.strftime('%A'))
1162 })
1163 except ValueError:
1164 pass
1165
1166 try:
1167 ts = pydt_replace(pydt_add(now, months = 1), day = day_val, strict = False)
1168 if ts.day == day_val:
1169 matches.append ({
1170 'data': ts,
1171 'label': _('%s-%s-%s: a %s next month') % (ts.year, ts.month, ts.day, ts.strftime('%A'))
1172 })
1173 except ValueError:
1174 pass
1175
1176 try:
1177 ts = pydt_replace(pydt_add(now, months = -1), day = day_val, strict = False)
1178 if ts.day == day_val:
1179 matches.append ({
1180 'data': ts,
1181 'label': _('%s-%s-%s: a %s last month') % (ts.year, ts.month, ts.day, ts.strftime('%A'))
1182 })
1183 except ValueError:
1184 pass
1185
1186 return matches
1187
1188
1190 """Expand fragments containing a single slash.
1191
1192 "5/"
1193 - 2005/ (2000 - 2025)
1194 - 1995/ (1990 - 1999)
1195 - Mai/current year
1196 - Mai/next year
1197 - Mai/last year
1198 - Mai/200x
1199 - Mai/20xx
1200 - Mai/199x
1201 - Mai/198x
1202 - Mai/197x
1203 - Mai/19xx
1204
1205 5/1999
1206 6/2004
1207 """
1208 str2parse = str2parse.strip()
1209
1210 now = pydt_now_here()
1211
1212
1213 if regex.match(r"^\d{1,2}(\s|\t)*/+(\s|\t)*\d{4}$", str2parse, flags = regex.UNICODE):
1214 month, year = regex.findall(r'\d+', str2parse, flags = regex.UNICODE)
1215 ts = pydt_replace(now, year = int(year), month = int(month), strict = False)
1216 return [{
1217 'data': ts,
1218 'label': ts.strftime('%Y-%m-%d')
1219 }]
1220
1221 matches = []
1222
1223 if regex.match(r"^\d{1,2}(\s|\t)*/+$", str2parse, flags = regex.UNICODE):
1224 val = int(str2parse.rstrip('/').strip())
1225
1226
1227 if val < 100 and val >= 0:
1228 matches.append ({
1229 'data': None,
1230 'label': '%s-' % (val + 1900)
1231 })
1232
1233 if val < 26 and val >= 0:
1234 matches.append ({
1235 'data': None,
1236 'label': '%s-' % (val + 2000)
1237 })
1238
1239 if val < 10 and val >= 0:
1240 matches.append ({
1241 'data': None,
1242 'label': '%s-' % (val + 1990)
1243 })
1244 if val < 13 and val > 0:
1245
1246 matches.append ({
1247 'data': None,
1248 'label': '%s-%.2d-' % (now.year, val)
1249 })
1250
1251 ts = pydt_add(now, years = 1)
1252 matches.append ({
1253 'data': None,
1254 'label': '%s-%.2d-' % (ts.year, val)
1255 })
1256
1257 ts = pydt_add(now, years = -1)
1258 matches.append ({
1259 'data': None,
1260 'label': '%s-%.2d-' % (ts.year, val)
1261 })
1262
1263 matches.append ({
1264 'data': None,
1265 'label': '201?-%.2d-' % val
1266 })
1267
1268 matches.append ({
1269 'data': None,
1270 'label': '200?-%.2d-' % val
1271 })
1272
1273 matches.append ({
1274 'data': None,
1275 'label': '20??-%.2d-' % val
1276 })
1277
1278 matches.append ({
1279 'data': None,
1280 'label': '199?-%.2d-' % val
1281 })
1282
1283 matches.append ({
1284 'data': None,
1285 'label': '198?-%.2d-' % val
1286 })
1287
1288 matches.append ({
1289 'data': None,
1290 'label': '197?-%.2d-' % val
1291 })
1292
1293 matches.append ({
1294 'data': None,
1295 'label': '19??-%.2d-' % val
1296 })
1297
1298 return matches
1299
1300
1302 """This matches on single numbers.
1303
1304 Spaces or tabs are discarded.
1305 """
1306 try:
1307 val = int(str2parse.strip())
1308 except ValueError:
1309 return []
1310
1311 now = pydt_now_here()
1312
1313 matches = []
1314
1315
1316 if (1850 < val) and (val < 2100):
1317 ts = pydt_replace(now, year = val, strict = False)
1318 matches.append ({
1319 'data': ts,
1320 'label': ts.strftime('%Y-%m-%d')
1321 })
1322
1323 if (val > 0) and (val <= gregorian_month_length[now.month]):
1324 ts = pydt_replace(now, day = val, strict = False)
1325 matches.append ({
1326 'data': ts,
1327 'label': _('%d. of %s (this month): a %s') % (val, ts.strftime('%B'), ts.strftime('%A'))
1328 })
1329
1330 if (val > 0) and (val < 32):
1331
1332 ts = pydt_replace(pydt_add(now, months = 1), day = val, strict = False)
1333 matches.append ({
1334 'data': ts,
1335 'label': _('%d. of %s (next month): a %s') % (val, ts.strftime('%B'), ts.strftime('%A'))
1336 })
1337
1338 ts = pydt_replace(pydt_add(now, months = -1), day = val, strict = False)
1339 matches.append ({
1340 'data': ts,
1341 'label': _('%d. of %s (last month): a %s') % (val, ts.strftime('%B'), ts.strftime('%A'))
1342 })
1343
1344 if (val > 0) and (val <= 400):
1345 ts = pydt_add(now, days = val)
1346 matches.append ({
1347 'data': ts,
1348 'label': _('in %d day(s): %s') % (val, ts.strftime('%A, %Y-%m-%d'))
1349 })
1350 if (val < 0) and (val >= -400):
1351 ts = pydt_add(now, days = val)
1352 matches.append ({
1353 'data': ts,
1354 'label': _('%d day(s) ago: %s') % (abs(val), ts.strftime('%A, %Y-%m-%d'))
1355 })
1356
1357 if (val > 0) and (val <= 50):
1358 ts = pydt_add(now, weeks = val)
1359 matches.append ({
1360 'data': ts,
1361 'label': _('in %d week(s): %s') % (val, ts.strftime('%A, %Y-%m-%d'))
1362 })
1363 if (val < 0) and (val >= -50):
1364 ts = pydt_add(now, weeks = val)
1365 matches.append ({
1366 'data': ts,
1367 'label': _('%d week(s) ago: %s') % (abs(val), ts.strftime('%A, %Y-%m-%d'))
1368 })
1369
1370
1371 if (val < 13) and (val > 0):
1372
1373 ts = pydt_replace(now, month = val, strict = False)
1374 matches.append ({
1375 'data': ts,
1376 'label': _('%s (%s this year)') % (ts.strftime('%Y-%m-%d'), ts.strftime('%B'))
1377 })
1378
1379 ts = pydt_replace(pydt_add(now, years = 1), month = val, strict = False)
1380 matches.append ({
1381 'data': ts,
1382 'label': _('%s (%s next year)') % (ts.strftime('%Y-%m-%d'), ts.strftime('%B'))
1383 })
1384
1385 ts = pydt_replace(pydt_add(now, years = -1), month = val, strict = False)
1386 matches.append ({
1387 'data': ts,
1388 'label': _('%s (%s last year)') % (ts.strftime('%Y-%m-%d'), ts.strftime('%B'))
1389 })
1390
1391 matches.append ({
1392 'data': None,
1393 'label': '200?-%s' % val
1394 })
1395 matches.append ({
1396 'data': None,
1397 'label': '199?-%s' % val
1398 })
1399 matches.append ({
1400 'data': None,
1401 'label': '198?-%s' % val
1402 })
1403 matches.append ({
1404 'data': None,
1405 'label': '19??-%s' % val
1406 })
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430 if (val < 100) and (val > 0):
1431 matches.append ({
1432 'data': None,
1433 'label': '%s-' % (1900 + val)
1434 })
1435
1436 if val == 201:
1437 matches.append ({
1438 'data': now,
1439 'label': now.strftime('%Y-%m-%d')
1440 })
1441 matches.append ({
1442 'data': None,
1443 'label': now.strftime('%Y-%m')
1444 })
1445 matches.append ({
1446 'data': None,
1447 'label': now.strftime('%Y')
1448 })
1449 matches.append ({
1450 'data': None,
1451 'label': '%s-' % (now.year + 1)
1452 })
1453 matches.append ({
1454 'data': None,
1455 'label': '%s-' % (now.year - 1)
1456 })
1457
1458 if val < 200 and val >= 190:
1459 for i in range(10):
1460 matches.append ({
1461 'data': None,
1462 'label': '%s%s-' % (val, i)
1463 })
1464
1465 return matches
1466
1467
1469 """Default is 'hdwmy':
1470 h - hours
1471 d - days
1472 w - weeks
1473 m - months
1474 y - years
1475
1476 This also defines the significance of the order of the characters.
1477 """
1478 if offset_chars is None:
1479 offset_chars = _('hdwmy (single character date offset triggers)')[:5].lower()
1480
1481 str2parse = str2parse.replace(' ', '').replace('\t', '')
1482
1483 if regex.fullmatch(r"(\+|-){,1}\d{1,3}[%s]" % offset_chars, str2parse) is None:
1484 return []
1485
1486 offset_val = int(str2parse[:-1])
1487 offset_char = str2parse[-1:]
1488 is_past = str2parse.startswith('-')
1489 now = pydt_now_here()
1490 ts = None
1491
1492
1493 if offset_char == offset_chars[0]:
1494 ts = pydt_add(now, hours = offset_val)
1495 if is_past:
1496 label = _('%d hour(s) ago: %s') % (abs(offset_val), ts.strftime('%H:%M'))
1497 else:
1498 label = _('in %d hour(s): %s') % (offset_val, ts.strftime('%H:%M'))
1499
1500 elif offset_char == offset_chars[1]:
1501 ts = pydt_add(now, days = offset_val)
1502 if is_past:
1503 label = _('%d day(s) ago: %s') % (abs(offset_val), ts.strftime('%A, %Y-%m-%d'))
1504 else:
1505 label = _('in %d day(s): %s') % (offset_val, ts.strftime('%A, %Y-%m-%d'))
1506
1507 elif offset_char == offset_chars[2]:
1508 ts = pydt_add(now, weeks = offset_val)
1509 if is_past:
1510 label = _('%d week(s) ago: %s') % (abs(offset_val), ts.strftime('%A, %Y-%m-%d'))
1511 else:
1512 label = _('in %d week(s): %s') % (offset_val, ts.strftime('%A, %Y-%m-%d'))
1513
1514 elif offset_char == offset_chars[3]:
1515 ts = pydt_add(now, months = offset_val)
1516 if is_past:
1517 label = _('%d month(s) ago: %s') % (abs(offset_val), ts.strftime('%A, %Y-%m-%d'))
1518 else:
1519 label = _('in %d month(s): %s') % (offset_val, ts.strftime('%A, %Y-%m-%d'))
1520
1521 elif offset_char == offset_chars[4]:
1522 ts = pydt_add(now, years = offset_val)
1523 if is_past:
1524 label = _('%d year(s) ago: %s') % (abs(offset_val), ts.strftime('%A, %Y-%m-%d'))
1525 else:
1526 label = _('in %d year(s): %s') % (offset_val, ts.strftime('%A, %Y-%m-%d'))
1527
1528 if ts is None:
1529 return []
1530
1531 return [{'data': ts, 'label': label}]
1532
1533
1535 """Turn a string into candidate dates and auto-completions the user is likely to type.
1536
1537 You MUST have called locale.setlocale(locale.LC_ALL, '')
1538 somewhere in your code previously.
1539
1540 @param patterns: list of time.strptime compatible date pattern
1541 @type patterns: list
1542 """
1543 matches = []
1544 matches.extend(__single_dot2py_dt(str2parse))
1545 matches.extend(__numbers_only2py_dt(str2parse))
1546 matches.extend(__single_slash2py_dt(str2parse))
1547 matches.extend(__single_char2py_dt(str2parse))
1548 matches.extend(__explicit_offset2py_dt(str2parse))
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567 if patterns is None:
1568 patterns = []
1569
1570 patterns.append('%Y-%m-%d')
1571 patterns.append('%y-%m-%d')
1572 patterns.append('%Y/%m/%d')
1573 patterns.append('%y/%m/%d')
1574
1575 patterns.append('%d-%m-%Y')
1576 patterns.append('%d-%m-%y')
1577 patterns.append('%d/%m/%Y')
1578 patterns.append('%d/%m/%y')
1579 patterns.append('%d.%m.%Y')
1580
1581 patterns.append('%m-%d-%Y')
1582 patterns.append('%m-%d-%y')
1583 patterns.append('%m/%d/%Y')
1584 patterns.append('%m/%d/%y')
1585
1586 patterns.append('%Y.%m.%d')
1587
1588 for pattern in patterns:
1589 try:
1590 date = pyDT.datetime.strptime(str2parse, pattern).replace (
1591 hour = 11,
1592 minute = 11,
1593 second = 11,
1594 tzinfo = gmCurrentLocalTimezone
1595 )
1596 matches.append ({
1597 'data': date,
1598 'label': pydt_strftime(date, format = '%Y-%m-%d', accuracy = acc_days)
1599 })
1600 except ValueError:
1601
1602 continue
1603
1604 return matches
1605
1606
1607
1608
1610 """Expand fragments containing a single slash.
1611
1612 "5/"
1613 - 2005/ (2000 - 2025)
1614 - 1995/ (1990 - 1999)
1615 - Mai/current year
1616 - Mai/next year
1617 - Mai/last year
1618 - Mai/200x
1619 - Mai/20xx
1620 - Mai/199x
1621 - Mai/198x
1622 - Mai/197x
1623 - Mai/19xx
1624 """
1625 matches = []
1626 now = pydt_now_here()
1627
1628 if regex.match("^(\s|\t)*\d{1,2}(\s|\t)*/+(\s|\t)*\d{4}(\s|\t)*$", str2parse, flags = regex.UNICODE):
1629 parts = regex.findall('\d+', str2parse, flags = regex.UNICODE)
1630 month = int(parts[0])
1631 if month in range(1, 13):
1632 fts = cFuzzyTimestamp (
1633 timestamp = now.replace(year = int(parts[1], month = month)),
1634 accuracy = acc_months
1635 )
1636 matches.append ({
1637 'data': fts,
1638 'label': fts.format_accurately()
1639 })
1640
1641 elif regex.match("^(\s|\t)*\d{1,2}(\s|\t)*/+(\s|\t)*$", str2parse, flags = regex.UNICODE):
1642 val = int(regex.findall('\d+', str2parse, flags = regex.UNICODE)[0])
1643
1644 if val < 100 and val >= 0:
1645 matches.append ({
1646 'data': None,
1647 'label': '%s/' % (val + 1900)
1648 })
1649
1650 if val < 26 and val >= 0:
1651 matches.append ({
1652 'data': None,
1653 'label': '%s/' % (val + 2000)
1654 })
1655
1656 if val < 10 and val >= 0:
1657 matches.append ({
1658 'data': None,
1659 'label': '%s/' % (val + 1990)
1660 })
1661
1662 if val < 13 and val > 0:
1663 matches.append ({
1664 'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_months),
1665 'label': '%.2d/%s' % (val, now.year)
1666 })
1667 ts = now.replace(year = now.year + 1)
1668 matches.append ({
1669 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_months),
1670 'label': '%.2d/%s' % (val, ts.year)
1671 })
1672 ts = now.replace(year = now.year - 1)
1673 matches.append ({
1674 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_months),
1675 'label': '%.2d/%s' % (val, ts.year)
1676 })
1677 matches.append ({
1678 'data': None,
1679 'label': '%.2d/200' % val
1680 })
1681 matches.append ({
1682 'data': None,
1683 'label': '%.2d/20' % val
1684 })
1685 matches.append ({
1686 'data': None,
1687 'label': '%.2d/199' % val
1688 })
1689 matches.append ({
1690 'data': None,
1691 'label': '%.2d/198' % val
1692 })
1693 matches.append ({
1694 'data': None,
1695 'label': '%.2d/197' % val
1696 })
1697 matches.append ({
1698 'data': None,
1699 'label': '%.2d/19' % val
1700 })
1701
1702 return matches
1703
1704
1706 """This matches on single numbers.
1707
1708 Spaces or tabs are discarded.
1709 """
1710 if not regex.match("^(\s|\t)*\d{1,4}(\s|\t)*$", str2parse, flags = regex.UNICODE):
1711 return []
1712
1713 now = pydt_now_here()
1714 val = int(regex.findall('\d{1,4}', str2parse, flags = regex.UNICODE)[0])
1715
1716 matches = []
1717
1718
1719 if (1850 < val) and (val < 2100):
1720 target_date = cFuzzyTimestamp (
1721 timestamp = now.replace(year = val),
1722 accuracy = acc_years
1723 )
1724 tmp = {
1725 'data': target_date,
1726 'label': '%s' % target_date
1727 }
1728 matches.append(tmp)
1729
1730
1731 if val <= gregorian_month_length[now.month]:
1732 ts = now.replace(day = val)
1733 target_date = cFuzzyTimestamp (
1734 timestamp = ts,
1735 accuracy = acc_days
1736 )
1737 tmp = {
1738 'data': target_date,
1739 'label': _('%d. of %s (this month) - a %s') % (val, ts.strftime('%B'), ts.strftime('%A'))
1740 }
1741 matches.append(tmp)
1742
1743
1744 next_month = get_next_month(now)
1745 if val <= gregorian_month_length[next_month]:
1746 ts = now.replace(day = val, month = next_month)
1747 target_date = cFuzzyTimestamp (
1748 timestamp = ts,
1749 accuracy = acc_days
1750 )
1751 tmp = {
1752 'data': target_date,
1753 'label': _('%d. of %s (next month) - a %s') % (val, ts.strftime('%B'), ts.strftime('%A'))
1754 }
1755 matches.append(tmp)
1756
1757
1758 last_month = get_last_month(now)
1759 if val <= gregorian_month_length[last_month]:
1760 ts = now.replace(day = val, month = last_month)
1761 target_date = cFuzzyTimestamp (
1762 timestamp = ts,
1763 accuracy = acc_days
1764 )
1765 tmp = {
1766 'data': target_date,
1767 'label': _('%d. of %s (last month) - a %s') % (val, ts.strftime('%B'), ts.strftime('%A'))
1768 }
1769 matches.append(tmp)
1770
1771
1772 if val <= 400:
1773 target_date = cFuzzyTimestamp(timestamp = now + pyDT.timedelta(days = val))
1774 tmp = {
1775 'data': target_date,
1776 'label': _('in %d day(s) - %s') % (val, target_date.timestamp.strftime('%A, %Y-%m-%d'))
1777 }
1778 matches.append(tmp)
1779
1780
1781 if val <= 50:
1782 target_date = cFuzzyTimestamp(timestamp = now + pyDT.timedelta(weeks = val))
1783 tmp = {
1784 'data': target_date,
1785 'label': _('in %d week(s) - %s') % (val, target_date.timestamp.strftime('%A, %Y-%m-%d'))
1786 }
1787 matches.append(tmp)
1788
1789
1790 if val < 13:
1791
1792 target_date = cFuzzyTimestamp (
1793 timestamp = pydt_replace(now, month = val, strict = False),
1794 accuracy = acc_months
1795 )
1796 tmp = {
1797 'data': target_date,
1798 'label': _('%s (%s this year)') % (target_date, ts.strftime('%B'))
1799 }
1800 matches.append(tmp)
1801
1802
1803 target_date = cFuzzyTimestamp (
1804 timestamp = pydt_add(pydt_replace(now, month = val, strict = False), years = 1),
1805 accuracy = acc_months
1806 )
1807 tmp = {
1808 'data': target_date,
1809 'label': _('%s (%s next year)') % (target_date, ts.strftime('%B'))
1810 }
1811 matches.append(tmp)
1812
1813
1814 target_date = cFuzzyTimestamp (
1815 timestamp = pydt_add(pydt_replace(now, month = val, strict = False), years = -1),
1816 accuracy = acc_months
1817 )
1818 tmp = {
1819 'data': target_date,
1820 'label': _('%s (%s last year)') % (target_date, ts.strftime('%B'))
1821 }
1822 matches.append(tmp)
1823
1824
1825 matches.append ({
1826 'data': None,
1827 'label': '%s/200' % val
1828 })
1829 matches.append ({
1830 'data': None,
1831 'label': '%s/199' % val
1832 })
1833 matches.append ({
1834 'data': None,
1835 'label': '%s/198' % val
1836 })
1837 matches.append ({
1838 'data': None,
1839 'label': '%s/19' % val
1840 })
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
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 if val < 100:
1882 matches.append ({
1883 'data': None,
1884 'label': '%s/' % (1900 + val)
1885 })
1886
1887
1888 if val == 200:
1889 tmp = {
1890 'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_days),
1891 'label': '%s' % target_date
1892 }
1893 matches.append(tmp)
1894 matches.append ({
1895 'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_months),
1896 'label': '%.2d/%s' % (now.month, now.year)
1897 })
1898 matches.append ({
1899 'data': None,
1900 'label': '%s/' % now.year
1901 })
1902 matches.append ({
1903 'data': None,
1904 'label': '%s/' % (now.year + 1)
1905 })
1906 matches.append ({
1907 'data': None,
1908 'label': '%s/' % (now.year - 1)
1909 })
1910
1911 if val < 200 and val >= 190:
1912 for i in range(10):
1913 matches.append ({
1914 'data': None,
1915 'label': '%s%s/' % (val, i)
1916 })
1917
1918 return matches
1919
1920
1922 """
1923 Turn a string into candidate fuzzy timestamps and auto-completions the user is likely to type.
1924
1925 You MUST have called locale.setlocale(locale.LC_ALL, '')
1926 somewhere in your code previously.
1927
1928 @param default_time: if you want to force the time part of the time
1929 stamp to a given value and the user doesn't type any time part
1930 this value will be used
1931 @type default_time: an mx.DateTime.DateTimeDelta instance
1932
1933 @param patterns: list of [time.strptime compatible date/time pattern, accuracy]
1934 @type patterns: list
1935 """
1936 matches = []
1937
1938 matches.extend(__numbers_only(str2parse))
1939 matches.extend(__single_slash(str2parse))
1940
1941 matches.extend ([
1942 { 'data': cFuzzyTimestamp(timestamp = m['data'], accuracy = acc_days),
1943 'label': m['label']
1944 } for m in __single_dot2py_dt(str2parse)
1945 ])
1946 matches.extend ([
1947 { 'data': cFuzzyTimestamp(timestamp = m['data'], accuracy = acc_days),
1948 'label': m['label']
1949 } for m in __single_char2py_dt(str2parse)
1950 ])
1951 matches.extend ([
1952 { 'data': cFuzzyTimestamp(timestamp = m['data'], accuracy = acc_days),
1953 'label': m['label']
1954 } for m in __explicit_offset2py_dt(str2parse)
1955 ])
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988 if patterns is None:
1989 patterns = []
1990 patterns.extend([
1991 ['%Y-%m-%d', acc_days],
1992 ['%y-%m-%d', acc_days],
1993 ['%Y/%m/%d', acc_days],
1994 ['%y/%m/%d', acc_days],
1995
1996 ['%d-%m-%Y', acc_days],
1997 ['%d-%m-%y', acc_days],
1998 ['%d/%m/%Y', acc_days],
1999 ['%d/%m/%y', acc_days],
2000 ['%d.%m.%Y', acc_days],
2001
2002 ['%m-%d-%Y', acc_days],
2003 ['%m-%d-%y', acc_days],
2004 ['%m/%d/%Y', acc_days],
2005 ['%m/%d/%y', acc_days]
2006 ])
2007 for pattern in patterns:
2008 try:
2009 ts = pyDT.datetime.strptime(str2parse, pattern[0]).replace (
2010 hour = 11,
2011 minute = 11,
2012 second = 11,
2013 tzinfo = gmCurrentLocalTimezone
2014 )
2015 fts = cFuzzyTimestamp(timestamp = ts, accuracy = pattern[1])
2016 matches.append ({
2017 'data': fts,
2018 'label': fts.format_accurately()
2019 })
2020 except ValueError:
2021
2022 continue
2023
2024 return matches
2025
2026
2027
2028
2030
2031
2032
2033 """A timestamp implementation with definable inaccuracy.
2034
2035 This class contains an datetime.datetime instance to
2036 hold the actual timestamp. It adds an accuracy attribute
2037 to allow the programmer to set the precision of the
2038 timestamp.
2039
2040 The timestamp will have to be initialzed with a fully
2041 precise value (which may, of course, contain partially
2042 fake data to make up for missing values). One can then
2043 set the accuracy value to indicate up to which part of
2044 the timestamp the data is valid. Optionally a modifier
2045 can be set to indicate further specification of the
2046 value (such as "summer", "afternoon", etc).
2047
2048 accuracy values:
2049 1: year only
2050 ...
2051 7: everything including milliseconds value
2052
2053 Unfortunately, one cannot directly derive a class from mx.DateTime.DateTime :-(
2054 """
2055
2057
2058 if timestamp is None:
2059 timestamp = pydt_now_here()
2060 accuracy = acc_subseconds
2061 modifier = ''
2062
2063 if (accuracy < 1) or (accuracy > 8):
2064 raise ValueError('%s.__init__(): <accuracy> must be between 1 and 8' % self.__class__.__name__)
2065
2066 if not isinstance(timestamp, pyDT.datetime):
2067 raise TypeError('%s.__init__(): <timestamp> must be of datetime.datetime type, but is %s' % self.__class__.__name__, type(timestamp))
2068
2069 if timestamp.tzinfo is None:
2070 raise ValueError('%s.__init__(): <tzinfo> must be defined' % self.__class__.__name__)
2071
2072 self.timestamp = timestamp
2073 self.accuracy = accuracy
2074 self.modifier = modifier
2075
2076
2077
2078
2080 """Return string representation meaningful to a user, also for %s formatting."""
2081 return self.format_accurately()
2082
2083
2085 """Return string meaningful to a programmer to aid in debugging."""
2086 tmp = '<[%s]: timestamp [%s], accuracy [%s] (%s), modifier [%s] at %s>' % (
2087 self.__class__.__name__,
2088 repr(self.timestamp),
2089 self.accuracy,
2090 _accuracy_strings[self.accuracy],
2091 self.modifier,
2092 id(self)
2093 )
2094 return tmp
2095
2096
2097
2098
2103
2104
2107
2108
2141
2142
2144 return self.timestamp
2145
2146
2147
2148
2149 if __name__ == '__main__':
2150
2151 if len(sys.argv) < 2:
2152 sys.exit()
2153
2154 if sys.argv[1] != "test":
2155 sys.exit()
2156
2157 from Gnumed.pycommon import gmI18N
2158 from Gnumed.pycommon import gmLog2
2159
2160
2161 intervals_as_str = [
2162 '7', '12', ' 12', '12 ', ' 12 ', ' 12 ', '0', '~12', '~ 12', ' ~ 12', ' ~ 12 ',
2163 '12a', '12 a', '12 a', '12j', '12J', '12y', '12Y', ' ~ 12 a ', '~0a',
2164 '12m', '17 m', '12 m', '17M', ' ~ 17 m ', ' ~ 3 / 12 ', '7/12', '0/12',
2165 '12w', '17 w', '12 w', '17W', ' ~ 17 w ', ' ~ 15 / 52', '2/52', '0/52',
2166 '12d', '17 d', '12 t', '17D', ' ~ 17 T ', ' ~ 12 / 7', '3/7', '0/7',
2167 '12h', '17 h', '12 H', '17H', ' ~ 17 h ', ' ~ 36 / 24', '7/24', '0/24',
2168 ' ~ 36 / 60', '7/60', '190/60', '0/60',
2169 '12a1m', '12 a 1 M', '12 a17m', '12j 12m', '12J7m', '12y7m', '12Y7M', ' ~ 12 a 37 m ', '~0a0m',
2170 '10m1w',
2171 'invalid interval input'
2172 ]
2173
2187
2188
2256
2258 print ("testing str2interval()")
2259 print ("----------------------")
2260
2261 for interval_as_str in intervals_as_str:
2262 print ("input: <%s>" % interval_as_str)
2263 print (" ==>", str2interval(str_interval=interval_as_str))
2264
2265 return True
2266
2268 print ("DST currently in effect:", dst_currently_in_effect)
2269 print ("current UTC offset:", current_local_utc_offset_in_seconds, "seconds")
2270 print ("current timezone (interval):", current_local_timezone_interval)
2271 print ("current timezone (ISO conformant numeric string):", current_local_iso_numeric_timezone_string)
2272 print ("local timezone class:", cPlatformLocalTimezone)
2273 print ("")
2274 tz = cPlatformLocalTimezone()
2275 print ("local timezone instance:", tz)
2276 print (" (total) UTC offset:", tz.utcoffset(pyDT.datetime.now()))
2277 print (" DST adjustment:", tz.dst(pyDT.datetime.now()))
2278 print (" timezone name:", tz.tzname(pyDT.datetime.now()))
2279 print ("")
2280 print ("current local timezone:", gmCurrentLocalTimezone)
2281 print (" (total) UTC offset:", gmCurrentLocalTimezone.utcoffset(pyDT.datetime.now()))
2282 print (" DST adjustment:", gmCurrentLocalTimezone.dst(pyDT.datetime.now()))
2283 print (" timezone name:", gmCurrentLocalTimezone.tzname(pyDT.datetime.now()))
2284 print ("")
2285 print ("now here:", pydt_now_here())
2286 print ("")
2287
2288
2290 print ("testing function str2fuzzy_timestamp_matches")
2291 print ("--------------------------------------------")
2292
2293 val = None
2294 while val != 'exit':
2295 val = input('Enter date fragment ("exit" quits): ')
2296 matches = str2fuzzy_timestamp_matches(str2parse = val)
2297 for match in matches:
2298 print ('label shown :', match['label'])
2299 print ('data attached:', match['data'], match['data'].timestamp)
2300 print ("")
2301 print ("---------------")
2302
2303
2305 print ("testing fuzzy timestamp class")
2306 print ("-----------------------------")
2307
2308 fts = cFuzzyTimestamp()
2309 print ("\nfuzzy timestamp <%s '%s'>" % ('class', fts.__class__.__name__))
2310 for accuracy in range(1,8):
2311 fts.accuracy = accuracy
2312 print (" accuracy : %s (%s)" % (accuracy, _accuracy_strings[accuracy]))
2313 print (" format_accurately:", fts.format_accurately())
2314 print (" strftime() :", fts.strftime('%Y %b %d %H:%M:%S'))
2315 print (" print ... :", fts)
2316 print (" print '%%s' %% ... : %s" % fts)
2317 print (" str() :", str(fts))
2318 print (" repr() :", repr(fts))
2319 input('press ENTER to continue')
2320
2321
2323 print ("testing platform for handling dates before 1970")
2324 print ("-----------------------------------------------")
2325 ts = mxDT.DateTime(1935, 4, 2)
2326 fts = cFuzzyTimestamp(timestamp=ts)
2327 print ("fts :", fts)
2328 print ("fts.get_pydt():", fts.get_pydt())
2329
2331
2332 start = pydt_now_here().replace(year = 2000).replace(month = 2).replace(day = 29)
2333 end = pydt_now_here().replace(year = 2012).replace(month = 2).replace(day = 27)
2334 print ("start is leap year: 29.2.2000")
2335 print (" ", calculate_apparent_age(start = start, end = end))
2336 print (" ", format_apparent_age_medically(calculate_apparent_age(start = start)))
2337
2338 start = pydt_now_here().replace(month = 10).replace(day = 23).replace(year = 1974)
2339 end = pydt_now_here().replace(year = 2012).replace(month = 2).replace(day = 29)
2340 print ("end is leap year: 29.2.2012")
2341 print (" ", calculate_apparent_age(start = start, end = end))
2342 print (" ", format_apparent_age_medically(calculate_apparent_age(start = start)))
2343
2344 start = pydt_now_here().replace(year = 2000).replace(month = 2).replace(day = 29)
2345 end = pydt_now_here().replace(year = 2012).replace(month = 2).replace(day = 29)
2346 print ("start is leap year: 29.2.2000")
2347 print ("end is leap year: 29.2.2012")
2348 print (" ", calculate_apparent_age(start = start, end = end))
2349 print (" ", format_apparent_age_medically(calculate_apparent_age(start = start)))
2350
2351 print ("leap year tests worked")
2352
2353 start = pydt_now_here().replace(month = 10).replace(day = 23).replace(year = 1974)
2354 print (calculate_apparent_age(start = start))
2355 print (format_apparent_age_medically(calculate_apparent_age(start = start)))
2356
2357 start = pydt_now_here().replace(month = 3).replace(day = 13).replace(year = 1979)
2358 print (calculate_apparent_age(start = start))
2359 print (format_apparent_age_medically(calculate_apparent_age(start = start)))
2360
2361 start = pydt_now_here().replace(month = 2, day = 2).replace(year = 1979)
2362 end = pydt_now_here().replace(month = 3).replace(day = 31).replace(year = 1979)
2363 print (calculate_apparent_age(start = start, end = end))
2364
2365 start = pydt_now_here().replace(month = 7, day = 21).replace(year = 2009)
2366 print (format_apparent_age_medically(calculate_apparent_age(start = start)))
2367
2368 print ("-------")
2369 start = pydt_now_here().replace(month = 1).replace(day = 23).replace(hour = 12).replace(minute = 11).replace(year = 2011)
2370 print (calculate_apparent_age(start = start))
2371 print (format_apparent_age_medically(calculate_apparent_age(start = start)))
2372
2374 print ("testing function str2pydt_matches")
2375 print ("---------------------------------")
2376
2377 val = None
2378 while val != 'exit':
2379 val = input('Enter date fragment ("exit" quits): ')
2380 matches = str2pydt_matches(str2parse = val)
2381 for match in matches:
2382 print ('label shown :', match['label'])
2383 print ('data attached:', match['data'])
2384 print ("")
2385 print ("---------------")
2386
2387
2402
2404 for year in range(1995, 2017):
2405 print (year, "leaps:", is_leap_year(year))
2406
2407
2424
2425
2426
2427 gmI18N.activate_locale()
2428 gmI18N.install_domain('gnumed')
2429
2430 init()
2431
2432 test_date_time()
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446