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