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