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