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