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
377 except ValueError:
378 _log.exception()
379 return 'strftime() error'
380
381
382 -def pydt_add(dt, years=0, months=0, weeks=0, days=0, hours=0, minutes=0, seconds=0, milliseconds=0, microseconds=0):
383 if months > 11 or months < -11:
384 raise ValueError('pydt_add(): months must be within [-11..11]')
385
386 dt = dt + pyDT.timedelta (
387 weeks = weeks,
388 days = days,
389 hours = hours,
390 minutes = minutes,
391 seconds = seconds,
392 milliseconds = milliseconds,
393 microseconds = microseconds
394 )
395 if (years == 0) and (months == 0):
396 return dt
397 target_year = dt.year + years
398 target_month = dt.month + months
399 if target_month > 12:
400 target_year += 1
401 target_month -= 12
402 elif target_month < 1:
403 target_year -= 1
404 target_month += 12
405 return pydt_replace(dt, year = target_year, month = target_month, strict = False)
406
407
408 -def pydt_replace(dt, strict=True, year=None, month=None, day=None, hour=None, minute=None, second=None, microsecond=None, tzinfo=None):
409
410
411 if year is None:
412 year = dt.year
413 if month is None:
414 month = dt.month
415 if day is None:
416 day = dt.day
417 if hour is None:
418 hour = dt.hour
419 if minute is None:
420 minute = dt.minute
421 if second is None:
422 second = dt.second
423 if microsecond is None:
424 microsecond = dt.microsecond
425 if tzinfo is None:
426 tzinfo = dt.tzinfo
427
428 if strict:
429 return dt.replace(year = year, month = month, day = day, hour = hour, minute = minute, second = second, microsecond = microsecond, tzinfo = tzinfo)
430
431 try:
432 return dt.replace(year = year, month = month, day = day, hour = hour, minute = minute, second = second, microsecond = microsecond, tzinfo = tzinfo)
433 except ValueError:
434 _log.debug('error replacing datetime member(s): %s', locals())
435
436
437 if month == 2:
438 if day > 28:
439 if is_leap_year(year):
440 day = 29
441 else:
442 day = 28
443 else:
444 if day == 31:
445 day = 30
446
447 return dt.replace(year = year, month = month, day = day, hour = hour, minute = minute, second = second, microsecond = microsecond, tzinfo = tzinfo)
448
449
459
460
464
465
468
469
470
471
473 if not wxDate.IsValid():
474 raise ValueError ('invalid wxDate: %s-%s-%s %s:%s %s.%s',
475 wxDate.GetYear(),
476 wxDate.GetMonth(),
477 wxDate.GetDay(),
478 wxDate.GetHour(),
479 wxDate.GetMinute(),
480 wxDate.GetSecond(),
481 wxDate.GetMillisecond()
482 )
483
484 try:
485 return pyDT.datetime (
486 year = wxDate.GetYear(),
487 month = wxDate.GetMonth() + 1,
488 day = wxDate.GetDay(),
489 tzinfo = gmCurrentLocalTimezone
490 )
491 except Exception:
492 _log.debug ('error converting wxDateTime to Python: %s-%s-%s %s:%s %s.%s',
493 wxDate.GetYear(),
494 wxDate.GetMonth(),
495 wxDate.GetDay(),
496 wxDate.GetHour(),
497 wxDate.GetMinute(),
498 wxDate.GetSecond(),
499 wxDate.GetMillisecond()
500 )
501 raise
502
503
504
505
642
643
706
707
716
717
724
725
727 if year < 1582:
728 _log.debug('%s: before Gregorian Reform', year)
729 return False
730
731
732 div, remainder = divmod(year, 4)
733
734
735 if remainder > 0:
736 return False
737
738
739 div, remainder = divmod(year, 100)
740
741
742
743 if remainder > 0:
744 return True
745
746
747 div, remainder = divmod(year, 400)
748
749
750
751
752 if remainder == 0:
753 return True
754
755
756
757 return False
758
759
761 """The result of this is a tuple (years, ..., seconds) as one would
762 'expect' an age to look like, that is, simple differences between
763 the fields:
764
765 (years, months, days, hours, minutes, seconds)
766
767 This does not take into account time zones which may
768 shift the result by one day.
769
770 <start> and <end> must by python datetime instances
771 <end> is assumed to be "now" if not given
772 """
773 if end is None:
774 end = pyDT.datetime.now(gmCurrentLocalTimezone)
775
776 if end < start:
777 raise ValueError('calculate_apparent_age(): <end> (%s) before <start> (%s)' % (end, start))
778
779 if end == start:
780 return (0, 0, 0, 0, 0, 0)
781
782
783 if end.month == 2:
784 if end.day == 29:
785 if not is_leap_year(start.year):
786 end = end.replace(day = 28)
787
788
789 years = end.year - start.year
790 end = end.replace(year = start.year)
791 if end < start:
792 years = years - 1
793
794
795 if end.month == start.month:
796 if end < start:
797 months = 11
798 else:
799 months = 0
800 else:
801 months = end.month - start.month
802 if months < 0:
803 months = months + 12
804 if end.day > gregorian_month_length[start.month]:
805 end = end.replace(month = start.month, day = gregorian_month_length[start.month])
806 else:
807 end = end.replace(month = start.month)
808 if end < start:
809 months = months - 1
810
811
812 if end.day == start.day:
813 if end < start:
814 days = gregorian_month_length[start.month] - 1
815 else:
816 days = 0
817 else:
818 days = end.day - start.day
819 if days < 0:
820 days = days + gregorian_month_length[start.month]
821 end = end.replace(day = start.day)
822 if end < start:
823 days = days - 1
824
825
826 if end.hour == start.hour:
827 hours = 0
828 else:
829 hours = end.hour - start.hour
830 if hours < 0:
831 hours = hours + 24
832 end = end.replace(hour = start.hour)
833 if end < start:
834 hours = hours - 1
835
836
837 if end.minute == start.minute:
838 minutes = 0
839 else:
840 minutes = end.minute - start.minute
841 if minutes < 0:
842 minutes = minutes + 60
843 end = end.replace(minute = start.minute)
844 if end < start:
845 minutes = minutes - 1
846
847
848 if end.second == start.second:
849 seconds = 0
850 else:
851 seconds = end.second - start.second
852 if seconds < 0:
853 seconds = seconds + 60
854 end = end.replace(second = start.second)
855 if end < start:
856 seconds = seconds - 1
857
858 return (years, months, days, hours, minutes, seconds)
859
860
964
966
967 unit_keys = {
968 'year': _('yYaA_keys_year'),
969 'month': _('mM_keys_month'),
970 'week': _('wW_keys_week'),
971 'day': _('dD_keys_day'),
972 'hour': _('hH_keys_hour')
973 }
974
975 str_interval = str_interval.strip()
976
977
978 keys = '|'.join(list(unit_keys['year'].replace('_keys_year', '')))
979 if regex.match('^~*(\s|\t)*\d+(%s)*$' % keys, str_interval, flags = regex.UNICODE):
980 return pyDT.timedelta(days = (int(regex.findall('\d+', str_interval, flags = regex.UNICODE)[0]) * avg_days_per_gregorian_year))
981
982
983 keys = '|'.join(list(unit_keys['month'].replace('_keys_month', '')))
984 if regex.match('^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.UNICODE):
985 years, months = divmod (
986 int(regex.findall('\d+', str_interval, flags = regex.UNICODE)[0]),
987 12
988 )
989 return pyDT.timedelta(days = ((years * avg_days_per_gregorian_year) + (months * avg_days_per_gregorian_month)))
990
991
992 keys = '|'.join(list(unit_keys['week'].replace('_keys_week', '')))
993 if regex.match('^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.UNICODE):
994 return pyDT.timedelta(weeks = int(regex.findall('\d+', str_interval, flags = regex.UNICODE)[0]))
995
996
997 keys = '|'.join(list(unit_keys['day'].replace('_keys_day', '')))
998 if regex.match('^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.UNICODE):
999 return pyDT.timedelta(days = int(regex.findall('\d+', str_interval, flags = regex.UNICODE)[0]))
1000
1001
1002 keys = '|'.join(list(unit_keys['hour'].replace('_keys_hour', '')))
1003 if regex.match('^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.UNICODE):
1004 return pyDT.timedelta(hours = int(regex.findall('\d+', str_interval, flags = regex.UNICODE)[0]))
1005
1006
1007 if regex.match('^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*12$', str_interval, flags = regex.UNICODE):
1008 years, months = divmod (
1009 int(regex.findall('\d+', str_interval, flags = regex.UNICODE)[0]),
1010 12
1011 )
1012 return pyDT.timedelta(days = ((years * avg_days_per_gregorian_year) + (months * avg_days_per_gregorian_month)))
1013
1014
1015 if regex.match('^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*52$', str_interval, flags = regex.UNICODE):
1016 return pyDT.timedelta(weeks = int(regex.findall('\d+', str_interval, flags = regex.UNICODE)[0]))
1017
1018
1019 if regex.match('^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*7$', str_interval, flags = regex.UNICODE):
1020 return pyDT.timedelta(days = int(regex.findall('\d+', str_interval, flags = regex.UNICODE)[0]))
1021
1022
1023 if regex.match('^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*24$', str_interval, flags = regex.UNICODE):
1024 return pyDT.timedelta(hours = int(regex.findall('\d+', str_interval, flags = regex.UNICODE)[0]))
1025
1026
1027 if regex.match('^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*60$', str_interval, flags = regex.UNICODE):
1028 return pyDT.timedelta(minutes = int(regex.findall('\d+', str_interval, flags = regex.UNICODE)[0]))
1029
1030
1031 keys_year = '|'.join(list(unit_keys['year'].replace('_keys_year', '')))
1032 keys_month = '|'.join(list(unit_keys['month'].replace('_keys_month', '')))
1033 if regex.match('^~*(\s|\t)*\d+(%s|\s|\t)+\d+(\s|\t)*(%s)+$' % (keys_year, keys_month), str_interval, flags = regex.UNICODE):
1034 parts = regex.findall('\d+', str_interval, flags = regex.UNICODE)
1035 years, months = divmod(int(parts[1]), 12)
1036 years += int(parts[0])
1037 return pyDT.timedelta(days = ((years * avg_days_per_gregorian_year) + (months * avg_days_per_gregorian_month)))
1038
1039
1040 keys_month = '|'.join(list(unit_keys['month'].replace('_keys_month', '')))
1041 keys_week = '|'.join(list(unit_keys['week'].replace('_keys_week', '')))
1042 if regex.match('^~*(\s|\t)*\d+(%s|\s|\t)+\d+(\s|\t)*(%s)+$' % (keys_month, keys_week), str_interval, flags = regex.UNICODE):
1043 parts = regex.findall('\d+', str_interval, flags = regex.UNICODE)
1044 months, weeks = divmod(int(parts[1]), 4)
1045 months += int(parts[0])
1046 return pyDT.timedelta(days = ((months * avg_days_per_gregorian_month) + (weeks * days_per_week)))
1047
1048 return None
1049
1050
1051
1052
1054 """This matches on single characters.
1055
1056 Spaces and tabs are discarded.
1057
1058 Default is 'ndmy':
1059 n - _N_ow
1060 d - to_D_ay
1061 m - to_M_orrow Someone please suggest a synonym ! ("2" does not cut it ...)
1062 y - _Y_esterday
1063
1064 This also defines the significance of the order of the characters.
1065 """
1066 str2parse = str2parse.strip().lower()
1067 if len(str2parse) != 1:
1068 return []
1069
1070 if trigger_chars is None:
1071 trigger_chars = _('ndmy (single character date triggers)')[:4].lower()
1072
1073 if str2parse not in trigger_chars:
1074 return []
1075
1076 now = pydt_now_here()
1077
1078
1079
1080
1081 if str2parse == trigger_chars[0]:
1082 return [{
1083 'data': now,
1084 'label': _('right now (%s, %s)') % (now.strftime('%A'), now)
1085 }]
1086
1087 if str2parse == trigger_chars[1]:
1088 return [{
1089 'data': now,
1090 'label': _('today (%s)') % now.strftime('%A, %Y-%m-%d')
1091 }]
1092
1093 if str2parse == trigger_chars[2]:
1094 ts = pydt_add(now, days = 1)
1095 return [{
1096 'data': ts,
1097 'label': _('tomorrow (%s)') % ts.strftime('%A, %Y-%m-%d')
1098 }]
1099
1100 if str2parse == trigger_chars[3]:
1101 ts = pydt_add(now, days = -1)
1102 return [{
1103 'data': ts,
1104 'label': _('yesterday (%s)') % ts.strftime('%A, %Y-%m-%d')
1105 }]
1106 return []
1107
1108
1110 """Expand fragments containing a single dot.
1111
1112 Standard colloquial date format in Germany: day.month.year
1113
1114 "14."
1115 - the 14th of the current month
1116 - the 14th of next month
1117 "-14."
1118 - the 14th of last month
1119 """
1120 str2parse = str2parse.replace(' ', '').replace('\t', '')
1121
1122 if not str2parse.endswith('.'):
1123 return []
1124 try:
1125 day_val = int(str2parse[:-1])
1126 except ValueError:
1127 return []
1128 if (day_val < -31) or (day_val > 31) or (day_val == 0):
1129 return []
1130
1131 now = pydt_now_here()
1132 matches = []
1133
1134
1135 if day_val < 0:
1136 ts = pydt_replace(pydt_add(now, months = -1), day = abs(day_val), strict = False)
1137 if ts.day == day_val:
1138 matches.append ({
1139 'data': ts,
1140 'label': _('%s-%s-%s: a %s last month') % (ts.year, ts.month, ts.day, ts.strftime('%A'))
1141 })
1142
1143
1144 if day_val > 0:
1145
1146 try:
1147 ts = pydt_replace(now, day = day_val, strict = False)
1148 matches.append ({
1149 'data': ts,
1150 'label': _('%s-%s-%s: a %s this month') % (ts.year, ts.month, ts.day, ts.strftime('%A'))
1151 })
1152 except ValueError:
1153 pass
1154
1155 try:
1156 ts = pydt_replace(pydt_add(now, months = 1), day = day_val, strict = False)
1157 if ts.day == day_val:
1158 matches.append ({
1159 'data': ts,
1160 'label': _('%s-%s-%s: a %s next month') % (ts.year, ts.month, ts.day, ts.strftime('%A'))
1161 })
1162 except ValueError:
1163 pass
1164
1165 try:
1166 ts = pydt_replace(pydt_add(now, months = -1), day = day_val, strict = False)
1167 if ts.day == day_val:
1168 matches.append ({
1169 'data': ts,
1170 'label': _('%s-%s-%s: a %s last month') % (ts.year, ts.month, ts.day, ts.strftime('%A'))
1171 })
1172 except ValueError:
1173 pass
1174
1175 return matches
1176
1177
1179 """Expand fragments containing a single slash.
1180
1181 "5/"
1182 - 2005/ (2000 - 2025)
1183 - 1995/ (1990 - 1999)
1184 - Mai/current year
1185 - Mai/next year
1186 - Mai/last year
1187 - Mai/200x
1188 - Mai/20xx
1189 - Mai/199x
1190 - Mai/198x
1191 - Mai/197x
1192 - Mai/19xx
1193
1194 5/1999
1195 6/2004
1196 """
1197 str2parse = str2parse.strip()
1198
1199 now = pydt_now_here()
1200
1201
1202 if regex.match(r"^\d{1,2}(\s|\t)*/+(\s|\t)*\d{4}$", str2parse, flags = regex.UNICODE):
1203 month, year = regex.findall(r'\d+', str2parse, flags = regex.UNICODE)
1204 ts = pydt_replace(now, year = int(year), month = int(month), strict = False)
1205 return [{
1206 'data': ts,
1207 'label': ts.strftime('%Y-%m-%d')
1208 }]
1209
1210 matches = []
1211
1212 if regex.match(r"^\d{1,2}(\s|\t)*/+$", str2parse, flags = regex.UNICODE):
1213 val = int(str2parse.rstrip('/').strip())
1214
1215
1216 if val < 100 and val >= 0:
1217 matches.append ({
1218 'data': None,
1219 'label': '%s-' % (val + 1900)
1220 })
1221
1222 if val < 26 and val >= 0:
1223 matches.append ({
1224 'data': None,
1225 'label': '%s-' % (val + 2000)
1226 })
1227
1228 if val < 10 and val >= 0:
1229 matches.append ({
1230 'data': None,
1231 'label': '%s-' % (val + 1990)
1232 })
1233 if val < 13 and val > 0:
1234
1235 matches.append ({
1236 'data': None,
1237 'label': '%s-%.2d-' % (now.year, val)
1238 })
1239
1240 ts = pydt_add(now, years = 1)
1241 matches.append ({
1242 'data': None,
1243 'label': '%s-%.2d-' % (ts.year, val)
1244 })
1245
1246 ts = pydt_add(now, years = -1)
1247 matches.append ({
1248 'data': None,
1249 'label': '%s-%.2d-' % (ts.year, val)
1250 })
1251
1252 matches.append ({
1253 'data': None,
1254 'label': '201?-%.2d-' % val
1255 })
1256
1257 matches.append ({
1258 'data': None,
1259 'label': '200?-%.2d-' % val
1260 })
1261
1262 matches.append ({
1263 'data': None,
1264 'label': '20??-%.2d-' % val
1265 })
1266
1267 matches.append ({
1268 'data': None,
1269 'label': '199?-%.2d-' % val
1270 })
1271
1272 matches.append ({
1273 'data': None,
1274 'label': '198?-%.2d-' % val
1275 })
1276
1277 matches.append ({
1278 'data': None,
1279 'label': '197?-%.2d-' % val
1280 })
1281
1282 matches.append ({
1283 'data': None,
1284 'label': '19??-%.2d-' % val
1285 })
1286
1287 return matches
1288
1289
1291 """This matches on single numbers.
1292
1293 Spaces or tabs are discarded.
1294 """
1295 try:
1296 val = int(str2parse.strip())
1297 except ValueError:
1298 return []
1299
1300 now = pydt_now_here()
1301
1302 matches = []
1303
1304
1305 if (1850 < val) and (val < 2100):
1306 ts = pydt_replace(now, year = val, strict = False)
1307 matches.append ({
1308 'data': ts,
1309 'label': ts.strftime('%Y-%m-%d')
1310 })
1311
1312 if (val > 0) and (val <= gregorian_month_length[now.month]):
1313 ts = pydt_replace(now, day = val, strict = False)
1314 matches.append ({
1315 'data': ts,
1316 'label': _('%d. of %s (this month): a %s') % (val, ts.strftime('%B'), ts.strftime('%A'))
1317 })
1318
1319 if (val > 0) and (val < 32):
1320
1321 ts = pydt_replace(pydt_add(now, months = 1), day = val, strict = False)
1322 matches.append ({
1323 'data': ts,
1324 'label': _('%d. of %s (next month): a %s') % (val, ts.strftime('%B'), ts.strftime('%A'))
1325 })
1326
1327 ts = pydt_replace(pydt_add(now, months = -1), day = val, strict = False)
1328 matches.append ({
1329 'data': ts,
1330 'label': _('%d. of %s (last month): a %s') % (val, ts.strftime('%B'), ts.strftime('%A'))
1331 })
1332
1333 if (val > 0) and (val <= 400):
1334 ts = pydt_add(now, days = val)
1335 matches.append ({
1336 'data': ts,
1337 'label': _('in %d day(s): %s') % (val, ts.strftime('%A, %Y-%m-%d'))
1338 })
1339 if (val < 0) and (val >= -400):
1340 ts = pydt_add(now, days = val)
1341 matches.append ({
1342 'data': ts,
1343 'label': _('%d day(s) ago: %s') % (abs(val), ts.strftime('%A, %Y-%m-%d'))
1344 })
1345
1346 if (val > 0) and (val <= 50):
1347 ts = pydt_add(now, weeks = val)
1348 matches.append ({
1349 'data': ts,
1350 'label': _('in %d week(s): %s') % (val, ts.strftime('%A, %Y-%m-%d'))
1351 })
1352 if (val < 0) and (val >= -50):
1353 ts = pydt_add(now, weeks = val)
1354 matches.append ({
1355 'data': ts,
1356 'label': _('%d week(s) ago: %s') % (abs(val), ts.strftime('%A, %Y-%m-%d'))
1357 })
1358
1359
1360 if (val < 13) and (val > 0):
1361
1362 ts = pydt_replace(now, month = val, strict = False)
1363 matches.append ({
1364 'data': ts,
1365 'label': _('%s (%s this year)') % (ts.strftime('%Y-%m-%d'), ts.strftime('%B'))
1366 })
1367
1368 ts = pydt_replace(pydt_add(now, years = 1), month = val, strict = False)
1369 matches.append ({
1370 'data': ts,
1371 'label': _('%s (%s next year)') % (ts.strftime('%Y-%m-%d'), ts.strftime('%B'))
1372 })
1373
1374 ts = pydt_replace(pydt_add(now, years = -1), month = val, strict = False)
1375 matches.append ({
1376 'data': ts,
1377 'label': _('%s (%s last year)') % (ts.strftime('%Y-%m-%d'), ts.strftime('%B'))
1378 })
1379
1380 matches.append ({
1381 'data': None,
1382 'label': '200?-%s' % val
1383 })
1384 matches.append ({
1385 'data': None,
1386 'label': '199?-%s' % val
1387 })
1388 matches.append ({
1389 'data': None,
1390 'label': '198?-%s' % val
1391 })
1392 matches.append ({
1393 'data': None,
1394 'label': '19??-%s' % val
1395 })
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419 if (val < 100) and (val > 0):
1420 matches.append ({
1421 'data': None,
1422 'label': '%s-' % (1900 + val)
1423 })
1424
1425 if val == 201:
1426 matches.append ({
1427 'data': now,
1428 'label': now.strftime('%Y-%m-%d')
1429 })
1430 matches.append ({
1431 'data': None,
1432 'label': now.strftime('%Y-%m')
1433 })
1434 matches.append ({
1435 'data': None,
1436 'label': now.strftime('%Y')
1437 })
1438 matches.append ({
1439 'data': None,
1440 'label': '%s-' % (now.year + 1)
1441 })
1442 matches.append ({
1443 'data': None,
1444 'label': '%s-' % (now.year - 1)
1445 })
1446
1447 if val < 200 and val >= 190:
1448 for i in range(10):
1449 matches.append ({
1450 'data': None,
1451 'label': '%s%s-' % (val, i)
1452 })
1453
1454 return matches
1455
1456
1458 """Default is 'hdwmy':
1459 h - hours
1460 d - days
1461 w - weeks
1462 m - months
1463 y - years
1464
1465 This also defines the significance of the order of the characters.
1466 """
1467 if offset_chars is None:
1468 offset_chars = _('hdwmy (single character date offset triggers)')[:5].lower()
1469
1470 str2parse = str2parse.replace(' ', '').replace('\t', '')
1471
1472 if regex.fullmatch(r"(\+|-){,1}\d{1,3}[%s]" % offset_chars, str2parse) is None:
1473 return []
1474
1475 offset_val = int(str2parse[:-1])
1476 offset_char = str2parse[-1:]
1477 is_past = str2parse.startswith('-')
1478 now = pydt_now_here()
1479 ts = None
1480
1481
1482 if offset_char == offset_chars[0]:
1483 ts = pydt_add(now, hours = offset_val)
1484 if is_past:
1485 label = _('%d hour(s) ago: %s') % (abs(offset_val), ts.strftime('%H:%M'))
1486 else:
1487 label = _('in %d hour(s): %s') % (offset_val, ts.strftime('%H:%M'))
1488
1489 elif offset_char == offset_chars[1]:
1490 ts = pydt_add(now, days = offset_val)
1491 if is_past:
1492 label = _('%d day(s) ago: %s') % (abs(offset_val), ts.strftime('%A, %Y-%m-%d'))
1493 else:
1494 label = _('in %d day(s): %s') % (offset_val, ts.strftime('%A, %Y-%m-%d'))
1495
1496 elif offset_char == offset_chars[2]:
1497 ts = pydt_add(now, weeks = offset_val)
1498 if is_past:
1499 label = _('%d week(s) ago: %s') % (abs(offset_val), ts.strftime('%A, %Y-%m-%d'))
1500 else:
1501 label = _('in %d week(s): %s') % (offset_val, ts.strftime('%A, %Y-%m-%d'))
1502
1503 elif offset_char == offset_chars[3]:
1504 ts = pydt_add(now, months = offset_val)
1505 if is_past:
1506 label = _('%d month(s) ago: %s') % (abs(offset_val), ts.strftime('%A, %Y-%m-%d'))
1507 else:
1508 label = _('in %d month(s): %s') % (offset_val, ts.strftime('%A, %Y-%m-%d'))
1509
1510 elif offset_char == offset_chars[4]:
1511 ts = pydt_add(now, years = offset_val)
1512 if is_past:
1513 label = _('%d year(s) ago: %s') % (abs(offset_val), ts.strftime('%A, %Y-%m-%d'))
1514 else:
1515 label = _('in %d year(s): %s') % (offset_val, ts.strftime('%A, %Y-%m-%d'))
1516
1517 if ts is None:
1518 return []
1519
1520 return [{'data': ts, 'label': label}]
1521
1522
1524 """Turn a string into candidate dates and auto-completions the user is likely to type.
1525
1526 You MUST have called locale.setlocale(locale.LC_ALL, '')
1527 somewhere in your code previously.
1528
1529 @param patterns: list of time.strptime compatible date pattern
1530 @type patterns: list
1531 """
1532 matches = []
1533 matches.extend(__single_dot2py_dt(str2parse))
1534 matches.extend(__numbers_only2py_dt(str2parse))
1535 matches.extend(__single_slash2py_dt(str2parse))
1536 matches.extend(__single_char2py_dt(str2parse))
1537 matches.extend(__explicit_offset2py_dt(str2parse))
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556 if patterns is None:
1557 patterns = []
1558
1559 patterns.append('%Y-%m-%d')
1560 patterns.append('%y-%m-%d')
1561 patterns.append('%Y/%m/%d')
1562 patterns.append('%y/%m/%d')
1563
1564 patterns.append('%d-%m-%Y')
1565 patterns.append('%d-%m-%y')
1566 patterns.append('%d/%m/%Y')
1567 patterns.append('%d/%m/%y')
1568 patterns.append('%d.%m.%Y')
1569
1570 patterns.append('%m-%d-%Y')
1571 patterns.append('%m-%d-%y')
1572 patterns.append('%m/%d/%Y')
1573 patterns.append('%m/%d/%y')
1574
1575 patterns.append('%Y.%m.%d')
1576
1577 for pattern in patterns:
1578 try:
1579 date = pyDT.datetime.strptime(str2parse, pattern).replace (
1580 hour = 11,
1581 minute = 11,
1582 second = 11,
1583 tzinfo = gmCurrentLocalTimezone
1584 )
1585 matches.append ({
1586 'data': date,
1587 'label': pydt_strftime(date, format = '%Y-%m-%d', accuracy = acc_days)
1588 })
1589 except ValueError:
1590
1591 continue
1592
1593 return matches
1594
1595
1596
1597
1599 """Expand fragments containing a single slash.
1600
1601 "5/"
1602 - 2005/ (2000 - 2025)
1603 - 1995/ (1990 - 1999)
1604 - Mai/current year
1605 - Mai/next year
1606 - Mai/last year
1607 - Mai/200x
1608 - Mai/20xx
1609 - Mai/199x
1610 - Mai/198x
1611 - Mai/197x
1612 - Mai/19xx
1613 """
1614 matches = []
1615 now = pydt_now_here()
1616
1617 if regex.match("^(\s|\t)*\d{1,2}(\s|\t)*/+(\s|\t)*\d{4}(\s|\t)*$", str2parse, flags = regex.UNICODE):
1618 parts = regex.findall('\d+', str2parse, flags = regex.UNICODE)
1619 month = int(parts[0])
1620 if month in range(1, 13):
1621 fts = cFuzzyTimestamp (
1622 timestamp = now.replace(year = int(parts[1], month = month)),
1623 accuracy = acc_months
1624 )
1625 matches.append ({
1626 'data': fts,
1627 'label': fts.format_accurately()
1628 })
1629
1630 elif regex.match("^(\s|\t)*\d{1,2}(\s|\t)*/+(\s|\t)*$", str2parse, flags = regex.UNICODE):
1631 val = int(regex.findall('\d+', str2parse, flags = regex.UNICODE)[0])
1632
1633 if val < 100 and val >= 0:
1634 matches.append ({
1635 'data': None,
1636 'label': '%s/' % (val + 1900)
1637 })
1638
1639 if val < 26 and val >= 0:
1640 matches.append ({
1641 'data': None,
1642 'label': '%s/' % (val + 2000)
1643 })
1644
1645 if val < 10 and val >= 0:
1646 matches.append ({
1647 'data': None,
1648 'label': '%s/' % (val + 1990)
1649 })
1650
1651 if val < 13 and val > 0:
1652 matches.append ({
1653 'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_months),
1654 'label': '%.2d/%s' % (val, now.year)
1655 })
1656 ts = now.replace(year = now.year + 1)
1657 matches.append ({
1658 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_months),
1659 'label': '%.2d/%s' % (val, ts.year)
1660 })
1661 ts = now.replace(year = now.year - 1)
1662 matches.append ({
1663 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_months),
1664 'label': '%.2d/%s' % (val, ts.year)
1665 })
1666 matches.append ({
1667 'data': None,
1668 'label': '%.2d/200' % val
1669 })
1670 matches.append ({
1671 'data': None,
1672 'label': '%.2d/20' % val
1673 })
1674 matches.append ({
1675 'data': None,
1676 'label': '%.2d/199' % val
1677 })
1678 matches.append ({
1679 'data': None,
1680 'label': '%.2d/198' % val
1681 })
1682 matches.append ({
1683 'data': None,
1684 'label': '%.2d/197' % val
1685 })
1686 matches.append ({
1687 'data': None,
1688 'label': '%.2d/19' % val
1689 })
1690
1691 return matches
1692
1693
1695 """This matches on single numbers.
1696
1697 Spaces or tabs are discarded.
1698 """
1699 if not regex.match("^(\s|\t)*\d{1,4}(\s|\t)*$", str2parse, flags = regex.UNICODE):
1700 return []
1701
1702 now = pydt_now_here()
1703 val = int(regex.findall('\d{1,4}', str2parse, flags = regex.UNICODE)[0])
1704
1705 matches = []
1706
1707
1708 if (1850 < val) and (val < 2100):
1709 target_date = cFuzzyTimestamp (
1710 timestamp = now.replace(year = val),
1711 accuracy = acc_years
1712 )
1713 tmp = {
1714 'data': target_date,
1715 'label': '%s' % target_date
1716 }
1717 matches.append(tmp)
1718
1719
1720 if val <= gregorian_month_length[now.month]:
1721 ts = now.replace(day = val)
1722 target_date = cFuzzyTimestamp (
1723 timestamp = ts,
1724 accuracy = acc_days
1725 )
1726 tmp = {
1727 'data': target_date,
1728 'label': _('%d. of %s (this month) - a %s') % (val, ts.strftime('%B'), ts.strftime('%A'))
1729 }
1730 matches.append(tmp)
1731
1732
1733 next_month = get_next_month(now)
1734 if val <= gregorian_month_length[next_month]:
1735 ts = now.replace(day = val, month = next_month)
1736 target_date = cFuzzyTimestamp (
1737 timestamp = ts,
1738 accuracy = acc_days
1739 )
1740 tmp = {
1741 'data': target_date,
1742 'label': _('%d. of %s (next month) - a %s') % (val, ts.strftime('%B'), ts.strftime('%A'))
1743 }
1744 matches.append(tmp)
1745
1746
1747 last_month = get_last_month(now)
1748 if val <= gregorian_month_length[last_month]:
1749 ts = now.replace(day = val, month = last_month)
1750 target_date = cFuzzyTimestamp (
1751 timestamp = ts,
1752 accuracy = acc_days
1753 )
1754 tmp = {
1755 'data': target_date,
1756 'label': _('%d. of %s (last month) - a %s') % (val, ts.strftime('%B'), ts.strftime('%A'))
1757 }
1758 matches.append(tmp)
1759
1760
1761 if val <= 400:
1762 target_date = cFuzzyTimestamp(timestamp = now + pyDT.timedelta(days = val))
1763 tmp = {
1764 'data': target_date,
1765 'label': _('in %d day(s) - %s') % (val, target_date.timestamp.strftime('%A, %Y-%m-%d'))
1766 }
1767 matches.append(tmp)
1768
1769
1770 if val <= 50:
1771 target_date = cFuzzyTimestamp(timestamp = now + pyDT.timedelta(weeks = val))
1772 tmp = {
1773 'data': target_date,
1774 'label': _('in %d week(s) - %s') % (val, target_date.timestamp.strftime('%A, %Y-%m-%d'))
1775 }
1776 matches.append(tmp)
1777
1778
1779 if val < 13:
1780
1781 target_date = cFuzzyTimestamp (
1782 timestamp = pydt_replace(now, month = val, strict = False),
1783 accuracy = acc_months
1784 )
1785 tmp = {
1786 'data': target_date,
1787 'label': _('%s (%s this year)') % (target_date, ts.strftime('%B'))
1788 }
1789 matches.append(tmp)
1790
1791
1792 target_date = cFuzzyTimestamp (
1793 timestamp = pydt_add(pydt_replace(now, month = val, strict = False), years = 1),
1794 accuracy = acc_months
1795 )
1796 tmp = {
1797 'data': target_date,
1798 'label': _('%s (%s next year)') % (target_date, ts.strftime('%B'))
1799 }
1800 matches.append(tmp)
1801
1802
1803 target_date = cFuzzyTimestamp (
1804 timestamp = pydt_add(pydt_replace(now, month = val, strict = False), years = -1),
1805 accuracy = acc_months
1806 )
1807 tmp = {
1808 'data': target_date,
1809 'label': _('%s (%s last year)') % (target_date, ts.strftime('%B'))
1810 }
1811 matches.append(tmp)
1812
1813
1814 matches.append ({
1815 'data': None,
1816 'label': '%s/200' % val
1817 })
1818 matches.append ({
1819 'data': None,
1820 'label': '%s/199' % val
1821 })
1822 matches.append ({
1823 'data': None,
1824 'label': '%s/198' % val
1825 })
1826 matches.append ({
1827 'data': None,
1828 'label': '%s/19' % val
1829 })
1830
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 if val < 100:
1871 matches.append ({
1872 'data': None,
1873 'label': '%s/' % (1900 + val)
1874 })
1875
1876
1877 if val == 200:
1878 tmp = {
1879 'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_days),
1880 'label': '%s' % target_date
1881 }
1882 matches.append(tmp)
1883 matches.append ({
1884 'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_months),
1885 'label': '%.2d/%s' % (now.month, now.year)
1886 })
1887 matches.append ({
1888 'data': None,
1889 'label': '%s/' % now.year
1890 })
1891 matches.append ({
1892 'data': None,
1893 'label': '%s/' % (now.year + 1)
1894 })
1895 matches.append ({
1896 'data': None,
1897 'label': '%s/' % (now.year - 1)
1898 })
1899
1900 if val < 200 and val >= 190:
1901 for i in range(10):
1902 matches.append ({
1903 'data': None,
1904 'label': '%s%s/' % (val, i)
1905 })
1906
1907 return matches
1908
1909
1911 """
1912 Turn a string into candidate fuzzy timestamps and auto-completions the user is likely to type.
1913
1914 You MUST have called locale.setlocale(locale.LC_ALL, '')
1915 somewhere in your code previously.
1916
1917 @param default_time: if you want to force the time part of the time
1918 stamp to a given value and the user doesn't type any time part
1919 this value will be used
1920 @type default_time: an mx.DateTime.DateTimeDelta instance
1921
1922 @param patterns: list of [time.strptime compatible date/time pattern, accuracy]
1923 @type patterns: list
1924 """
1925 matches = []
1926
1927 matches.extend(__numbers_only(str2parse))
1928 matches.extend(__single_slash(str2parse))
1929
1930 matches.extend ([
1931 { 'data': cFuzzyTimestamp(timestamp = m['data'], accuracy = acc_days),
1932 'label': m['label']
1933 } for m in __single_dot2py_dt(str2parse)
1934 ])
1935 matches.extend ([
1936 { 'data': cFuzzyTimestamp(timestamp = m['data'], accuracy = acc_days),
1937 'label': m['label']
1938 } for m in __single_char2py_dt(str2parse)
1939 ])
1940 matches.extend ([
1941 { 'data': cFuzzyTimestamp(timestamp = m['data'], accuracy = acc_days),
1942 'label': m['label']
1943 } for m in __explicit_offset2py_dt(str2parse)
1944 ])
1945
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 if patterns is None:
1978 patterns = []
1979 patterns.extend([
1980 ['%Y-%m-%d', acc_days],
1981 ['%y-%m-%d', acc_days],
1982 ['%Y/%m/%d', acc_days],
1983 ['%y/%m/%d', acc_days],
1984
1985 ['%d-%m-%Y', acc_days],
1986 ['%d-%m-%y', acc_days],
1987 ['%d/%m/%Y', acc_days],
1988 ['%d/%m/%y', acc_days],
1989 ['%d.%m.%Y', acc_days],
1990
1991 ['%m-%d-%Y', acc_days],
1992 ['%m-%d-%y', acc_days],
1993 ['%m/%d/%Y', acc_days],
1994 ['%m/%d/%y', acc_days]
1995 ])
1996 for pattern in patterns:
1997 try:
1998 ts = pyDT.datetime.strptime(str2parse, pattern[0]).replace (
1999 hour = 11,
2000 minute = 11,
2001 second = 11,
2002 tzinfo = gmCurrentLocalTimezone
2003 )
2004 fts = cFuzzyTimestamp(timestamp = ts, accuracy = pattern[1])
2005 matches.append ({
2006 'data': fts,
2007 'label': fts.format_accurately()
2008 })
2009 except ValueError:
2010
2011 continue
2012
2013 return matches
2014
2015
2016
2017
2019
2020
2021
2022 """A timestamp implementation with definable inaccuracy.
2023
2024 This class contains an datetime.datetime instance to
2025 hold the actual timestamp. It adds an accuracy attribute
2026 to allow the programmer to set the precision of the
2027 timestamp.
2028
2029 The timestamp will have to be initialzed with a fully
2030 precise value (which may, of course, contain partially
2031 fake data to make up for missing values). One can then
2032 set the accuracy value to indicate up to which part of
2033 the timestamp the data is valid. Optionally a modifier
2034 can be set to indicate further specification of the
2035 value (such as "summer", "afternoon", etc).
2036
2037 accuracy values:
2038 1: year only
2039 ...
2040 7: everything including milliseconds value
2041
2042 Unfortunately, one cannot directly derive a class from mx.DateTime.DateTime :-(
2043 """
2044
2046
2047 if timestamp is None:
2048 timestamp = pydt_now_here()
2049 accuracy = acc_subseconds
2050 modifier = ''
2051
2052 if (accuracy < 1) or (accuracy > 8):
2053 raise ValueError('%s.__init__(): <accuracy> must be between 1 and 8' % self.__class__.__name__)
2054
2055 if not isinstance(timestamp, pyDT.datetime):
2056 raise TypeError('%s.__init__(): <timestamp> must be of datetime.datetime type, but is %s' % self.__class__.__name__, type(timestamp))
2057
2058 if timestamp.tzinfo is None:
2059 raise ValueError('%s.__init__(): <tzinfo> must be defined' % self.__class__.__name__)
2060
2061 self.timestamp = timestamp
2062 self.accuracy = accuracy
2063 self.modifier = modifier
2064
2065
2066
2067
2069 """Return string representation meaningful to a user, also for %s formatting."""
2070 return self.format_accurately()
2071
2072
2074 """Return string meaningful to a programmer to aid in debugging."""
2075 tmp = '<[%s]: timestamp [%s], accuracy [%s] (%s), modifier [%s] at %s>' % (
2076 self.__class__.__name__,
2077 repr(self.timestamp),
2078 self.accuracy,
2079 _accuracy_strings[self.accuracy],
2080 self.modifier,
2081 id(self)
2082 )
2083 return tmp
2084
2085
2086
2087
2092
2093
2096
2097
2130
2131
2133 return self.timestamp
2134
2135
2136
2137
2138 if __name__ == '__main__':
2139
2140 if len(sys.argv) < 2:
2141 sys.exit()
2142
2143 if sys.argv[1] != "test":
2144 sys.exit()
2145
2146 from Gnumed.pycommon import gmI18N
2147 from Gnumed.pycommon import gmLog2
2148
2149
2150 intervals_as_str = [
2151 '7', '12', ' 12', '12 ', ' 12 ', ' 12 ', '0', '~12', '~ 12', ' ~ 12', ' ~ 12 ',
2152 '12a', '12 a', '12 a', '12j', '12J', '12y', '12Y', ' ~ 12 a ', '~0a',
2153 '12m', '17 m', '12 m', '17M', ' ~ 17 m ', ' ~ 3 / 12 ', '7/12', '0/12',
2154 '12w', '17 w', '12 w', '17W', ' ~ 17 w ', ' ~ 15 / 52', '2/52', '0/52',
2155 '12d', '17 d', '12 t', '17D', ' ~ 17 T ', ' ~ 12 / 7', '3/7', '0/7',
2156 '12h', '17 h', '12 H', '17H', ' ~ 17 h ', ' ~ 36 / 24', '7/24', '0/24',
2157 ' ~ 36 / 60', '7/60', '190/60', '0/60',
2158 '12a1m', '12 a 1 M', '12 a17m', '12j 12m', '12J7m', '12y7m', '12Y7M', ' ~ 12 a 37 m ', '~0a0m',
2159 '10m1w',
2160 'invalid interval input'
2161 ]
2162
2176
2177
2245
2247 print ("testing str2interval()")
2248 print ("----------------------")
2249
2250 for interval_as_str in intervals_as_str:
2251 print ("input: <%s>" % interval_as_str)
2252 print (" ==>", str2interval(str_interval=interval_as_str))
2253
2254 return True
2255
2257 print ("DST currently in effect:", dst_currently_in_effect)
2258 print ("current UTC offset:", current_local_utc_offset_in_seconds, "seconds")
2259
2260 print ("current timezone (ISO conformant numeric string):", current_local_iso_numeric_timezone_string)
2261 print ("local timezone class:", cPlatformLocalTimezone)
2262 print ("")
2263 tz = cPlatformLocalTimezone()
2264 print ("local timezone instance:", tz)
2265 print (" (total) UTC offset:", tz.utcoffset(pyDT.datetime.now()))
2266 print (" DST adjustment:", tz.dst(pyDT.datetime.now()))
2267 print (" timezone name:", tz.tzname(pyDT.datetime.now()))
2268 print ("")
2269 print ("current local timezone:", gmCurrentLocalTimezone)
2270 print (" (total) UTC offset:", gmCurrentLocalTimezone.utcoffset(pyDT.datetime.now()))
2271 print (" DST adjustment:", gmCurrentLocalTimezone.dst(pyDT.datetime.now()))
2272 print (" timezone name:", gmCurrentLocalTimezone.tzname(pyDT.datetime.now()))
2273 print ("")
2274 print ("now here:", pydt_now_here())
2275 print ("")
2276
2277
2279 print ("testing function str2fuzzy_timestamp_matches")
2280 print ("--------------------------------------------")
2281
2282 val = None
2283 while val != 'exit':
2284 val = input('Enter date fragment ("exit" quits): ')
2285 matches = str2fuzzy_timestamp_matches(str2parse = val)
2286 for match in matches:
2287 print ('label shown :', match['label'])
2288 print ('data attached:', match['data'], match['data'].timestamp)
2289 print ("")
2290 print ("---------------")
2291
2292
2294 print ("testing fuzzy timestamp class")
2295 print ("-----------------------------")
2296
2297 fts = cFuzzyTimestamp()
2298 print ("\nfuzzy timestamp <%s '%s'>" % ('class', fts.__class__.__name__))
2299 for accuracy in range(1,8):
2300 fts.accuracy = accuracy
2301 print (" accuracy : %s (%s)" % (accuracy, _accuracy_strings[accuracy]))
2302 print (" format_accurately:", fts.format_accurately())
2303 print (" strftime() :", fts.strftime('%Y %b %d %H:%M:%S'))
2304 print (" print ... :", fts)
2305 print (" print '%%s' %% ... : %s" % fts)
2306 print (" str() :", str(fts))
2307 print (" repr() :", repr(fts))
2308 input('press ENTER to continue')
2309
2310
2312 print ("testing platform for handling dates before 1970")
2313 print ("-----------------------------------------------")
2314 ts = mxDT.DateTime(1935, 4, 2)
2315 fts = cFuzzyTimestamp(timestamp=ts)
2316 print ("fts :", fts)
2317 print ("fts.get_pydt():", fts.get_pydt())
2318
2320
2321 start = pydt_now_here().replace(year = 2000).replace(month = 2).replace(day = 29)
2322 end = pydt_now_here().replace(year = 2012).replace(month = 2).replace(day = 27)
2323 print ("start is leap year: 29.2.2000")
2324 print (" ", calculate_apparent_age(start = start, end = end))
2325 print (" ", format_apparent_age_medically(calculate_apparent_age(start = start)))
2326
2327 start = pydt_now_here().replace(month = 10).replace(day = 23).replace(year = 1974)
2328 end = pydt_now_here().replace(year = 2012).replace(month = 2).replace(day = 29)
2329 print ("end is leap year: 29.2.2012")
2330 print (" ", calculate_apparent_age(start = start, end = end))
2331 print (" ", format_apparent_age_medically(calculate_apparent_age(start = start)))
2332
2333 start = pydt_now_here().replace(year = 2000).replace(month = 2).replace(day = 29)
2334 end = pydt_now_here().replace(year = 2012).replace(month = 2).replace(day = 29)
2335 print ("start is leap year: 29.2.2000")
2336 print ("end is leap year: 29.2.2012")
2337 print (" ", calculate_apparent_age(start = start, end = end))
2338 print (" ", format_apparent_age_medically(calculate_apparent_age(start = start)))
2339
2340 print ("leap year tests worked")
2341
2342 start = pydt_now_here().replace(month = 10).replace(day = 23).replace(year = 1974)
2343 print (calculate_apparent_age(start = start))
2344 print (format_apparent_age_medically(calculate_apparent_age(start = start)))
2345
2346 start = pydt_now_here().replace(month = 3).replace(day = 13).replace(year = 1979)
2347 print (calculate_apparent_age(start = start))
2348 print (format_apparent_age_medically(calculate_apparent_age(start = start)))
2349
2350 start = pydt_now_here().replace(month = 2, day = 2).replace(year = 1979)
2351 end = pydt_now_here().replace(month = 3).replace(day = 31).replace(year = 1979)
2352 print (calculate_apparent_age(start = start, end = end))
2353
2354 start = pydt_now_here().replace(month = 7, day = 21).replace(year = 2009)
2355 print (format_apparent_age_medically(calculate_apparent_age(start = start)))
2356
2357 print ("-------")
2358 start = pydt_now_here().replace(month = 1).replace(day = 23).replace(hour = 12).replace(minute = 11).replace(year = 2011)
2359 print (calculate_apparent_age(start = start))
2360 print (format_apparent_age_medically(calculate_apparent_age(start = start)))
2361
2363 print ("testing function str2pydt_matches")
2364 print ("---------------------------------")
2365
2366 val = None
2367 while val != 'exit':
2368 val = input('Enter date fragment ("exit" quits): ')
2369 matches = str2pydt_matches(str2parse = val)
2370 for match in matches:
2371 print ('label shown :', match['label'])
2372 print ('data attached:', match['data'])
2373 print ("")
2374 print ("---------------")
2375
2376
2391
2393 for idx in range(120):
2394 year = 1993 + idx
2395 tmp, offset = divmod(idx, 4)
2396 if is_leap_year(year):
2397 print (offset+1, '--', year, 'leaps')
2398 else:
2399 print (offset+1, '--', year)
2400
2401
2418
2419
2420
2421 gmI18N.activate_locale()
2422 gmI18N.install_domain('gnumed')
2423
2424 init()
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437 test_is_leap_year()
2438
2439
2440