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