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