Package Gnumed :: Package timelinelib :: Package db :: Package objects :: Module timeperiod |
[frames] | no frames]

# Source Code for Module Gnumed.timelinelib.db.objects.timeperiod

      1  # Copyright (C) 2009, 2010, 2011  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 -class TimePeriod(object):
     20      """
     21      Represents a period in time using a start and end time.
     22
     23      This is used both to store the time period for an event and for storing the
     24      currently displayed time period in the GUI.
     25      """
     26
     27 -    def __init__(self, time_type, start_time, end_time):
     28          """
     29          Create a time period.
     30
     31          `start_time` and `end_time` should be of a type that can be handled
     32          by the time_type object.
     33          """
     34          self.time_type = time_type
     35          self.start_time, self.end_time = self._update(start_time, end_time)
     36
     37 -    def clone(self):
     38          return TimePeriod(self.time_type, self.start_time, self.end_time)
     39
     40 -    def __eq__(self, other):
     41          if isinstance(other, TimePeriod):
     42              return (self.start_time == other.start_time and
     43                      self.end_time == other.end_time)
     44          return False
     45
     46 -    def __ne__(self, other):
     47          return not (self == other)
     48
     49 -    def __repr__(self):
     50          return "TimePeriod<%s, %s>" % (self.start_time, self.end_time)
     51
     52 -    def update(self, start_time, end_time,
     53                 start_delta=None, end_delta=None):
     54          new_start, new_end = self._update(start_time, end_time, start_delta, end_delta)
     55          return TimePeriod(self.time_type, new_start, new_end)
     56
     57 -    def _update(self, start_time, end_time,
     58                 start_delta=None, end_delta=None):
     59          """
     60          Change the time period data.
     61
     62          Optionally add the deltas to the times like this: time + delta.
     63
     64          If data is invalid, it will not be set, and a ValueError will be raised
     65          instead.
     66
     67          Data is invalid if time + delta is not within the range
     68          [self.time_type.get_min_time(), self.time_type.get_max_time()] or if
     69          the start time is larger than the end time.
     70          """
     71          new_start = self._ensure_within_range(start_time, start_delta,
     72                                                _("Start time "))
     73          new_end = self._ensure_within_range(end_time, end_delta,
     74                                              _("End time "))
     75          self._assert_period_is_valid(new_start, new_end)
     76          return (new_start, new_end)
     77
     78 -    def _assert_period_is_valid(self, new_start, new_end):
     79          self._assert_start_gt_end(new_start, new_end)
     80          self._assert_period_lt_max(new_start, new_end)
     81
     82 -    def _assert_start_gt_end(self, new_start, new_end):
     83          if new_start > new_end:
     84              raise ValueError(_("Start time can't be after end time"))
     85
     86 -    def _assert_period_lt_max(self, new_start, new_end):
     87          MAX_ZOOM_DELTA, max_zoom_error_text = self.time_type.get_max_zoom_delta()
     88          if MAX_ZOOM_DELTA and (new_end - new_start > MAX_ZOOM_DELTA):
     89              raise PeriodTooLongError(max_zoom_error_text)
     90
     91 -    def inside(self, time):
     92          """
     93          Return True if the given time is inside this period or on the border,
     94          otherwise False.
     95          """
     96          return time >= self.start_time and time <= self.end_time
     97
     98 -    def overlap(self, time_period):
     99          """Return True if this time period has any overlap with the given."""
    100          return not (time_period.end_time < self.start_time or
    101                      time_period.start_time > self.end_time)
    102
    103 -    def is_period(self):
    104          """
    105          Return True if this time period is longer than just a point in time,
    106          otherwise False.
    107          """
    108          return self.start_time != self.end_time
    109
    110 -    def mean_time(self):
    111          """
    112          Return the time in the middle if this time period is longer than just a
    113          point in time, otherwise the point in time for this time period.
    114          """
    115          return self.start_time + self.time_type.half_delta(self.delta())
    116
    117 -    def zoom(self, times, ratio=0.5):
    118          MAX_ZOOM_DELTA, max_zoom_error_text = self.time_type.get_max_zoom_delta()
    119          MIN_ZOOM_DELTA, min_zoom_error_text = self.time_type.get_min_zoom_delta()
    120          start_delta = self.time_type.mult_timedelta(self.delta(), times * ratio / 5.0)
    121          end_delta = self.time_type.mult_timedelta(self.delta(), -times * (1.0 - ratio) / 5.0)
    122          new_delta = self.delta() - 2 * start_delta
    123          if MAX_ZOOM_DELTA and new_delta > MAX_ZOOM_DELTA:
    124              raise ValueError(max_zoom_error_text)
    125          if new_delta < MIN_ZOOM_DELTA:
    126              raise ValueError(min_zoom_error_text)
    127          return self.update(self.start_time, self.end_time, start_delta, end_delta)
    128
    129 -    def move(self, direction):
    130          """
    131          Move this time period one 10th to the given direction.
    132
    133          Direction should be -1 for moving to the left or 1 for moving to the
    134          right.
    135          """
    136          delta = self.time_type.mult_timedelta(self.delta(), direction / 10.0)
    137          return self.move_delta(delta)
    138
    139 -    def move_delta(self, delta):
    140          return self.update(self.start_time, self.end_time, delta, delta)
    141
    142 -    def delta(self):
    143          """Return the length of this time period as a timedelta object."""
    144          return self.end_time - self.start_time
    145
    146 -    def center(self, time):
    147          """
    148          Center time period around time keeping the length.
    149
    150          If we can't center because we are on the edge, we do as good as we can.
    151          """
    152          delta = time - self.mean_time()
    153          start_overflow = self._calculate_overflow(self.start_time, delta)[1]
    154          end_overflow = self._calculate_overflow(self.end_time, delta)[1]
    155          if start_overflow == -1:
    156              delta = self.time_type.get_min_time()[0] - self.start_time
    157          elif end_overflow == 1:
    158              delta = self.time_type.get_max_time()[0] - self.end_time
    159          return self.move_delta(delta)
    160
    161 -    def _ensure_within_range(self, time, delta, error_prefix):
    162          """
    163          Return new time (time + delta) or raise ValueError if it is not within
    164          the range [self.time_type.get_min_time(),
    165          self.time_type.get_max_time()].
    166          """
    167          if delta == None:
    168              delta = self.time_type.get_zero_delta()
    169          new_time, overflow, error_text = self._calculate_overflow(time, delta)
    170          if overflow != 0:
    171              error_text = "%s %s" % (error_prefix, error_text)
    172              raise ValueError(error_text)
    173          else:
    174              return new_time
    175
    176 -    def _calculate_overflow(self, time, delta):
    177          """
    178          Return a tuple (new time, overflow flag).
    179
    180          Overflow flag can be -1 (overflow to the left), 0 (no overflow), or 1
    181          (overflow to the right).
    182
    183          If overflow flag is 0 new time is time + delta, otherwise None.
    184          """
    185          try:
    186              min_time, min_error_text = self.time_type.get_min_time()
    187              max_time, max_error_text = self.time_type.get_max_time()
    188              new_time = time + delta
    189              if min_time and new_time < min_time:
    190                  return (None, -1, min_error_text)
    191              if max_time and new_time > max_time:
    192                  return (None, 1, max_error_text)
    193              return (new_time, 0, "")
    194          except OverflowError:
    195              if delta > self.time_type.get_zero_delta():
    196                  return (None, 1, max_error_text)
    197              else:
    198                  return (None, -1, min_error_text)
    199
    200 -    def get_label(self):
    201          """Returns a unicode string describing the time period."""
    202          return self.time_type.format_period(self)
    203
    204 -    def has_nonzero_time(self):
    205          return self.time_type.time_period_has_nonzero_time(self)
    206
    207
    208 -class TimeOutOfRangeLeftError(ValueError):
    209      pass
    210
    211
    212 -class TimeOutOfRangeRightError(ValueError):
    213      pass
    214
    215
    216 -class PeriodTooLongError(ValueError):
    217      pass
    218
    219
    220 -def time_period_center(time_type, time, length):
    221      """
    222      TimePeriod factory method.
    223
    224      Return a time period with the given length (represented as a timedelta)
    225      centered around `time`.
    226      """
    227      half_length = time_type.mult_timedelta(length, 0.5)
    228      start_time = time - half_length
    229      end_time = time + half_length
    230      return TimePeriod(time_type, start_time, end_time)
    231