workpattern 0.3.4 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Rakefile CHANGED
@@ -9,3 +9,12 @@ Rake::TestTask.new do |test|
9
9
  test.test_files = Dir["test/test_*.rb"]
10
10
  test.verbose = true
11
11
  end
12
+
13
+ task :console do
14
+ require 'irb'
15
+ require 'irb/completion'
16
+ require 'workpattern' # You know what to do.
17
+ ARGV.clear
18
+ IRB.start
19
+ end
20
+
data/lib/workpattern.rb CHANGED
@@ -3,148 +3,88 @@
3
3
  #
4
4
  # email: barrie@callenb.org
5
5
  #++
6
- $:.unshift(File.dirname(__FILE__)) unless
7
- $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__)) unless
7
+ $LOAD_PATH.include?(File.dirname(__FILE__)) || $LOAD_PATH.include?(File.expand_path(File.dirname(__FILE__)))
8
8
 
9
9
  require 'rubygems'
10
+ require 'sorted_set' if RUBY_VERSION >= "2.4"
10
11
  require 'date'
11
- require 'workpattern/utility/base.rb'
12
12
  require 'workpattern/clock'
13
- require 'workpattern/hour'
13
+ require 'workpattern/constants'
14
14
  require 'workpattern/day'
15
15
  require 'workpattern/week'
16
16
  require 'workpattern/workpattern'
17
+ require 'workpattern/week_pattern'
17
18
 
18
19
  #
19
20
  # workpattern.rb - date calculation library that takes into account patterns of
20
- # working and resting time and is aimed at supporting scheduling applications such
21
- # as critical path analysis.
21
+ # working and resting time and is aimed at supporting scheduling applications
22
+ # such as critical path analysis.
22
23
  #
23
24
  # Author: Barrie Callender 2011
24
25
  #
25
26
  # Documentation: Barrie Callender <barrie@callenb.org>
26
27
  #
27
28
  module Workpattern
28
-
29
- # Represents a full working hour
30
- # @since 0.2.0
31
- WORKING_HOUR = 2**60-1
32
-
33
- # Represents a full resting hour
34
- # @since 0.2.0
35
- RESTING_HOUR = 0
36
-
37
- # The default workpattern name
38
- # @since 0.2.0
39
- DEFAULT_WORKPATTERN_NAME = 'default'
40
-
41
- # The default base year
42
- # @since 0.2.0
43
- DEFAULT_BASE_YEAR = 2000
44
-
45
- # The default span in years
46
- # @since 0.2.0
47
- DEFAULT_SPAN = 100
48
-
49
- # Hour in terms of days
50
- # @since 0.2.0
51
- HOUR = Rational(1,24)
52
-
53
- # Minute in terms of days
54
- # @since 0.2.0
55
- MINUTE = Rational(1,1440)
56
-
57
- # Earliest or first time in the day
58
- # @since 0.0.1
59
- FIRST_TIME_IN_DAY=Clock.new(0,0)
60
-
61
- # Latest or last time in the day
62
- # @since 0.0.1
63
- LAST_TIME_IN_DAY=Clock.new(23,59)
64
-
65
- # Specifies a working pattern
66
- # @since 0.0.1
67
- WORK = 1
68
-
69
- # Specifies a resting pattern
70
- # @since 0.0.1
71
- REST = 0
72
-
73
- # Represents the days of the week to be used in applying working and resting patterns.
74
- # Values exist for each day of the week as well as for the weekend (Saturday and Sunday),
75
- # the week (Monday to Friday) and all days in the week.
76
- #
77
- # @since 0.0.1
78
- DAYNAMES={:sun => [0],:mon => [1], :tue => [2], :wed => [3], :thu => [4], :fri => [5], :sat => [6],
79
- :weekday => [1,2,3,4,5],
80
- :weekend => [0,6],
81
- :all => [0,1,2,3,4,5,6]}
82
-
83
- # Covenience method to obtain a new <tt>Workpattern</tt>
29
+
30
+ # Covenience method to obtain a new <tt>Workpattern</tt>
84
31
  #
85
32
  # A negative <tt>span</tt> counts back from the <tt>base</tt> year
86
33
  #
87
34
  # @param [String] name Every workpattern has a unique name.
88
35
  # @param [Integer] base Workpattern starts on the 1st January of this year.
