| Home | Trees | Indices | Help |
|
|---|
|
|
1 __doc__ = """
2 GNUmed date/time handling.
3
4 This modules provides access to date/time handling
5 and offers an fuzzy timestamp implementation
6
7 It utilizes
8
9 - Python time
10 - Python datetime
11 - mxDateTime
12
13 Note that if you want locale-aware formatting you need to call
14
15 locale.setlocale(locale.LC_ALL, '')
16
17 somewhere before importing this script.
18
19 Note regarding UTC offsets
20 --------------------------
21
22 Looking from Greenwich:
23 WEST (IOW "behind"): negative values
24 EAST (IOW "ahead"): positive values
25
26 This is in compliance with what datetime.tzinfo.utcoffset()
27 does but NOT what time.altzone/time.timezone do !
28
29 This module also implements a class which allows the
30 programmer to define the degree of fuzziness, uncertainty
31 or imprecision of the timestamp contained within.
32
33 This is useful in fields such as medicine where only partial
34 timestamps may be known for certain events.
35 """
36 #===========================================================================
37 __version__ = "$Revision: 1.34 $"
38 __author__ = "K. Hilbert <Karsten.Hilbert@gmx.net>"
39 __license__ = "GPL (details at http://www.gnu.org)"
40
41 # stdlib
42 import sys, datetime as pyDT, time, os, re as regex, locale, logging
43
44
45 # 3rd party
46 import mx.DateTime as mxDT
47 import psycopg2 # this will go once datetime has timezone classes
48
49
50 if __name__ == '__main__':
51 sys.path.insert(0, '../../')
52 from Gnumed.pycommon import gmI18N
53
54
55 _log = logging.getLogger('gm.datetime')
56 _log.info(__version__)
57 _log.info(u'mx.DateTime version: %s', mxDT.__version__)
58
59 dst_locally_in_use = None
60 dst_currently_in_effect = None
61
62 current_local_utc_offset_in_seconds = None
63 current_local_timezone_interval = None
64 current_local_iso_numeric_timezone_string = None
65 current_local_timezone_name = None
66 py_timezone_name = None
67 py_dst_timezone_name = None
68
69 cLocalTimezone = psycopg2.tz.LocalTimezone # remove as soon as datetime supports timezone classes
70 cFixedOffsetTimezone = psycopg2.tz.FixedOffsetTimezone # remove as soon as datetime supports timezone classes
71 gmCurrentLocalTimezone = 'gmCurrentLocalTimezone not initialized'
72
73
74 ( acc_years,
75 acc_months,
76 acc_weeks,
77 acc_days,
78 acc_hours,
79 acc_minutes,
80 acc_seconds,
81 acc_subseconds
82 ) = range(1,9)
83
84 _accuracy_strings = {
85 1: 'years',
86 2: 'months',
87 3: 'weeks',
88 4: 'days',
89 5: 'hours',
90 6: 'minutes',
91 7: 'seconds',
92 8: 'subseconds'
93 }
94
95 gregorian_month_length = {
96 1: 31,
97 2: 28, # FIXME: make leap year aware
98 3: 31,
99 4: 30,
100 5: 31,
101 6: 30,
102 7: 31,
103 8: 31,
104 9: 30,
105 10: 31,
106 11: 30,
107 12: 31
108 }
109
110 avg_days_per_gregorian_year = 365
111 avg_days_per_gregorian_month = 30
112 avg_seconds_per_day = 24 * 60 * 60
113 days_per_week = 7
114
115 #===========================================================================
116 # module init
117 #---------------------------------------------------------------------------
119
120 _log.debug('mx.DateTime.now(): [%s]' % mxDT.now())
121 _log.debug('datetime.now() : [%s]' % pyDT.datetime.now())
122 _log.debug('time.localtime() : [%s]' % str(time.localtime()))
123 _log.debug('time.gmtime() : [%s]' % str(time.gmtime()))
124
125 try:
126 _log.debug('$TZ: [%s]' % os.environ['TZ'])
127 except KeyError:
128 _log.debug('$TZ not defined')
129
130 _log.debug('time.daylight: [%s] (whether or not DST is locally used at all)' % time.daylight)
131 _log.debug('time.timezone: [%s] seconds' % time.timezone)
132 _log.debug('time.altzone : [%s] seconds' % time.altzone)
133 _log.debug('time.tzname : [%s / %s] (non-DST / DST)' % time.tzname)
134 _log.debug('mx.DateTime.now().gmtoffset(): [%s]' % mxDT.now().gmtoffset())
135
136 global py_timezone_name
137 py_timezone_name = time.tzname[0].decode(gmI18N.get_encoding(), 'replace')
138
139 global py_dst_timezone_name
140 py_dst_timezone_name = time.tzname[1].decode(gmI18N.get_encoding(), 'replace')
141
142 global dst_locally_in_use
143 dst_locally_in_use = (time.daylight != 0)
144
145 global dst_currently_in_effect
146 dst_currently_in_effect = bool(time.localtime()[8])
147 _log.debug('DST currently in effect: [%s]' % dst_currently_in_effect)
148
149 if (not dst_locally_in_use) and dst_currently_in_effect:
150 _log.error('system inconsistency: DST not in use - but DST currently in effect ?')
151
152 global current_local_utc_offset_in_seconds
153 msg = 'DST currently%sin effect: using UTC offset of [%s] seconds instead of [%s] seconds'
154 if dst_currently_in_effect:
155 current_local_utc_offset_in_seconds = time.altzone * -1
156 _log.debug(msg % (' ', time.altzone * -1, time.timezone * -1))
157 else:
158 current_local_utc_offset_in_seconds = time.timezone * -1
159 _log.debug(msg % (' not ', time.timezone * -1, time.altzone * -1))
160
161 if current_local_utc_offset_in_seconds > 0:
162 _log.debug('UTC offset is positive, assuming EAST of Greenwich (clock is "ahead")')
163 elif current_local_utc_offset_in_seconds < 0:
164 _log.debug('UTC offset is negative, assuming WEST of Greenwich (clock is "behind")')
165 else:
166 _log.debug('UTC offset is ZERO, assuming Greenwich Time')
167
168 global current_local_timezone_interval
169 current_local_timezone_interval = mxDT.now().gmtoffset()
170 _log.debug('ISO timezone: [%s] (taken from mx.DateTime.now().gmtoffset())' % current_local_timezone_interval)
171
172 global current_local_iso_numeric_timezone_string
173 current_local_iso_numeric_timezone_string = str(current_local_timezone_interval).replace(',', '.')
174
175 global current_local_timezone_name
176 try:
177 current_local_timezone_name = os.environ['TZ'].decode(gmI18N.get_encoding(), 'replace')
178 except KeyError:
179 if dst_currently_in_effect:
180 current_local_timezone_name = time.tzname[1].decode(gmI18N.get_encoding(), 'replace')
181 else:
182 current_local_timezone_name = time.tzname[0].decode(gmI18N.get_encoding(), 'replace')
183
184 # do some magic to convert Python's timezone to a valid ISO timezone
185 # is this safe or will it return things like 13.5 hours ?
186 #_default_client_timezone = "%+.1f" % (-tz / 3600.0)
187 #_log.info('assuming default client time zone of [%s]' % _default_client_timezone)
188
189 global gmCurrentLocalTimezone
190 gmCurrentLocalTimezone = cFixedOffsetTimezone (
191 offset = (current_local_utc_offset_in_seconds / 60),
192 name = current_local_iso_numeric_timezone_string
193 )
194 #===========================================================================
196 """Returns NOW @ HERE (IOW, in the local timezone."""
197 return pyDT.datetime.now(gmCurrentLocalTimezone)
198 #---------------------------------------------------------------------------
200 """Returns NOW @ HERE (IOW, in the local timezone."""
201 return py_dt2wxDate(py_dt = pydt_now_here(), wx = wx)
202 #===========================================================================
203 # wxPython conversions
204 #---------------------------------------------------------------------------
206 if not wxDate.IsValid():
207 raise ArgumentError (u'invalid wxDate: %s-%s-%s %s:%s %s.%s',
208 wxDate.GetYear(),
209 wxDate.GetMonth(),
210 wxDate.GetDay(),
211 wxDate.GetHour(),
212 wxDate.GetMinute(),
213 wxDate.GetSecond(),
214 wxDate.GetMillisecond()
215 )
216
217 try:
218 return pyDT.datetime (
219 year = wxDate.GetYear(),
220 month = wxDate.GetMonth() + 1,
221 day = wxDate.GetDay(),
222 tzinfo = gmCurrentLocalTimezone
223 )
224 except:
225 _log.debug (u'error converting wxDateTime to Python: %s-%s-%s %s:%s %s.%s',
226 wxDate.GetYear(),
227 wxDate.GetMonth(),
228 wxDate.GetDay(),
229 wxDate.GetHour(),
230 wxDate.GetMinute(),
231 wxDate.GetSecond(),
232 wxDate.GetMillisecond()
233 )
234 raise
235 #---------------------------------------------------------------------------
237 _log.debug(u'setting wx.DateTime from: %s-%s-%s', py_dt.year, py_dt.month, py_dt.day)
238 # Robin Dunn says that for SetYear/*Month/*Day the wx.DateTime MUST already
239 # be valid (by definition) or, put the other way round, you must Set() day,
240 # month, and year at once
241 wxdt = wx.DateTimeFromDMY(py_dt.day, py_dt.month-1, py_dt.year)
242 return wxdt
243 #===========================================================================
244 # interval related
245 #---------------------------------------------------------------------------
247
248 years, days = divmod(interval.days, avg_days_per_gregorian_year)
249 months, days = divmod(days, avg_days_per_gregorian_month)
250 weeks, days = divmod(days, days_per_week)
251 days, secs = divmod((days * avg_seconds_per_day) + interval.seconds, avg_seconds_per_day)
252 hours, secs = divmod(secs, 3600)
253 mins, secs = divmod(secs, 60)
254
255 tmp = u''
256
257 if years > 0:
258 tmp += u'%s%s' % (int(years), _('interval_format_tag::years::y')[-1:])
259
260 if accuracy_wanted < acc_months:
261 return tmp.strip()
262
263 if months > 0:
264 tmp += u' %s%s' % (int(months), _('interval_format_tag::months::m')[-1:])
265
266 if accuracy_wanted < acc_weeks:
267 return tmp.strip()
268
269 if weeks > 0:
270 tmp += u' %s%s' % (int(weeks), _('interval_format_tag::weeks::w')[-1:])
271
272 if accuracy_wanted < acc_days:
273 return tmp.strip()
274
275 if days > 0:
276 tmp += u' %s%s' % (int(days), _('interval_format_tag::days::d')[-1:])
277
278 if accuracy_wanted < acc_hours:
279 return tmp.strip()
280
281 if hours > 0:
282 tmp += u' %s/24' % int(hours)
283
284 if accuracy_wanted < acc_minutes:
285 return tmp.strip()
286
287 if mins > 0:
288 tmp += u' %s/60' % int(mins)
289
290 if accuracy_wanted < acc_seconds:
291 return tmp.strip()
292
293 if secs > 0:
294 tmp += u' %s/60' % int(secs)
295
296 return tmp.strip()
297 #---------------------------------------------------------------------------
299 """Formats an interval.
300
301 This isn't mathematically correct but close enough for display.
302 """
303 # FIXME: i18n for abbrevs
304
305 # more than 1 year ?
306 if interval.days > 363:
307 years, days = divmod(interval.days, 364)
308 leap_days, tmp = divmod(years, 4)
309 months, day = divmod((days + leap_days), 30.33)
310 if int(months) == 0:
311 return "%sy" % int(years)
312 return "%sy %sm" % (int(years), int(months))
313
314 # more than 30 days / 1 month ?
315 if interval.days > 30:
316 months, days = divmod(interval.days, 30.33)
317 weeks, days = divmod(days, 7)
318 if int(weeks + days) == 0:
319 result = '%smo' % int(months)
320 else:
321 result = '%sm' % int(months)
322 if int(weeks) != 0:
323 result += ' %sw' % int(weeks)
324 if int(days) != 0:
325 result += ' %sd' % int(days)
326 return result
327
328 # between 7 and 30 days ?
329 if interval.days > 7:
330 return "%sd" % interval.days
331
332 # between 1 and 7 days ?
333 if interval.days > 0:
334 hours, seconds = divmod(interval.seconds, 3600)
335 if hours == 0:
336 return '%sd' % interval.days
337 return "%sd (%sh)" % (interval.days, int(hours))
338
339 # between 5 hours and 1 day
340 if interval.seconds > (5*3600):
341 return "%sh" % int(interval.seconds // 3600)
342
343 # between 1 and 5 hours
344 if interval.seconds > 3600:
345 hours, seconds = divmod(interval.seconds, 3600)
346 minutes = seconds // 60
347 if minutes == 0:
348 return '%sh' % int(hours)
349 return "%s:%02d" % (int(hours), int(minutes))
350
351 # minutes only
352 if interval.seconds > (5*60):
353 return "0:%02d" % (int(interval.seconds // 60))
354
355 # seconds
356 minutes, seconds = divmod(interval.seconds, 60)
357 if minutes == 0:
358 return '%ss' % int(seconds)
359 if seconds == 0:
360 return '0:%02d' % int(minutes)
361 return "%s.%ss" % (int(minutes), int(seconds))
362 #---------------------------------------------------------------------------
364 """The result of this is a tuple (years, ..., seconds) as one would
365 'expect' a date to look like, that is, simple differences between
366 the fields.
367
368 No need for 100/400 years leap days rule because 2000 WAS a leap year.
369
370 This does not take into account time zones which may
371 shift the result by one day.
372
373 <start> and <end> must by python datetime instances
374 <end> is assumed to be "now" if not given
375 """
376 if end is None:
377 end = pyDT.datetime.now(gmCurrentLocalTimezone)
378
379 if end < start:
380 raise ValueError('calculate_apparent_age(): <end> (%s) before <start> %s' % (end, start))
381
382 if end == start:
383 years = months = days = hours = minutes = seconds = 0
384 return (years, months, days, hours, minutes, seconds)
385
386 # years
387 years = end.year - start.year
388 end = end.replace(year = start.year)
389 if end < start:
390 years = years - 1
391
392 # months
393 if end.month == start.month:
394 months = 0
395 else:
396 months = end.month - start.month
397 if months < 0:
398 months = months + 12
399 end = end.replace(month = start.month)
400 if end < start:
401 months = months - 1
402
403 # days
404 if end.day == start.day:
405 days = 0
406 else:
407 days = end.day - start.day
408 if days < 0:
409 days = days + gregorian_month_length[start.month]
410 end = end.replace(day = start.day)
411 if end < start:
412 days = days - 1
413
414 # hours
415 if end.hour == start.hour:
416 hours = 0
417 else:
418 hours = end.hour - start.hour
419 if hours < 0:
420 hours = hours + 24
421 end = end.replace(hour = start.hour)
422 if end < start:
423 hours = hours - 1
424
425 # minutes
426 if end.minute == start.minute:
427 minutes = 0
428 else:
429 minutes = end.minute - start.minute
430 if minutes < 0:
431 minutes = minutes + 60
432 end = end.replace(minute = start.minute)
433 if end < start:
434 minutes = minutes - 1
435
436 # seconds
437 if end.second == start.second:
438 seconds = 0
439 else:
440 seconds = end.second - start.second
441 if seconds < 0:
442 seconds = seconds + 60
443 end = end.replace(second = start.second)
444 if end < start:
445 seconds = seconds - 1
446
447 return (years, months, days, hours, minutes, seconds)
448 #---------------------------------------------------------------------------
450 """<age> must be a tuple as created by calculate_apparent_age()"""
451
452 (years, months, days, hours, minutes, seconds) = age
453
454 # more than 1 year ?
455 if years > 1:
456 if months == 0:
457 return u'%s%s' % (
458 years,
459 _('y::year_abbreviation').replace('::year_abbreviation', u'')
460 )
461 return u'%s%s %s%s' % (
462 years,
463 _('y::year_abbreviation').replace('::year_abbreviation', u''),
464 months,
465 _('m::month_abbreviation').replace('::month_abbreviation', u'')
466 )
467
468 # more than 1 month ?
469 if months > 1:
470 if days == 0:
471 return u'%s%s' % (
472 months,
473 _('mo::month_only_abbreviation').replace('::month_only_abbreviation', u'')
474 )
475
476 result = u'%s%s' % (
477 months,
478 _('m::month_abbreviation').replace('::month_abbreviation', u'')
479 )
480
481 weeks, days = divmod(days, 7)
482 if int(weeks) != 0:
483 result += u'%s%s' % (
484 int(weeks),
485 _('w::week_abbreviation').replace('::week_abbreviation', u'')
486 )
487 if int(days) != 0:
488 result += u'%s%s' % (
489 int(days),
490 _('d::day_abbreviation').replace('::day_abbreviation', u'')
491 )
492
493 return result
494
495 # between 7 days and 1 month
496 if days > 7:
497 return u"%s%s" % (
498 days,
499 _('d::day_abbreviation').replace('::day_abbreviation', u'')
500 )
501
502 # between 1 and 7 days ?
503 if days > 0:
504 if hours == 0:
505 return u'%s%s' % (
506 days,
507 _('d::day_abbreviation').replace('::day_abbreviation', u'')
508 )
509 return u'%s%s (%s%s)' % (
510 days,
511 _('d::day_abbreviation').replace('::day_abbreviation', u''),
512 hours,
513 _('h::hour_abbreviation').replace('::hour_abbreviation', u'')
514 )
515
516 # between 5 hours and 1 day
517 if hours > 5:
518 return u'%s%s' % (
519 hours,
520 _('h::hour_abbreviation').replace('::hour_abbreviation', u'')
521 )
522
523 # between 1 and 5 hours
524 if hours > 1:
525 if minutes == 0:
526 return u'%s%s' % (
527 hours,
528 _('h::hour_abbreviation').replace('::hour_abbreviation', u'')
529 )
530 return u'%s:%02d' % (
531 hours,
532 minutes
533 )
534
535 # between 5 and 60 minutes
536 if minutes > 5:
537 return u"0:%02d" % minutes
538
539 # less than 5 minutes
540 if minutes == 0:
541 return u'%s%s' % (
542 seconds,
543 _('s::second_abbreviation').replace('::second_abbreviation', u'')
544 )
545 if seconds == 0:
546 return u"0:%02d" % minutes
547 return "%s.%s%s" % (
548 minutes,
549 seconds,
550 _('s::second_abbreviation').replace('::second_abbreviation', u'')
551 )
552 #---------------------------------------------------------------------------
554
555 unit_keys = {
556 'year': _('yYaA_keys_year'),
557 'month': _('mM_keys_month'),
558 'week': _('wW_keys_week'),
559 'day': _('dD_keys_day'),
560 'hour': _('hH_keys_hour')
561 }
562
563 str_interval = str_interval.strip()
564
565 # "(~)35(yY)" - at age 35 years
566 keys = '|'.join(list(unit_keys['year'].replace('_keys_year', u'')))
567 if regex.match(u'^~*(\s|\t)*\d+(%s)*$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE):
568 return pyDT.timedelta(days = (int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]) * avg_days_per_gregorian_year))
569
570 # "(~)12mM" - at age 12 months
571 keys = '|'.join(list(unit_keys['month'].replace('_keys_month', u'')))
572 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE):
573 years, months = divmod (
574 int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]),
575 12
576 )
577 return pyDT.timedelta(days = ((years * avg_days_per_gregorian_year) + (months * avg_days_per_gregorian_month)))
578
579 # weeks
580 keys = '|'.join(list(unit_keys['week'].replace('_keys_week', u'')))
581 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE):
582 return pyDT.timedelta(weeks = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
583
584 # days
585 keys = '|'.join(list(unit_keys['day'].replace('_keys_day', u'')))
586 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE):
587 return pyDT.timedelta(days = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
588
589 # hours
590 keys = '|'.join(list(unit_keys['hour'].replace('_keys_hour', u'')))
591 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE):
592 return pyDT.timedelta(hours = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
593
594 # x/12 - months
595 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*12$', str_interval, flags = regex.LOCALE | regex.UNICODE):
596 years, months = divmod (
597 int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]),
598 12
599 )
600 return pyDT.timedelta(days = ((years * avg_days_per_gregorian_year) + (months * avg_days_per_gregorian_month)))
601
602 # x/52 - weeks
603 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*52$', str_interval, flags = regex.LOCALE | regex.UNICODE):
604 # return pyDT.timedelta(days = (int(regex.findall('\d+', str_interval)[0]) * days_per_week))
605 return pyDT.timedelta(weeks = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
606
607 # x/7 - days
608 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*7$', str_interval, flags = regex.LOCALE | regex.UNICODE):
609 return pyDT.timedelta(days = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
610
611 # x/24 - hours
612 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*24$', str_interval, flags = regex.LOCALE | regex.UNICODE):
613 return pyDT.timedelta(hours = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
614
615 # x/60 - minutes
616 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*60$', str_interval, flags = regex.LOCALE | regex.UNICODE):
617 return pyDT.timedelta(minutes = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
618
619 # nYnM - years, months
620 keys_year = '|'.join(list(unit_keys['year'].replace('_keys_year', u'')))
621 keys_month = '|'.join(list(unit_keys['month'].replace('_keys_month', u'')))
622 if regex.match(u'^~*(\s|\t)*\d+(%s|\s|\t)+\d+(\s|\t)*(%s)+$' % (keys_year, keys_month), str_interval, flags = regex.LOCALE | regex.UNICODE):
623 parts = regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)
624 years, months = divmod(int(parts[1]), 12)
625 years += int(parts[0])
626 return pyDT.timedelta(days = ((years * avg_days_per_gregorian_year) + (months * avg_days_per_gregorian_month)))
627
628 # nMnW - months, weeks
629 keys_month = '|'.join(list(unit_keys['month'].replace('_keys_month', u'')))
630 keys_week = '|'.join(list(unit_keys['week'].replace('_keys_week', u'')))
631 if regex.match(u'^~*(\s|\t)*\d+(%s|\s|\t)+\d+(\s|\t)*(%s)+$' % (keys_month, keys_week), str_interval, flags = regex.LOCALE | regex.UNICODE):
632 parts = regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)
633 months, weeks = divmod(int(parts[1]), 4)
634 months += int(parts[0])
635 return pyDT.timedelta(days = ((months * avg_days_per_gregorian_month) + (weeks * days_per_week)))
636
637 return None
638
639 #===========================================================================
640 # string -> timestamp parsers
641 #---------------------------------------------------------------------------
643 """
644 Default is 'hdwm':
645 h - hours
646 d - days
647 w - weeks
648 m - months
649 y - years
650
651 This also defines the significance of the order of the characters.
652 """
653 if offset_chars is None:
654 offset_chars = _('hdwmy (single character date offset triggers)')[:5].lower()
655
656 # "+/-XXd/w/m/t"
657 if not regex.match(u"^(\s|\t)*(\+|-)?(\s|\t)*\d{1,2}(\s|\t)*[%s](\s|\t)*$" % offset_chars, str2parse, flags = regex.LOCALE | regex.UNICODE):
658 return []
659 val = int(regex.findall(u'\d{1,2}', str2parse, flags = regex.LOCALE | regex.UNICODE)[0])
660 offset_char = regex.findall(u'[%s]' % offset_chars, str2parse, flags = regex.LOCALE | regex.UNICODE)[0].lower()
661
662 now = mxDT.now()
663 enc = gmI18N.get_encoding()
664
665 # allow past ?
666 is_future = True
667 if str2parse.find('-') > -1:
668 is_future = False
669
670 ts = None
671 # hours
672 if offset_char == offset_chars[0]:
673 if is_future:
674 ts = now + mxDT.RelativeDateTime(hours = val)
675 label = _('in %d hour(s) - %s') % (val, ts.strftime('%H:%M'))
676 else:
677 ts = now - mxDT.RelativeDateTime(hours = val)
678 label = _('%d hour(s) ago - %s') % (val, ts.strftime('%H:%M'))
679 accuracy = acc_subseconds
680 # days
681 elif offset_char == offset_chars[1]:
682 if is_future:
683 ts = now + mxDT.RelativeDateTime(days = val)
684 label = _('in %d day(s) - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
685 else:
686 ts = now - mxDT.RelativeDateTime(days = val)
687 label = _('%d day(s) ago - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
688 accuracy = acc_days
689 # weeks
690 elif offset_char == offset_chars[2]:
691 if is_future:
692 ts = now + mxDT.RelativeDateTime(weeks = val)
693 label = _('in %d week(s) - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
694 else:
695 ts = now - mxDT.RelativeDateTime(weeks = val)
696 label = _('%d week(s) ago - %s)') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
697 accuracy = acc_days
698 # months
699 elif offset_char == offset_chars[3]:
700 if is_future:
701 ts = now + mxDT.RelativeDateTime(months = val)
702 label = _('in %d month(s) - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
703 else:
704 ts = now - mxDT.RelativeDateTime(months = val)
705 label = _('%d month(s) ago - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
706 accuracy = acc_days
707 # years
708 elif offset_char == offset_chars[4]:
709 if is_future:
710 ts = now + mxDT.RelativeDateTime(years = val)
711 label = _('in %d year(s) - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
712 else:
713 ts = now - mxDT.RelativeDateTime(years = val)
714 label = _('%d year(s) ago - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
715 accuracy = acc_months
716
717 if ts is None:
718 return []
719
720 tmp = {
721 'data': cFuzzyTimestamp(timestamp = ts, accuracy = accuracy),
722 'label': label
723 }
724 return [tmp]
725 #---------------------------------------------------------------------------
727 """This matches on single characters.
728
729 Spaces and tabs are discarded.
730
731 Default is 'ndmy':
732 n - now
733 d - toDay
734 m - toMorrow Someone please suggest a synonym !
735 y - yesterday
736
737 This also defines the significance of the order of the characters.
738 """
739 if trigger_chars is None:
740 trigger_chars = _('ndmy (single character date triggers)')[:4].lower()
741
742 if not regex.match(u'^(\s|\t)*[%s]{1}(\s|\t)*$' % trigger_chars, str2parse, flags = regex.LOCALE | regex.UNICODE):
743 return []
744 val = str2parse.strip().lower()
745
746 now = mxDT.now()
747 enc = gmI18N.get_encoding()
748
749 # FIXME: handle uebermorgen/vorgestern ?
750
751 # right now
752 if val == trigger_chars[0]:
753 ts = now
754 return [{
755 'data': cFuzzyTimestamp (
756 timestamp = ts,
757 accuracy = acc_subseconds
758 ),
759 'label': _('right now (%s, %s)') % (ts.strftime('%A').decode(enc), ts)
760 }]
761
762 # today
763 if val == trigger_chars[1]:
764 return [{
765 'data': cFuzzyTimestamp (
766 timestamp = now,
767 accuracy = acc_days
768 ),
769 'label': _('today (%s)') % now.strftime('%A, %Y-%m-%d').decode(enc)
770 }]
771
772 # tomorrow
773 if val == trigger_chars[2]:
774 ts = now + mxDT.RelativeDateTime(days = +1)
775 return [{
776 'data': cFuzzyTimestamp (
777 timestamp = ts,
778 accuracy = acc_days
779 ),
780 'label': _('tomorrow (%s)') % ts.strftime('%A, %Y-%m-%d').decode(enc)
781 }]
782
783 # yesterday
784 if val == trigger_chars[3]:
785 ts = now + mxDT.RelativeDateTime(days = -1)
786 return [{
787 'data': cFuzzyTimestamp (
788 timestamp = ts,
789 accuracy = acc_days
790 ),
791 'label': _('yesterday (%s)') % ts.strftime('%A, %Y-%m-%d').decode(enc)
792 }]
793
794 return []
795 #---------------------------------------------------------------------------
797 """Expand fragments containing a single slash.
798
799 "5/"
800 - 2005/ (2000 - 2025)
801 - 1995/ (1990 - 1999)
802 - Mai/current year
803 - Mai/next year
804 - Mai/last year
805 - Mai/200x
806 - Mai/20xx
807 - Mai/199x
808 - Mai/198x
809 - Mai/197x
810 - Mai/19xx
811 """
812 matches = []
813 now = mxDT.now()
814 if regex.match(u"^(\s|\t)*\d{1,2}(\s|\t)*/+(\s|\t)*$", str2parse, flags = regex.LOCALE | regex.UNICODE):
815 val = int(regex.findall(u'\d+', str2parse, flags = regex.LOCALE | regex.UNICODE)[0])
816
817 if val < 100 and val >= 0:
818 matches.append ({
819 'data': None,
820 'label': '%s/' % (val + 1900)
821 })
822
823 if val < 26 and val >= 0:
824 matches.append ({
825 'data': None,
826 'label': '%s/' % (val + 2000)
827 })
828
829 if val < 10 and val >= 0:
830 matches.append ({
831 'data': None,
832 'label': '%s/' % (val + 1990)
833 })
834
835 if val < 13 and val > 0:
836 matches.append ({
837 'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_months),
838 'label': '%.2d/%s' % (val, now.year)
839 })
840 ts = now + mxDT.RelativeDateTime(years = 1)
841 matches.append ({
842 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_months),
843 'label': '%.2d/%s' % (val, ts.year)
844 })
845 ts = now + mxDT.RelativeDateTime(years = -1)
846 matches.append ({
847 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_months),
848 'label': '%.2d/%s' % (val, ts.year)
849 })
850 matches.append ({
851 'data': None,
852 'label': '%.2d/200' % val
853 })
854 matches.append ({
855 'data': None,
856 'label': '%.2d/20' % val
857 })
858 matches.append ({
859 'data': None,
860 'label': '%.2d/199' % val
861 })
862 matches.append ({
863 'data': None,
864 'label': '%.2d/198' % val
865 })
866 matches.append ({
867 'data': None,
868 'label': '%.2d/197' % val
869 })
870 matches.append ({
871 'data': None,
872 'label': '%.2d/19' % val
873 })
874
875 elif regex.match(u"^(\s|\t)*\d{1,2}(\s|\t)*/+(\s|\t)*\d{4}(\s|\t)*$", str2parse, flags = regex.LOCALE | regex.UNICODE):
876 parts = regex.findall(u'\d+', str2parse, flags = regex.LOCALE | regex.UNICODE)
877 fts = cFuzzyTimestamp (
878 timestamp = mxDT.now() + mxDT.RelativeDateTime(year = int(parts[1]), month = int(parts[0])),
879 accuracy = acc_months
880 )
881 matches.append ({
882 'data': fts,
883 'label': fts.format_accurately()
884 })
885
886 return matches
887 #---------------------------------------------------------------------------
889 """This matches on single numbers.
890
891 Spaces or tabs are discarded.
892 """
893 if not regex.match(u"^(\s|\t)*\d{1,4}(\s|\t)*$", str2parse, flags = regex.LOCALE | regex.UNICODE):
894 return []
895
896 # strftime() returns str but in the localized encoding,
897 # so we may need to decode that to unicode
898 enc = gmI18N.get_encoding()
899 now = mxDT.now()
900 val = int(regex.findall(u'\d{1,4}', str2parse, flags = regex.LOCALE | regex.UNICODE)[0])
901
902 matches = []
903
904 # that year
905 if (1850 < val) and (val < 2100):
906 ts = now + mxDT.RelativeDateTime(year = val)
907 target_date = cFuzzyTimestamp (
908 timestamp = ts,
909 accuracy = acc_years
910 )
911 tmp = {
912 'data': target_date,
913 'label': '%s' % target_date
914 }
915 matches.append(tmp)
916
917 # day X of this month
918 if val <= gregorian_month_length[now.month]:
919 ts = now + mxDT.RelativeDateTime(day = val)
920 target_date = cFuzzyTimestamp (
921 timestamp = ts,
922 accuracy = acc_days
923 )
924 tmp = {
925 'data': target_date,
926 'label': _('%d. of %s (this month) - a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc))
927 }
928 matches.append(tmp)
929
930 # day X of next month
931 if val <= gregorian_month_length[(now + mxDT.RelativeDateTime(months = 1)).month]:
932 ts = now + mxDT.RelativeDateTime(months = 1, day = val)
933 target_date = cFuzzyTimestamp (
934 timestamp = ts,
935 accuracy = acc_days
936 )
937 tmp = {
938 'data': target_date,
939 'label': _('%d. of %s (next month) - a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc))
940 }
941 matches.append(tmp)
942
943 # day X of last month
944 if val <= gregorian_month_length[(now + mxDT.RelativeDateTime(months = -1)).month]:
945 ts = now + mxDT.RelativeDateTime(months = -1, day = val)
946 target_date = cFuzzyTimestamp (
947 timestamp = ts,
948 accuracy = acc_days
949 )
950 tmp = {
951 'data': target_date,
952 'label': _('%d. of %s (last month) - a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc))
953 }
954 matches.append(tmp)
955
956 # X days from now
957 if val <= 400: # more than a year ahead in days ?? nah !
958 ts = now + mxDT.RelativeDateTime(days = val)
959 target_date = cFuzzyTimestamp (
960 timestamp = ts
961 )
962 tmp = {
963 'data': target_date,
964 'label': _('in %d day(s) - %s') % (val, target_date.timestamp.strftime('%A, %Y-%m-%d').decode(enc))
965 }
966 matches.append(tmp)
967
968 # X weeks from now
969 if val <= 50: # pregnancy takes about 40 weeks :-)
970 ts = now + mxDT.RelativeDateTime(weeks = val)
971 target_date = cFuzzyTimestamp (
972 timestamp = ts
973 )
974 tmp = {
975 'data': target_date,
976 'label': _('in %d week(s) - %s') % (val, target_date.timestamp.strftime('%A, %Y-%m-%d').decode(enc))
977 }
978 matches.append(tmp)
979
980 # month X of ...
981 if val < 13:
982 # ... this year
983 ts = now + mxDT.RelativeDateTime(month = val)
984 target_date = cFuzzyTimestamp (
985 timestamp = ts,
986 accuracy = acc_months
987 )
988 tmp = {
989 'data': target_date,
990 'label': _('%s (%s this year)') % (target_date, ts.strftime('%B').decode(enc))
991 }
992 matches.append(tmp)
993
994 # ... next year
995 ts = now + mxDT.RelativeDateTime(years = 1, month = val)
996 target_date = cFuzzyTimestamp (
997 timestamp = ts,
998 accuracy = acc_months
999 )
1000 tmp = {
1001 'data': target_date,
1002 'label': _('%s (%s next year)') % (target_date, ts.strftime('%B').decode(enc))
1003 }
1004 matches.append(tmp)
1005
1006 # ... last year
1007 ts = now + mxDT.RelativeDateTime(years = -1, month = val)
1008 target_date = cFuzzyTimestamp (
1009 timestamp = ts,
1010 accuracy = acc_months
1011 )
1012 tmp = {
1013 'data': target_date,
1014 'label': _('%s (%s last year)') % (target_date, ts.strftime('%B').decode(enc))
1015 }
1016 matches.append(tmp)
1017
1018 # fragment expansion
1019 matches.append ({
1020 'data': None,
1021 'label': '%s/200' % val
1022 })
1023 matches.append ({
1024 'data': None,
1025 'label': '%s/199' % val
1026 })
1027 matches.append ({
1028 'data': None,
1029 'label': '%s/198' % val
1030 })
1031 matches.append ({
1032 'data': None,
1033 'label': '%s/19' % val
1034 })
1035
1036 # day X of ...
1037 if val < 8:
1038 # ... this week
1039 ts = now + mxDT.RelativeDateTime(weekday = (val-1, 0))
1040 target_date = cFuzzyTimestamp (
1041 timestamp = ts,
1042 accuracy = acc_days
1043 )
1044 tmp = {
1045 'data': target_date,
1046 'label': _('%s this week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc))
1047 }
1048 matches.append(tmp)
1049
1050 # ... next week
1051 ts = now + mxDT.RelativeDateTime(weeks = +1, weekday = (val-1, 0))
1052 target_date = cFuzzyTimestamp (
1053 timestamp = ts,
1054 accuracy = acc_days
1055 )
1056 tmp = {
1057 'data': target_date,
1058 'label': _('%s next week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc))
1059 }
1060 matches.append(tmp)
1061
1062 # ... last week
1063 ts = now + mxDT.RelativeDateTime(weeks = -1, weekday = (val-1, 0))
1064 target_date = cFuzzyTimestamp (
1065 timestamp = ts,
1066 accuracy = acc_days
1067 )
1068 tmp = {
1069 'data': target_date,
1070 'label': _('%s last week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc))
1071 }
1072 matches.append(tmp)
1073
1074 if val < 100:
1075 matches.append ({
1076 'data': None,
1077 'label': '%s/' % (1900 + val)
1078 })
1079
1080 if val == 200:
1081 tmp = {
1082 'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_days),
1083 'label': '%s' % target_date
1084 }
1085 matches.append(tmp)
1086 matches.append ({
1087 'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_months),
1088 'label': '%.2d/%s' % (now.month, now.year)
1089 })
1090 matches.append ({
1091 'data': None,
1092 'label': '%s/' % now.year
1093 })
1094 matches.append ({
1095 'data': None,
1096 'label': '%s/' % (now.year + 1)
1097 })
1098 matches.append ({
1099 'data': None,
1100 'label': '%s/' % (now.year - 1)
1101 })
1102
1103 if val < 200 and val >= 190:
1104 for i in range(10):
1105 matches.append ({
1106 'data': None,
1107 'label': '%s%s/' % (val, i)
1108 })
1109
1110 return matches
1111 #---------------------------------------------------------------------------
1113 """Expand fragments containing a single dot.
1114
1115 Standard colloquial date format in Germany: day.month.year
1116
1117 "14."
1118 - 14th current month this year
1119 - 14th next month this year
1120 """
1121 if not regex.match(u"^(\s|\t)*\d{1,2}\.{1}(\s|\t)*$", str2parse, flags = regex.LOCALE | regex.UNICODE):
1122 return []
1123
1124 val = int(regex.findall(u'\d+', str2parse, flags = regex.LOCALE | regex.UNICODE)[0])
1125 now = mxDT.now()
1126 enc = gmI18N.get_encoding()
1127
1128 matches = []
1129
1130 # day X of this month
1131 ts = now + mxDT.RelativeDateTime(day = val)
1132 if val > 0 and val <= gregorian_month_length[ts.month]:
1133 matches.append ({
1134 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_days),
1135 'label': '%s.%s.%s - a %s this month' % (ts.day, ts.month, ts.year, ts.strftime('%A').decode(enc))
1136 })
1137
1138 # day X of next month
1139 ts = now + mxDT.RelativeDateTime(day = val, months = +1)
1140 if val > 0 and val <= gregorian_month_length[ts.month]:
1141 matches.append ({
1142 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_days),
1143 'label': '%s.%s.%s - a %s next month' % (ts.day, ts.month, ts.year, ts.strftime('%A').decode(enc))
1144 })
1145
1146 # day X of last month
1147 ts = now + mxDT.RelativeDateTime(day = val, months = -1)
1148 if val > 0 and val <= gregorian_month_length[ts.month]:
1149 matches.append ({
1150 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_days),
1151 'label': '%s.%s.%s - a %s last month' % (ts.day, ts.month, ts.year, ts.strftime('%A').decode(enc))
1152 })
1153
1154 return matches
1155 #---------------------------------------------------------------------------
1157 """
1158 Turn a string into candidate fuzzy timestamps and auto-completions the user is likely to type.
1159
1160 You MUST have called locale.setlocale(locale.LC_ALL, '')
1161 somewhere in your code previously.
1162
1163 @param default_time: if you want to force the time part of the time
1164 stamp to a given value and the user doesn't type any time part
1165 this value will be used
1166 @type default_time: an mx.DateTime.DateTimeDelta instance
1167
1168 @param patterns: list of [time.strptime compatible date/time pattern, accuracy]
1169 @type patterns: list
1170 """
1171 matches = __single_dot(str2parse)
1172 matches.extend(__numbers_only(str2parse))
1173 matches.extend(__single_slash(str2parse))
1174 matches.extend(__single_char(str2parse))
1175 matches.extend(__explicit_offset(str2parse))
1176
1177 # try mxDT parsers
1178 if mxDT is not None:
1179 try:
1180 # date ?
1181 date_only = mxDT.Parser.DateFromString (
1182 text = str2parse,
1183 formats = ('euro', 'iso', 'us', 'altus', 'altiso', 'lit', 'altlit', 'eurlit')
1184 )
1185 # time, too ?
1186 time_part = mxDT.Parser.TimeFromString(text = str2parse)
1187 datetime = date_only + time_part
1188 if datetime == date_only:
1189 accuracy = acc_days
1190 if isinstance(default_time, mxDT.DateTimeDeltaType):
1191 datetime = date_only + default_time
1192 accuracy = acc_minutes
1193 else:
1194 accuracy = acc_subseconds
1195 fts = cFuzzyTimestamp (
1196 timestamp = datetime,
1197 accuracy = accuracy
1198 )
1199 matches.append ({
1200 'data': fts,
1201 'label': fts.format_accurately()
1202 })
1203 except (ValueError, mxDT.RangeError):
1204 pass
1205
1206 if patterns is None:
1207 patterns = []
1208
1209 patterns.append(['%Y.%m.%d', acc_days])
1210 patterns.append(['%Y/%m/%d', acc_days])
1211
1212 for pattern in patterns:
1213 try:
1214 fts = cFuzzyTimestamp (
1215 timestamp = pyDT.datetime.fromtimestamp(time.mktime(time.strptime(str2parse, pattern[0]))),
1216 accuracy = pattern[1]
1217 )
1218 matches.append ({
1219 'data': fts,
1220 'label': fts.format_accurately()
1221 })
1222 except AttributeError:
1223 # strptime() only available starting with Python 2.5
1224 break
1225 except OverflowError:
1226 # time.mktime() cannot handle dates older than a platform-dependant limit :-(
1227 continue
1228 except ValueError:
1229 # C-level overflow
1230 continue
1231
1232 return matches
1233 #===========================================================================
1234 # fuzzy timestamp class
1235 #---------------------------------------------------------------------------
1237
1238 # FIXME: add properties for year, month, ...
1239
1240 """A timestamp implementation with definable inaccuracy.
1241
1242 This class contains an mxDateTime.DateTime instance to
1243 hold the actual timestamp. It adds an accuracy attribute
1244 to allow the programmer to set the precision of the
1245 timestamp.
1246
1247 The timestamp will have to be initialzed with a fully
1248 precise value (which may, of course, contain partially
1249 fake data to make up for missing values). One can then
1250 set the accuracy value to indicate up to which part of
1251 the timestamp the data is valid. Optionally a modifier
1252 can be set to indicate further specification of the
1253 value (such as "summer", "afternoon", etc).
1254
1255 accuracy values:
1256 1: year only
1257 ...
1258 7: everything including milliseconds value
1259
1260 Unfortunately, one cannot directly derive a class from mx.DateTime.DateTime :-(
1261 """
1262 #-----------------------------------------------------------------------
1264
1265 if timestamp is None:
1266 timestamp = mxDT.now()
1267 accuracy = acc_subseconds
1268 modifier = ''
1269
1270 if isinstance(timestamp, pyDT.datetime):
1271 timestamp = mxDT.DateTime(timestamp.year, timestamp.month, timestamp.day, timestamp.hour, timestamp.minute, timestamp.second)
1272
1273 if type(timestamp) != mxDT.DateTimeType:
1274 raise TypeError, '%s.__init__(): <timestamp> must be of mx.DateTime.DateTime or datetime.datetime type' % self.__class__.__name__
1275
1276 self.timestamp = timestamp
1277
1278 if (accuracy < 1) or (accuracy > 8):
1279 raise ValueError, '%s.__init__(): <accuracy> must be between 1 and 7' % self.__class__.__name__
1280 self.accuracy = accuracy
1281
1282 self.modifier = modifier
1283
1284 #-----------------------------------------------------------------------
1285 # magic API
1286 #-----------------------------------------------------------------------
1288 """Return string representation meaningful to a user, also for %s formatting."""
1289 return self.format_accurately()
1290 #-----------------------------------------------------------------------
1292 """Return string meaningful to a programmer to aid in debugging."""
1293 tmp = '<[%s]: timestamp [%s], accuracy [%s] (%s), modifier [%s] at %s>' % (
1294 self.__class__.__name__,
1295 repr(self.timestamp),
1296 self.accuracy,
1297 _accuracy_strings[self.accuracy],
1298 self.modifier,
1299 id(self)
1300 )
1301 return tmp
1302 #-----------------------------------------------------------------------
1303 # external API
1304 #-----------------------------------------------------------------------
1306 if self.accuracy == 7:
1307 return self.timestamp.strftime(format_string)
1308 return self.format_accurately()
1309 #-----------------------------------------------------------------------
1311 return self.strftime(format_string)
1312 #-----------------------------------------------------------------------
1314 if self.accuracy == acc_years:
1315 return unicode(self.timestamp.year)
1316
1317 if self.accuracy == acc_months:
1318 return unicode(self.timestamp.strftime('%m/%Y')) # FIXME: use 3-letter month ?
1319
1320 if self.accuracy == acc_days:
1321 return unicode(self.timestamp.strftime('%Y-%m-%d'))
1322
1323 if self.accuracy == acc_hours:
1324 return unicode(self.timestamp.strftime("%Y-%m-%d %I%p"))
1325
1326 if self.accuracy == acc_minutes:
1327 return unicode(self.timestamp.strftime("%Y-%m-%d %H:%M"))
1328
1329 if self.accuracy == acc_seconds:
1330 return unicode(self.timestamp.strftime("%Y-%m-%d %H:%M:%S"))
1331
1332 if self.accuracy == acc_subseconds:
1333 return unicode(self.timestamp)
1334
1335 raise ValueError, '%s.format_accurately(): <accuracy> (%s) must be between 1 and 7' % (
1336 self.__class__.__name__,
1337 self.accuracy
1338 )
1339 #-----------------------------------------------------------------------
1342 #-----------------------------------------------------------------------
1344 try:
1345 gmtoffset = self.timestamp.gmtoffset()
1346 except mxDT.Error:
1347 # Windows cannot deal with dates < 1970, so
1348 # when that happens switch to now()
1349 now = mxDT.now()
1350 gmtoffset = now.gmtoffset()
1351 tz = cFixedOffsetTimezone(gmtoffset.minutes, self.timestamp.tz)
1352 secs, msecs = divmod(self.timestamp.second, 1)
1353 ts = pyDT.datetime (
1354 year = self.timestamp.year,
1355 month = self.timestamp.month,
1356 day = self.timestamp.day,
1357 hour = self.timestamp.hour,
1358 minute = self.timestamp.minute,
1359 second = secs,
1360 microsecond = msecs,
1361 tzinfo = tz
1362 )
1363 return ts
1364 #===========================================================================
1365 # main
1366 #---------------------------------------------------------------------------
1367 if __name__ == '__main__':
1368
1369 intervals_as_str = [
1370 '7', '12', ' 12', '12 ', ' 12 ', ' 12 ', '0', '~12', '~ 12', ' ~ 12', ' ~ 12 ',
1371 '12a', '12 a', '12 a', '12j', '12J', '12y', '12Y', ' ~ 12 a ', '~0a',
1372 '12m', '17 m', '12 m', '17M', ' ~ 17 m ', ' ~ 3 / 12 ', '7/12', '0/12',
1373 '12w', '17 w', '12 w', '17W', ' ~ 17 w ', ' ~ 15 / 52', '2/52', '0/52',
1374 '12d', '17 d', '12 t', '17D', ' ~ 17 T ', ' ~ 12 / 7', '3/7', '0/7',
1375 '12h', '17 h', '12 H', '17H', ' ~ 17 h ', ' ~ 36 / 24', '7/24', '0/24',
1376 ' ~ 36 / 60', '7/60', '190/60', '0/60',
1377 '12a1m', '12 a 1 M', '12 a17m', '12j 12m', '12J7m', '12y7m', '12Y7M', ' ~ 12 a 37 m ', '~0a0m',
1378 '10m1w',
1379 'invalid interval input'
1380 ]
1381 #-----------------------------------------------------------------------
1383 for tmp in intervals_as_str:
1384 intv = str2interval(str_interval = tmp)
1385 for acc in _accuracy_strings.keys():
1386 print '[%s]: "%s" -> "%s"' % (acc, tmp, format_interval(intv, acc))
1387 #-----------------------------------------------------------------------
1389
1390 intervals = [
1391 pyDT.timedelta(seconds = 1),
1392 pyDT.timedelta(seconds = 5),
1393 pyDT.timedelta(seconds = 30),
1394 pyDT.timedelta(seconds = 60),
1395 pyDT.timedelta(seconds = 94),
1396 pyDT.timedelta(seconds = 120),
1397 pyDT.timedelta(minutes = 5),
1398 pyDT.timedelta(minutes = 30),
1399 pyDT.timedelta(minutes = 60),
1400 pyDT.timedelta(minutes = 90),
1401 pyDT.timedelta(minutes = 120),
1402 pyDT.timedelta(minutes = 200),
1403 pyDT.timedelta(minutes = 400),
1404 pyDT.timedelta(minutes = 600),
1405 pyDT.timedelta(minutes = 800),
1406 pyDT.timedelta(minutes = 1100),
1407 pyDT.timedelta(minutes = 2000),
1408 pyDT.timedelta(minutes = 3500),
1409 pyDT.timedelta(minutes = 4000),
1410 pyDT.timedelta(hours = 1),
1411 pyDT.timedelta(hours = 2),
1412 pyDT.timedelta(hours = 4),
1413 pyDT.timedelta(hours = 8),
1414 pyDT.timedelta(hours = 12),
1415 pyDT.timedelta(hours = 20),
1416 pyDT.timedelta(hours = 23),
1417 pyDT.timedelta(hours = 24),
1418 pyDT.timedelta(hours = 25),
1419 pyDT.timedelta(hours = 30),
1420 pyDT.timedelta(hours = 48),
1421 pyDT.timedelta(hours = 98),
1422 pyDT.timedelta(hours = 120),
1423 pyDT.timedelta(days = 1),
1424 pyDT.timedelta(days = 2),
1425 pyDT.timedelta(days = 4),
1426 pyDT.timedelta(days = 16),
1427 pyDT.timedelta(days = 29),
1428 pyDT.timedelta(days = 30),
1429 pyDT.timedelta(days = 31),
1430 pyDT.timedelta(days = 37),
1431 pyDT.timedelta(days = 40),
1432 pyDT.timedelta(days = 47),
1433 pyDT.timedelta(days = 126),
1434 pyDT.timedelta(days = 127),
1435 pyDT.timedelta(days = 128),
1436 pyDT.timedelta(days = 300),
1437 pyDT.timedelta(days = 359),
1438 pyDT.timedelta(days = 360),
1439 pyDT.timedelta(days = 361),
1440 pyDT.timedelta(days = 362),
1441 pyDT.timedelta(days = 363),
1442 pyDT.timedelta(days = 364),
1443 pyDT.timedelta(days = 365),
1444 pyDT.timedelta(days = 366),
1445 pyDT.timedelta(days = 367),
1446 pyDT.timedelta(days = 400),
1447 pyDT.timedelta(weeks = 52 * 30),
1448 pyDT.timedelta(weeks = 52 * 79, days = 33)
1449 ]
1450
1451 idx = 1
1452 for intv in intervals:
1453 print '%s) %s -> %s' % (idx, intv, format_interval_medically(intv))
1454 idx += 1
1455 #-----------------------------------------------------------------------
1457 print "testing str2interval()"
1458 print "----------------------"
1459
1460 for interval_as_str in intervals_as_str:
1461 print "input: <%s>" % interval_as_str
1462 print " ==>", str2interval(str_interval=interval_as_str)
1463
1464 return True
1465 #-------------------------------------------------
1467 print "DST currently in effect:", dst_currently_in_effect
1468 print "current UTC offset:", current_local_utc_offset_in_seconds, "seconds"
1469 print "current timezone (interval):", current_local_timezone_interval
1470 print "current timezone (ISO conformant numeric string):", current_local_iso_numeric_timezone_string
1471 print "local timezone class:", cLocalTimezone
1472 print ""
1473 tz = cLocalTimezone()
1474 print "local timezone instance:", tz
1475 print " (total) UTC offset:", tz.utcoffset(pyDT.datetime.now())
1476 print " DST adjustment:", tz.dst(pyDT.datetime.now())
1477 print " timezone name:", tz.tzname(pyDT.datetime.now())
1478 print ""
1479 print "current local timezone:", gmCurrentLocalTimezone
1480 print " (total) UTC offset:", gmCurrentLocalTimezone.utcoffset(pyDT.datetime.now())
1481 print " DST adjustment:", gmCurrentLocalTimezone.dst(pyDT.datetime.now())
1482 print " timezone name:", gmCurrentLocalTimezone.tzname(pyDT.datetime.now())
1483 print ""
1484 print "now here:", pydt_now_here()
1485 print ""
1486 #-------------------------------------------------
1488 print "testing function str2fuzzy_timestamp_matches"
1489 print "--------------------------------------------"
1490
1491 val = None
1492 while val != 'exit':
1493 val = raw_input('Enter date fragment ("exit" quits): ')
1494 matches = str2fuzzy_timestamp_matches(str2parse = val)
1495 for match in matches:
1496 print 'label shown :', match['label']
1497 print 'data attached:', match['data']
1498 print ""
1499 print "---------------"
1500 #-------------------------------------------------
1502 print "testing fuzzy timestamp class"
1503 print "-----------------------------"
1504
1505 ts = mxDT.now()
1506 print "mx.DateTime timestamp", type(ts)
1507 print " print ... :", ts
1508 print " print '%%s' %% ...: %s" % ts
1509 print " str() :", str(ts)
1510 print " repr() :", repr(ts)
1511
1512 fts = cFuzzyTimestamp()
1513 print "\nfuzzy timestamp <%s '%s'>" % ('class', fts.__class__.__name__)
1514 for accuracy in range(1,8):
1515 fts.accuracy = accuracy
1516 print " accuracy : %s (%s)" % (accuracy, _accuracy_strings[accuracy])
1517 print " format_accurately:", fts.format_accurately()
1518 print " strftime() :", fts.strftime('%c')
1519 print " print ... :", fts
1520 print " print '%%s' %% ... : %s" % fts
1521 print " str() :", str(fts)
1522 print " repr() :", repr(fts)
1523 raw_input('press ENTER to continue')
1524 #-------------------------------------------------
1526 print "testing platform for handling dates before 1970"
1527 print "-----------------------------------------------"
1528 ts = mxDT.DateTime(1935, 4, 2)
1529 fts = cFuzzyTimestamp(timestamp=ts)
1530 print "fts :", fts
1531 print "fts.get_pydt():", fts.get_pydt()
1532 #-------------------------------------------------
1534 start = pydt_now_here().replace(year = 1974).replace(month = 10).replace(day = 23)
1535 print calculate_apparent_age(start = start)
1536 print format_apparent_age_medically(calculate_apparent_age(start = start))
1537 start = pydt_now_here().replace(year = 1979).replace(month = 3).replace(day = 13)
1538 print calculate_apparent_age(start = start)
1539 print format_apparent_age_medically(calculate_apparent_age(start = start))
1540 #-------------------------------------------------
1541 if len(sys.argv) > 1 and sys.argv[1] == "test":
1542
1543 # GNUmed libs
1544 gmI18N.activate_locale()
1545 gmI18N.install_domain('gnumed')
1546
1547 init()
1548
1549 #test_date_time()
1550 #test_str2fuzzy_timestamp_matches()
1551 #test_cFuzzyTimeStamp()
1552 #test_get_pydt()
1553 #test_str2interval()
1554 #test_format_interval()
1555 #test_format_interval_medically()
1556 test_calculate_apparent_age()
1557
1558 #===========================================================================
1559
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Fri Oct 1 04:06:05 2010 | http://epydoc.sourceforge.net |