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