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