File Coverage

blib/lib/Time/WorkHours.pm
Criterion Covered Total %
statement 77 77 100.0
branch 13 16 81.2
condition 6 12 50.0
subroutine 14 14 100.0
pod 8 8 100.0
total 118 127 92.9


line stmt bran cond sub pod time code
1             package Time::WorkHours;
2              
3             =head1 NAME
4              
5             Time::WorkHours - proportional shift DateTime to a work hours and few work hours routines
6              
7             =head1 SYNOPSIS
8              
9             # our work hours is from 02:00 until 07:00
10             my $wh = Time::WorkHours->new(
11             work_interval_start => '02h',
12             work_interval => '5h', # or 5*60
13             );
14            
15             # shift given date to our work hours
16             my $new_datetime = $wh->shift_to_work_time(DateTime->new(
17             'hour' => 14,
18             'minute' => 32,
19             # ... what ever month, year, day
20             ));
21              
22             =head1 DESCRIPTION
23              
24             Purpouse of this module is to equaly distribute tasks that are comming
25             through the whole day into certain day interval.
26              
27             Or just move comming request to the start of work hours.
28              
29             For example imagine you want to process smoke tests only in the idle
30             server hours - in the night. But the CPAN modules are comming through
31             all day. You can note down the modules as they are comming and set
32             the processing time to C<< $wh->shift_to_work_time() >> so that
33             it will not take the processing time when the server has to do it's
34             "real" job.
35              
36             =cut
37              
38 2     2   153943 use warnings;
  2         4  
  2         80  
39 2     2   183 use strict;
  2         5  
  2         107  
40              
41             our $VERSION = '0.01';
42              
43 2     2   8254 use DateTime;
  2         588119  
  2         94  
44 2     2   2044 use Carp::Clan 'croak';
  2         4123  
  2         20  
45              
46 2     2   341 use base 'Class::Accessor::Fast';
  2         4  
  2         2218  