89
- # @param [Integer] span Workpattern spans this number of years ending on 31st December.
36
+ # @param [Integer] number of years ending on 31st December.
90
37
  # @return [Workpattern]
38
+ # @raise [NameError] creating a Workpattern with a name that already exists
91
39
  #
92
- # @since 0.2.0
93
- #
94
- def self.new(name=DEFAULT_WORKPATTERN_NAME, base=DEFAULT_BASE_YEAR, span=DEFAULT_SPAN)
95
- return Workpattern.new(name, base,span)
40
+ def self.new(name = DEFAULT_WORKPATTERN_NAME,
41
+ base = DEFAULT_BASE_YEAR,
42
+ span = DEFAULT_SPAN)
43
+ Workpattern.new(name, base, span)
96
44
  end
97
-
98
- # Covenience method to obtain an Array of all the known <tt>Workpattern</tt> objects
45
+
46
+ # Covenience method to obtain an Array of all the known <tt>Workpattern</tt>
47
+ # objects
99
48
  #
100
49
  # @return [Array] all <tt>Workpattern</tt> objects
101
50
  #
102
- # @since 0.2.0
103
- #
104
- def self.to_a()
105
- return Workpattern.to_a
51
+ def self.to_a
52
+ Workpattern.to_a
106
53
  end
107
54
 
108
- # Covenience method to obtain an existing <tt>Workpattern</tt>
55
+ # Covenience method to obtain an existing <tt>Workpattern</tt>
109
56
  #
110
57
  # @param [String] name The name of the Workpattern to retrieve.
111
58
  # @return [Workpattern]
112
59
  #
113
- # @since 0.2.0
114
- #
115
60
  def self.get(name)
116
- return Workpattern.get(name)
61
+ Workpattern.get(name)
117
62
  end
118
-
63
+
119
64
  # Convenience method to delete the named <tt>Workpattern</tt>
120
65
  #
121
66
  # @param [String] name The name of the Workpattern to be deleted.
122
67
  #
123
- # @since 0.2.0
124
- #
125
68
  def self.delete(name)
126
69
  Workpattern.delete(name)
127
70
  end
128
-
71
+
129
72
  # Convenience method to delete all Workpatterns.
130
- #
131
- # @since 0.2.0
132
73
  #
133
74
  def self.clear
134
75
  Workpattern.clear
135
76
  end
136
-
137
- # Convenience method to create a Clock object. This can be used for specifying times
138
- # if you don't want to create a <tt>DateTime</tt> object
139
- #
77
+
78
+ # Convenience method to create a Clock object. This can be used for
79
+ # specifying times if you don't want to create a <tt>DateTime</tt> object
80
+ #
140
81
  # @param [Integer] hour the number of hours.
141
82
  # @param [Integer] min the number of minutes
142
83
  # @return [Clock]
143
84
  # @see Clock
144
85
  #
145
- # @since 0.2.0
146
- #
147
- def self.clock(hour,min)
148
- return Clock.new(hour,min)
86
+ def self.clock(hour, min)
87
+ Clock.new(hour, min)
149
88
  end
89
+
150
90
  end
@@ -7,7 +7,7 @@ module Workpattern
7
7
  # myClock.hour #=> 3
8
8
  # myClock.min #=> 32
9
9
  # myClock.time #=> Time.new(1963,6,10,3,32)
10
- # myClock.to_s #=> 3:32 212
10
+ # myClock.to_s #=> 3:32 212
11
11
  #
12
12
  # aClock=Clock.new(27,80)
13
13
  # aClock.minutes #=> 1700
@@ -19,60 +19,61 @@ module Workpattern
19
19
  # @since 0.2.0
20
20
  #
21
21
  class Clock
22
-
23
- # Initialises an instance of <tt>Clock</tt> using the hours and minutes supplied
24
- # or 0 if they are absent. Although there are 24 hours in a day
22
+ # Initialises an instance of <tt>Clock</tt> using the hours and minutes
23
+ # supplied or 0 if they are absent. Although there are 24 hours in a day
25
24
  # (0-23) and 60 minutes in an hour (0-59), <tt>Clock</tt> calculates
26
25
  # the full hours and remaining minutes of whatever is supplied.
27
26
  #
28
27
  # @param [Integer] hour number of hours
29
28
  # @param [Integer] min number of minutes
30
29
  #
