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