| Home | Trees | Indices | Help |
|
|---|
|
|
1 # Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 Rickard Lindberg, Roger Lindberg
2 #
3 # This file is part of Timeline.
4 #
5 # Timeline is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # Timeline is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with Timeline. If not, see <http://www.gnu.org/licenses/>.
17
18
19 from datetime import datetime
20 import re
21
22 from timelinelib.calendar.gregorian.timetype import GregorianTimeType
23 from timelinelib.calendar.pharaonic.pharaonic import PharaonicDateTime,\
24 julian_day_to_pharaonic_ymd
25 from timelinelib.calendar.pharaonic.monthnames import abbreviated_name_of_month
26 from timelinelib.calendar.pharaonic.time import PharaonicDelta
27 from timelinelib.calendar.pharaonic.time import PharaonicTime
28 from timelinelib.calendar.pharaonic.time import SECONDS_IN_DAY
29 from timelinelib.calendar.pharaonic.weekdaynames import abbreviated_name_of_weekday
30 from timelinelib.calendar.timetype import TimeType
31 from timelinelib.canvas.data import TimeOutOfRangeLeftError
32 from timelinelib.canvas.data import TimeOutOfRangeRightError
33 from timelinelib.canvas.data import TimePeriod
34 from timelinelib.canvas.data import time_period_center
35 from timelinelib.canvas.drawing.interface import Strip
36 from timelinelib.calendar.gregorian.gregorian import gregorian_ymd_to_julian_day
37 from timelinelib.calendar.pharaonic.pharaonic import julian_day_to_pharaonic_ymd
38
39
40 BC = _("BC")
44
46 return isinstance(other, PharaonicTimeType)
47
50
52 match = re.search(r"^(-?\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)$", time_string)
53 if match:
54 year = int(match.group(1))
55 month = int(match.group(2))
56 day = int(match.group(3))
57 hour = int(match.group(4))
58 minute = int(match.group(5))
59 second = int(match.group(6))
60 try:
61 return PharaonicDateTime(year, month, day, hour, minute, second).to_time()
62 except ValueError:
63 raise ValueError("Invalid time, time string = '%s'" % time_string)
64 else:
65 raise ValueError("Time not on correct format = '%s'" % time_string)
66
89
91 """Returns a unicode string describing the time period."""
92 def label_with_time(time):
93 return "%s %s" % (label_without_time(time), time_label(time))
94
95 def label_without_time(time):
96 pharaonic_datetime = PharaonicDateTime.from_time(time)
97 return "%s %s %s" % (
98 pharaonic_datetime.day,
99 abbreviated_name_of_month(pharaonic_datetime.month),
100 format_year(pharaonic_datetime.year)
101 )
102
103 def time_label(time):
104 return "%02d:%02d" % time.get_time_of_day()[:-1]
105 if time_period.is_period():
106 if has_nonzero_time(time_period):
107 label = "%s to %s" % (label_with_time(time_period.start_time),
108 label_with_time(time_period.end_time))
109 else:
110 label = "%s to %s" % (label_without_time(time_period.start_time),
111 label_without_time(time_period.end_time))
112 else:
113 if has_nonzero_time(time_period):
114 label = "%s" % label_with_time(time_period.start_time)
115 else:
116 label = "%s" % label_without_time(time_period.start_time)
117 return label
118
120 days = abs(delta.get_days())
121 seconds = abs(delta.seconds) - days * SECONDS_IN_DAY
122 delta_format = (YEARS, DAYS, HOURS, MINUTES, SECONDS)
123 return DurationFormatter([days, seconds]).format(delta_format)
124
127
129 return PharaonicTime(5369833, 0)
130
132 """
133 Return a tuple (major_strip, minor_strip) for current time period and
134 window size.
135 """
136 day_period = TimePeriod(PharaonicTime(0, 0),PharaonicTime(1, 0))
137 one_day_width = metrics.calc_exact_width(day_period)
138 if one_day_width > 20000:
139 return StripHour(), StripMinute()
140 elif one_day_width > 600:
141 return StripDay(), StripHour()
142 elif one_day_width > 45:
143 return StripWeek(appearance), StripWeekday()
144 elif one_day_width > 25:
145 return StripMonth(), StripDay()
146 elif one_day_width > 1.5:
147 return StripYear(), StripMonth()
148 elif one_day_width > 0.12:
149 return StripDecade(), StripYear()
150 elif one_day_width > 0.012:
151 return StripCentury(), StripDecade()
152 else:
153 return StripCentury(), StripCentury()
154
157
160
163
165 py = datetime.now()
166 julian_day = gregorian_ymd_to_julian_day(py.year, py.month, py.day)
167 year, month, day = julian_day_to_pharaonic_ymd(julian_day)
168 pharaonic = PharaonicDateTime(
169 year,
170 month,
171 day,
172 py.hour,
173 py.minute,
174 py.second
175 )
176 return pharaonic.to_time()
177
180
183
185 return [
186 (_("Day"), move_period_num_days),
187 (_("Week"), move_period_num_weeks),
188 (_("Month"), move_period_num_months),
189 (_("Year"), move_period_num_years),
190 ]
191
194
195 """TO DO: the last day of the month, not week, are considered weekends in the pharaonic calendar. Currently the last two days of the week are considered weekends.
196 """
198 pharaonic_time = PharaonicDateTime.from_time(time)
199 return pharaonic_time.day in (9, 10, 19, 20, 29, 30)
200
202 return time.julian_day % 10
203
205 from timelinelib.calendar.pharaonic.timepicker.datetime import PharaonicDateTimePicker
206 return PharaonicDateTimePicker(parent, *args, **kwargs)
207
209 from timelinelib.calendar.pharaonic.timepicker.period import PharaonicPeriodPicker
210 return PharaonicPeriodPicker(parent, *args, **kwargs)
211
215
220 main_frame.display_time_editor_dialog(
221 PharaonicTimeType(), current_period.mean_time(), navigate_to, _("Go to Date"))
222
226
230
233 if _whole_number_of_years(current_period):
234 _move_page_years(current_period, navigation_fn, direction)
235 elif _whole_number_of_months(current_period):
236 _move_page_months(current_period, navigation_fn, direction)
237 else:
238 navigation_fn(lambda tp: tp.move_delta(direction * current_period.delta()))
239
242 """
243 >>> from timelinelib.test.utils import gregorian_period
244
245 >>> _whole_number_of_years(gregorian_period("13 Jun 287", "12 Jun 288"))
246 True
247
248 >>> _whole_number_of_years(gregorian_period("24 Apr 490", "24 Apr 491"))
249 True
250
251 >>> _whole_number_of_years(gregorian_period("8 Dec 1776", "8 Dec 1777"))
252 False
253
254 >>> _whole_number_of_years(gregorian_period("6 Sep 2013", "11 Sep 2014"))
255 False
256 """
257 return (PharaonicDateTime.from_time(period.start_time).is_first_day_in_year() and
258 PharaonicDateTime.from_time(period.end_time).is_first_day_in_year() and
259 _calculate_year_diff(period) > 0)
260
263 def navigate(tp):
264 year_delta = direction * _calculate_year_diff(curret_period)
265 pharaonic_start = PharaonicDateTime.from_time(curret_period.start_time)
266 pharaonic_end = PharaonicDateTime.from_time(curret_period.end_time)
267 new_start_year = pharaonic_start.year + year_delta
268 new_end_year = pharaonic_end.year + year_delta
269 try:
270 new_start = pharaonic_start.replace(year=new_start_year).to_time()
271 new_end = pharaonic_end.replace(year=new_end_year).to_time()
272 if new_end > PharaonicTimeType().get_max_time():
273 raise ValueError()
274 if new_start < PharaonicTimeType().get_min_time():
275 raise ValueError()
276 except ValueError:
277 if direction < 0:
278 raise TimeOutOfRangeLeftError()
279 else:
280 raise TimeOutOfRangeRightError()
281 return tp.update(new_start, new_end)
282 navigation_fn(navigate)
283
286 return (PharaonicDateTime.from_time(period.end_time).year -
287 PharaonicDateTime.from_time(period.start_time).year)
288
291
292 """
293 >>> from timelinelib.test.utils import gregorian_period
294
295 >>> _whole_number_of_months(gregorian_period("24 May 490", "23 Jun 490"))
296 True
297
298 >>> _whole_number_of_months(gregorian_period("16 Aug 1", "21 Aug 1 "))
299 True
300
301 >>> _whole_number_of_months(gregorian_period("2 Jan 2013", "2 Mar 2014"))
302 False
303
304 >>> _whole_number_of_months(gregorian_period("1 Jan 2013 12:00", "1 Mar 2014"))
305 False
306 """
307 start, end = PharaonicDateTime.from_time(period.start_time), PharaonicDateTime.from_time(period.end_time)
308 start_months = start.year * 13 + start.month
309 end_months = end.year * 13 + end.month
310 month_diff = end_months - start_months
311
312 return (start.is_first_of_month() and
313 end.is_first_of_month() and
314 month_diff > 0)
315
318 def navigate(tp):
319 start = PharaonicDateTime.from_time(curret_period.start_time)
320 end = PharaonicDateTime.from_time(curret_period.end_time)
321 start_months = start.year * 13 + start.month
322 end_months = end.year * 13 + end.month
323 month_diff = end_months - start_months
324 month_delta = month_diff * direction
325 new_start_year, new_start_month = _months_to_year_and_month(start_months + month_delta)
326 new_end_year, new_end_month = _months_to_year_and_month(end_months + month_delta)
327 try:
328 new_start = start.replace(year=new_start_year, month=new_start_month)
329 new_end = end.replace(year=new_end_year, month=new_end_month)
330 start = new_start.to_time()
331 end = new_end.to_time()
332 if end > PharaonicTimeType().get_max_time():
333 raise ValueError()
334 if start < PharaonicTimeType().get_min_time():
335 raise ValueError()
336 except ValueError:
337 if direction < 0:
338 raise TimeOutOfRangeLeftError()
339 else:
340 raise TimeOutOfRangeRightError()
341 return tp.update(start, end)
342 navigation_fn(navigate)
343
346 years = int(months // 13)
347 month = months - years * 13
348 if month == 0:
349 month = 13
350 years -= 1
351 return years, month
352
357
362
375
378 navigate_month_step(current_period, navigation_fn, 1)
379
382 navigate_month_step(current_period, navigation_fn, -1)
383
388
393
396 mean = PharaonicDateTime.from_time(current_period.mean_time())
397 if mean.year > get_millenium_max_year():
398 year = get_millenium_max_year()
399 else:
400 year = max(get_min_year_containing_jan_1(), int(mean.year // 1000) * 1000)
401 start = PharaonicDateTime.from_ymd(year, 1, 1).to_time()
402 end = PharaonicDateTime.from_ymd(year + 1000, 1, 1).to_time()
403 navigation_fn(lambda tp: tp.update(start, end))
404
408
412
415 mean = PharaonicDateTime.from_time(current_period.mean_time())
416 start = PharaonicDateTime.from_ymd(mean.year, mean.month, mean.day).to_time()
417 weekday = PharaonicTimeType().get_day_of_week(start)
418 start = start - PharaonicDelta.from_days(weekday)
419 if not main_frame.week_starts_on_monday():
420 start = start - PharaonicDelta.from_days(1)
421 end = start + PharaonicDelta.from_days(10)
422 navigation_fn(lambda tp: tp.update(start, end))
423
426 def fit(main_frame, current_period, navigation_fn):
427 def navigate(time_period):
428 strip = strip_cls()
429 start = strip.start(current_period.mean_time())
430 end = strip.increment(start)
431 return time_period.update(start, end)
432 navigation_fn(navigate)
433 return fit
434
437
438 """
439 Year Name | Year integer | Decade name
440 ----------+--------------+------------
441 .. | .. |
442 200 BC | -199 | 200s BC (100 years)
443 ----------+--------------+------------
444 199 BC | -198 |
445 ... | ... | 100s BC (100 years)
446 100 BC | -99 |
447 ----------+--------------+------------
448 99 BC | -98 |
449 ... | ... | 0s BC (only 99 years)
450 1 BC | 0 |
451 ----------+--------------+------------
452 1 | 1 |
453 ... | ... | 0s (only 99 years)
454 99 | 99 |
455 ----------+--------------+------------
456 100 | 100 |
457 .. | .. | 100s (100 years)
458 199 | 199 |
459 ----------+--------------+------------
460 200 | 200 | 200s (100 years)
461 .. | .. |
462 """
463
465 if major:
466 pharaonic_time = PharaonicDateTime.from_time(time)
467 return self._format_century(
468 self._century_number(
469 self._century_start_year(pharaonic_time.year)
470 ),
471 pharaonic_time.is_bc()
472 )
473 else:
474 return ""
475
477 return PharaonicDateTime.from_ymd(
478 self._century_start_year(PharaonicDateTime.from_time(time).year),
479 1,
480 1
481 ).to_time()
482
484 pharaonic_time = PharaonicDateTime.from_time(time)
485 return pharaonic_time.replace(
486 year=self._next_century_start_year(pharaonic_time.year)
487 ).to_time()
488
490 if century_start_year > 99:
491 return century_start_year
492 elif century_start_year >= -98:
493 return 0
494 else: # century_start_year < -98:
495 return self._century_number(-century_start_year - 98)
496
499
505
507 if is_bc:
508 return "{century}s {bc}".format(century=century_number, bc=BC)
509 else:
510 return "{century}s".format(century=century_number)
511
521
524
525 """
526 Year Name | Year integer | Decade name
527 ----------+--------------+------------
528 .. | .. |
529 20 BC | -19 | 20s BC (10 years)
530 ----------+--------------+------------
531 19 BC | -18 |
532 18 BC | -17 |
533 17 BC | -16 |
534 16 BC | -15 |
535 15 BC | -14 | 10s BC (10 years)
536 14 BC | -13 |
537 13 BC | -12 |
538 12 BC | -11 |
539 11 BC | -10 |
540 10 BC | -9 |
541 ----------+--------------+------------
542 9 BC | -8 |
543 8 BC | -7 |
544 7 BC | -6 |
545 6 BC | -5 |
546 5 BC | -4 | 0s BC (only 9 years)
547 4 BC | -3 |
548 3 BC | -2 |
549 2 BC | -1 |
550 1 BC | 0 |
551 ----------+--------------+------------
552 1 | 1 |
553 2 | 2 |
554 3 | 3 |
555 4 | 4 |
556 5 | 5 | 0s (only 9 years)
557 6 | 6 |
558 7 | 7 |
559 8 | 8 |
560 9 | 9 |
561 ----------+--------------+------------
562 10 | 10 |
563 11 | 11 |
564 12 | 12 |
565 13 | 13 |
566 14 | 14 |
567 15 | 15 | 10s (10 years)
568 16 | 16 |
569 17 | 17 |
570 18 | 18 |
571 19 | 19 |
572 ----------+--------------+------------
573 20 | 20 | 20s (10 years)
574 .. | .. |
575 """
576
579
581 pharaonic_time = PharaonicDateTime.from_time(time)
582 return self._format_decade(
583 self._decade_number(self._decade_start_year(pharaonic_time.year)),
584 pharaonic_time.is_bc()
585 )
586
588 return PharaonicDateTime.from_ymd(
589 self._decade_start_year(PharaonicDateTime.from_time(time).year),
590 1,
591 1
592 ).to_time()
593
595 pharaonic_time = PharaonicDateTime.from_time(time)
596 return pharaonic_time.replace(
597 year=self._next_decacde_start_year(pharaonic_time.year)
598 ).to_time()
599
601 self.skip_s_in_decade_text = value
602
604 parts = []
605 parts.append("{0}".format(decade_number))
606 if not self.skip_s_in_decade_text:
607 parts.append("s")
608 if is_bc:
609 parts.append(" ")
610 parts.append(BC)
611 return "".join(parts)
612
614 if year > 9:
615 return int(year) - (int(year) % 10)
616 elif year >= 1:
617 return 1
618 elif year >= -8:
619 return -8
620 else: # year < -8
621 return -self._decade_start_year(-year + 1) - 8
622
625
631
639
642
645
647 pharaonic_time = PharaonicDateTime.from_time(time)
648 new_pharaonic = PharaonicDateTime.from_ymd(pharaonic_time.year, 1, 1)
649 return new_pharaonic.to_time()
650
652 pharaonic_time = PharaonicDateTime.from_time(time)
653 return pharaonic_time.replace(year=pharaonic_time.year + 1).to_time()
654
657
659 time = PharaonicDateTime.from_time(time)
660 if major:
661 return "%s %s" % (abbreviated_name_of_month(time.month),
662 format_year(time.year))
663 return abbreviated_name_of_month(time.month)
664
666 pharaonic_time = PharaonicDateTime.from_time(time)
667 return PharaonicDateTime.from_ymd(
668 pharaonic_time.year,
669 pharaonic_time.month,
670 1
671 ).to_time()
672
674 return time + PharaonicDelta.from_days(
675 PharaonicDateTime.from_time(time).days_in_month()
676 )
677
680
682 time = PharaonicDateTime.from_time(time)
683 if major:
684 return "%s %s %s" % (time.day,
685 abbreviated_name_of_month(time.month),
686 format_year(time.year))
687 return str(time.day)
688
690 pharaonic_time = PharaonicDateTime.from_time(time)
691 return PharaonicDateTime.from_ymd(
692 pharaonic_time.year,
693 pharaonic_time.month,
694 pharaonic_time.day
695 ).to_time()
696
699
702
705
709
711 if major:
712 first_weekday = self.start(time)
713 next_first_weekday = self.increment(first_weekday)
714 last_weekday = next_first_weekday - PharaonicDelta.from_days(1)
715 range_string = self._time_range_string(first_weekday, last_weekday)
716 if self.appearance.get_week_start() == "tkyriaka":
717 return (_("Week") + " %s (%s)") % (
718 PharaonicDateTime.from_time(time).week_number,
719 range_string
720 )
721 else:
722 # It is Psabbaton (don't know what to do about week numbers here)
723 return range_string
724 # This strip should never be used as minor
725 return ""
726
728 start = PharaonicDateTime.from_time(start)
729 end = PharaonicDateTime.from_time(end)
730 if start.year == end.year:
731 if start.month == end.month:
732 return "%s-%s %s %s" % (start.day, end.day,
733 abbreviated_name_of_month(start.month),
734 format_year(start.year))
735 return "%s %s-%s %s %s" % (start.day,
736 abbreviated_name_of_month(start.month),
737 end.day,
738 abbreviated_name_of_month(end.month),
739 format_year(start.year))
740 return "%s %s %s-%s %s %s" % (start.day,
741 abbreviated_name_of_month(start.month),
742 format_year(start.year),
743 end.day,
744 abbreviated_name_of_month(end.month),
745 format_year(end.year))
746
748 if self.appearance.get_week_start() == "tkyriaka":
749 days_to_subtract = PharaonicTimeType().get_day_of_week(time)
750 else:
751 # It is Psabbaton.
752 days_to_subtract = (PharaonicTimeType().get_day_of_week(time) + 1) % 7
753 return PharaonicTime(time.julian_day - days_to_subtract, 0)
754
757
760
762 day_of_week = PharaonicTimeType().get_day_of_week(time)
763 if major:
764 time = PharaonicDateTime.from_time(time)
765 return "%s %s %s %s" % (abbreviated_name_of_weekday(day_of_week),
766 time.day,
767 abbreviated_name_of_month(time.month),
768 format_year(time.year))
769 return (abbreviated_name_of_weekday(day_of_week) +
770 " %s" % PharaonicDateTime.from_time(time).day)
771
773 pharaonic_time = PharaonicDateTime.from_time(time)
774 new_pharaonic = PharaonicDateTime.from_ymd(pharaonic_time.year, pharaonic_time.month, pharaonic_time.day)
775 return new_pharaonic.to_time()
776
779
782
785
787 time = PharaonicDateTime.from_time(time)
788 if major:
789 return "%s %s %s: %sh" % (time.day, abbreviated_name_of_month(time.month),
790 format_year(time.year), time.hour)
791 return str(time.hour)
792
794 (hours, _, _) = time.get_time_of_day()
795 return PharaonicTime(time.julian_day, hours * 60 * 60)
796
799
802
804 time = PharaonicDateTime.from_time(time)
805 if major:
806 return "%s %s %s: %s:%s" % (time.day, abbreviated_name_of_month(time.month),
807 format_year(time.year), time.hour, time.minute)
808 return str(time.minute)
809
811 (hours, minutes, _) = time.get_time_of_day()
812 return PharaonicTime(time.julian_day, minutes * 60 + hours * 60 * 60)
813
816
823
826 delta = PharaonicDelta.from_days(1) * num
827 start_time = period.start_time + delta
828 end_time = period.end_time + delta
829 return TimePeriod(start_time, end_time)
830
833 delta = PharaonicDelta.from_days(7) * num
834 start_time = period.start_time + delta
835 end_time = period.end_time + delta
836 return TimePeriod(start_time, end_time)
837
840 def move_time(time):
841 pharaonic_time = PharaonicDateTime.from_time(time)
842 new_month = pharaonic_time.month + num
843 new_year = pharaonic_time.year
844 while new_month < 1:
845 new_month += 13
846 new_year -= 1
847 while new_month > 13:
848 new_month -= 13
849 new_year += 1
850 return pharaonic_time.replace(year=new_year, month=new_month).to_time()
851 try:
852 return TimePeriod(
853 move_time(period.start_time),
854 move_time(period.end_time)
855 )
856 except ValueError:
857 return None
858
861 try:
862 delta = num
863 start_year = PharaonicDateTime.from_time(period.start_time).year
864 end_year = PharaonicDateTime.from_time(period.end_time).year
865 start_time = PharaonicDateTime.from_time(period.start_time).replace(year=start_year + delta)
866 end_time = PharaonicDateTime.from_time(period.end_time).replace(year=end_year + delta)
867 return TimePeriod(start_time.to_time(), end_time.to_time())
868 except ValueError:
869 return None
870
875
878
880 self._name = name
881 self._single_name = single_name
882 self._value_fn = value_fn
883 self._remainder_fn = remainder_fn
884
885 @property
888
889 @property
892
893 @property
896
897 @property
900
901
902 YEARS = DurationType(_('years'), _('year'),
903 lambda ds: ds[0] // 365,
904 lambda ds: (ds[0] % 365, ds[1]))
905 MONTHS = DurationType(_('months'), _('month'),
906 lambda ds: ds[0] // 30,
907 lambda ds: (ds[0] % 30, ds[1]))
908 WEEKS = DurationType(_('weeks'), _('week'),
909 lambda ds: ds[0] // 7,
910 lambda ds: (ds[0] % 7, ds[1]))
911 DAYS = DurationType(_('days'), _('day'),
912 lambda ds: ds[0],
913 lambda ds: (0, ds[1]))
914 HOURS = DurationType(_('hours'), _('hour'),
915 lambda ds: ds[0] * 24 + ds[1] // 3600,
916 lambda ds: (0, ds[1] % 3600))
917 MINUTES = DurationType(_('minutes'), _('minute'),
918 lambda ds: ds[0] * 1440 + ds[1] // 60,
919 lambda ds: (0, ds[1] % 60))
920 SECONDS = DurationType(_('seconds'), _('second'),
921 lambda ds: ds[0] * 86400 + ds[1],
922 lambda ds: (0, 0))
926
930
932 """
933 Return a string describing a time duration. Such a string
934 can look like::
935
936 2 years 1 month 3 weeks
937
938 The argument duration_parts is a tuple where each element
939 describes a duration type like YEARS, WEEKS etc.
940 """
941 values = self._calc_duration_values(self._duration, duration_parts)
942 return self._format_parts(zip(values, duration_parts))
943
945 values = []
946 for duration_part in duration_parts:
947 value = duration_part.value_fn(duration)
948 duration[0], duration[1] = duration_part.remainder_fn(duration)
949 values.append(value)
950 return values
951
953 durations = self._remov_zero_value_parts(duration_parts)
954 return " ". join(self._format_durations_parts(durations))
955
959
961 return [self._format_part(duration_value, duration_type) for
962 duration_value, duration_type in durations]
963
965 if value == 1:
966 heading = duration_type.single_name
967 else:
968 heading = duration_type.name
969 return '%d %s' % (value, heading)
970
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Sat Feb 29 02:55:27 2020 | http://epydoc.sourceforge.net |