31
- def initialize(hour=0,min=0)
32
- @hour=hour
33
- @min=min
34
- total_minutes = minutes
35
- @hour=total_minutes.div(60)
36
- @min=total_minutes % 60
30
+ def initialize(hour = 0, min = 0)
31
+ @hour = total_minutes(hour, min).div(60)
32
+ @min = total_minutes(hour, min) % 60
37
33
  end
38
-
34
+
39
35
  # Returns the total number of minutes
40
36
  #
41
37
  # @return [Integer] total minutes represented by the Clock object
42
38
  #
43
39
  def minutes
44
- return (@hour*60)+@min
40
+ total_minutes(@hour, @min)
45
41
  end
46
-
42
+
47
43
  # Returns the hour of the clock (0-23)
48
44
  #
49
45
  # @return [Integer] hour of Clock from 0 to 23.
50
46
  #
51
47
  def hour
52
- return @hour % 24
48
+ @hour % 24
53
49
  end
54
-
50
+
55
51
  # Returns the minute of the clock (0-59)
56
52
  #
57
53
  # @return [Integer] minute of Clock from 0 to 59
58
54
  #
59
55
  def min
60
- return @min % 60
56
+ @min % 60
61
57
  end
62
-
58
+
63
59
  # Returns a <tt>Time</tt> object with the correct
64
60
  # <tt>hour</tt> and <tt>min</tt> values. The date
65
61
  # is 10th June 1963.
66
62
  #
67
63
  # @return [DateTime] The time using the date of 10th June 1963 (My Birthday)
68
64
  def time
69
- return DateTime.new(1963,6,10,hour,min)
65
+ DateTime.new(1963, 6, 10, hour, min)
70
66
  end
71
-
72
67
 
73
68
  # @return [String] representation of <tt>Clock</tt> value as 'hh:mn minutes'
74
- def to_s
69
+ def to_s
75
70
  hour.to_s.concat(':').concat(min.to_s).concat(' ').concat(minutes.to_s)
76
71
  end
72
+
73
+ private
74
+
75
+ def total_minutes(hours, mins)
76
+ (hours * 60) + mins
77
+ end
77
78
  end
78
79
  end
@@ -0,0 +1,72 @@
1
+ module Workpattern
2
+
3
+ # default name of a new Workpattern
4
+ DEFAULT_WORKPATTERN_NAME = 'default'.freeze
5
+
6
+ # default base year for a new workpattern
7
+ DEFAULT_BASE_YEAR = 2000
8
+
9
+ # default span of years for a new Workpattern
10
+ DEFAULT_SPAN = 100
11
+
12
+ # 60 seconds in a minute
13
+ MINUTE = 60
14
+
15
+ # 60 minutes in an hour
16
+ HOUR = MINUTE * 60
17
+
18
+ # 24 hours in a day
19
+ HOURS_IN_DAY = 24
20
+
21
+ # Seconds in a day
22
+ DAY = HOUR * HOURS_IN_DAY
23
+
24
+ # 60 minutes in a working hour as binary bit per minute
25
+ WORKING_HOUR = 2**MINUTE - 1
26
+
27
+ # 0 minutes in a working hour as binary bits per minute
28
+ RESTING_HOUR = 0
29
+
30
+ # Earliest or first time in the day
31
+ FIRST_TIME_IN_DAY = Clock.new(0, 0)
32
+
33
+ # Latest or last time in the day
34
+ LAST_TIME_IN_DAY = Clock.new(23, 59)
35
+
36
+ # Flags for calculations
37
+ PREVIOUS_DAY = -1
38
+ SAME_DAY = 0
39
+ NEXT_DAY = 1
40
+
41
+ # Specifies a working pattern
42
+ WORK_TYPE = 1
43
+
44
+ # Specifies a resting pattern
45
+ REST_TYPE = 0
46
+
47
+ # All the days of the week
48
+ SUNDAY=0
49
+ MONDAY=1
50
+ TUESDAY=2
51
+ WEDNESDAY=3
52
+ THURSDAY=4
53
+ FRIDAY=5
54
+ SATURDAY=6
55
+
56
+ # first and last day of week
57
+ FIRST_DAY_OF_WEEK = SUNDAY
58
+ LAST_DAY_OF_WEEK = SATURDAY
59
+
60
+ # Represents the days of the week to be used in applying working
61
+ # and resting patterns.
62
+ # Values exist for each day of the week as well as for the weekend
63
+ # (Saturday and Sunday),
64
+ # the week (Monday to Friday) and all days in the week.
65
+ #
66
+ daynames = { sun: [0], mon: [1], tue: [2], wed: [3],
67
+ thu: [4], fri: [5], sat: [6],
68
+ weekday: [1, 2, 3, 4, 5],
69
+ weekend: [0, 6],
70
+ all: [0, 1, 2, 3, 4, 5, 6] }
71
+ DAYNAMES = daynames.freeze
72
+ end
@@ -1,324 +1,245 @@
1
1
  module Workpattern
