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