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