2
2
 
3
- # @author Barrie Callender
4
- # @!attribute values
5
- # @return [Array] each hour of the day
6
- # @!attribute hours
7
- # @return [Integer] number of hours in the day
8
- # @!attribute first_hour
9
- # @return [Integer] first working hour in the day
10
- # @!attribute first_min
11
- # @return [Integer] first working minute in first working hour in the day
12
- # @!attribute last_hour
13
- # @return [Integer] last working hour in the day
14
- # @!attribute last_min
15
- # @return [Integer] last working minute in last working hour in the day
16
- # @!attribute total
17
- # @return [Integer] total number of minutes in the day
18
- #
19
- # Represents the 24 hours of a day.
20
- #
21
- # @since 0.2.0
22
- # @todo implement a day with different number of hours in it to support daylight saving
23
- #
24
3
  class Day
25
- include Workpattern::Utility
26
- attr_accessor :values, :hours, :first_hour, :first_min, :last_hour, :last_min, :total
27
4
 
28
- # The new <tt>Day</tt> object can be created as either working or resting.
29
- #
30
- # @param [Integer] type is working (1) or resting (0)
31
- #
32
- def initialize(type=1)
33
- @hours=24
34
- hour=WORKING_HOUR if type==1
35
- hour=RESTING_HOUR if type==0
36
- @values=Array.new(@hours) {|index| hour }
37
-
38
- set_attributes
5
+ attr_accessor :pattern, :hours_per_day, :first_working_minute, :last_working_minute
6
+
7
+ def initialize(hours_per_day = HOURS_IN_DAY, type = WORK_TYPE)
8
+ @hours_per_day = hours_per_day
9
+ @pattern = initial_day(type)
10
+ set_first_and_last_minutes
39
11
  end
40
-
41
- # Creates a duplicate of the current <tt>Day</tt> instance.
42
- #
43
- # @return [Day]
44
- #
45
- def duplicate
46
- duplicate_day = Day.new()
47
- duplicate_values=Array.new(@values.size)
48
- @values.each_index {|index|
49
- duplicate_values[index]=@values[index]
50
- }
51
- duplicate_day.values=duplicate_values
52
- duplicate_day.hours = @hours
53
- duplicate_day.first_hour=@first_hour
54
- duplicate_day.first_min=@first_min
55
- duplicate_day.last_hour=@last_hour
56
- duplicate_day.last_min=@last_min
57
- duplicate_day.total = @total
58
- duplicate_day.refresh
59
- return duplicate_day
12
+
13
+ def set_resting(start_time, finish_time)
14
+ mask = resting_mask(start_time, finish_time)
15
+ @pattern = @pattern & mask
16
+ set_first_and_last_minutes
60
17
  end
61
-
62
- # Recalculates characteristics for this day
63
- #
64
- def refresh
65
- set_attributes
18
+
19
+ def set_working(from_time, to_time)
20
+ @pattern = @pattern | working_mask(from_time, to_time)
21
+ set_first_and_last_minutes
66
22
  end
23
+
24
+ def working_minutes(from_time = FIRST_TIME_IN_DAY, to_time = LAST_TIME_IN_DAY)
25
+ section = @pattern & working_mask(from_time, to_time)
26
+ section.to_s(2).count('1')
27
+ end
28
+
29
+ def working?(hour, minute)
30
+ mask = (2**((hour * 60) + minute))
31
+ result = mask & @pattern
32
+ mask == result
33
+ end
34
+
35
+ def resting?(hour, minute)
36
+ !working?(hour,minute)
37
+ end
38
+
39
+ def calc(a_date, a_duration)
40
+ if a_duration == 0
41
+ return a_date, a_duration, SAME_DAY
42
+ else
43
+ return a_duration > 0 ? add(a_date, a_duration) : subtract(a_date, a_duration)
44
+ end
45
+ end
67
46
 
