workpattern 0.5.0 → 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.
- checksums.yaml +5 -5
- data/.gitignore +8 -20
- data/.travis.yml +18 -12
- data/{CHANGELOG → CHANGELOG.md} +19 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +31 -0
- data/LICENSE.txt +21 -0
- data/README.md +2 -27
- data/Rakefile +0 -0
- data/lib/workpattern.rb +5 -73
- data/lib/workpattern/clock.rb +0 -0
- data/lib/workpattern/constants.rb +72 -0
- data/lib/workpattern/day.rb +245 -0
- data/lib/workpattern/version.rb +1 -1
- data/lib/workpattern/week.rb +119 -296
- data/lib/workpattern/week_pattern.rb +144 -0
- data/lib/workpattern/workpattern.rb +73 -125
- data/script/console +0 -0
- data/script/destroy +0 -0
- data/script/generate +0 -0
- data/script/txt2html +0 -0
- data/workpattern.gemspec +32 -20
- metadata +21 -33
- data/lib/workpattern/utility/base.rb +0 -27
- data/test/test_clock.rb +0 -29
- data/test/test_helper.rb +0 -2
- data/test/test_week.rb +0 -532
- data/test/test_workpattern.rb +0 -307
- data/test/test_workpattern_module.rb +0 -88
@@ -0,0 +1,245 @@
|
|
1
|
+
module Workpattern
|
2
|
+
|
3
|
+
class Day
|
4
|
+
|
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
|
11
|
+
end
|
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
|
17
|
+
end
|
18
|
+
|
19
|
+
def set_working(from_time, to_time)
|
20
|
+
@pattern = @pattern | working_mask(from_time, to_time)
|
21
|
+
set_first_and_last_minutes
|
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
|
46
|
+
|
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)
|
55
|
+
else
|
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]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
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]
|
70
|
+
else
|
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]
|
75
|
+
end
|
76
|
+
end
|
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
|
88
|
+
end
|
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]
|
94
|
+
else
|
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]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def working_day
|
104
|
+
2**((60 * @hours_per_day) +1) - 1
|
105
|
+
end
|
106
|
+
|
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
|
113
|
+
end
|
114
|
+
|
115
|
+
pattern
|
116
|
+
end
|
117
|
+
|
118
|
+
def working_mask(start_time, finish_time)
|
119
|
+
|
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)
|
127
|
+
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
|
143
|
+
end
|
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
|
+
|
171
|
+
else
|
172
|
+
not_done = false
|
173
|
+
|
174
|
+
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
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
minutes_to_time(mark)
|
186
|
+
|
187
|
+
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
|
217
|
+
end
|
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))
|
225
|
+
end
|
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
|
+
|
243
|
+
end
|
244
|
+
|
245
|
+
end
|
data/lib/workpattern/version.rb
CHANGED
data/lib/workpattern/week.rb
CHANGED
@@ -8,15 +8,16 @@ module Workpattern
|
|
8
8
|
#
|
9
9
|
# @private
|
10
10
|
class Week
|
11
|
-
attr_accessor :
|
11
|
+
attr_accessor :hours_per_day, :start, :finish, :days
|
12
|
+
attr_writer :week_total, :total
|
12
13
|
|
13
|
-
def initialize(start, finish, type =
|
14
|
+
def initialize(start, finish, type = WORK_TYPE, hours_per_day = HOURS_IN_DAY)
|
14
15
|
@hours_per_day = hours_per_day
|
15
16
|
@start = Time.gm(start.year, start.month, start.day)
|
16
17
|
@finish = Time.gm(finish.year, finish.month, finish.day)
|
17
|
-
@
|
18
|
-
|
19
|
-
@
|
18
|
+
@days = Array.new(LAST_DAY_OF_WEEK)
|
19
|
+
FIRST_DAY_OF_WEEK.upto(LAST_DAY_OF_WEEK) do |i|
|
20
|
+
@days[i] = Day.new(hours_per_day, type)
|
20
21
|
end
|
21
22
|
end
|
22
23
|
|
@@ -27,7 +28,7 @@ module Workpattern
|
|
27
28
|
end
|
28
29
|
|
29
30
|
def week_total
|
30
|
-
elapsed_days > 6 ?
|
31
|
+
elapsed_days > 6 ? full_week_working_minutes : part_week_total_minutes
|
31
32
|
end
|
32
33
|
|
33
34
|
def total
|
@@ -36,56 +37,62 @@ module Workpattern
|
|
36
37
|
|
37
38
|
def workpattern(days, from_time, to_time, type)
|
38
39
|
DAYNAMES[days].each do |day|
|
39
|
-
if type ==
|
40
|
-
|
40
|
+
if type == WORK_TYPE
|
41
|
+
@days[day].set_working(from_time, to_time)
|
41
42
|
else
|
42
|
-
|
43
|
+
@days[day].set_resting(from_time, to_time)
|
43
44
|
end
|
44
45
|
end
|
45
46
|
end
|
46
47
|
|
47
48
|
def duplicate
|
48
|
-
duplicate_week = Week.new(start, finish)
|
49
|
-
|
49
|
+
duplicate_week = Week.new(@start, @finish)
|
50
|
+
FIRST_DAY_OF_WEEK.upto(LAST_DAY_OF_WEEK) do |i|
|
51
|
+
duplicate_week.days[i] = @days[i].clone
|
52
|
+
duplicate_week.days[i].hours_per_day = @days[i].hours_per_day
|
53
|
+
duplicate_week.days[i].pattern = @days[i].pattern
|
54
|
+
end
|
50
55
|
duplicate_week
|
51
56
|
end
|
52
57
|
|
53
|
-
def calc(
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
+
def calc(a_date, a_duration, a_day = SAME_DAY)
|
59
|
+
if a_duration == 0
|
60
|
+
return a_date, a_duration
|
61
|
+
elsif a_duration > 0
|
62
|
+
return add(a_date, a_duration)
|
63
|
+
else
|
64
|
+
subtract(a_date, a_duration, a_day)
|
65
|
+
end
|
58
66
|
end
|
59
67
|
|
60
68
|
def working?(time)
|
61
|
-
|
62
|
-
false
|
69
|
+
@days[time.wday].working?(time.hour, time.min)
|
63
70
|
end
|
64
71
|
|
65
|
-
def resting?(
|
66
|
-
|
72
|
+
def resting?(time)
|
73
|
+
@days[time.wday].resting?(time.hour, time.min)
|
67
74
|
end
|
68
75
|
|
69
|
-
def diff(
|
70
|
-
|
76
|
+
def diff(start_date, finish_date)
|
77
|
+
if start_date > finish_date
|
78
|
+
start_date, finish_date = finish_date, start_date
|
79
|
+
end
|
71
80
|
|
72
|
-
|
73
|
-
|
74
|
-
|
81
|
+
if jd(start_date) == jd(finish_date)
|
82
|
+
return diff_in_same_day(start_date, finish_date)
|
83
|
+
else
|
84
|
+
return diff_in_same_weekpattern(start_date, finish_date)
|
85
|
+
end
|
75
86
|
end
|
76
|
-
|
87
|
+
|
77
88
|
private
|
78
89
|
|
79
|
-
def working_minutes_in(day)
|
80
|
-
day.to_s(2).count('1')
|
81
|
-
end
|
82
|
-
|
83
90
|
def elapsed_days
|
84
|
-
(finish - start).to_i /
|
91
|
+
(finish - start).to_i / DAY + 1
|
85
92
|
end
|
86
93
|
|
87
|
-
def
|
88
|
-
minutes_in_day_range
|
94
|
+
def full_week_working_minutes
|
95
|
+
minutes_in_day_range FIRST_DAY_OF_WEEK, LAST_DAY_OF_WEEK
|
89
96
|
end
|
90
97
|
|
91
98
|
def part_week_total_minutes
|
@@ -114,341 +121,157 @@ module Workpattern
|
|
114
121
|
end
|
115
122
|
|
116
123
|
def minutes_to_first_saturday
|
117
|
-
minutes_in_day_range(start.wday,
|
124
|
+
minutes_in_day_range(start.wday, LAST_DAY_OF_WEEK)
|
118
125
|
end
|
119
126
|
|
120
127
|
def minutes_to_finish_day
|
121
|
-
minutes_in_day_range(
|
128
|
+
minutes_in_day_range(FIRST_DAY_OF_WEEK, finish.wday)
|
122
129
|
end
|
123
130
|
|
124
131
|
def minutes_in_day_range(first, last)
|
125
|
-
@
|
132
|
+
@days[first..last].inject(0) { |sum, day| sum + day.working_minutes }
|
126
133
|
end
|
127
134
|
|
128
|
-
def add(
|
129
|
-
running_date, duration = add_to_end_of_day(initial_date, duration)
|
135
|
+
def add(a_date, a_duration)
|
130
136
|
|
131
|
-
|
132
|
-
running_date, duration = add_full_weeks running_date, duration
|
133
|
-
running_date, duration = add_remaining_days running_date, duration
|
134
|
-
[running_date, duration, false]
|
135
|
-
end
|
137
|
+
r_date, r_duration = add_to_end_of_day(a_date, a_duration)
|
136
138
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
duration -= available_minutes_in_day
|
142
|
-
initial_date = start_of_next_day(initial_date)
|
143
|
-
elsif available_minutes_in_day == duration
|
144
|
-
duration -= available_minutes_in_day
|
145
|
-
initial_date = end_of_this_day(initial_date)
|
146
|
-
else
|
147
|
-
initial_date = consume_minutes(initial_date, duration)
|
148
|
-
duration = 0
|
149
|
-
end
|
150
|
-
[initial_date, duration]
|
139
|
+
r_date, r_duration = add_to_finish_day r_date, r_duration
|
140
|
+
r_date, r_duration = add_full_weeks r_date, r_duration
|
141
|
+
r_date, r_duration = add_remaining_days r_date, r_duration
|
142
|
+
[r_date, r_duration, false]
|
151
143
|
end
|
152
144
|
|
153
|
-
def
|
154
|
-
|
155
|
-
date, duration = add_to_end_of_day(date, duration)
|
156
|
-
end
|
145
|
+
def add_to_end_of_day(a_date, a_duration)
|
146
|
+
r_date, r_duration, r_day = @days[a_date.wday].calc(a_date,a_duration)
|
157
147
|
|
158
|
-
|
159
|
-
|
148
|
+
if r_day == NEXT_DAY
|
149
|
+
r_date = start_of_next_day(r_date)
|
160
150
|
|
161
|
-
def add_full_weeks(date, duration)
|
162
|
-
while (duration != 0) && (duration >= week_total) && ((jd(date) + (6 * 86_400)) <= jd(finish))
|
163
|
-
duration -= week_total
|
164
|
-
date += (7 * 86_400)
|
165
151
|
end
|
166
152
|
|
167
|
-
[
|
153
|
+
[r_date, r_duration]
|
168
154
|
end
|
169
155
|
|
170
|
-
def
|
171
|
-
while (
|
172
|
-
|
173
|
-
end
|
174
|
-
[date, duration]
|
175
|
-
end
|
176
|
-
|
177
|
-
def add_to_finish_day(date, duration)
|
178
|
-
while ( duration != 0) && (date.wday != next_day(self.finish).wday) && (jd(date) <= jd(self.finish))
|
179
|
-
date, duration = add_to_end_of_day(date,duration)
|
156
|
+
def add_to_finish_day(a_date, a_duration)
|
157
|
+
while ( a_duration != 0) && (a_date.wday != next_day(self.finish).wday) && (jd(a_date) <= jd(self.finish))
|
158
|
+
a_date, a_duration = add_to_end_of_day(a_date,a_duration)
|
180
159
|
end
|
181
160
|
|
182
|
-
|
161
|
+
[a_date, a_duration]
|
183
162
|
end
|
184
163
|
|
185
|
-
def add_full_weeks(
|
164
|
+
def add_full_weeks(a_date, a_duration)
|
186
165
|
|
187
|
-
while (
|
188
|
-
|
189
|
-
|
166
|
+
while (a_duration != 0) && (a_duration >= self.week_total) && ((jd(a_date) + (6*86400)) <= jd(self.finish))
|
167
|
+
a_duration -= self.week_total
|
168
|
+
a_date += (7*86400)
|
190
169
|
end
|
191
170
|
|
192
|
-
|
171
|
+
[a_date, a_duration]
|
193
172
|
end
|
194
173
|
|
195
|
-
def add_remaining_days(
|
196
|
-
while (
|
197
|
-
|
174
|
+
def add_remaining_days(a_date, a_duration)
|
175
|
+
while (a_duration != 0) && (jd(a_date) <= jd(self.finish))
|
176
|
+
a_date, a_duration = add_to_end_of_day(a_date,a_duration)
|
198
177
|
end
|
199
|
-
|
200
|
-
end
|
201
|
-
|
202
|
-
def work_on_day(day,from_time,to_time)
|
203
|
-
self.values[day] = self.values[day] | time_mask(from_time, to_time)
|
204
|
-
end
|
205
|
-
|
206
|
-
def rest_on_day(day,from_time,to_time)
|
207
|
-
mask_of_1s = time_mask(from_time, to_time)
|
208
|
-
mask = mask_of_1s ^ working_day & working_day
|
209
|
-
self.values[day] = self.values[day] & mask
|
210
|
-
end
|
211
|
-
|
212
|
-
def time_mask(from_time, to_time)
|
213
|
-
bit_pos(to_time.hour, to_time.min + 1) - bit_pos(from_time.hour, from_time.min)
|
214
|
-
end
|
215
|
-
|
216
|
-
def bit_pos(hour,minute)
|
217
|
-
2**( (hour * 60) + minute )
|
218
|
-
end
|
219
|
-
|
220
|
-
def work_on_day(day, from_time, to_time)
|
221
|
-
values[day] = values[day] | time_mask(from_time, to_time)
|
222
|
-
end
|
223
|
-
|
224
|
-
def rest_on_day(day, from_time, to_time)
|
225
|
-
mask_of_ones = time_mask(from_time, to_time)
|
226
|
-
mask = mask_of_ones ^ working_day & working_day
|
227
|
-
values[day] = values[day] & mask
|
228
|
-
end
|
229
|
-
|
230
|
-
def time_mask(from_time, to_time)
|
231
|
-
bit_pos(to_time.hour, to_time.min + 1) - bit_pos(from_time.hour, from_time.min)
|
232
|
-
end
|
233
|
-
|
234
|
-
def bit_pos(hour, minute)
|
235
|
-
2**((hour * 60) + minute)
|
236
|
-
end
|
237
|
-
|
238
|
-
def minutes_to_end_of_day(date)
|
239
|
-
working_minutes_in pattern_to_end_of_day(date)
|
240
|
-
end
|
241
|
-
|
242
|
-
def pattern_to_end_of_day(date)
|
243
|
-
mask = mask_to_end_of_day(date)
|
244
|
-
(values[date.wday] & mask)
|
245
|
-
end
|
246
|
-
|
247
|
-
def mask_to_end_of_day(date)
|
248
|
-
bit_pos(hours_per_day, 0) - bit_pos(date.hour, date.min)
|
249
|
-
end
|
250
|
-
|
251
|
-
def working_day
|
252
|
-
2**(60 * hours_per_day) - 1
|
178
|
+
[a_date, a_duration]
|
253
179
|
end
|
254
180
|
|
255
181
|
def start_of_next_day(date)
|
256
182
|
next_day(date) - (HOUR * date.hour) - (MINUTE * date.min)
|
257
183
|
end
|
258
184
|
|
259
|
-
def
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
def start_of_today(date)
|
264
|
-
start_of_next_day(prev_day(date))
|
265
|
-
end
|
266
|
-
|
267
|
-
def end_of_this_day(date)
|
268
|
-
position = pattern_to_end_of_day(date).to_s(2).size
|
269
|
-
adjust_date(date, position)
|
270
|
-
end
|
271
|
-
|
272
|
-
def adjust_date(date, adjustment)
|
273
|
-
date - (HOUR * date.hour) - (MINUTE * date.min) + (MINUTE * adjustment)
|
274
|
-
end
|
275
|
-
|
276
|
-
def mask_to_start_of_day(date)
|
277
|
-
bit_pos(date.hour, date.min) - bit_pos(0, 0)
|
278
|
-
end
|
279
|
-
|
280
|
-
def pattern_to_start_of_day(date)
|
281
|
-
mask = mask_to_start_of_day(date)
|
282
|
-
(values[date.wday] & mask)
|
283
|
-
end
|
284
|
-
|
285
|
-
def minutes_to_start_of_day(date)
|
286
|
-
working_minutes_in pattern_to_start_of_day(date)
|
287
|
-
end
|
288
|
-
|
289
|
-
def consume_minutes(date, duration)
|
290
|
-
minutes = pattern_to_end_of_day(date).to_s(2).reverse! if duration > 0
|
291
|
-
minutes = pattern_to_start_of_day(date).to_s(2) if duration < 0
|
292
|
-
|
293
|
-
top = minutes.size
|
294
|
-
bottom = 1
|
295
|
-
mark = top / 2
|
296
|
-
|
297
|
-
while minutes[0, mark].count('1') != duration.abs
|
298
|
-
last_mark = mark
|
299
|
-
if minutes[0, mark].count('1') < duration.abs
|
300
|
-
|
301
|
-
bottom = mark
|
302
|
-
mark = (top - mark) / 2 + mark
|
303
|
-
mark = top if last_mark == mark
|
304
|
-
|
305
|
-
else
|
306
|
-
|
307
|
-
top = mark
|
308
|
-
mark = (mark - bottom) / 2 + bottom
|
309
|
-
mark = bottom if last_mark == mark
|
310
|
-
|
311
|
-
end
|
312
|
-
end
|
313
|
-
|
314
|
-
mark = minutes_addition_adjustment(minutes, mark) if duration > 0
|
315
|
-
mark = minutes_subtraction_adjustment(minutes, mark) if duration < 0
|
316
|
-
|
317
|
-
return adjust_date(date, mark) if duration > 0
|
318
|
-
return start_of_today(date) + (MINUTE * mark) if duration < 0
|
319
|
-
end
|
320
|
-
|
321
|
-
def minutes_subtraction_adjustment(minutes, mark)
|
322
|
-
i = mark - 1
|
185
|
+
def subtract_to_start_of_day(a_date, a_duration, a_day)
|
186
|
+
|
187
|
+
|
188
|
+
a_date, a_duration, a_day = handle_midnight(a_date, a_duration, a_day)
|
323
189
|
|
324
|
-
|
325
|
-
i -= 1
|
326
|
-
end
|
190
|
+
r_date, r_duration, r_day = @days[a_date.wday].calc(a_date, a_duration)
|
327
191
|
|
328
|
-
|
329
|
-
end
|
330
|
-
|
331
|
-
def minutes_addition_adjustment(minutes, mark)
|
332
|
-
minutes = minutes[0, mark]
|
192
|
+
[r_date, r_duration, r_day]
|
333
193
|
|
334
|
-
while minutes[minutes.size - 1] == '0'
|
335
|
-
minutes.chop!
|
336
|
-
end
|
337
|
-
|
338
|
-
minutes.size
|
339
194
|
end
|
340
195
|
|
341
|
-
def
|
342
|
-
|
343
|
-
|
196
|
+
def handle_midnight(a_date, a_duration, a_day)
|
197
|
+
|
198
|
+
if a_day == PREVIOUS_DAY
|
199
|
+
a_date -= DAY
|
200
|
+
a_date = Time.gm(a_date.year, a_date.month, a_date.day,LAST_TIME_IN_DAY.hour, LAST_TIME_IN_DAY.min)
|
344
201
|
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
initial_date = start_of_previous_day(initial_date)
|
349
|
-
midnight = true
|
350
|
-
else
|
351
|
-
initial_date = consume_minutes(initial_date, duration)
|
352
|
-
duration = 0
|
353
|
-
midnight = false
|
354
|
-
end
|
202
|
+
if @days[a_date.wday].working?(a_date.hour, a_date.min)
|
203
|
+
a_duration += 1
|
204
|
+
end
|
355
205
|
end
|
356
|
-
[initial_date, duration, midnight]
|
357
|
-
end
|
358
|
-
|
359
|
-
def handle_midnight(initial_date, duration)
|
360
|
-
if working?(start_of_next_day(initial_date) - MINUTE)
|
361
|
-
duration += 1
|
362
|
-
end
|
363
|
-
|
364
|
-
initial_date -= (HOUR * initial_date.hour)
|
365
|
-
initial_date -= (MINUTE * initial_date.min)
|
366
|
-
initial_date = next_day(initial_date) - MINUTE
|
367
206
|
|
368
|
-
[
|
207
|
+
[a_date, a_duration, SAME_DAY]
|
369
208
|
end
|
370
209
|
|
371
|
-
def subtract(
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
210
|
+
def subtract(a_date, a_duration, a_day)
|
211
|
+
a_date, a_duration, a_day = handle_midnight(a_date, a_duration, a_day)
|
212
|
+
a_date, a_duration, a_day = subtract_to_start_of_day(a_date, a_duration, a_day)
|
213
|
+
|
214
|
+
while (a_duration != 0) && (a_date.wday != start.wday) && (jd(a_date) > jd(start))
|
215
|
+
a_date, a_duration, a_day = handle_midnight(a_date, a_duration, a_day)
|
216
|
+
a_date, a_duration, a_day = subtract_to_start_of_day(a_date, a_duration, a_day)
|
378
217
|
end
|
379
218
|
|
380
|
-
while (
|
381
|
-
|
382
|
-
|
219
|
+
while (a_duration != 0) && (a_duration >= week_total) && ((jd(a_date) - (6 * DAY)) >= jd(start))
|
220
|
+
a_duration += week_total
|
221
|
+
a_date -= 7
|
383
222
|
end
|
384
223
|
|
385
|
-
while (
|
386
|
-
|
387
|
-
duration,
|
388
|
-
midnight)
|
224
|
+
while (a_duration != 0) && (jd(a_date) > jd(start))
|
225
|
+
a_date, a_duration, a_day = subtract_to_start_of_day(a_date,a_duration,a_day)
|
389
226
|
end
|
390
227
|
|
391
|
-
[
|
228
|
+
[a_date, a_duration, a_day]
|
392
229
|
end
|
393
230
|
|
394
231
|
def diff_in_same_weekpattern(start_date, finish_date)
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
duration += minutes_to_end_of_day(start_date)
|
401
|
-
start_date = start_of_next_day(start_date)
|
232
|
+
minutes = @days[start_date.wday].working_minutes(start_date, LAST_TIME_IN_DAY)
|
233
|
+
run_date = start_of_next_day(start_date)
|
234
|
+
while (run_date.wday != start.wday) && (jd(run_date) < jd(finish)) && (jd(run_date) != jd(finish_date))
|
235
|
+
minutes += @days[run_date.wday].working_minutes
|
236
|
+
run_date += DAY
|
402
237
|
end
|
403
238
|
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
duration += week_total
|
408
|
-
start_date += (7 * 86_400)
|
239
|
+
while ((jd(run_date) + (7 * DAY)) < jd(finish_date)) && ((jd(run_date) + (7 * DAY)) < jd(finish))
|
240
|
+
minutes += week_total
|
241
|
+
run_date += (7 * DAY)
|
409
242
|
end
|
410
243
|
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
duration += minutes_to_end_of_day(start_date)
|
415
|
-
start_date = start_of_next_day(start_date)
|
244
|
+
while (jd(run_date) < jd(finish_date)) && (jd(run_date) <= jd(finish))
|
245
|
+
minutes += @days[run_date.wday].working_minutes
|
246
|
+
run_date += DAY
|
416
247
|
end
|
417
248
|
|
418
|
-
if
|
419
|
-
|
249
|
+
if run_date != finish_date
|
250
|
+
|
251
|
+
if (jd(run_date) == jd(finish_date)) && (jd(run_date) <= jd(finish))
|
252
|
+
minutes += @days[run_date.wday].working_minutes(run_date, finish_date - MINUTE)
|
253
|
+
run_date = finish_date
|
254
|
+
elsif (jd(run_date) <= jd(finish))
|
255
|
+
minutes += @days[run_date.wday].working_minutes
|
256
|
+
run_date += DAY
|
257
|
+
end
|
420
258
|
end
|
421
|
-
duration += interim_duration unless interim_duration.nil?
|
422
|
-
[duration, start_date]
|
423
|
-
end
|
424
259
|
|
425
|
-
|
426
|
-
duration, start_date = diff_in_same_weekpattern(start_date, finish_date)
|
427
|
-
[duration, start_date]
|
428
|
-
end
|
260
|
+
[minutes, run_date]
|
429
261
|
|
430
|
-
def diff_to_tomorrow(start_date)
|
431
|
-
finish_bit_pos = bit_pos(hours_per_day, 0)
|
432
|
-
start_bit_pos = bit_pos(start_date.hour, start_date.min)
|
433
|
-
mask = finish_bit_pos - start_bit_pos
|
434
|
-
minutes = working_minutes_in(values[start_date.wday] & mask)
|
435
|
-
[minutes, start_of_next_day(start_date)]
|
436
262
|
end
|
437
263
|
|
438
264
|
def diff_in_same_day(start_date, finish_date)
|
439
|
-
|
440
|
-
start_bit_pos = bit_pos(start_date.hour, start_date.min)
|
441
|
-
mask = finish_bit_pos - start_bit_pos
|
442
|
-
minutes = working_minutes_in(values[start_date.wday] & mask)
|
265
|
+
minutes = @days[start_date.wday].working_minutes(start_date, finish_date - MINUTE)
|
443
266
|
[minutes, finish_date]
|
444
267
|
end
|
445
268
|
|
446
269
|
def next_day(time)
|
447
|
-
time +
|
270
|
+
time + DAY
|
448
271
|
end
|
449
272
|
|
450
273
|
def prev_day(time)
|
451
|
-
time -
|
274
|
+
time - DAY
|
452
275
|
end
|
453
276
|
|
454
277
|
def jd(time)
|