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:
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:
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))
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 ts = pydt_replace(now, day = day_val)
1158 matches.append ({
1159 'data': ts,
1160 'label': _('%s-%s-%s: a %s this month') % (ts.year, ts.month, ts.day, ts.strftime('%A'))
1161 })
1162
1163 ts = pydt_replace(pydt_add(now, months = 1), day = day_val)
1164 if ts.day == day_val:
1165 matches.append ({
1166 'data': ts,
1167 'label': _('%s-%s-%s: a %s next month') % (ts.year, ts.month, ts.day, ts.strftime('%A'))
1168 })
1169
1170 ts = pydt_replace(pydt_add(now, months = -1), day = day_val)
1171 if ts.day == day_val:
1172 matches.append ({
1173 'data': ts,
1174 'label': _('%s-%s-%s: a %s last month') % (ts.year, ts.month, ts.day, ts.strftime('%A'))
1175 })
1176
1177 return matches
1178
1179
1181 """Expand fragments containing a single slash.
1182
1183 "5/"
1184 - 2005/ (2000 - 2025)
1185 - 1995/ (1990 - 1999)
1186 - Mai/current year
1187 - Mai/next year
1188 - Mai/last year
1189 - Mai/200x
1190 - Mai/20xx
1191 - Mai/199x
1192 - Mai/198x
1193 - Mai/197x
1194 - Mai/19xx
1195
1196 5/1999
1197 6/2004
1198 """
1199 str2parse = str2parse.strip()
1200
1201 now = pydt_now_here()
1202
1203
1204 if regex.match(r"^\d{1,2}(\s|\t)*/+(\s|\t)*\d{4}$", str2parse, flags = regex.UNICODE):
1205 month, year = regex.findall(r'\d+', str2parse, flags = regex.UNICODE)
1206 ts = pydt_replace(now, year = int(year), month = int(month))
1207 return [{
1208 'data': ts,
1209 'label': ts.strftime('%Y-%m-%d')
1210 }]
1211
1212 matches = []
1213
1214 if regex.match(r"^\d{1,2}(\s|\t)*/+$", str2parse, flags = regex.UNICODE):
1215 val = int(str2parse.rstrip('/').strip())
1216
1217
1218 if val < 100 and val >= 0:
1219 matches.append ({
1220 'data': None,
1221 'label': '%s-' % (val + 1900)
1222 })
1223
1224 if val < 26 and val >= 0:
1225 matches.append ({
1226 'data': None,
1227 'label': '%s-' % (val + 2000)
1228 })
1229
1230 if val < 10 and val >= 0:
1231 matches.append ({
1232 'data': None,
1233 'label': '%s-' % (val + 1990)
1234 })
1235 if val < 13 and val > 0:
1236
1237 matches.append ({
1238 'data': None,
1239 'label': '%s-%.2d-' % (now.year, val)
1240 })
1241
1242 ts = pydt_add(now, years = 1)
1243 matches.append ({
1244 'data': None,
1245 'label': '%s-%.2d-' % (ts.year, val)
1246 })
1247
1248 ts = pydt_add(now, years = -1)
1249 matches.append ({
1250 'data': None,
1251 'label': '%s-%.2d-' % (ts.year, val)
1252 })
1253
1254 matches.append ({
1255 'data': None,
1256 'label': '201?-%.2d-' % val
1257 })
1258
1259 matches.append ({
1260 'data': None,
1261 'label': '200?-%.2d-' % val
1262 })
1263
1264 matches.append ({
1265 'data': None,
1266 'label': '20??-%.2d-' % val
1267 })
1268
1269 matches.append ({
1270 'data': None,
1271 'label': '199?-%.2d-' % val
1272 })
1273
1274 matches.append ({
1275 'data': None,
1276 'label': '198?-%.2d-' % val
1277 })
1278
1279 matches.append ({
1280 'data': None,
1281 'label': '197?-%.2d-' % val
1282 })
1283
1284 matches.append ({
1285 'data': None,
1286 'label': '19??-%.2d-' % val
1287 })
1288
1289 return matches
1290
1291
1293 """This matches on single numbers.
1294
1295 Spaces or tabs are discarded.
1296 """
1297 try:
1298 val = int(str2parse.strip())
1299 except ValueError:
1300 return []
1301
1302 now = pydt_now_here()
1303
1304 matches = []
1305
1306
1307 if (1850 < val) and (val < 2100):
1308 ts = pydt_replace(now, year = val)
1309 matches.append ({
1310 'data': ts,
1311 'label': ts.strftime('%Y-%m-%d')
1312 })
1313
1314 if (val > 0) and (val <= gregorian_month_length[now.month]):
1315 ts = pydt_replace(now, day = val)
1316 matches.append ({
1317 'data': ts,
1318 'label': _('%d. of %s (this month): a %s') % (val, ts.strftime('%B'), ts.strftime('%A'))
1319 })
1320
1321 if (val > 0) and (val < 32):
1322
1323 ts = pydt_replace(pydt_add(now, months = 1), day = val)
1324 matches.append ({
1325 'data': ts,
1326 'label': _('%d. of %s (next month): a %s') % (val, ts.strftime('%B'), ts.strftime('%A'))
1327 })
1328
1329 ts = pydt_replace(pydt_add(now, months = -1), day = val)
1330 matches.append ({
1331 'data': ts,
1332 'label': _('%d. of %s (last month): a %s') % (val, ts.strftime('%B'), ts.strftime('%A'))
1333 })
1334
1335 if (val > 0) and (val <= 400):
1336 ts = pydt_add(now, days = val)
1337 matches.append ({
1338 'data': ts,
1339 'label': _('in %d day(s): %s') % (val, ts.strftime('%A, %Y-%m-%d'))
1340 })
1341 if (val < 0) and (val >= -400):
1342 ts = pydt_add(now, days = val)
1343 matches.append ({
1344 'data': ts,
1345 'label': _('%d day(s) ago: %s') % (abs(val), ts.strftime('%A, %Y-%m-%d'))
1346 })
1347
1348 if (val > 0) and (val <= 50):
1349 ts = pydt_add(now, weeks = val)
1350 matches.append ({
1351 'data': ts,
1352 'label': _('in %d week(s): %s') % (val, ts.strftime('%A, %Y-%m-%d'))
1353 })
1354 if (val < 0) and (val >= -50):
1355 ts = pydt_add(now, weeks = val)
1356 matches.append ({
1357 'data': ts,
1358 'label': _('%d week(s) ago: %s') % (abs(val), ts.strftime('%A, %Y-%m-%d'))
1359 })
1360
1361
1362 if (val < 13) and (val > 0):
1363
1364 ts = pydt_replace(now, month = val)
1365 matches.append ({
1366 'data': ts,
1367 'label': _('%s (%s this year)') % (ts.strftime('%Y-%m-%d'), ts.strftime('%B'))
1368 })
1369
1370 ts = pydt_replace(pydt_add(now, years = 1), month = val)
1371 matches.append ({
1372 'data': ts,
1373 'label': _('%s (%s next year)') % (ts.strftime('%Y-%m-%d'), ts.strftime('%B'))
1374 })
1375
1376 ts = pydt_replace(pydt_add(now, years = -1), month = val)
1377 matches.append ({
1378 'data': ts,
1379 'label': _('%s (%s last year)') % (ts.strftime('%Y-%m-%d'), ts.strftime('%B'))
1380 })
1381
1382 matches.append ({
1383 'data': None,
1384 'label': '200?-%s' % val
1385 })
1386 matches.append ({
1387 'data': None,
1388 'label': '199?-%s' % val
1389 })
1390 matches.append ({
1391 'data': None,
1392 'label': '198?-%s' % val
1393 })
1394 matches.append ({
1395 'data': None,
1396 'label': '19??-%s' % val
1397 })
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421 if (val < 100) and (val > 0):
1422 matches.append ({
1423 'data': None,
1424 'label': '%s-' % (1900 + val)
1425 })
1426
1427 if val == 201:
1428 matches.append ({
1429 'data': now,
1430 'label': now.strftime('%Y-%m-%d')
1431 })
1432 matches.append ({
1433 'data': None,
1434 'label': now.strftime('%Y-%m')
1435 })
1436 matches.append ({
1437 'data': None,
1438 'label': now.strftime('%Y')
1439 })
1440 matches.append ({
1441 'data': None,
1442 'label': '%s-' % (now.year + 1)
1443 })
1444 matches.append ({
1445 'data': None,
1446 'label': '%s-' % (now.year - 1)
1447 })
1448
1449 if val < 200 and val >= 190:
1450 for i in range(10):
1451 matches.append ({
1452 'data': None,
1453 'label': '%s%s-' % (val, i)
1454 })
1455
1456 return matches
1457
1458
1460 """Default is 'hdwmy':
1461 h - hours
1462 d - days
1463 w - weeks
1464 m - months
1465 y - years
1466
1467 This also defines the significance of the order of the characters.
1468 """
1469 if offset_chars is None:
1470 offset_chars = _('hdwmy (single character date offset triggers)')[:5].lower()
1471
1472 str2parse = str2parse.replace(' ', '').replace('\t', '')
1473
1474 if regex.fullmatch(r"(\+|-){,1}\d{1,3}[%s]" % offset_chars, str2parse) is None:
1475 return []
1476
1477 offset_val = int(str2parse[:-1])
1478 offset_char = str2parse[-1:]
1479 is_past = str2parse.startswith('-')
1480 now = pydt_now_here()
1481 ts = None
1482
1483
1484 if offset_char == offset_chars[0]:
1485 ts = pydt_add(now, hours = offset_val)
1486 if is_past:
1487 label = _('%d hour(s) ago: %s') % (abs(offset_val), ts.strftime('%H:%M'))
1488 else:
1489 label = _('in %d hour(s): %s') % (offset_val, ts.strftime('%H:%M'))
1490
1491 elif offset_char == offset_chars[1]:
1492 ts = pydt_add(now, days = offset_val)
1493 if is_past:
1494 label = _('%d day(s) ago: %s') % (abs(offset_val), ts.strftime('%A, %Y-%m-%d'))
1495 else:
1496 label = _('in %d day(s): %s') % (offset_val, ts.strftime('%A, %Y-%m-%d'))
1497
1498 elif offset_char == offset_chars[2]:
1499 ts = pydt_add(now, weeks = offset_val)
1500 if is_past:
1501 label = _('%d week(s) ago: %s') % (abs(offset_val), ts.strftime('%A, %Y-%m-%d'))
1502 else:
1503 label = _('in %d week(s): %s') % (offset_val, ts.strftime('%A, %Y-%m-%d'))
1504
1505 elif offset_char == offset_chars[3]:
1506 ts = pydt_add(now, months = offset_val)
1507 if is_past:
1508 label = _('%d month(s) ago: %s') % (abs(offset_val), ts.strftime('%A, %Y-%m-%d'))
1509 else:
1510 label = _('in %d month(s): %s') % (offset_val, ts.strftime('%A, %Y-%m-%d'))
1511
1512 elif offset_char == offset_chars[4]:
1513 ts = pydt_add(now, years = offset_val)
1514 if is_past:
1515 label = _('%d year(s) ago: %s') % (abs(offset_val), ts.strftime('%A, %Y-%m-%d'))
1516 else:
1517 label = _('in %d year(s): %s') % (offset_val, ts.strftime('%A, %Y-%m-%d'))
1518
1519 if ts is None:
1520 return []
1521
1522 return [{'data': ts, 'label': label}]
1523
1524
1526 """Turn a string into candidate dates and auto-completions the user is likely to type.
1527
1528 You MUST have called locale.setlocale(locale.LC_ALL, '')
1529 somewhere in your code previously.
1530
1531 @param patterns: list of time.strptime compatible date pattern
1532 @type patterns: list
1533 """
1534 matches = []
1535 matches.extend(__single_dot2py_dt(str2parse))
1536 matches.extend(__numbers_only2py_dt(str2parse))
1537 matches.extend(__single_slash2py_dt(str2parse))
1538 matches.extend(__single_char2py_dt(str2parse))
1539 matches.extend(__explicit_offset2py_dt(str2parse))
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558 if patterns is None:
1559 patterns = []
1560
1561 patterns.append('%Y-%m-%d')
1562 patterns.append('%y-%m-%d')
1563 patterns.append('%Y/%m/%d')
1564 patterns.append('%y/%m/%d')
1565
1566 patterns.append('%d-%m-%Y')
1567 patterns.append('%d-%m-%y')
1568 patterns.append('%d/%m/%Y')
1569 patterns.append('%d/%m/%y')
1570 patterns.append('%d.%m.%Y')
1571
1572 patterns.append('%m-%d-%Y')
1573 patterns.append('%m-%d-%y')
1574 patterns.append('%m/%d/%Y')
1575 patterns.append('%m/%d/%y')
1576
1577 patterns.append('%Y.%m.%d')
1578
1579 for pattern in patterns:
1580 try:
1581 date = pyDT.datetime.strptime(str2parse, pattern).replace (
1582 hour = 11,
1583 minute = 11,
1584 second = 11,
1585 tzinfo = gmCurrentLocalTimezone
1586 )
1587 matches.append ({
1588 'data': date,
1589 'label': pydt_strftime(date, format = '%Y-%m-%d', accuracy = acc_days)
1590 })
1591 except ValueError:
1592
1593 continue
1594
1595 return matches
1596
1597
1598
1599
1601 """Expand fragments containing a single slash.
1602
1603 "5/"
1604 - 2005/ (2000 - 2025)
1605 - 1995/ (1990 - 1999)
1606 - Mai/current year
1607 - Mai/next year
1608 - Mai/last year
1609 - Mai/200x
1610 - Mai/20xx
1611 - Mai/199x
1612 - Mai/198x
1613 - Mai/197x
1614 - Mai/19xx
1615 """
1616 matches = []
1617 now = pydt_now_here()
1618
1619 if regex.match("^(\s|\t)*\d{1,2}(\s|\t)*/+(\s|\t)*\d{4}(\s|\t)*$", str2parse, flags = regex.UNICODE):
1620 parts = regex.findall('\d+', str2parse, flags = regex.UNICODE)
1621 month = int(parts[0])
1622 if month in range(1, 13):
1623 fts = cFuzzyTimestamp (
1624 timestamp = now.replace(year = int(parts[1], month = month)),
1625 accuracy = acc_months
1626 )
1627 matches.append ({
1628 'data': fts,
1629 'label': fts.format_accurately()
1630 })
1631
1632 elif regex.match("^(\s|\t)*\d{1,2}(\s|\t)*/+(\s|\t)*$", str2parse, flags = regex.UNICODE):
1633 val = int(regex.findall('\d+', str2parse, flags = regex.UNICODE)[0])
1634
1635 if val < 100 and val >= 0:
1636 matches.append ({
1637 'data': None,
1638 'label': '%s/' % (val + 1900)
1639 })
1640
1641 if val < 26 and val >= 0:
1642 matches.append ({
1643 'data': None,
1644 'label': '%s/' % (val + 2000)
1645 })
1646
1647 if val < 10 and val >= 0:
1648 matches.append ({
1649 'data': None,
1650 'label': '%s/' % (val + 1990)
1651 })
1652
1653 if val < 13 and val > 0:
1654 matches.append ({
1655 'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_months),
1656 'label': '%.2d/%s' % (val, now.year)
1657 })
1658 ts = now.replace(year = now.year + 1)
1659 matches.append ({
1660 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_months),
1661 'label': '%.2d/%s' % (val, ts.year)
1662 })
1663 ts = now.replace(year = now.year - 1)
1664 matches.append ({
1665 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_months),
1666 'label': '%.2d/%s' % (val, ts.year)
1667 })
1668 matches.append ({
1669 'data': None,
1670 'label': '%.2d/200' % val
1671 })
1672 matches.append ({
1673 'data': None,
1674 'label': '%.2d/20' % val
1675 })
1676 matches.append ({
1677 'data': None,
1678 'label': '%.2d/199' % val
1679 })
1680 matches.append ({
1681 'data': None,
1682 'label': '%.2d/198' % val
1683 })
1684 matches.append ({
1685 'data': None,
1686 'label': '%.2d/197' % val
1687 })
1688 matches.append ({
1689 'data': None,
1690 'label': '%.2d/19' % val
1691 })
1692
1693 return matches
1694
1695
1697 """This matches on single numbers.
1698
1699 Spaces or tabs are discarded.
1700 """
1701 if not regex.match("^(\s|\t)*\d{1,4}(\s|\t)*$", str2parse, flags = regex.UNICODE):
1702 return []
1703
1704 now = pydt_now_here()
1705 val = int(regex.findall('\d{1,4}', str2parse, flags = regex.UNICODE)[0])
1706
1707 matches = []
1708
1709
1710 if (1850 < val) and (val < 2100):
1711 target_date = cFuzzyTimestamp (
1712 timestamp = now.replace(year = val),
1713 accuracy = acc_years
1714 )
1715 tmp = {
1716 'data': target_date,
1717 'label': '%s' % target_date
1718 }
1719 matches.append(tmp)
1720
1721
1722 if val <= gregorian_month_length[now.month]:
1723 ts = now.replace(day = val)
1724 target_date = cFuzzyTimestamp (
1725 timestamp = ts,
1726 accuracy = acc_days
1727 )
1728 tmp = {
1729 'data': target_date,
1730 'label': _('%d. of %s (this month) - a %s') % (val, ts.strftime('%B'), ts.strftime('%A'))
1731 }
1732 matches.append(tmp)
1733
1734
1735 next_month = get_next_month(now)
1736 if val <= gregorian_month_length[next_month]:
1737 ts = now.replace(day = val, month = next_month)
1738 target_date = cFuzzyTimestamp (
1739 timestamp = ts,
1740 accuracy = acc_days
1741 )
1742 tmp = {
1743 'data': target_date,
1744 'label': _('%d. of %s (next month) - a %s') % (val, ts.strftime('%B'), ts.strftime('%A'))
1745 }
1746 matches.append(tmp)
1747
1748
1749 last_month = get_last_month(now)
1750 if val <= gregorian_month_length[last_month]:
1751 ts = now.replace(day = val, month = last_month)
1752 target_date = cFuzzyTimestamp (
1753 timestamp = ts,
1754 accuracy = acc_days
1755 )
1756 tmp = {
1757 'data': target_date,
1758 'label': _('%d. of %s (last month) - a %s') % (val, ts.strftime('%B'), ts.strftime('%A'))
1759 }
1760 matches.append(tmp)
1761
1762
1763 if val <= 400:
1764 target_date = cFuzzyTimestamp(timestamp = now + pyDT.timedelta(days = val))
1765 tmp = {
1766 'data': target_date,
1767 'label': _('in %d day(s) - %s') % (val, target_date.timestamp.strftime('%A, %Y-%m-%d'))
1768 }
1769 matches.append(tmp)
1770
1771
1772 if val <= 50:
1773 target_date = cFuzzyTimestamp(timestamp = now + pyDT.timedelta(weeks = val))
1774 tmp = {
1775 'data': target_date,
1776 'label': _('in %d week(s) - %s') % (val, target_date.timestamp.strftime('%A, %Y-%m-%d'))
1777 }
1778 matches.append(tmp)
1779
1780
1781 if val < 13:
1782
1783 target_date = cFuzzyTimestamp (
1784 timestamp = pydt_replace(now, month = val),
1785 accuracy = acc_months
1786 )
1787 tmp = {
1788 'data': target_date,
1789 'label': _('%s (%s this year)') % (target_date, ts.strftime('%B'))
1790 }
1791 matches.append(tmp)
1792
1793
1794 target_date = cFuzzyTimestamp (
1795 timestamp = pydt_add(pydt_replace(now, month = val), years = 1),
1796 accuracy = acc_months
1797 )
1798 tmp = {
1799 'data': target_date,
1800 'label': _('%s (%s next year)') % (target_date, ts.strftime('%B'))
1801 }
1802 matches.append(tmp)
1803
1804
1805 target_date = cFuzzyTimestamp (
1806 timestamp = pydt_add(pydt_replace(now, month = val), years = -1),
1807 accuracy = acc_months
1808 )
1809 tmp = {
1810 'data': target_date,
1811 'label': _('%s (%s last year)') % (target_date, ts.strftime('%B'))
1812 }
1813 matches.append(tmp)
1814
1815
1816 matches.append ({
1817 'data': None,
1818 'label': '%s/200' % val
1819 })
1820 matches.append ({
1821 'data': None,
1822 'label': '%s/199' % val
1823 })
1824 matches.append ({
1825 'data': None,
1826 'label': '%s/198' % val
1827 })
1828 matches.append ({
1829 'data': None,
1830 'label': '%s/19' % val
1831 })
1832
1833
1834
1835
1836
1837
1838
1839
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 if val < 100:
1873 matches.append ({
1874 'data': None,
1875 'label': '%s/' % (1900 + val)
1876 })
1877
1878
1879 if val == 200:
1880 tmp = {
1881 'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_days),
1882 'label': '%s' % target_date
1883 }
1884 matches.append(tmp)
1885 matches.append ({
1886 'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_months),
1887 'label': '%.2d/%s' % (now.month, now.year)
1888 })
1889 matches.append ({
1890 'data': None,
1891 'label': '%s/' % now.year
1892 })
1893 matches.append ({
1894 'data': None,
1895 'label': '%s/' % (now.year + 1)
1896 })
1897 matches.append ({
1898 'data': None,
1899 'label': '%s/' % (now.year - 1)
1900 })
1901
1902 if val < 200 and val >= 190:
1903 for i in range(10):
1904 matches.append ({
1905 'data': None,
1906 'label': '%s%s/' % (val, i)
1907 })
1908
1909 return matches
1910
1911
1913 """
1914 Turn a string into candidate fuzzy timestamps and auto-completions the user is likely to type.
1915
1916 You MUST have called locale.setlocale(locale.LC_ALL, '')
1917 somewhere in your code previously.
1918
1919 @param default_time: if you want to force the time part of the time
1920 stamp to a given value and the user doesn't type any time part
1921 this value will be used
1922 @type default_time: an mx.DateTime.DateTimeDelta instance
1923
1924 @param patterns: list of [time.strptime compatible date/time pattern, accuracy]
1925 @type patterns: list
1926 """
1927 matches = []
1928
1929 matches.extend(__numbers_only(str2parse))
1930 matches.extend(__single_slash(str2parse))
1931
1932 matches.extend ([
1933 { 'data': cFuzzyTimestamp(timestamp = m['data'], accuracy = acc_days),
1934 'label': m['label']
1935 } for m in __single_dot2py_dt(str2parse)
1936 ])
1937 matches.extend ([
1938 { 'data': cFuzzyTimestamp(timestamp = m['data'], accuracy = acc_days),
1939 'label': m['label']
1940 } for m in __single_char2py_dt(str2parse)
1941 ])
1942 matches.extend ([
1943 { 'data': cFuzzyTimestamp(timestamp = m['data'], accuracy = acc_days),
1944 'label': m['label']
1945 } for m in __explicit_offset2py_dt(str2parse)
1946 ])
1947
1948
1949
1950
1951
1952
1953
1954
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 if patterns is None:
1980 patterns = []
1981 patterns.extend([
1982 ['%Y-%m-%d', acc_days],
1983 ['%y-%m-%d', acc_days],
1984 ['%Y/%m/%d', acc_days],
1985 ['%y/%m/%d', acc_days],
1986
1987 ['%d-%m-%Y', acc_days],
1988 ['%d-%m-%y', acc_days],
1989 ['%d/%m/%Y', acc_days],
1990 ['%d/%m/%y', acc_days],
1991 ['%d.%m.%Y', acc_days],
1992
1993 ['%m-%d-%Y', acc_days],
1994 ['%m-%d-%y', acc_days],
1995 ['%m/%d/%Y', acc_days],
1996 ['%m/%d/%y', acc_days]
1997 ])
1998 for pattern in patterns:
1999 try:
2000 ts = pyDT.datetime.strptime(str2parse, pattern[0]).replace (
2001 hour = 11,
2002 minute = 11,
2003 second = 11,
2004 tzinfo = gmCurrentLocalTimezone
2005 )
2006 fts = cFuzzyTimestamp(timestamp = ts, accuracy = pattern[1])
2007 matches.append ({
2008 'data': fts,
2009 'label': fts.format_accurately()
2010 })
2011 except ValueError:
2012
2013 continue
2014
2015 return matches
2016
2017
2018
2019
2021
2022
2023
2024 """A timestamp implementation with definable inaccuracy.
2025
2026 This class contains an datetime.datetime instance to
2027 hold the actual timestamp. It adds an accuracy attribute
2028 to allow the programmer to set the precision of the
2029 timestamp.
2030
2031 The timestamp will have to be initialzed with a fully
2032 precise value (which may, of course, contain partially
2033 fake data to make up for missing values). One can then
2034 set the accuracy value to indicate up to which part of
2035 the timestamp the data is valid. Optionally a modifier
2036 can be set to indicate further specification of the
2037 value (such as "summer", "afternoon", etc).
2038
2039 accuracy values:
2040 1: year only
2041 ...
2042 7: everything including milliseconds value
2043
2044 Unfortunately, one cannot directly derive a class from mx.DateTime.DateTime :-(
2045 """
2046
2048
2049 if timestamp is None:
2050 timestamp = pydt_now_here()
2051 accuracy = acc_subseconds
2052 modifier = ''
2053
2054 if (accuracy < 1) or (accuracy > 8):
2055 raise ValueError('%s.__init__(): <accuracy> must be between 1 and 8' % self.__class__.__name__)
2056
2057 if not isinstance(timestamp, pyDT.datetime):
2058 raise TypeError('%s.__init__(): <timestamp> must be of datetime.datetime type, but is %s' % self.__class__.__name__, type(timestamp))
2059
2060 if timestamp.tzinfo is None:
2061 raise ValueError('%s.__init__(): <tzinfo> must be defined' % self.__class__.__name__)
2062
2063 self.timestamp = timestamp
2064 self.accuracy = accuracy
2065 self.modifier = modifier
2066
2067
2068
2069
2071 """Return string representation meaningful to a user, also for %s formatting."""
2072 return self.format_accurately()
2073
2074
2076 """Return string meaningful to a programmer to aid in debugging."""
2077 tmp = '<[%s]: timestamp [%s], accuracy [%s] (%s), modifier [%s] at %s>' % (
2078 self.__class__.__name__,
2079 repr(self.timestamp),
2080 self.accuracy,
2081 _accuracy_strings[self.accuracy],
2082 self.modifier,
2083 id(self)
2084 )
2085 return tmp
2086
2087
2088
2089
2094
2095
2098
2099
2132
2133
2135 return self.timestamp
2136
2137
2138
2139
2140 if __name__ == '__main__':
2141
2142 if len(sys.argv) < 2:
2143 sys.exit()
2144
2145 if sys.argv[1] != "test":
2146 sys.exit()
2147
2148 from Gnumed.pycommon import gmI18N
2149 from Gnumed.pycommon import gmLog2
2150
2151
2152 intervals_as_str = [
2153 '7', '12', ' 12', '12 ', ' 12 ', ' 12 ', '0', '~12', '~ 12', ' ~ 12', ' ~ 12 ',
2154 '12a', '12 a', '12 a', '12j', '12J', '12y', '12Y', ' ~ 12 a ', '~0a',
2155 '12m', '17 m', '12 m', '17M', ' ~ 17 m ', ' ~ 3 / 12 ', '7/12', '0/12',
2156 '12w', '17 w', '12 w', '17W', ' ~ 17 w ', ' ~ 15 / 52', '2/52', '0/52',
2157 '12d', '17 d', '12 t', '17D', ' ~ 17 T ', ' ~ 12 / 7', '3/7', '0/7',
2158 '12h', '17 h', '12 H', '17H', ' ~ 17 h ', ' ~ 36 / 24', '7/24', '0/24',
2159 ' ~ 36 / 60', '7/60', '190/60', '0/60',
2160 '12a1m', '12 a 1 M', '12 a17m', '12j 12m', '12J7m', '12y7m', '12Y7M', ' ~ 12 a 37 m ', '~0a0m',
2161 '10m1w',
2162 'invalid interval input'
2163 ]
2164
2178
2179
2247
2249 print ("testing str2interval()")
2250 print ("----------------------")
2251
2252 for interval_as_str in intervals_as_str:
2253 print ("input: <%s>" % interval_as_str)
2254 print (" ==>", str2interval(str_interval=interval_as_str))
2255
2256 return True
2257
2259 print ("DST currently in effect:", dst_currently_in_effect)
2260 print ("current UTC offset:", current_local_utc_offset_in_seconds, "seconds")
2261 print ("current timezone (interval):", current_local_timezone_interval)
2262 print ("current timezone (ISO conformant numeric string):", current_local_iso_numeric_timezone_string)
2263 print ("local timezone class:", cPlatformLocalTimezone)
2264 print ("")
2265 tz = cPlatformLocalTimezone()
2266 print ("local timezone instance:", tz)
2267 print (" (total) UTC offset:", tz.utcoffset(pyDT.datetime.now()))
2268 print (" DST adjustment:", tz.dst(pyDT.datetime.now()))
2269 print (" timezone name:", tz.tzname(pyDT.datetime.now()))
2270 print ("")
2271 print ("current local timezone:", gmCurrentLocalTimezone)
2272 print (" (total) UTC offset:", gmCurrentLocalTimezone.utcoffset(pyDT.datetime.now()))
2273 print (" DST adjustment:", gmCurrentLocalTimezone.dst(pyDT.datetime.now()))
2274 print (" timezone name:", gmCurrentLocalTimezone.tzname(pyDT.datetime.now()))
2275 print ("")
2276 print ("now here:", pydt_now_here())
2277 print ("")
2278
2279
2281 print ("testing function str2fuzzy_timestamp_matches")
2282 print ("--------------------------------------------")
2283
2284 val = None
2285 while val != 'exit':
2286 val = input('Enter date fragment ("exit" quits): ')
2287 matches = str2fuzzy_timestamp_matches(str2parse = val)
2288 for match in matches:
2289 print ('label shown :', match['label'])
2290 print ('data attached:', match['data'], match['data'].timestamp)
2291 print ("")
2292 print ("---------------")
2293
2294
2296 print ("testing fuzzy timestamp class")
2297 print ("-----------------------------")
2298
2299 fts = cFuzzyTimestamp()
2300 print ("\nfuzzy timestamp <%s '%s'>" % ('class', fts.__class__.__name__))
2301 for accuracy in range(1,8):
2302 fts.accuracy = accuracy
2303 print (" accuracy : %s (%s)" % (accuracy, _accuracy_strings[accuracy]))
2304 print (" format_accurately:", fts.format_accurately())
2305 print (" strftime() :", fts.strftime('%Y %b %d %H:%M:%S'))
2306 print (" print ... :", fts)
2307 print (" print '%%s' %% ... : %s" % fts)
2308 print (" str() :", str(fts))
2309 print (" repr() :", repr(fts))
2310 input('press ENTER to continue')
2311
2312
2314 print ("testing platform for handling dates before 1970")
2315 print ("-----------------------------------------------")
2316 ts = mxDT.DateTime(1935, 4, 2)
2317 fts = cFuzzyTimestamp(timestamp=ts)
2318 print ("fts :", fts)
2319 print ("fts.get_pydt():", fts.get_pydt())
2320
2322
2323 start = pydt_now_here().replace(year = 2000).replace(month = 2).replace(day = 29)
2324 end = pydt_now_here().replace(year = 2012).replace(month = 2).replace(day = 27)
2325 print ("start is leap year: 29.2.2000")
2326 print (" ", calculate_apparent_age(start = start, end = end))
2327 print (" ", format_apparent_age_medically(calculate_apparent_age(start = start)))
2328
2329 start = pydt_now_here().replace(month = 10).replace(day = 23).replace(year = 1974)
2330 end = pydt_now_here().replace(year = 2012).replace(month = 2).replace(day = 29)
2331 print ("end is leap year: 29.2.2012")
2332 print (" ", calculate_apparent_age(start = start, end = end))
2333 print (" ", format_apparent_age_medically(calculate_apparent_age(start = start)))
2334
2335 start = pydt_now_here().replace(year = 2000).replace(month = 2).replace(day = 29)
2336 end = pydt_now_here().replace(year = 2012).replace(month = 2).replace(day = 29)
2337 print ("start is leap year: 29.2.2000")
2338 print ("end is leap year: 29.2.2012")
2339 print (" ", calculate_apparent_age(start = start, end = end))
2340 print (" ", format_apparent_age_medically(calculate_apparent_age(start = start)))
2341
2342 print ("leap year tests worked")
2343
2344 start = pydt_now_here().replace(month = 10).replace(day = 23).replace(year = 1974)
2345 print (calculate_apparent_age(start = start))
2346 print (format_apparent_age_medically(calculate_apparent_age(start = start)))
2347
2348 start = pydt_now_here().replace(month = 3).replace(day = 13).replace(year = 1979)
2349 print (calculate_apparent_age(start = start))
2350 print (format_apparent_age_medically(calculate_apparent_age(start = start)))
2351
2352 start = pydt_now_here().replace(month = 2, day = 2).replace(year = 1979)
2353 end = pydt_now_here().replace(month = 3).replace(day = 31).replace(year = 1979)
2354 print (calculate_apparent_age(start = start, end = end))
2355
2356 start = pydt_now_here().replace(month = 7, day = 21).replace(year = 2009)
2357 print (format_apparent_age_medically(calculate_apparent_age(start = start)))
2358
2359 print ("-------")
2360 start = pydt_now_here().replace(month = 1).replace(day = 23).replace(hour = 12).replace(minute = 11).replace(year = 2011)
2361 print (calculate_apparent_age(start = start))
2362 print (format_apparent_age_medically(calculate_apparent_age(start = start)))
2363
2365 print ("testing function str2pydt_matches")
2366 print ("---------------------------------")
2367
2368 val = None
2369 while val != 'exit':
2370 val = input('Enter date fragment ("exit" quits): ')
2371 matches = str2pydt_matches(str2parse = val)
2372 for match in matches:
2373 print ('label shown :', match['label'])
2374 print ('data attached:', match['data'])
2375 print ("")
2376 print ("---------------")
2377
2378
2393
2395 for year in range(1995, 2017):
2396 print (year, "leaps:", is_leap_year(year))
2397
2398
2415
2416
2417
2418 gmI18N.activate_locale()
2419 gmI18N.install_domain('gnumed')
2420
2421 init()
2422
2423 test_date_time()
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437