68
- # Sets all minutes in a date range to be working or resting.
69
- #
70
- # @param [(#hour,#min)] start_time is the start of the range to set
71
- # @param [(#hour, #min)] finish_time is the finish of the range to be set
72
- # @param [Integer] type is either working (1) or resting (0)
73
- #
74
- def workpattern(start_time,finish_time,type)
75
-
76
- if start_time.hour==finish_time.hour
77
- @values[start_time.hour]=@values[start_time.hour].wp_workpattern(start_time.min,finish_time.min,type)
47
+ private
48
+
49
+ def add(a_date, a_duration)
50
+ minutes_left = working_minutes(a_date)
51
+ if a_duration > minutes_left
52
+ return [a_date, a_duration - minutes_left, NEXT_DAY]
53
+ elsif a_duration < minutes_left
54
+ return add_minutes(a_date, a_duration)
78
55
  else
79
- test_hour=start_time.hour
80
- @values[test_hour]=@values[test_hour].wp_workpattern(start_time.min,59,type)
81
-
82
- while ((test_hour+1)<finish_time.hour)
83
- test_hour+=1
84
- @values[test_hour]=@values[test_hour].wp_workpattern(0,59,type)
56
+ if working?(LAST_TIME_IN_DAY.hour, LAST_TIME_IN_DAY.min)
57
+ return [a_date, 0, NEXT_DAY]
58
+ else
59
+ return_date = Time.gm(a_date.year, a_date.month, a_date.day, @last_working_minute.hour, @last_working_minute.min) + 60
60
+ return [ return_date, 0, SAME_DAY]
85
61
  end
86
-
87
- @values[finish_time.hour]=@values[finish_time.hour].wp_workpattern(0,finish_time.min,type)
88
- end
89
- set_attributes
62
+ end
90
63
  end
91
-
92
- # Calculates the result of adding <tt>duration</tt> to
93
- # <tt>time</tt>. The <tt>duration</tt> can be negative in
94
- # which case it subtracts from <tt>time</tt>.
95
- #
96
- # An addition where there are less working minutes left in
97
- # the day than are being added will result in the time
98
- # returned being 00:00 on the following day.
99
- #
100
- # A subtraction where there are less working minutes left in
101
- # the day than are being added will result in the time
102
- # returned being the previous day with the <tt>midnight</tt> flag set to true.
103
- #
104
- # @param [DateTime] time when the calculation starts from
105
- # @param [Integer] duration is the number of minutes to add or subtract if it is negative
106
- # @param [Boolean] midnight is a flag used in subtraction to pretend the time is actually midnight
107
- # @return [DateTime,Integer,Boolean] Calculated time along with any remaining duration and the midnight flag
108
- #
109
- def calc(time,duration,midnight=false)
110
-
111
- if (duration<0)
112
- return subtract(time,duration, midnight)
113
- elsif (duration>0)
114
- return add(time,duration)
64
+
65
+ def add_minutes(a_date, a_duration)
66
+ elapsed_date = a_date + (a_duration * 60) - 60
67
+
68
+ if working_minutes(a_date, elapsed_date) == a_duration
69
+ return [elapsed_date += 60, 0, SAME_DAY]
115
70
  else
116
- return time,duration, false
71
+ begin
72
+ elapsed_date += 60
73
+ end while working_minutes(a_date, elapsed_date) != a_duration
74
+ return [elapsed_date += 60, 0, SAME_DAY]
117
75
  end
118
-
119
76
  end
120
-
121
- # Returns true if the given minute is working and false if it is resting
122
- #
123
- # @param [(#hour, #min)] start is the time in the day to inspect
124
- # @return [Boolean] true if the time is working and false if it is resting
125
- #
126
- def working?(start)
127
- return true if minutes(start.hour,start.min,start.hour,start.min)==1
128
- return false
77
+
78
+ def subtract(a_date, a_duration)
79
+ minutes_left = working_minutes(FIRST_TIME_IN_DAY,a_date - 60)
80
+ abs_duration = a_duration.abs
81
+ if abs_duration > minutes_left
82
+ return [a_date, a_duration + minutes_left, PREVIOUS_DAY]
83
+ elsif abs_duration < minutes_left
84
+ return subtract_minutes(a_date, abs_duration)
85
+ else
86
+ return [Time.gm(a_date.year,a_date.month,a_date.day,@first_working_minute.hour,@first_working_minute.min), 0, SAME_DAY]
87
+ end
129
88
  end
