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