| 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 #===========================================================================
195 # mxDateTime conversions
196 #---------------------------------------------------------------------------
198
199 if isinstance(mxDateTime, pyDT.datetime):
200 return mxDateTime
201
202 if dst_currently_in_effect:
203 tz = cFixedOffsetTimezone (
204 offset = ((time.altzone * -1) / 60),
205 name = str(mxDateTime.gmtoffset()).replace(',', '.')
206 )
207 else:
208 tz = cFixedOffsetTimezone (
209 offset = ((time.timezone * -1) / 60),
210 name = str(mxDateTime.gmtoffset()).replace(',', '.')
211 )
212
213 try:
214 return pyDT.datetime (
215 year = mxDateTime.year,
216 month = mxDateTime.month,
217 day = mxDateTime.day,
218 tzinfo = tz
219 )
220 except:
221 _log.debug (u'error converting mx.DateTime.DateTime to Python: %s-%s-%s %s:%s %s.%s',
222 mxDateTime.year,
223 mxDateTime.month,
224 mxDateTime.day,
225 mxDateTime.hour,
226 mxDateTime.minute,
227 mxDateTime.second,
228 mxDateTime.tz
229 )
230 raise
231 #===========================================================================
233 """Returns NOW @ HERE (IOW, in the local timezone."""
234 return pyDT.datetime.now(gmCurrentLocalTimezone)
235 #---------------------------------------------------------------------------
237 return pyDT.datetime.max.replace(tzinfo = gmCurrentLocalTimezone)
238 #---------------------------------------------------------------------------
240 """Returns NOW @ HERE (IOW, in the local timezone."""
241 return py_dt2wxDate(py_dt = pydt_now_here(), wx = wx)
242 #===========================================================================
243 # wxPython conversions
244 #---------------------------------------------------------------------------
246 if not wxDate.IsValid():
247 raise ValueError (u'invalid wxDate: %s-%s-%s %s:%s %s.%s',
248 wxDate.GetYear(),
249 wxDate.GetMonth(),
250 wxDate.GetDay(),
251 wxDate.GetHour(),
252 wxDate.GetMinute(),
253 wxDate.GetSecond(),
254 wxDate.GetMillisecond()
255 )
256
257 try:
258 return pyDT.datetime (
259 year = wxDate.GetYear(),
260 month = wxDate.GetMonth() + 1,
261 day = wxDate.GetDay(),
262 tzinfo = gmCurrentLocalTimezone
263 )
264 except:
265 _log.debug (u'error converting wxDateTime to Python: %s-%s-%s %s:%s %s.%s',
266 wxDate.GetYear(),
267 wxDate.GetMonth(),
268 wxDate.GetDay(),
269 wxDate.GetHour(),
270 wxDate.GetMinute(),
271 wxDate.GetSecond(),
272 wxDate.GetMillisecond()
273 )
274 raise
275 #---------------------------------------------------------------------------
277 _log.debug(u'setting wx.DateTime from: %s-%s-%s', py_dt.year, py_dt.month, py_dt.day)
278 # Robin Dunn says that for SetYear/*Month/*Day the wx.DateTime MUST already
279 # be valid (by definition) or, put the other way round, you must Set() day,
280 # month, and year at once
281 wxdt = wx.DateTimeFromDMY(py_dt.day, py_dt.month-1, py_dt.year)
282 return wxdt
283 #===========================================================================
284 # interval related
285 #---------------------------------------------------------------------------
287
288 years, days = divmod(interval.days, avg_days_per_gregorian_year)
289 months, days = divmod(days, avg_days_per_gregorian_month)
290 weeks, days = divmod(days, days_per_week)
291 days, secs = divmod((days * avg_seconds_per_day) + interval.seconds, avg_seconds_per_day)
292 hours, secs = divmod(secs, 3600)
293 mins, secs = divmod(secs, 60)
294
295 tmp = u''
296
297 if years > 0:
298 tmp += u'%s%s' % (int(years), _('interval_format_tag::years::y')[-1:])
299
300 if accuracy_wanted < acc_months:
301 return tmp.strip()
302
303 if months > 0:
304 tmp += u' %s%s' % (int(months), _('interval_format_tag::months::m')[-1:])
305
306 if accuracy_wanted < acc_weeks:
307 return tmp.strip()
308
309 if weeks > 0:
310 tmp += u' %s%s' % (int(weeks), _('interval_format_tag::weeks::w')[-1:])
311
312 if accuracy_wanted < acc_days:
313 return tmp.strip()
314
315 if days > 0:
316 tmp += u' %s%s' % (int(days), _('interval_format_tag::days::d')[-1:])
317
318 if accuracy_wanted < acc_hours:
319 return tmp.strip()
320
321 if hours > 0:
322 tmp += u' %s/24' % int(hours)
323
324 if accuracy_wanted < acc_minutes:
325 return tmp.strip()
326
327 if mins > 0:
328 tmp += u' %s/60' % int(mins)
329
330 if accuracy_wanted < acc_seconds:
331 return tmp.strip()
332
333 if secs > 0:
334 tmp += u' %s/60' % int(secs)
335
336 return tmp.strip()
337 #---------------------------------------------------------------------------
339 """Formats an interval.
340
341 This isn't mathematically correct but close enough for display.
342 """
343 # FIXME: i18n for abbrevs
344
345 # more than 1 year ?
346 if interval.days > 363:
347 years, days = divmod(interval.days, 364)
348 leap_days, tmp = divmod(years, 4)
349 months, day = divmod((days + leap_days), 30.33)
350 if int(months) == 0:
351 return "%sy" % int(years)
352 return "%sy %sm" % (int(years), int(months))
353
354 # more than 30 days / 1 month ?
355 if interval.days > 30:
356 months, days = divmod(interval.days, 30.33)
357 weeks, days = divmod(days, 7)
358 if int(weeks + days) == 0:
359 result = '%smo' % int(months)
360 else:
361 result = '%sm' % int(months)
362 if int(weeks) != 0:
363 result += ' %sw' % int(weeks)
364 if int(days) != 0:
365 result += ' %sd' % int(days)
366 return result
367
368 # between 7 and 30 days ?
369 if interval.days > 7:
370 return "%sd" % interval.days
371
372 # between 1 and 7 days ?
373 if interval.days > 0:
374 hours, seconds = divmod(interval.seconds, 3600)
375 if hours == 0:
376 return '%sd' % interval.days
377 return "%sd (%sh)" % (interval.days, int(hours))
378
379 # between 5 hours and 1 day
380 if interval.seconds > (5*3600):
381 return "%sh" % int(interval.seconds // 3600)
382
383 # between 1 and 5 hours
384 if interval.seconds > 3600:
385 hours, seconds = divmod(interval.seconds, 3600)
386 minutes = seconds // 60
387 if minutes == 0:
388 return '%sh' % int(hours)
389 return "%s:%02d" % (int(hours), int(minutes))
390
391 # minutes only
392 if interval.seconds > (5*60):
393 return "0:%02d" % (int(interval.seconds // 60))
394
395 # seconds
396 minutes, seconds = divmod(interval.seconds, 60)
397 if minutes == 0:
398 return '%ss' % int(seconds)
399 if seconds == 0:
400 return '0:%02d' % int(minutes)
401 return "%s.%ss" % (int(minutes), int(seconds))
402 #---------------------------------------------------------------------------
404 """The result of this is a tuple (years, ..., seconds) as one would
405 'expect' a date to look like, that is, simple differences between
406 the fields.
407
408 No need for 100/400 years leap days rule because 2000 WAS a leap year.
409
410 This does not take into account time zones which may
411 shift the result by one day.
412
413 <start> and <end> must by python datetime instances
414 <end> is assumed to be "now" if not given
415 """
416 if end is None:
417 end = pyDT.datetime.now(gmCurrentLocalTimezone)
418
419 if end < start:
420 raise ValueError('calculate_apparent_age(): <end> (%s) before <start> %s' % (end, start))
421
422 if end == start:
423 years = months = days = hours = minutes = seconds = 0
424 return (years, months, days, hours, minutes, seconds)
425
426 # years
427 years = end.year - start.year
428 end = end.replace(year = start.year)
429 if end < start:
430 years = years - 1
431
432 # months
433 if end.month == start.month:
434 months = 0
435 else:
436 months = end.month - start.month
437 if months < 0:
438 months = months + 12
439 if end.day > gregorian_month_length[start.month]:
440 end = end.replace(month = start.month, day = gregorian_month_length[start.month])
441 else:
442 end = end.replace(month = start.month)
443 if end < start:
444 months = months - 1
445
446 # days
447 if end.day == start.day:
448 days = 0
449 else:
450 days = end.day - start.day
451 if days < 0:
452 days = days + gregorian_month_length[start.month]
453 end = end.replace(day = start.day)
454 if end < start:
455 days = days - 1
456
457 # hours
458 if end.hour == start.hour:
459 hours = 0
460 else:
461 hours = end.hour - start.hour
462 if hours < 0:
463 hours = hours + 24
464 end = end.replace(hour = start.hour)
465 if end < start:
466 hours = hours - 1
467
468 # minutes
469 if end.minute == start.minute:
470 minutes = 0
471 else:
472 minutes = end.minute - start.minute
473 if minutes < 0:
474 minutes = minutes + 60
475 end = end.replace(minute = start.minute)
476 if end < start:
477 minutes = minutes - 1
478
479 # seconds
480 if end.second == start.second:
481 seconds = 0
482 else:
483 seconds = end.second - start.second
484 if seconds < 0:
485 seconds = seconds + 60
486 end = end.replace(second = start.second)
487 if end < start:
488 seconds = seconds - 1
489
490 return (years, months, days, hours, minutes, seconds)
491 #---------------------------------------------------------------------------
493 """<age> must be a tuple as created by calculate_apparent_age()"""
494
495 (years, months, days, hours, minutes, seconds) = age
496
497 # at least 1 year ?
498 if years > 0:
499 if months == 0:
500 return u'%s%s' % (
501 years,
502 _('y::year_abbreviation').replace('::year_abbreviation', u'')
503 )
504 return u'%s%s %s%s' % (
505 years,
506 _('y::year_abbreviation').replace('::year_abbreviation', u''),
507 months,
508 _('m::month_abbreviation').replace('::month_abbreviation', u'')
509 )
510
511 # more than 1 month ?
512 if months > 1:
513 if days == 0:
514 return u'%s%s' % (
515 months,
516 _('mo::month_only_abbreviation').replace('::month_only_abbreviation', u'')
517 )
518
519 result = u'%s%s' % (
520 months,
521 _('m::month_abbreviation').replace('::month_abbreviation', u'')
522 )
523
524 weeks, days = divmod(days, 7)
525 if int(weeks) != 0:
526 result += u'%s%s' % (
527 int(weeks),
528 _('w::week_abbreviation').replace('::week_abbreviation', u'')
529 )
530 if int(days) != 0:
531 result += u'%s%s' % (
532 int(days),
533 _('d::day_abbreviation').replace('::day_abbreviation', u'')
534 )
535
536 return result
537
538 # between 7 days and 1 month
539 if days > 7:
540 return u"%s%s" % (
541 days,
542 _('d::day_abbreviation').replace('::day_abbreviation', u'')
543 )
544
545 # between 1 and 7 days ?
546 if days > 0:
547 if hours == 0:
548 return u'%s%s' % (
549 days,
550 _('d::day_abbreviation').replace('::day_abbreviation', u'')
551 )
552 return u'%s%s (%s%s)' % (
553 days,
554 _('d::day_abbreviation').replace('::day_abbreviation', u''),
555 hours,
556 _('h::hour_abbreviation').replace('::hour_abbreviation', u'')
557 )
558
559 # between 5 hours and 1 day
560 if hours > 5:
561 return u'%s%s' % (
562 hours,
563 _('h::hour_abbreviation').replace('::hour_abbreviation', u'')
564 )
565
566 # between 1 and 5 hours
567 if hours > 1:
568 if minutes == 0:
569 return u'%s%s' % (
570 hours,
571 _('h::hour_abbreviation').replace('::hour_abbreviation', u'')
572 )
573 return u'%s:%02d' % (
574 hours,
575 minutes
576 )
577
578 # between 5 and 60 minutes
579 if minutes > 5:
580 return u"0:%02d" % minutes
581
582 # less than 5 minutes
583 if minutes == 0:
584 return u'%s%s' % (
585 seconds,
586 _('s::second_abbreviation').replace('::second_abbreviation', u'')
587 )
588 if seconds == 0:
589 return u"0:%02d" % minutes
590 return "%s.%s%s" % (
591 minutes,
592 seconds,
593 _('s::second_abbreviation').replace('::second_abbreviation', u'')
594 )
595 #---------------------------------------------------------------------------
597
598 unit_keys = {
599 'year': _('yYaA_keys_year'),
600 'month': _('mM_keys_month'),
601 'week': _('wW_keys_week'),
602 'day': _('dD_keys_day'),
603 'hour': _('hH_keys_hour')
604 }
605
606 str_interval = str_interval.strip()
607
608 # "(~)35(yY)" - at age 35 years
609 keys = '|'.join(list(unit_keys['year'].replace('_keys_year', u'')))
610 if regex.match(u'^~*(\s|\t)*\d+(%s)*$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE):
611 return pyDT.timedelta(days = (int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]) * avg_days_per_gregorian_year))
612
613 # "(~)12mM" - at age 12 months
614 keys = '|'.join(list(unit_keys['month'].replace('_keys_month', u'')))
615 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE):
616 years, months = divmod (
617 int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]),
618 12
619 )
620 return pyDT.timedelta(days = ((years * avg_days_per_gregorian_year) + (months * avg_days_per_gregorian_month)))
621
622 # weeks
623 keys = '|'.join(list(unit_keys['week'].replace('_keys_week', u'')))
624 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE):
625 return pyDT.timedelta(weeks = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
626
627 # days
628 keys = '|'.join(list(unit_keys['day'].replace('_keys_day', u'')))
629 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE):
630 return pyDT.timedelta(days = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
631
632 # hours
633 keys = '|'.join(list(unit_keys['hour'].replace('_keys_hour', u'')))
634 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*(%s)+$' % keys, str_interval, flags = regex.LOCALE | regex.UNICODE):
635 return pyDT.timedelta(hours = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
636
637 # x/12 - months
638 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*12$', str_interval, flags = regex.LOCALE | regex.UNICODE):
639 years, months = divmod (
640 int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]),
641 12
642 )
643 return pyDT.timedelta(days = ((years * avg_days_per_gregorian_year) + (months * avg_days_per_gregorian_month)))
644
645 # x/52 - weeks
646 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*52$', str_interval, flags = regex.LOCALE | regex.UNICODE):
647 # return pyDT.timedelta(days = (int(regex.findall('\d+', str_interval)[0]) * days_per_week))
648 return pyDT.timedelta(weeks = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
649
650 # x/7 - days
651 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*7$', str_interval, flags = regex.LOCALE | regex.UNICODE):
652 return pyDT.timedelta(days = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
653
654 # x/24 - hours
655 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*24$', str_interval, flags = regex.LOCALE | regex.UNICODE):
656 return pyDT.timedelta(hours = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
657
658 # x/60 - minutes
659 if regex.match(u'^~*(\s|\t)*\d+(\s|\t)*/(\s|\t)*60$', str_interval, flags = regex.LOCALE | regex.UNICODE):
660 return pyDT.timedelta(minutes = int(regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)[0]))
661
662 # nYnM - years, months
663 keys_year = '|'.join(list(unit_keys['year'].replace('_keys_year', u'')))
664 keys_month = '|'.join(list(unit_keys['month'].replace('_keys_month', u'')))
665 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):
666 parts = regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)
667 years, months = divmod(int(parts[1]), 12)
668 years += int(parts[0])
669 return pyDT.timedelta(days = ((years * avg_days_per_gregorian_year) + (months * avg_days_per_gregorian_month)))
670
671 # nMnW - months, weeks
672 keys_month = '|'.join(list(unit_keys['month'].replace('_keys_month', u'')))
673 keys_week = '|'.join(list(unit_keys['week'].replace('_keys_week', u'')))
674 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):
675 parts = regex.findall(u'\d+', str_interval, flags = regex.LOCALE | regex.UNICODE)
676 months, weeks = divmod(int(parts[1]), 4)
677 months += int(parts[0])
678 return pyDT.timedelta(days = ((months * avg_days_per_gregorian_month) + (weeks * days_per_week)))
679
680 return None
681 #===========================================================================
682 # string -> date parser
683 #---------------------------------------------------------------------------
685 """This matches on single characters.
686
687 Spaces and tabs are discarded.
688
689 Default is 'dmy':
690 d - toDay
691 m - toMorrow Someone please suggest a synonym !
692 y - Yesterday
693
694 This also defines the significance of the order of the characters.
695 """
696 if trigger_chars is None:
697 trigger_chars = _('dmy (single character date triggers)')[:3].lower()
698
699 str2parse = str2parse.strip().lower()
700
701 if len(str2parse) != 1:
702 return []
703
704 if str2parse not in trigger_chars:
705 return []
706
707 now = mxDT.now()
708 enc = gmI18N.get_encoding()
709
710 # today
711 if str2parse == trigger_chars[0]:
712 return [{
713 'data': mxdt2py_dt(now),
714 'label': _('today (%s)') % now.strftime('%A, %Y-%m-%d').decode(enc)
715 }]
716
717 # tomorrow
718 if str2parse == trigger_chars[1]:
719 ts = now + mxDT.RelativeDateTime(days = +1)
720 return [{
721 'data': mxdt2py_dt(ts),
722 'label': _('tomorrow (%s)') % ts.strftime('%A, %Y-%m-%d').decode(enc)
723 }]
724
725 # yesterday
726 if str2parse == trigger_chars[2]:
727 ts = now + mxDT.RelativeDateTime(days = -1)
728 return [{
729 'data': mxdt2py_dt(ts),
730 'label': _('yesterday (%s)') % ts.strftime('%A, %Y-%m-%d').decode(enc)
731 }]
732
733 return []
734 #---------------------------------------------------------------------------
736 """Expand fragments containing a single dot.
737
738 Standard colloquial date format in Germany: day.month.year
739
740 "14."
741 - the 14th of the current month
742 - the 14th of next month
743 "-14."
744 - the 14th of last month
745 """
746 str2parse = str2parse.strip()
747
748 if not str2parse.endswith(u'.'):
749 return []
750
751 str2parse = str2parse[:-1]
752 try:
753 day_val = int(str2parse)
754 except ValueError:
755 return []
756
757 if (day_val < -31) or (day_val > 31) or (day_val == 0):
758 return []
759
760 now = mxDT.now()
761 enc = gmI18N.get_encoding()
762 matches = []
763
764 # day X of last month only
765 if day_val < 0:
766 ts = now + mxDT.RelativeDateTime(day = abs(day_val), months = -1)
767 if abs(day_val) <= gregorian_month_length[ts.month]:
768 matches.append ({
769 'data': mxdt2py_dt(ts),
770 'label': _('%s-%s-%s: a %s last month') % (ts.year, ts.month, ts.day, ts.strftime('%A').decode(enc))
771 })
772
773 # day X of this month
774 if day_val > 0:
775 ts = now + mxDT.RelativeDateTime(day = day_val)
776 if day_val <= gregorian_month_length[ts.month]:
777 matches.append ({
778 'data': mxdt2py_dt(ts),
779 'label': _('%s-%s-%s: a %s this month') % (ts.year, ts.month, ts.day, ts.strftime('%A').decode(enc))
780 })
781
782 # day X of next month
783 if day_val > 0:
784 ts = now + mxDT.RelativeDateTime(day = day_val, months = +1)
785 if day_val <= gregorian_month_length[ts.month]:
786 matches.append ({
787 'data': mxdt2py_dt(ts),
788 'label': _('%s-%s-%s: a %s next month') % (ts.year, ts.month, ts.day, ts.strftime('%A').decode(enc))
789 })
790
791 # day X of last month
792 if day_val > 0:
793 ts = now + mxDT.RelativeDateTime(day = day_val, months = -1)
794 if day_val <= gregorian_month_length[ts.month]:
795 matches.append ({
796 'data': mxdt2py_dt(ts),
797 'label': _('%s-%s-%s: a %s last month') % (ts.year, ts.month, ts.day, ts.strftime('%A').decode(enc))
798 })
799
800 return matches
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 5/1999
819 6/2004
820 """
821 str2parse = str2parse.strip()
822
823 now = mxDT.now()
824
825 # 5/1999
826 if regex.match(r"^\d{1,2}(\s|\t)*/+(\s|\t)*\d{4}$", str2parse, flags = regex.LOCALE | regex.UNICODE):
827 parts = regex.findall(r'\d+', str2parse, flags = regex.LOCALE | regex.UNICODE)
828 ts = now + mxDT.RelativeDateTime(year = int(parts[1]), month = int(parts[0]))
829 return [{
830 'data': mxdt2py_dt(ts),
831 'label': ts.strftime('%Y-%m-%d').decode(enc)
832 }]
833
834 matches = []
835 # 5/
836 if regex.match(r"^\d{1,2}(\s|\t)*/+$", str2parse, flags = regex.LOCALE | regex.UNICODE):
837 val = int(str2parse[:-1].strip())
838
839 # "55/" -> "1955"
840 if val < 100 and val >= 0:
841 matches.append ({
842 'data': None,
843 'label': '%s-' % (val + 1900)
844 })
845
846 # "11/" -> "2011"
847 if val < 26 and val >= 0:
848 matches.append ({
849 'data': None,
850 'label': '%s-' % (val + 2000)
851 })
852
853 # "5/" -> "1995"
854 if val < 10 and val >= 0:
855 matches.append ({
856 'data': None,
857 'label': '%s-' % (val + 1990)
858 })
859
860 if val < 13 and val > 0:
861 # "11/" -> "11/this year"
862 matches.append ({
863 'data': None,
864 'label': '%s-%.2d-' % (now.year, val)
865 })
866 # "11/" -> "11/next year"
867 ts = now + mxDT.RelativeDateTime(years = 1)
868 matches.append ({
869 'data': None,
870 'label': '%s-%.2d-' % (ts.year, val)
871 })
872 # "11/" -> "11/last year"
873 ts = now + mxDT.RelativeDateTime(years = -1)
874 matches.append ({
875 'data': None,
876 'label': '%s-%.2d-' % (ts.year, val)
877 })
878 # "11/" -> "201?-11-"
879 matches.append ({
880 'data': None,
881 'label': '201?-%.2d-' % val
882 })
883 # "11/" -> "200?-11-"
884 matches.append ({
885 'data': None,
886 'label': '200?-%.2d-' % val
887 })
888 # "11/" -> "20??-11-"
889 matches.append ({
890 'data': None,
891 'label': '20??-%.2d-' % val
892 })
893 # "11/" -> "199?-11-"
894 matches.append ({
895 'data': None,
896 'label': '199?-%.2d-' % val
897 })
898 # "11/" -> "198?-11-"
899 matches.append ({
900 'data': None,
901 'label': '198?-%.2d-' % val
902 })
903 # "11/" -> "198?-11-"
904 matches.append ({
905 'data': None,
906 'label': '197?-%.2d-' % val
907 })
908 # "11/" -> "19??-11-"
909 matches.append ({
910 'data': None,
911 'label': '19??-%.2d-' % val
912 })
913
914 return matches
915 #---------------------------------------------------------------------------
917 """This matches on single numbers.
918
919 Spaces or tabs are discarded.
920 """
921 try:
922 val = int(str2parse.strip())
923 except ValueError:
924 return []
925
926 # strftime() returns str but in the localized encoding,
927 # so we may need to decode that to unicode
928 enc = gmI18N.get_encoding()
929 now = mxDT.now()
930
931 matches = []
932
933 # that year
934 if (1850 < val) and (val < 2100):
935 ts = now + mxDT.RelativeDateTime(year = val)
936 matches.append ({
937 'data': mxdt2py_dt(ts),
938 'label': ts.strftime('%Y-%m-%d')
939 })
940
941 # day X of this month
942 if (val > 0) and (val <= gregorian_month_length[now.month]):
943 ts = now + mxDT.RelativeDateTime(day = val)
944 matches.append ({
945 'data': mxdt2py_dt(ts),
946 'label': _('%d. of %s (this month): a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc))
947 })
948
949 # day X of next month
950 ts = now + mxDT.RelativeDateTime(months = 1, day = val)
951 if (val > 0) and (val <= gregorian_month_length[ts.month]):
952 matches.append ({
953 'data': mxdt2py_dt(ts),
954 'label': _('%d. of %s (next month): a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc))
955 })
956
957 # day X of last month
958 ts = now + mxDT.RelativeDateTime(months = -1, day = val)
959 if (val > 0) and (val <= gregorian_month_length[ts.month]):
960 matches.append ({
961 'data': mxdt2py_dt(ts),
962 'label': _('%d. of %s (last month): a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc))
963 })
964
965 # X days from now
966 if (val > 0) and (val <= 400): # more than a year ahead in days ?? nah !
967 ts = now + mxDT.RelativeDateTime(days = val)
968 matches.append ({
969 'data': mxdt2py_dt(ts),
970 'label': _('in %d day(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
971 })
972 if (val < 0) and (val >= -400): # more than a year back in days ?? nah !
973 ts = now - mxDT.RelativeDateTime(days = abs(val))
974 matches.append ({
975 'data': mxdt2py_dt(ts),
976 'label': _('%d day(s) ago: %s') % (abs(val), ts.strftime('%A, %Y-%m-%d').decode(enc))
977 })
978
979 # X weeks from now
980 if (val > 0) and (val <= 50): # pregnancy takes about 40 weeks :-)
981 ts = now + mxDT.RelativeDateTime(weeks = val)
982 matches.append ({
983 'data': mxdt2py_dt(ts),
984 'label': _('in %d week(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
985 })
986 if (val < 0) and (val >= -50): # pregnancy takes about 40 weeks :-)
987 ts = now - mxDT.RelativeDateTime(weeks = abs(val))
988 matches.append ({
989 'data': mxdt2py_dt(ts),
990 'label': _('%d week(s) ago: %s') % (abs(val), ts.strftime('%A, %Y-%m-%d').decode(enc))
991 })
992
993 # month X of ...
994 if (val < 13) and (val > 0):
995 # ... this year
996 ts = now + mxDT.RelativeDateTime(month = val)
997 matches.append ({
998 'data': mxdt2py_dt(ts),
999 'label': _('%s (%s this year)') % (ts.strftime('%Y-%m-%d'), ts.strftime('%B').decode(enc))
1000 })
1001
1002 # ... next year
1003 ts = now + mxDT.RelativeDateTime(years = 1, month = val)
1004 matches.append ({
1005 'data': mxdt2py_dt(ts),
1006 'label': _('%s (%s next year)') % (ts.strftime('%Y-%m-%d'), ts.strftime('%B').decode(enc))
1007 })
1008
1009 # ... last year
1010 ts = now + mxDT.RelativeDateTime(years = -1, month = val)
1011 matches.append ({
1012 'data': mxdt2py_dt(ts),
1013 'label': _('%s (%s last year)') % (ts.strftime('%Y-%m-%d'), ts.strftime('%B').decode(enc))
1014 })
1015
1016 # fragment expansion
1017 matches.append ({
1018 'data': None,
1019 'label': '200?-%s' % val
1020 })
1021 matches.append ({
1022 'data': None,
1023 'label': '199?-%s' % val
1024 })
1025 matches.append ({
1026 'data': None,
1027 'label': '198?-%s' % val
1028 })
1029 matches.append ({
1030 'data': None,
1031 'label': '19??-%s' % val
1032 })
1033
1034 # day X of ...
1035 if (val < 8) and (val > 0):
1036 # ... this week
1037 ts = now + mxDT.RelativeDateTime(weekday = (val-1, 0))
1038 matches.append ({
1039 'data': mxdt2py_dt(ts),
1040 'label': _('%s this week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc))
1041 })
1042
1043 # ... next week
1044 ts = now + mxDT.RelativeDateTime(weeks = +1, weekday = (val-1, 0))
1045 matches.append ({
1046 'data': mxdt2py_dt(ts),
1047 'label': _('%s next week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc))
1048 })
1049
1050 # ... last week
1051 ts = now + mxDT.RelativeDateTime(weeks = -1, weekday = (val-1, 0))
1052 matches.append ({
1053 'data': mxdt2py_dt(ts),
1054 'label': _('%s last week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc))
1055 })
1056
1057 if (val < 100) and (val > 0):
1058 matches.append ({
1059 'data': None,
1060 'label': '%s-' % (1900 + val)
1061 })
1062
1063 if val == 201:
1064 tmp = {
1065 'data': mxdt2py_dt(now),
1066 'label': now.strftime('%Y-%m-%d')
1067 }
1068 matches.append(tmp)
1069 matches.append ({
1070 'data': None,
1071 'label': now.strftime('%Y-%m')
1072 })
1073 matches.append ({
1074 'data': None,
1075 'label': now.strftime('%Y')
1076 })
1077 matches.append ({
1078 'data': None,
1079 'label': '%s-' % (now.year + 1)
1080 })
1081 matches.append ({
1082 'data': None,
1083 'label': '%s-' % (now.year - 1)
1084 })
1085
1086 if val < 200 and val >= 190:
1087 for i in range(10):
1088 matches.append ({
1089 'data': None,
1090 'label': '%s%s-' % (val, i)
1091 })
1092
1093 return matches
1094 #---------------------------------------------------------------------------
1096 """
1097 Default is 'hdwmy':
1098 h - hours
1099 d - days
1100 w - weeks
1101 m - months
1102 y - years
1103
1104 This also defines the significance of the order of the characters.
1105 """
1106 if offset_chars is None:
1107 offset_chars = _('hdwmy (single character date offset triggers)')[:5].lower()
1108
1109 str2parse = str2parse.strip()
1110
1111 # "+/-XXd/w/m/t"
1112 if not regex.match(r"^(\+|-)?(\s|\t)*\d{1,2}(\s|\t)*[%s]$" % offset_chars, str2parse, flags = regex.LOCALE | regex.UNICODE):
1113 return []
1114
1115 # into the past ?
1116 if str2parse.startswith(u'-'):
1117 is_future = False
1118 str2parse = str2parse[1:].strip()
1119 else:
1120 is_future = True
1121 str2parse = str2parse.replace(u'+', u'').strip()
1122
1123 val = int(regex.findall(u'\d{1,2}', str2parse, flags = regex.LOCALE | regex.UNICODE)[0])
1124 offset_char = regex.findall(u'[%s]' % offset_chars, str2parse, flags = regex.LOCALE | regex.UNICODE)[0].lower()
1125
1126 now = mxDT.now()
1127 enc = gmI18N.get_encoding()
1128
1129 ts = None
1130 # hours
1131 if offset_char == offset_chars[0]:
1132 if is_future:
1133 ts = now + mxDT.RelativeDateTime(hours = val)
1134 label = _('in %d hour(s): %s') % (val, ts.strftime('%H:%M'))
1135 else:
1136 ts = now - mxDT.RelativeDateTime(hours = val)
1137 label = _('%d hour(s) ago: %s') % (val, ts.strftime('%H:%M'))
1138 # days
1139 elif offset_char == offset_chars[1]:
1140 if is_future:
1141 ts = now + mxDT.RelativeDateTime(days = val)
1142 label = _('in %d day(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1143 else:
1144 ts = now - mxDT.RelativeDateTime(days = val)
1145 label = _('%d day(s) ago: %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1146 # weeks
1147 elif offset_char == offset_chars[2]:
1148 if is_future:
1149 ts = now + mxDT.RelativeDateTime(weeks = val)
1150 label = _('in %d week(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1151 else:
1152 ts = now - mxDT.RelativeDateTime(weeks = val)
1153 label = _('%d week(s) ago: %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1154 # months
1155 elif offset_char == offset_chars[3]:
1156 if is_future:
1157 ts = now + mxDT.RelativeDateTime(months = val)
1158 label = _('in %d month(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1159 else:
1160 ts = now - mxDT.RelativeDateTime(months = val)
1161 label = _('%d month(s) ago: %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1162 # years
1163 elif offset_char == offset_chars[4]:
1164 if is_future:
1165 ts = now + mxDT.RelativeDateTime(years = val)
1166 label = _('in %d year(s): %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1167 else:
1168 ts = now - mxDT.RelativeDateTime(years = val)
1169 label = _('%d year(s) ago: %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1170
1171 if ts is None:
1172 return []
1173
1174 return [{
1175 'data': mxdt2py_dt(ts),
1176 'label': label
1177 }]
1178 #---------------------------------------------------------------------------
1180 """Turn a string into candidate dates and auto-completions the user is likely to type.
1181
1182 You MUST have called locale.setlocale(locale.LC_ALL, '')
1183 somewhere in your code previously.
1184
1185 @param patterns: list of time.strptime compatible date pattern
1186 @type patterns: list
1187 """
1188 matches = []
1189 matches.extend(__single_dot2py_dt(str2parse))
1190 matches.extend(__numbers_only2py_dt(str2parse))
1191 matches.extend(__single_slash2py_dt(str2parse))
1192 matches.extend(__single_char2py_dt(str2parse))
1193 matches.extend(__explicit_offset2py_dt(str2parse))
1194
1195 # try mxDT parsers
1196 try:
1197 date = mxDT.Parser.DateFromString (
1198 text = str2parse,
1199 formats = ('euro', 'iso', 'us', 'altus', 'altiso', 'lit', 'altlit', 'eurlit')
1200 )
1201 # FIXME: convert to python datetime
1202 matches.append ({
1203 'data': mxdt2py_dt(date),
1204 'label': date.strftime('%Y-%m-%d')
1205 })
1206 except (ValueError, mxDT.RangeError):
1207 pass
1208
1209 # apply explicit patterns
1210 if patterns is None:
1211 patterns = []
1212
1213 patterns.append('%Y.%m.%d')
1214 patterns.append('%Y/%m/%d')
1215 patterns.append('%Y-%m-%d')
1216
1217 for pattern in patterns:
1218 try:
1219 date = pyDT.datetime.strptime(str2parse, pattern)
1220 matches.append ({
1221 'data': mxdt2py_dt(date),
1222 'label': date.strftime('%Y-%m-%d')
1223 })
1224 except AttributeError:
1225 # strptime() only available starting with Python 2.5
1226 break
1227 except OverflowError:
1228 # time.mktime() cannot handle dates older than a platform-dependant limit :-(
1229 continue
1230 except ValueError:
1231 # C-level overflow
1232 continue
1233
1234 return matches
1235 #===========================================================================
1236 # string -> timestamp parser
1237 #---------------------------------------------------------------------------
1239 """
1240 Default is 'hdwm':
1241 h - hours
1242 d - days
1243 w - weeks
1244 m - months
1245 y - years
1246
1247 This also defines the significance of the order of the characters.
1248 """
1249 if offset_chars is None:
1250 offset_chars = _('hdwmy (single character date offset triggers)')[:5].lower()
1251
1252 # "+/-XXd/w/m/t"
1253 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):
1254 return []
1255 val = int(regex.findall(u'\d{1,2}', str2parse, flags = regex.LOCALE | regex.UNICODE)[0])
1256 offset_char = regex.findall(u'[%s]' % offset_chars, str2parse, flags = regex.LOCALE | regex.UNICODE)[0].lower()
1257
1258 now = mxDT.now()
1259 enc = gmI18N.get_encoding()
1260
1261 # allow past ?
1262 is_future = True
1263 if str2parse.find('-') > -1:
1264 is_future = False
1265
1266 ts = None
1267 # hours
1268 if offset_char == offset_chars[0]:
1269 if is_future:
1270 ts = now + mxDT.RelativeDateTime(hours = val)
1271 label = _('in %d hour(s) - %s') % (val, ts.strftime('%H:%M'))
1272 else:
1273 ts = now - mxDT.RelativeDateTime(hours = val)
1274 label = _('%d hour(s) ago - %s') % (val, ts.strftime('%H:%M'))
1275 accuracy = acc_subseconds
1276 # days
1277 elif offset_char == offset_chars[1]:
1278 if is_future:
1279 ts = now + mxDT.RelativeDateTime(days = val)
1280 label = _('in %d day(s) - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1281 else:
1282 ts = now - mxDT.RelativeDateTime(days = val)
1283 label = _('%d day(s) ago - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1284 accuracy = acc_days
1285 # weeks
1286 elif offset_char == offset_chars[2]:
1287 if is_future:
1288 ts = now + mxDT.RelativeDateTime(weeks = val)
1289 label = _('in %d week(s) - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1290 else:
1291 ts = now - mxDT.RelativeDateTime(weeks = val)
1292 label = _('%d week(s) ago - %s)') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1293 accuracy = acc_days
1294 # months
1295 elif offset_char == offset_chars[3]:
1296 if is_future:
1297 ts = now + mxDT.RelativeDateTime(months = val)
1298 label = _('in %d month(s) - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1299 else:
1300 ts = now - mxDT.RelativeDateTime(months = val)
1301 label = _('%d month(s) ago - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1302 accuracy = acc_days
1303 # years
1304 elif offset_char == offset_chars[4]:
1305 if is_future:
1306 ts = now + mxDT.RelativeDateTime(years = val)
1307 label = _('in %d year(s) - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1308 else:
1309 ts = now - mxDT.RelativeDateTime(years = val)
1310 label = _('%d year(s) ago - %s') % (val, ts.strftime('%A, %Y-%m-%d').decode(enc))
1311 accuracy = acc_months
1312
1313 if ts is None:
1314 return []
1315
1316 tmp = {
1317 'data': cFuzzyTimestamp(timestamp = ts, accuracy = accuracy),
1318 'label': label
1319 }
1320 return [tmp]
1321 #---------------------------------------------------------------------------
1323 """This matches on single characters.
1324
1325 Spaces and tabs are discarded.
1326
1327 Default is 'ndmy':
1328 n - now
1329 d - toDay
1330 m - toMorrow Someone please suggest a synonym !
1331 y - yesterday
1332
1333 This also defines the significance of the order of the characters.
1334 """
1335 if trigger_chars is None:
1336 trigger_chars = _('ndmy (single character date triggers)')[:4].lower()
1337
1338 if not regex.match(u'^(\s|\t)*[%s]{1}(\s|\t)*$' % trigger_chars, str2parse, flags = regex.LOCALE | regex.UNICODE):
1339 return []
1340 val = str2parse.strip().lower()
1341
1342 now = mxDT.now()
1343 enc = gmI18N.get_encoding()
1344
1345 # FIXME: handle uebermorgen/vorgestern ?
1346
1347 # right now
1348 if val == trigger_chars[0]:
1349 ts = now
1350 return [{
1351 'data': cFuzzyTimestamp (
1352 timestamp = ts,
1353 accuracy = acc_subseconds
1354 ),
1355 'label': _('right now (%s, %s)') % (ts.strftime('%A').decode(enc), ts)
1356 }]
1357
1358 # today
1359 if val == trigger_chars[1]:
1360 return [{
1361 'data': cFuzzyTimestamp (
1362 timestamp = now,
1363 accuracy = acc_days
1364 ),
1365 'label': _('today (%s)') % now.strftime('%A, %Y-%m-%d').decode(enc)
1366 }]
1367
1368 # tomorrow
1369 if val == trigger_chars[2]:
1370 ts = now + mxDT.RelativeDateTime(days = +1)
1371 return [{
1372 'data': cFuzzyTimestamp (
1373 timestamp = ts,
1374 accuracy = acc_days
1375 ),
1376 'label': _('tomorrow (%s)') % ts.strftime('%A, %Y-%m-%d').decode(enc)
1377 }]
1378
1379 # yesterday
1380 if val == trigger_chars[3]:
1381 ts = now + mxDT.RelativeDateTime(days = -1)
1382 return [{
1383 'data': cFuzzyTimestamp (
1384 timestamp = ts,
1385 accuracy = acc_days
1386 ),
1387 'label': _('yesterday (%s)') % ts.strftime('%A, %Y-%m-%d').decode(enc)
1388 }]
1389
1390 return []
1391 #---------------------------------------------------------------------------
1393 """Expand fragments containing a single slash.
1394
1395 "5/"
1396 - 2005/ (2000 - 2025)
1397 - 1995/ (1990 - 1999)
1398 - Mai/current year
1399 - Mai/next year
1400 - Mai/last year
1401 - Mai/200x
1402 - Mai/20xx
1403 - Mai/199x
1404 - Mai/198x
1405 - Mai/197x
1406 - Mai/19xx
1407 """
1408 matches = []
1409 now = mxDT.now()
1410 if regex.match(u"^(\s|\t)*\d{1,2}(\s|\t)*/+(\s|\t)*$", str2parse, flags = regex.LOCALE | regex.UNICODE):
1411 val = int(regex.findall(u'\d+', str2parse, flags = regex.LOCALE | regex.UNICODE)[0])
1412
1413 if val < 100 and val >= 0:
1414 matches.append ({
1415 'data': None,
1416 'label': '%s/' % (val + 1900)
1417 })
1418
1419 if val < 26 and val >= 0:
1420 matches.append ({
1421 'data': None,
1422 'label': '%s/' % (val + 2000)
1423 })
1424
1425 if val < 10 and val >= 0:
1426 matches.append ({
1427 'data': None,
1428 'label': '%s/' % (val + 1990)
1429 })
1430
1431 if val < 13 and val > 0:
1432 matches.append ({
1433 'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_months),
1434 'label': '%.2d/%s' % (val, now.year)
1435 })
1436 ts = now + mxDT.RelativeDateTime(years = 1)
1437 matches.append ({
1438 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_months),
1439 'label': '%.2d/%s' % (val, ts.year)
1440 })
1441 ts = now + mxDT.RelativeDateTime(years = -1)
1442 matches.append ({
1443 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_months),
1444 'label': '%.2d/%s' % (val, ts.year)
1445 })
1446 matches.append ({
1447 'data': None,
1448 'label': '%.2d/200' % val
1449 })
1450 matches.append ({
1451 'data': None,
1452 'label': '%.2d/20' % val
1453 })
1454 matches.append ({
1455 'data': None,
1456 'label': '%.2d/199' % val
1457 })
1458 matches.append ({
1459 'data': None,
1460 'label': '%.2d/198' % val
1461 })
1462 matches.append ({
1463 'data': None,
1464 'label': '%.2d/197' % val
1465 })
1466 matches.append ({
1467 'data': None,
1468 'label': '%.2d/19' % val
1469 })
1470
1471 elif regex.match(u"^(\s|\t)*\d{1,2}(\s|\t)*/+(\s|\t)*\d{4}(\s|\t)*$", str2parse, flags = regex.LOCALE | regex.UNICODE):
1472 parts = regex.findall(u'\d+', str2parse, flags = regex.LOCALE | regex.UNICODE)
1473 fts = cFuzzyTimestamp (
1474 timestamp = mxDT.now() + mxDT.RelativeDateTime(year = int(parts[1]), month = int(parts[0])),
1475 accuracy = acc_months
1476 )
1477 matches.append ({
1478 'data': fts,
1479 'label': fts.format_accurately()
1480 })
1481
1482 return matches
1483 #---------------------------------------------------------------------------
1485 """This matches on single numbers.
1486
1487 Spaces or tabs are discarded.
1488 """
1489 if not regex.match(u"^(\s|\t)*\d{1,4}(\s|\t)*$", str2parse, flags = regex.LOCALE | regex.UNICODE):
1490 return []
1491
1492 # strftime() returns str but in the localized encoding,
1493 # so we may need to decode that to unicode
1494 enc = gmI18N.get_encoding()
1495 now = mxDT.now()
1496 val = int(regex.findall(u'\d{1,4}', str2parse, flags = regex.LOCALE | regex.UNICODE)[0])
1497
1498 matches = []
1499
1500 # that year
1501 if (1850 < val) and (val < 2100):
1502 ts = now + mxDT.RelativeDateTime(year = val)
1503 target_date = cFuzzyTimestamp (
1504 timestamp = ts,
1505 accuracy = acc_years
1506 )
1507 tmp = {
1508 'data': target_date,
1509 'label': '%s' % target_date
1510 }
1511 matches.append(tmp)
1512
1513 # day X of this month
1514 if val <= gregorian_month_length[now.month]:
1515 ts = now + mxDT.RelativeDateTime(day = val)
1516 target_date = cFuzzyTimestamp (
1517 timestamp = ts,
1518 accuracy = acc_days
1519 )
1520 tmp = {
1521 'data': target_date,
1522 'label': _('%d. of %s (this month) - a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc))
1523 }
1524 matches.append(tmp)
1525
1526 # day X of next month
1527 if val <= gregorian_month_length[(now + mxDT.RelativeDateTime(months = 1)).month]:
1528 ts = now + mxDT.RelativeDateTime(months = 1, day = val)
1529 target_date = cFuzzyTimestamp (
1530 timestamp = ts,
1531 accuracy = acc_days
1532 )
1533 tmp = {
1534 'data': target_date,
1535 'label': _('%d. of %s (next month) - a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc))
1536 }
1537 matches.append(tmp)
1538
1539 # day X of last month
1540 if val <= gregorian_month_length[(now + mxDT.RelativeDateTime(months = -1)).month]:
1541 ts = now + mxDT.RelativeDateTime(months = -1, day = val)
1542 target_date = cFuzzyTimestamp (
1543 timestamp = ts,
1544 accuracy = acc_days
1545 )
1546 tmp = {
1547 'data': target_date,
1548 'label': _('%d. of %s (last month) - a %s') % (val, ts.strftime('%B').decode(enc), ts.strftime('%A').decode(enc))
1549 }
1550 matches.append(tmp)
1551
1552 # X days from now
1553 if val <= 400: # more than a year ahead in days ?? nah !
1554 ts = now + mxDT.RelativeDateTime(days = val)
1555 target_date = cFuzzyTimestamp (
1556 timestamp = ts
1557 )
1558 tmp = {
1559 'data': target_date,
1560 'label': _('in %d day(s) - %s') % (val, target_date.timestamp.strftime('%A, %Y-%m-%d').decode(enc))
1561 }
1562 matches.append(tmp)
1563
1564 # X weeks from now
1565 if val <= 50: # pregnancy takes about 40 weeks :-)
1566 ts = now + mxDT.RelativeDateTime(weeks = val)
1567 target_date = cFuzzyTimestamp (
1568 timestamp = ts
1569 )
1570 tmp = {
1571 'data': target_date,
1572 'label': _('in %d week(s) - %s') % (val, target_date.timestamp.strftime('%A, %Y-%m-%d').decode(enc))
1573 }
1574 matches.append(tmp)
1575
1576 # month X of ...
1577 if val < 13:
1578 # ... this year
1579 ts = now + mxDT.RelativeDateTime(month = val)
1580 target_date = cFuzzyTimestamp (
1581 timestamp = ts,
1582 accuracy = acc_months
1583 )
1584 tmp = {
1585 'data': target_date,
1586 'label': _('%s (%s this year)') % (target_date, ts.strftime('%B').decode(enc))
1587 }
1588 matches.append(tmp)
1589
1590 # ... next year
1591 ts = now + mxDT.RelativeDateTime(years = 1, month = val)
1592 target_date = cFuzzyTimestamp (
1593 timestamp = ts,
1594 accuracy = acc_months
1595 )
1596 tmp = {
1597 'data': target_date,
1598 'label': _('%s (%s next year)') % (target_date, ts.strftime('%B').decode(enc))
1599 }
1600 matches.append(tmp)
1601
1602 # ... last year
1603 ts = now + mxDT.RelativeDateTime(years = -1, month = val)
1604 target_date = cFuzzyTimestamp (
1605 timestamp = ts,
1606 accuracy = acc_months
1607 )
1608 tmp = {
1609 'data': target_date,
1610 'label': _('%s (%s last year)') % (target_date, ts.strftime('%B').decode(enc))
1611 }
1612 matches.append(tmp)
1613
1614 # fragment expansion
1615 matches.append ({
1616 'data': None,
1617 'label': '%s/200' % val
1618 })
1619 matches.append ({
1620 'data': None,
1621 'label': '%s/199' % val
1622 })
1623 matches.append ({
1624 'data': None,
1625 'label': '%s/198' % val
1626 })
1627 matches.append ({
1628 'data': None,
1629 'label': '%s/19' % val
1630 })
1631
1632 # day X of ...
1633 if val < 8:
1634 # ... this week
1635 ts = now + mxDT.RelativeDateTime(weekday = (val-1, 0))
1636 target_date = cFuzzyTimestamp (
1637 timestamp = ts,
1638 accuracy = acc_days
1639 )
1640 tmp = {
1641 'data': target_date,
1642 'label': _('%s this week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc))
1643 }
1644 matches.append(tmp)
1645
1646 # ... next week
1647 ts = now + mxDT.RelativeDateTime(weeks = +1, weekday = (val-1, 0))
1648 target_date = cFuzzyTimestamp (
1649 timestamp = ts,
1650 accuracy = acc_days
1651 )
1652 tmp = {
1653 'data': target_date,
1654 'label': _('%s next week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc))
1655 }
1656 matches.append(tmp)
1657
1658 # ... last week
1659 ts = now + mxDT.RelativeDateTime(weeks = -1, weekday = (val-1, 0))
1660 target_date = cFuzzyTimestamp (
1661 timestamp = ts,
1662 accuracy = acc_days
1663 )
1664 tmp = {
1665 'data': target_date,
1666 'label': _('%s last week (%s of %s)') % (ts.strftime('%A').decode(enc), ts.day, ts.strftime('%B').decode(enc))
1667 }
1668 matches.append(tmp)
1669
1670 if val < 100:
1671 matches.append ({
1672 'data': None,
1673 'label': '%s/' % (1900 + val)
1674 })
1675
1676 if val == 200:
1677 tmp = {
1678 'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_days),
1679 'label': '%s' % target_date
1680 }
1681 matches.append(tmp)
1682 matches.append ({
1683 'data': cFuzzyTimestamp(timestamp = now, accuracy = acc_months),
1684 'label': '%.2d/%s' % (now.month, now.year)
1685 })
1686 matches.append ({
1687 'data': None,
1688 'label': '%s/' % now.year
1689 })
1690 matches.append ({
1691 'data': None,
1692 'label': '%s/' % (now.year + 1)
1693 })
1694 matches.append ({
1695 'data': None,
1696 'label': '%s/' % (now.year - 1)
1697 })
1698
1699 if val < 200 and val >= 190:
1700 for i in range(10):
1701 matches.append ({
1702 'data': None,
1703 'label': '%s%s/' % (val, i)
1704 })
1705
1706 return matches
1707 #---------------------------------------------------------------------------
1709 """Expand fragments containing a single dot.
1710
1711 Standard colloquial date format in Germany: day.month.year
1712
1713 "14."
1714 - 14th current month this year
1715 - 14th next month this year
1716 """
1717 if not regex.match(u"^(\s|\t)*\d{1,2}\.{1}(\s|\t)*$", str2parse, flags = regex.LOCALE | regex.UNICODE):
1718 return []
1719
1720 val = int(regex.findall(u'\d+', str2parse, flags = regex.LOCALE | regex.UNICODE)[0])
1721 now = mxDT.now()
1722 enc = gmI18N.get_encoding()
1723
1724 matches = []
1725
1726 # day X of this month
1727 ts = now + mxDT.RelativeDateTime(day = val)
1728 if val > 0 and val <= gregorian_month_length[ts.month]:
1729 matches.append ({
1730 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_days),
1731 'label': '%s.%s.%s - a %s this month' % (ts.day, ts.month, ts.year, ts.strftime('%A').decode(enc))
1732 })
1733
1734 # day X of next month
1735 ts = now + mxDT.RelativeDateTime(day = val, months = +1)
1736 if val > 0 and val <= gregorian_month_length[ts.month]:
1737 matches.append ({
1738 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_days),
1739 'label': '%s.%s.%s - a %s next month' % (ts.day, ts.month, ts.year, ts.strftime('%A').decode(enc))
1740 })
1741
1742 # day X of last month
1743 ts = now + mxDT.RelativeDateTime(day = val, months = -1)
1744 if val > 0 and val <= gregorian_month_length[ts.month]:
1745 matches.append ({
1746 'data': cFuzzyTimestamp(timestamp = ts, accuracy = acc_days),
1747 'label': '%s.%s.%s - a %s last month' % (ts.day, ts.month, ts.year, ts.strftime('%A').decode(enc))
1748 })
1749
1750 return matches
1751 #---------------------------------------------------------------------------
1753 """
1754 Turn a string into candidate fuzzy timestamps and auto-completions the user is likely to type.
1755
1756 You MUST have called locale.setlocale(locale.LC_ALL, '')
1757 somewhere in your code previously.
1758
1759 @param default_time: if you want to force the time part of the time
1760 stamp to a given value and the user doesn't type any time part
1761 this value will be used
1762 @type default_time: an mx.DateTime.DateTimeDelta instance
1763
1764 @param patterns: list of [time.strptime compatible date/time pattern, accuracy]
1765 @type patterns: list
1766 """
1767 matches = __single_dot(str2parse)
1768 matches.extend(__numbers_only(str2parse))
1769 matches.extend(__single_slash(str2parse))
1770 matches.extend(__single_char(str2parse))
1771 matches.extend(__explicit_offset(str2parse))
1772
1773 # try mxDT parsers
1774 if mxDT is not None:
1775 try:
1776 # date ?
1777 date_only = mxDT.Parser.DateFromString (
1778 text = str2parse,
1779 formats = ('euro', 'iso', 'us', 'altus', 'altiso', 'lit', 'altlit', 'eurlit')
1780 )
1781 # time, too ?
1782 time_part = mxDT.Parser.TimeFromString(text = str2parse)
1783 datetime = date_only + time_part
1784 if datetime == date_only:
1785 accuracy = acc_days
1786 if isinstance(default_time, mxDT.DateTimeDeltaType):
1787 datetime = date_only + default_time
1788 accuracy = acc_minutes
1789 else:
1790 accuracy = acc_subseconds
1791 fts = cFuzzyTimestamp (
1792 timestamp = datetime,
1793 accuracy = accuracy
1794 )
1795 matches.append ({
1796 'data': fts,
1797 'label': fts.format_accurately()
1798 })
1799 except (ValueError, mxDT.RangeError):
1800 pass
1801
1802 if patterns is None:
1803 patterns = []
1804
1805 patterns.append(['%Y.%m.%d', acc_days])
1806 patterns.append(['%Y/%m/%d', acc_days])
1807
1808 for pattern in patterns:
1809 try:
1810 fts = cFuzzyTimestamp (
1811 timestamp = pyDT.datetime.fromtimestamp(time.mktime(time.strptime(str2parse, pattern[0]))),
1812 accuracy = pattern[1]
1813 )
1814 matches.append ({
1815 'data': fts,
1816 'label': fts.format_accurately()
1817 })
1818 except AttributeError:
1819 # strptime() only available starting with Python 2.5
1820 break
1821 except OverflowError:
1822 # time.mktime() cannot handle dates older than a platform-dependant limit :-(
1823 continue
1824 except ValueError:
1825 # C-level overflow
1826 continue
1827
1828 return matches
1829 #===========================================================================
1830 # fuzzy timestamp class
1831 #---------------------------------------------------------------------------
1833
1834 # FIXME: add properties for year, month, ...
1835
1836 """A timestamp implementation with definable inaccuracy.
1837
1838 This class contains an mxDateTime.DateTime instance to
1839 hold the actual timestamp. It adds an accuracy attribute
1840 to allow the programmer to set the precision of the
1841 timestamp.
1842
1843 The timestamp will have to be initialzed with a fully
1844 precise value (which may, of course, contain partially
1845 fake data to make up for missing values). One can then
1846 set the accuracy value to indicate up to which part of
1847 the timestamp the data is valid. Optionally a modifier
1848 can be set to indicate further specification of the
1849 value (such as "summer", "afternoon", etc).
1850
1851 accuracy values:
1852 1: year only
1853 ...
1854 7: everything including milliseconds value
1855
1856 Unfortunately, one cannot directly derive a class from mx.DateTime.DateTime :-(
1857 """
1858 #-----------------------------------------------------------------------
1860
1861 if timestamp is None:
1862 timestamp = mxDT.now()
1863 accuracy = acc_subseconds
1864 modifier = ''
1865
1866 if isinstance(timestamp, pyDT.datetime):
1867 timestamp = mxDT.DateTime(timestamp.year, timestamp.month, timestamp.day, timestamp.hour, timestamp.minute, timestamp.second)
1868
1869 if type(timestamp) != mxDT.DateTimeType:
1870 raise TypeError, '%s.__init__(): <timestamp> must be of mx.DateTime.DateTime or datetime.datetime type' % self.__class__.__name__
1871
1872 self.timestamp = timestamp
1873
1874 if (accuracy < 1) or (accuracy > 8):
1875 raise ValueError, '%s.__init__(): <accuracy> must be between 1 and 7' % self.__class__.__name__
1876 self.accuracy = accuracy
1877
1878 self.modifier = modifier
1879
1880 #-----------------------------------------------------------------------
1881 # magic API
1882 #-----------------------------------------------------------------------
1884 """Return string representation meaningful to a user, also for %s formatting."""
1885 return self.format_accurately()
1886 #-----------------------------------------------------------------------
1888 """Return string meaningful to a programmer to aid in debugging."""
1889 tmp = '<[%s]: timestamp [%s], accuracy [%s] (%s), modifier [%s] at %s>' % (
1890 self.__class__.__name__,
1891 repr(self.timestamp),
1892 self.accuracy,
1893 _accuracy_strings[self.accuracy],
1894 self.modifier,
1895 id(self)
1896 )
1897 return tmp
1898 #-----------------------------------------------------------------------
1899 # external API
1900 #-----------------------------------------------------------------------
1902 if self.accuracy == 7:
1903 return self.timestamp.strftime(format_string)
1904 return self.format_accurately()
1905 #-----------------------------------------------------------------------
1907 return self.strftime(format_string)
1908 #-----------------------------------------------------------------------
1910 if self.accuracy == acc_years:
1911 return unicode(self.timestamp.year)
1912
1913 if self.accuracy == acc_months:
1914 return unicode(self.timestamp.strftime('%m/%Y')) # FIXME: use 3-letter month ?
1915
1916 if self.accuracy == acc_weeks:
1917 return unicode(self.timestamp.strftime('%m/%Y')) # FIXME: use 3-letter month ?
1918
1919 if self.accuracy == acc_days:
1920 return unicode(self.timestamp.strftime('%Y-%m-%d'))
1921
1922 if self.accuracy == acc_hours:
1923 return unicode(self.timestamp.strftime("%Y-%m-%d %I%p"))
1924
1925 if self.accuracy == acc_minutes:
1926 return unicode(self.timestamp.strftime("%Y-%m-%d %H:%M"))
1927
1928 if self.accuracy == acc_seconds:
1929 return unicode(self.timestamp.strftime("%Y-%m-%d %H:%M:%S"))
1930
1931 if self.accuracy == acc_subseconds:
1932 return unicode(self.timestamp)
1933
1934 raise ValueError, '%s.format_accurately(): <accuracy> (%s) must be between 1 and 7' % (
1935 self.__class__.__name__,
1936 self.accuracy
1937 )
1938 #-----------------------------------------------------------------------
1941 #-----------------------------------------------------------------------
1943 try:
1944 gmtoffset = self.timestamp.gmtoffset()
1945 except mxDT.Error:
1946 # Windows cannot deal with dates < 1970, so
1947 # when that happens switch to now()
1948 now = mxDT.now()
1949 gmtoffset = now.gmtoffset()
1950 tz = cFixedOffsetTimezone(gmtoffset.minutes, self.timestamp.tz)
1951 secs, msecs = divmod(self.timestamp.second, 1)
1952 ts = pyDT.datetime (
1953 year = self.timestamp.year,
1954 month = self.timestamp.month,
1955 day = self.timestamp.day,
1956 hour = self.timestamp.hour,
1957 minute = self.timestamp.minute,
1958 second = int(secs),
1959 microsecond = int(msecs * 1000),
1960 tzinfo = tz
1961 )
1962 return ts
1963 #===========================================================================
1964 # main
1965 #---------------------------------------------------------------------------
1966 if __name__ == '__main__':
1967
1968 if len(sys.argv) < 2:
1969 sys.exit()
1970
1971 if sys.argv[1] != "test":
1972 sys.exit()
1973
1974 #-----------------------------------------------------------------------
1975 intervals_as_str = [
1976 '7', '12', ' 12', '12 ', ' 12 ', ' 12 ', '0', '~12', '~ 12', ' ~ 12', ' ~ 12 ',
1977 '12a', '12 a', '12 a', '12j', '12J', '12y', '12Y', ' ~ 12 a ', '~0a',
1978 '12m', '17 m', '12 m', '17M', ' ~ 17 m ', ' ~ 3 / 12 ', '7/12', '0/12',
1979 '12w', '17 w', '12 w', '17W', ' ~ 17 w ', ' ~ 15 / 52', '2/52', '0/52',
1980 '12d', '17 d', '12 t', '17D', ' ~ 17 T ', ' ~ 12 / 7', '3/7', '0/7',
1981 '12h', '17 h', '12 H', '17H', ' ~ 17 h ', ' ~ 36 / 24', '7/24', '0/24',
1982 ' ~ 36 / 60', '7/60', '190/60', '0/60',
1983 '12a1m', '12 a 1 M', '12 a17m', '12j 12m', '12J7m', '12y7m', '12Y7M', ' ~ 12 a 37 m ', '~0a0m',
1984 '10m1w',
1985 'invalid interval input'
1986 ]
1987 #-----------------------------------------------------------------------
1989 for tmp in intervals_as_str:
1990 intv = str2interval(str_interval = tmp)
1991 for acc in _accuracy_strings.keys():
1992 print '[%s]: "%s" -> "%s"' % (acc, tmp, format_interval(intv, acc))
1993 #-----------------------------------------------------------------------
1995
1996 intervals = [
1997 pyDT.timedelta(seconds = 1),
1998 pyDT.timedelta(seconds = 5),
1999 pyDT.timedelta(seconds = 30),
2000 pyDT.timedelta(seconds = 60),
2001 pyDT.timedelta(seconds = 94),
2002 pyDT.timedelta(seconds = 120),
2003 pyDT.timedelta(minutes = 5),
2004 pyDT.timedelta(minutes = 30),
2005 pyDT.timedelta(minutes = 60),
2006 pyDT.timedelta(minutes = 90),
2007 pyDT.timedelta(minutes = 120),
2008 pyDT.timedelta(minutes = 200),
2009 pyDT.timedelta(minutes = 400),
2010 pyDT.timedelta(minutes = 600),
2011 pyDT.timedelta(minutes = 800),
2012 pyDT.timedelta(minutes = 1100),
2013 pyDT.timedelta(minutes = 2000),
2014 pyDT.timedelta(minutes = 3500),
2015 pyDT.timedelta(minutes = 4000),
2016 pyDT.timedelta(hours = 1),
2017 pyDT.timedelta(hours = 2),
2018 pyDT.timedelta(hours = 4),
2019 pyDT.timedelta(hours = 8),
2020 pyDT.timedelta(hours = 12),
2021 pyDT.timedelta(hours = 20),
2022 pyDT.timedelta(hours = 23),
2023 pyDT.timedelta(hours = 24),
2024 pyDT.timedelta(hours = 25),
2025 pyDT.timedelta(hours = 30),
2026 pyDT.timedelta(hours = 48),
2027 pyDT.timedelta(hours = 98),
2028 pyDT.timedelta(hours = 120),
2029 pyDT.timedelta(days = 1),
2030 pyDT.timedelta(days = 2),
2031 pyDT.timedelta(days = 4),
2032 pyDT.timedelta(days = 16),
2033 pyDT.timedelta(days = 29),
2034 pyDT.timedelta(days = 30),
2035 pyDT.timedelta(days = 31),
2036 pyDT.timedelta(days = 37),
2037 pyDT.timedelta(days = 40),
2038 pyDT.timedelta(days = 47),
2039 pyDT.timedelta(days = 126),
2040 pyDT.timedelta(days = 127),
2041 pyDT.timedelta(days = 128),
2042 pyDT.timedelta(days = 300),
2043 pyDT.timedelta(days = 359),
2044 pyDT.timedelta(days = 360),
2045 pyDT.timedelta(days = 361),
2046 pyDT.timedelta(days = 362),
2047 pyDT.timedelta(days = 363),
2048 pyDT.timedelta(days = 364),
2049 pyDT.timedelta(days = 365),
2050 pyDT.timedelta(days = 366),
2051 pyDT.timedelta(days = 367),
2052 pyDT.timedelta(days = 400),
2053 pyDT.timedelta(weeks = 52 * 30),
2054 pyDT.timedelta(weeks = 52 * 79, days = 33)
2055 ]
2056
2057 idx = 1
2058 for intv in intervals:
2059 print '%s) %s -> %s' % (idx, intv, format_interval_medically(intv))
2060 idx += 1
2061 #-----------------------------------------------------------------------
2063 print "testing str2interval()"
2064 print "----------------------"
2065
2066 for interval_as_str in intervals_as_str:
2067 print "input: <%s>" % interval_as_str
2068 print " ==>", str2interval(str_interval=interval_as_str)
2069
2070 return True
2071 #-------------------------------------------------
2073 print "DST currently in effect:", dst_currently_in_effect
2074 print "current UTC offset:", current_local_utc_offset_in_seconds, "seconds"
2075 print "current timezone (interval):", current_local_timezone_interval
2076 print "current timezone (ISO conformant numeric string):", current_local_iso_numeric_timezone_string
2077 print "local timezone class:", cLocalTimezone
2078 print ""
2079 tz = cLocalTimezone()
2080 print "local timezone instance:", tz
2081 print " (total) UTC offset:", tz.utcoffset(pyDT.datetime.now())
2082 print " DST adjustment:", tz.dst(pyDT.datetime.now())
2083 print " timezone name:", tz.tzname(pyDT.datetime.now())
2084 print ""
2085 print "current local timezone:", gmCurrentLocalTimezone
2086 print " (total) UTC offset:", gmCurrentLocalTimezone.utcoffset(pyDT.datetime.now())
2087 print " DST adjustment:", gmCurrentLocalTimezone.dst(pyDT.datetime.now())
2088 print " timezone name:", gmCurrentLocalTimezone.tzname(pyDT.datetime.now())
2089 print ""
2090 print "now here:", pydt_now_here()
2091 print ""
2092 #-------------------------------------------------
2094 print "testing function str2fuzzy_timestamp_matches"
2095 print "--------------------------------------------"
2096
2097 val = None
2098 while val != 'exit':
2099 val = raw_input('Enter date fragment ("exit" quits): ')
2100 matches = str2fuzzy_timestamp_matches(str2parse = val)
2101 for match in matches:
2102 print 'label shown :', match['label']
2103 print 'data attached:', match['data']
2104 print ""
2105 print "---------------"
2106 #-------------------------------------------------
2108 print "testing fuzzy timestamp class"
2109 print "-----------------------------"
2110
2111 ts = mxDT.now()
2112 print "mx.DateTime timestamp", type(ts)
2113 print " print ... :", ts
2114 print " print '%%s' %% ...: %s" % ts
2115 print " str() :", str(ts)
2116 print " repr() :", repr(ts)
2117
2118 fts = cFuzzyTimestamp()
2119 print "\nfuzzy timestamp <%s '%s'>" % ('class', fts.__class__.__name__)
2120 for accuracy in range(1,8):
2121 fts.accuracy = accuracy
2122 print " accuracy : %s (%s)" % (accuracy, _accuracy_strings[accuracy])
2123 print " format_accurately:", fts.format_accurately()
2124 print " strftime() :", fts.strftime('%c')
2125 print " print ... :", fts
2126 print " print '%%s' %% ... : %s" % fts
2127 print " str() :", str(fts)
2128 print " repr() :", repr(fts)
2129 raw_input('press ENTER to continue')
2130 #-------------------------------------------------
2132 print "testing platform for handling dates before 1970"
2133 print "-----------------------------------------------"
2134 ts = mxDT.DateTime(1935, 4, 2)
2135 fts = cFuzzyTimestamp(timestamp=ts)
2136 print "fts :", fts
2137 print "fts.get_pydt():", fts.get_pydt()
2138 #-------------------------------------------------
2140 start = pydt_now_here().replace(year = 1974).replace(month = 10).replace(day = 23)
2141 print calculate_apparent_age(start = start)
2142 print format_apparent_age_medically(calculate_apparent_age(start = start))
2143
2144 start = pydt_now_here().replace(year = 1979).replace(month = 3).replace(day = 13)
2145 print calculate_apparent_age(start = start)
2146 print format_apparent_age_medically(calculate_apparent_age(start = start))
2147
2148 start = pydt_now_here().replace(year = 1979).replace(month = 2, day = 2)
2149 end = pydt_now_here().replace(year = 1979).replace(month = 3).replace(day = 31)
2150 print calculate_apparent_age(start = start, end = end)
2151
2152 start = pydt_now_here().replace(year = 2009).replace(month = 7, day = 21)
2153 print format_apparent_age_medically(calculate_apparent_age(start = start))
2154 #-------------------------------------------------
2156 print "testing function str2pydt_matches"
2157 print "---------------------------------"
2158
2159 val = None
2160 while val != 'exit':
2161 val = raw_input('Enter date fragment ("exit" quits): ')
2162 matches = str2pydt_matches(str2parse = val)
2163 for match in matches:
2164 print 'label shown :', match['label']
2165 print 'data attached:', match['data']
2166 print ""
2167 print "---------------"
2168 #-------------------------------------------------
2169 # GNUmed libs
2170 gmI18N.activate_locale()
2171 gmI18N.install_domain('gnumed')
2172
2173 init()
2174
2175 #test_date_time()
2176 #test_str2fuzzy_timestamp_matches()
2177 test_cFuzzyTimeStamp()
2178 #test_get_pydt()
2179 #test_str2interval()
2180 #test_format_interval()
2181 #test_format_interval_medically()
2182 #test_calculate_apparent_age()
2183 #test_str2pydt()
2184
2185 #===========================================================================
2186
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Tue Apr 12 03:58:38 2011 | http://epydoc.sourceforge.net |