130
-
131
- # Returns the difference in working minutes between two times.
132
- #
133
- # @param [(#hour, #min)] start start time in the range
134
- # @param [(#hour, #min)] finish finish time in the range
135
- # @return [Integer] number of working minutes
136
- #
137
- def diff(start,finish)
138
- start,finish=finish,start if ((start <=> finish))==1
139
- # calculate to end of hour
140
- #
141
- if (start.jd==finish.jd) # same day
142
- duration=minutes(start.hour,start.min,finish.hour, finish.min)
143
- duration -=1 if working?(finish)
144
- start=finish
89
+
90
+ def subtract_minutes(a_date, abs_duration)
91
+ elapsed_date = a_date - (abs_duration * 60)
92
+ if working_minutes(elapsed_date, a_date - 60) == abs_duration
93
+ return [elapsed_date, 0, SAME_DAY]
145
94
  else
146
- duration=minutes(start.hour,start.min,23, 59)
147
- start=start+((23-start.hour)*HOUR) +((60-start.min)*MINUTE)
95
+ a_date -= 60
96
+ begin
97
+ elapsed_date -= 60
98
+ end while working_minutes(elapsed_date, a_date) != abs_duration
99
+ return [elapsed_date, 0, SAME_DAY]
148
100
  end
149
- return duration, start
101
+ end
102
+
103
+ def working_day
104
+ 2**((60 * @hours_per_day) +1) - 1
150
105
  end
151
106
 
152
- # Returns the total number of minutes between two times.
153
- #
154
- # @param [Integer] start_hour first hour in range
155
- # @param [Integer] start_min first minute of first hour in range
156
- # @param [Integer] finish_hour last hour in range
157
- # @param [Integer] finish_min last minute of last hour in range
158
- # @return [Integer] minutes between supplied hours and minutes
159
- #
160
- # @todo can this method and #diff method be combined?
161
- #
162
- def minutes(start_hour,start_min,finish_hour,finish_min)
163
-
164
- if (start_hour > finish_hour) || ((finish_hour==start_hour) && (start_min > finish_min))
165
- start_hour,start_min,finish_hour,finish_min=finish_hour,finish_min,start_hour,finish_min
107
+ def initial_day(type = WORK_TYPE)
108
+
109
+ pattern = 2**((60 * @hours_per_day) + 1)
110
+
111
+ if type == WORK_TYPE
112
+ pattern = pattern - 1
166
113
  end
167
114
 
168
- if (start_hour==finish_hour)
169
- retval=@values[start_hour].wp_minutes(start_min,finish_min)
170
- else
171
-
172
- retval=@values[start_hour].wp_minutes(start_min,59)
173
- while (start_hour+1<finish_hour)
174
- retval+=@values[start_hour+1].wp_total
175
- start_hour+=1
176
- end
177
- retval+=@values[finish_hour].wp_minutes(0,finish_min)
178
- end
179
-
180
- return retval
115
+ pattern
181
116
  end
182
117
 
183
- private
118
+ def working_mask(start_time, finish_time)
184
119
 
185
- # Calculates the attributes that describe the day. Called after changes.
186
- #
187
- def set_attributes
188
- @first_hour=nil
189
- @first_min=nil
190
- @last_hour=nil
191
- @last_min=nil
192
- @total=0
193
- 0.upto(@hours-1) {|index|
194
- @first_hour=index if ((@first_hour.nil?) && (@values[index].wp_total!=0))
195
- @first_min=@values[index].wp_first if ((@first_min.nil?) && (!@values[index].wp_first.nil?))
196
- @last_hour=index if (@values[index].wp_total!=0)
197
- @last_min=@values[index].wp_last if (@values[index].wp_total!=0)
198
- @total+=@values[index].wp_total
199
- }
120
+ start = minutes_in_time(start_time)
121
+ finish = minutes_in_time(finish_time)
122
+
123
+ mask = initial_day
124
+
125
+ mask = mask - ((2**start) - 1)
126
+ mask & ((2**(finish + 1)) -1)
200
127
  end
