File: Interval.rb

package info (click to toggle)
tj3 3.8.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 5,048 kB
  • sloc: ruby: 36,481; javascript: 1,113; sh: 19; makefile: 17
file content (290 lines) | stat: -rw-r--r-- 8,817 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
#!/usr/bin/env ruby -w
# encoding: UTF-8
#
# = Interval.rb -- The TaskJuggler III Project Management Software
#
# Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014
#               by Chris Schlaeger <cs@taskjuggler.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#

require 'taskjuggler/TjTime'

class TaskJuggler

  # This is the based class used to store several kinds of intervals in
  # derived classes.
  class Interval

    attr_reader :start, :end

    # Create a new Interval object. _s_ is the interval start, _e_ the
    # interval end (not included).
    def initialize(s, e)
      @start = s
      @end = e
      # The end must not be before the start.
      if @end < @start
        raise ArgumentError, "Invalid interval (#{s} - #{e})"
      end
    end

    # Return true if _arg_ is contained within the Interval. It can either
    # be a single TjTime or another Interval.
    def contains?(arg)
      if arg.is_a?(Interval)
        raise ArgumentError, "Class mismatch" if self.class != arg.class
        return @start <= arg.start && arg.end <= @end
      else
        raise ArgumentError, "Class mismatch" if @start.class != arg.class
        return @start <= arg && arg < @end
      end
    end

    # Check whether the Interval _arg_ overlaps with this Interval.
    def overlaps?(arg)
      if arg.is_a?(Interval)
        raise ArgumentError, "Class mismatch" if self.class != arg.class
        return (@start <= arg.start && arg.start < @end) ||
               (arg.start <= @start && @start < arg.end)
      else
        raise ArgumentError, "Class mismatch" if @start.class != arg.class
        return @start <= arg && arg < @end
      end
    end

    # Return a new Interval that contains the overlap of self and the Interval
    # _iv_. In case there is no overlap, nil is returned.
    def intersection(iv)
      raise ArgumentError, "Class mismatch" if self.class != iv.class
      newStart = @start > iv.start ? @start : iv.start
      newEnd = @end < iv.end ? @end : iv.end
      newStart < newEnd ? self.class.new(newStart, newEnd) : nil
    end

    # Append or prepend the Interval _iv_ to self. If _iv_ does not directly
    # attach to self, just return self.
    def combine(iv)
      raise ArgumentError, "Class mismatch" if self.class != iv.class
      if iv.end == @start
        # Prepend iv
        Array.new self.class.new(iv.start, @end)
      elsif @end == iv.start
        # Append iv
        Array.new self.class.new(@start, iv.end)
      else
        self
      end
    end

    # Compare self with Interval _iv_. This function only works for
    # non-overlapping Interval objects.
    def <=>(iv)
      raise ArgumentError, "Class mismatch" if self.class != iv.class
      if @end < iv.start
        -1
      elsif iv.end < @start
        1
      end
      0
    end

    # Return true if the Interval _iv_ describes an identical time period.
    def ==(iv)
      raise ArgumentError, "Class mismatch" if self.class != iv.class
      @start == iv.start && @end == iv.end
    end

  end

  # The TimeInterval class provides objects that model a time interval. The
  # start end end time are represented as seconds after Jan 1, 1970. The start
  # is part of the interval, the end is not.
  class TimeInterval < Interval

    attr_accessor :start, :end

    # Create a new TimeInterval. _args_ can be three different kind of arguments.
    #
    # a and b should be TjTime objects.
    #
    # TimeInterval.new(a, b)  | -> Interval(a, b)
    # TimeInterval.new(a)     | -> Interval(a, a)
    # TimeInterval.new(iv)    | -> Interval(iv.start, iv.end)
    #
    def initialize(*args)
      if args.length == 1
        if args[0].is_a?(TjTime)
          # Just one argument, a date
          super(args[0], args[0])
        elsif args[0].is_a?(TimeInterval)
          # Just one argument, a TimeInterval
          super(args[0].start, args[0].end)
        else
          raise ArgumentError, "Illegal argument 1: #{args[0].class}"
        end
      elsif args.length == 2
        # Two arguments, a start and end date
        unless args[0].is_a?(TjTime)
          raise ArgumentError, "Interval start must be a date, not a " +
                "#{args[0].class}"
        end
        unless args[1].is_a?(TjTime)
          raise ArgumentError, "Interval end must be a date, not a" +
                "#{args[1].class}"
        end
        super(args[0], args[1])
      else
        raise ArgumentError, "Too many arguments: #{args.length}"
      end
    end

    # Return the duration of the TimeInterval.
    def duration
      @end - @start
    end

    # Turn the TimeInterval into a human readable form.
    def to_s
      @start.to_s + ' - ' + @end.to_s
    end

  end

  # This class describes an interval of a scoreboard. The start and end of the
  # interval are stored as indexes but can always be converted back to TjTime
  # objects if needed.
  class ScoreboardInterval < Interval

    attr_reader :sbStart, :slotDuration

    # Create a new ScoreboardInterval. _args_ can be three different kind of
    # arguments.
    #
    # sbStart must be a TjTime of the scoreboard start
    # slotDuration must be the duration of the scoreboard slots in seconds
    # a and b should be TjTime or Integer objects that describe the start and
    # end time or index of the interval.
    #
    # TimeInterval.new(iv)
    # TimeInterval.new(sbStart, slotDuration, a)
    # TimeInterval.new(sbStart, slotDuration, a, b)
    #
    def initialize(*args)
      case args.length
      when 1
        # If there is only one argument, it must be a ScoreboardInterval.
        if args[0].is_a?(ScoreboardInterval)
          @sbStart = args[0].sbStart
          @slotDuration = args[0].slotDuration
          # Just one argument, a TimeInterval
          super(args[0].start, args[0].end)
        else
          raise ArgumentError, "Illegal argument 1: #{args[0].class}"
        end
      when 3
        @sbStart = args[0]
        @slotDuration = args[1]
        # If the third argument is a date we convert it to a scoreboard index.
        args[2] = dateToIndex(args[2]) if args[2].is_a?(TjTime)

        if args[2].is_a?(Integer)
          super(args[2], args[2])
        else
          raise ArgumentError, "Illegal argument 3: #{args[0].class}"
        end
      when 4
        @sbStart = args[0]
        @slotDuration = args[1]
        # If the third and forth arguments are a date we convert them to a
        # scoreboard index.
        args[2] = dateToIndex(args[2]) if args[2].is_a?(TjTime)
        args[3] = dateToIndex(args[3]) if args[3].is_a?(TjTime)

        if !(args[2].is_a?(Integer))
          raise ArgumentError, "Interval start must be an index or TjTime, " +
                "not a #{args[2].class}"
        end
        if !(args[3].is_a?(Integer))
          raise ArgumentError, "Interval end must be an index or TjTime, " +
                "not a #{args[3].class}"
        end
        super(args[2], args[3])
      else
        raise ArgumentError, "Wrong number of arguments: #{args.length}"
      end

      unless @sbStart.is_a?(TjTime)
        raise ArgumentError, "sbStart must be a TjTime object, not a" +
              "#{@sbStart.class}"
      end
      unless @slotDuration.is_a?(Integer)
        raise ArgumentError, "slotDuration must be an Integer, not a " +
              "#{@slotDuration.class}"
      end

    end

    # Assign the start of the interval. +arg+ can be an Integer or
    # TjTime object.
    def start=(arg)
      case arg
      when Integer
        @start = arg
      when TjTime
        @start = dateToIndex(arg)
      else
        raise ArgumentError, "Unsupported class #{arg.class}"
      end
    end

    # Assign the start of the interval. +arg+ can be an Integer or
    # TjTime object.
    def end=(arg)
      case arg
      when Integer
        @end = arg
      when TjTime
        @end = dateToIndex(arg)
      else
        raise ArgumentError, "Unsupported class #{arg.class}"
      end
    end

    # Return the interval start as TjTime object.
    def startDate
      indexToDate(@start)
    end

    # Return the interval end as TjTime object.
    def endDate
      indexToDate(@end)
    end

    # Return the duration of the ScoreboardInterval.
    def duration
      indexToDate(@end) - indexToDate(@start)
    end

    # Turn the ScoreboardInterval into a human readable form.
    def to_s
      indexToDate(@start).to_s + ' - ' + indexToDate(@end).to_s
    end

    private

    def dateToIndex(date)
      (date - @sbStart).to_i / @slotDuration
    end

    def indexToDate(index)
      @sbStart + (index * @slotDuration)
    end

  end

end