47              
48             =head1 PROPERTIES
49              
50             work_interval_start
51             work_interval
52              
53             =cut
54              
55             __PACKAGE__->mk_accessors(qw{
56             work_interval_start
57             work_interval
58             });
59              
60              
61             my $DAY_MINUTES = 24*60;
62              
63              
64             =head1 METHODS
65              
66             =head2 new()
67              
68             Object constructor. Pass two mandatory arguments. C
69             and C.
70              
71             C is the minute (or hour) when the work hours
72             start.
73              
74             C is how many minutes (or hours) does the work interval
75             last.
76              
77             Both can be passed as a number in that case must represent minutes or as a string
78             with numbers and 'h' at the end representing the value in hours.
79              
80             Example:
81              
82             $wh = Time::WorkHours->new(
83             work_interval_start => '2h', # or 120
84             work_interval => 180, # or '3h'
85             );
86              
87             Work interval starts at 02:00 and lasts for 3 hours.
88              
89             =cut
90              
91             sub new {
92 207     207 1 186475 my $class = shift;
93 207         1790 my $self = $class->SUPER::new({ @_ });
94            
95 207 50       2814 croak 'pass work_interval_start'
96             if not defined $self->work_interval_start;
97 207 50       1661 croak 'pass work_interval'
98             if not defined $self->work_interval;
99            
100 207         1464 return $self;
101             }
102              
103              
104             =head2 work_start($datetime)
105              
106             Return nearest DateTime when the work time starts. If inside
107             the work interval then returns start datetime of this
108             interval.
109              
110             If argument not passed the default is C<< DateTime->now >>.
111              
112             =cut
113              
114             sub work_start {
115 2554     2554 1 58569 my $self = shift;
116 2554         3663 my $date = shift;
117            
118             # make a copy of passed DateTime
119 2554 100       7501 if ($date) {
120 2553         100234 $date = $date->clone();
121             }
122             else {
123 1         5 $date = DateTime->now();
124             }
125 2554         34089 _strip_seconds($date);
126 2554         7367 my $work_start = $date->clone;
127              
128 2554         31115 my $work_interval_start = $self->work_interval_start_minute;
129 2554         5629 my $work_interval = $self->work_interval_minutes;
130 2554         3757 my $work_interval_end = $work_interval_start + $work_interval;
131 2554         3658 my $work_interval_shift += $DAY_MINUTES - $work_interval_end;
132              
133 2554         6567 $date->add('minutes' => $work_interval_shift);
134 2554         1455742 my $date_minutes = $date->hour*60 + $date->minute;
135            
136 2554         28003 $work_start->add('minutes' => $work_interval_start + $work_interval_shift - $date_minutes);
137            
138 2554         1449562 return $work_start;
139             }
140              
141              
142             =head2 next_work_start($datetime)
143              
144             Same as work_start but will always return DateTime in the
145             future.
146              
147             =cut
148              
149             sub next_work_start {
150 2     2 1 2495 my $self = shift;
151 2         5 my $date = shift;
152            
153 2         10 my $work_start = $self->work_start($date);
154            
155             # shift by 24h if date is within work hours so the work_start is in the past
156 2 100       12 $work_start->add('hours' => 24)
157             if $work_start < $date;
158            
159 2         818 return $work_start;
160             }
161              
162              
163             =head2 work_end($datetime)
164              
165             Returns nearest end of the work time.
166              
167             If argument not passed the default is C<< DateTime->now >>.
168              
169             =cut
170              
171             sub work_end {
172 1174     1174 1 27908 my $self = shift;
173 1174   66     3859 my $date = shift || DateTime->now;
174            
175 1174         49702 my $work_start = $self->work_start($date);
176 1174         3304 my $work_interval = $self->work_interval_minutes;
177              
178 1174         3738 return $work_start->add('minutes' => $work_interval);;
179             }
180              
181              
182             =head2 within($datetime)
183              
184             Return true/false if the $datetime lies within work hours.
185              
186             If argument not passed the default is C<< DateTime->now >>.
187              
188             =cut
189              
190             sub within {
191 1072     1072 1 630331 my $self = shift;
192 1072   33     3786 my $date = shift || DateTime->now;
193            
194 1072         51696 my $work_start_datetime = $self->work_start($date);
195 1072         3252 my $work_end_datetime = $self->work_end($date);
196            
197 1072 100 66     618068 return 1
198             if (($date >= $work_start_datetime) and ($date < $work_end_datetime));
199 713         63739 return 0;
200             }
201              
202              
203             =head2 shift_to_work_time($date)
204              
205             Takes the $date and moves it to the neares work time interval.
206             The shift is calculated proportionaly so that the time shifts are
207             distributed in the work hour interval in the same order as
208             they occure in 24h interval.
209              
210             Example:
211              
212             my $wh = Time::WorkHours->new(
213             work_interval_start => '22h',
214             work_interval => '4h',
215             );
216             my $new_datetime = $wh->shift_to_work_time(DateTime->new(
217             'day' => 5,
218             'hour' => 14,
219             'minute' => 00,
220             # ... what ever month, year
221             ));
222              
223             Will shift to next day to 00:00 as 14:00 is just in the middle of 02:00 - (22:00) - 02:00
224             interval so it's shifted to the middle of 22:00 - 02:00 work hours.
225              
226             If the DateTime will be at 01:59 (last minute of the work interval) there will be no shift.
227              
228             If the DateTime will be at 02:00 (first non work minute) the shift will be to 22:00.
229              
230             If argument not passed the default is C<< DateTime->now >>.
231              
232             =cut
233              
234             sub shift_to_work_time {
235 103     103 1 43231 my $self = shift;
236 103   33     451 my $date = shift || DateTime->now;
237            
238 103 50       4451 croak 'pass DataTime object as argument'
239             if ref $date ne 'DateTime';
240            
241 103         319 $date = $date->clone;
242            
243 103         1237 my $work_interval_start = $self->work_interval_start_minute;
244 103         230 my $work_interval = $self->work_interval_minutes;
245 103         263 my $work_start = $self->work_start($date);
246 103         197 my $work_interval_end = $work_interval_start + $work_interval;
247 103         150 my $work_interval_shift = $DAY_MINUTES - $work_interval_end;
248              
249 103         301 $date->add('minutes' => $work_interval_shift);
250 103         62071 my $date_minutes = $date->hour*60 + $date->minute;
251            
252 103         1118 my $event_date = $work_start->add('minutes' => ($date_minutes / $DAY_MINUTES) * $work_interval);
253            
254 103         57650 return $event_date;
255             }
256              
257              
258             =head2 work_interval_start_minute()
259              
260             Return number of minute in the day when the work interval starts.
261              
262             =cut
263              
264             sub work_interval_start_minute {
265 2658     2658 1 4998 my $self = shift;
266 2658         7872 my $work_interval_start = $self->work_interval_start;
267            
268 2658 100       23690 if ($work_interval_start =~ m/\b([0-9]+)h$/) {
269 2358         9619 return $1*60;
270             }
271             else {
272 300         761 return $work_interval_start;
273             }
274             }
275              
276              
277             =head2 work_interval_minutes()
278              
279             Return for how many minutes does the work interval lasts.
280              
281             =cut
282              
283             sub work_interval_minutes {
284 3832     3832 1 5111 my $self = shift;
285 3832         9850 my $work_interval = $self->work_interval;
286            
287 3832 100       27640 if ($work_interval =~ m/\b([0-9]+)h$/) {
288 2932         8700 return $1*60;
289             }
290             else {
291 900         2276 return $work_interval;
292             }
293             }
294              
295             sub _strip_seconds {
296 2554     2554   3354 my $date = shift;
297 2554         7309 $date->add('seconds' => -$date->second);
298 2554         1463651 return $date;
299             }
300              
301              
302             'ROMERQUELLE(R)';
303              
304              
305             __END__