201
-
202
- # Returns the first working minute as a <tt>DateTime</tt> or <tt>oo:oo</tt>
203
- # when there is no working minutes in the day. Used by the <tt>#subtract</tt> method
204
- #
205
- # @param [DateTime] time day for which the first working time is sought.
206
- # @return [DateTime] the first working time of the day
207
- #
208
- def first_working_minute(time)
209
- if @first_hour.nil?
210
- return time - (HOUR*time.hour) - (MINUTE*time.min)
211
- else
212
- time = time - HOUR * (time.hour - @first_hour)
213
- time = time - MINUTE * (time.min - @first_min )
214
- return time
215
- end
128
+
129
+ def resting_mask(start_time, finish_time)
130
+
131
+ start = minutes_in_time(start_time)
132
+ finish_clock = Clock.new(finish_time.hour, finish_time.min + 1)
133
+
134
+ mask = initial_day(REST_TYPE)
135
+ if minutes_in_time(finish_time) != LAST_TIME_IN_DAY.minutes
136
+ mask = mask | working_mask(finish_clock,LAST_TIME_IN_DAY)
137
+ end
138
+ mask | ((2**start) - 1)
139
+ end
140
+
141
+ def minutes_in_time(a_time)
142
+ (a_time.hour * 60) + a_time.min
216
143
  end
217
-
218
- # Handles the subtraction of a duration from a time in the day.
219
- #
220
- # @param [DateTime] time when the subtraction starts from
221
- # @param [Integer] duration is the number of minutes to subtract from the <tt>time</tt>
222
- # @param [Boolean] midnight is a flag used in subtraction to pretend the time is actually midnight
223
- # @return [DateTime,Integer,Boolean] Calculated time along with any remaining duration and the midnight flag
224
- #
225
- def subtract(time,duration,midnight=false)
226
- if (time.hour==0 && time.min==0)
227
- if midnight
228
- duration+=minutes(23,59,23,59)
229
- time=time+(HOUR*23)+(MINUTE*59)
230
- return calc(time,duration)
144
+
145
+ def last_minute
146
+ if working?(LAST_TIME_IN_DAY.hour, LAST_TIME_IN_DAY.min)
147
+ return LAST_TIME_IN_DAY
148
+ end
149
+
150
+ top = minutes_in_time(LAST_TIME_IN_DAY)
151
+ bottom = minutes_in_time(FIRST_TIME_IN_DAY)
152
+ mark = top / 2
153
+
154
+ not_done = true
155
+ while not_done
156
+
157
+ minutes = working_minutes(minutes_to_time(mark), minutes_to_time(top))
158
+
159
+ if minutes > 1
160
+ bottom = mark
161
+ mark = mark + ((top - bottom) / 2)
162
+
163
+ elsif minutes == 0
164
+ top = mark
165
+ mark = mark - (( top - bottom) / 2)
166
+
167
+ elsif minutes == 1 && is_resting(mark)
168
+ bottom = mark
169
+ mark = mark + ((top - bottom) / 2)
170
+
231
171
  else
232
- return time.prev_day, duration,true
172
+ not_done = false
173
+
233
174
  end
234
- elsif (time.hour==@first_hour && time.min==@first_min)
235
- time=time-(HOUR*@first_hour) - (MINUTE*@first_min)
236
- return time.prev_day, duration, true
237
- elsif (time.min>0)
238
- available_minutes=minutes(0,0,time.hour,time.min-1)
239
- else
240
- available_minutes=minutes(0,0,time.hour-1,59)
241
- end
242
- if ((duration+available_minutes)<0) # not enough minutes in the day
243
- time = midnight_before(time.prev_day)
244
- duration = duration + available_minutes
245
- return time, duration, true
246
- elsif ((duration+available_minutes)==0)
247
- duration=0
248
- time=first_working_minute(time)
249
- else
250
- minutes_this_hour=@values[time.hour].wp_minutes(0,time.min-1)
251
- this_hour=time.hour
252
- until (duration==0)
253
- if (minutes_this_hour<duration.abs)
254
- duration+=minutes_this_hour
255
- time = time - (MINUTE*time.min) - HOUR
256
- this_hour-=1
257
- minutes_this_hour=@values[this_hour].wp_total
258
- else
259
- next_hour=(time.min==0)
260
- time,duration=@values[this_hour].wp_calc(time,duration, next_hour)
261
- end
175
+
176
+ if mark == bottom #& last_mark != mark
177
+ mark = mark + 1
178
+ end
179
+
180
+ if mark == 1 && top == 1
181
+ mark = 0
262
182
  end
263
- end
264
- return time,duration, false
183
+
184
+ end
185
+ minutes_to_time(mark)
186
+
265
187
  end
266
-
267
- # Returns the result of adding <tt>duration</tt> to the given <tt>time</tt>
268
- # When there are not enough minutes in the day it returns the date
269
- # for the start of the following day.
270
- #
271
- # @param [DateTime] time when the calculation starts from
272
- # @param [Integer] duration is the number of minutes to add
273
- # @return [DateTime,Integer] Calculated time along with any remaining duration
274
- #
275
- def add(time,duration)
276
- available_minutes=minutes(time.hour,time.min,@hours-1,59)
277
- if ((duration-available_minutes)>0) # not enough minutes left in the day
278
-
279
- result_date= time.next_day - (HOUR*time.hour) - (MINUTE*time.min)
280
- duration = duration - available_minutes
281
- else
282
- total=@values[time.hour].wp_minutes(time.min,59)
283
- if (total==duration) # this hour satisfies
284
- result_date=time - (MINUTE*time.min) + (MINUTE*@values[time.hour].wp_last) + MINUTE
285
- duration = 0
286
- else
287
- result_date = time
288
- until (duration==0)
289
- if (total<duration)
290
- duration-=total
291
- result_date=result_date + HOUR - (MINUTE*result_date.min)
292
- else
293
- result_date,duration=@values[result_date.hour].wp_calc(result_date,duration)
294
- end
295
- total=@values[result_date.hour].wp_total
296
- end
188
+
189
+ def first_minute
190
+ if working?(FIRST_TIME_IN_DAY.hour, FIRST_TIME_IN_DAY.min)
191
+ return FIRST_TIME_IN_DAY
192
+ end
193
+
194
+ top = minutes_in_time(LAST_TIME_IN_DAY)
195
+ bottom = minutes_in_time(FIRST_TIME_IN_DAY)
196
+ mark = top / 2
197
+
198
+ not_done = true
199
+ while not_done
200
+
201
+ minutes = working_minutes(minutes_to_time(bottom), minutes_to_time(mark))
202
+ if minutes > 1
203
+ top = mark
204
+ mark = mark - ((top - bottom) / 2)
205
+ elsif minutes == 0
206
+ bottom = mark
207
+ mark = mark + (( top - bottom) / 2)
208
+ elsif minutes == 1 && is_resting(mark)
209
+ top = mark
210
+ mark = mark - ((top - bottom) / 2)
211
+ else
212
+ not_done = false
213
+ end
214
+
215
+ if mark == 1 && top == 1
216
+ mark = 0
297
217
  end
298
- end
299
- return result_date,duration, false
300
- end
301
-
302
- # Returns the start of the next hour.
303
- #
304
- # The next hour could be the start of the following day.
305
- #
306
- # @param [DateTime] start is the <tt>DateTime</tt> for which the following hour is required.
307
- # @return [DateTime] the start of the next hour following the <tt>DateTime</tt> supplied
308
- #
309
- def next_hour(start)
310
- return start+HOUR-(start.min*MINUTE)
311
- end
312
-
313
- # Returns the number of working minutes left in the current hour
314
- #
315
- # @param [DateTime] start is the <tt>DateTime</tt> for which the remaining working minutes
316
- # in the hour are required
317
- # @return [Integer] number of remaining working minutes
318
- #
319
- def minutes_left_in_hour(start)
320
- return @values[start.hour].wp_diff(start.min,60)
218
+ end
219
+
220
+ minutes_to_time(mark)
221
+ end
222
+
223
+ def minutes_to_time(minutes)
224
+ Time.gm(1963,6,10,minutes / 60, minutes - (minutes / 60 * 60))
321
225
  end
322
-
226
+
227
+ def is_resting(minutes)
228
+ a_time =(minutes_to_time(minutes))
229
+ resting?(a_time.hour, a_time.min)
230
+ end
231
+
232
+ def set_first_and_last_minutes
233
+ if working_minutes == 0
234
+ @first_working_minute = nil
235
+ @last_working_minute = nil
236
+ else
237
+ @first_working_minute = first_minute
238
+ @last_working_minute = last_minute
239
+ end
240
+ end
241
+
242
+
323
243
  end
244
+
324
245
  end