File Coverage

blib/lib/App/JobLog/Vacation/Period.pm
Criterion Covered Total %
statement 36 187 19.2
branch 0 66 0.0
condition 0 40 0.0
subroutine 12 33 36.3
pod 14 15 93.3
total 62 341 18.1


line stmt bran cond sub pod time code
1             package App::JobLog::Vacation::Period;
2             $App::JobLog::Vacation::Period::VERSION = '1.041';
3             # ABSTRACT: extension of L to handle special properties of vacation periods
4              
5              
6 2     2   11 use Exporter 'import';
  2         4  
  2         106  
7             our @EXPORT_OK = qw(
8             FLEX
9             FIXED
10             ANNUAL
11             MONTHLY
12             );
13              
14 2     2   11 use base 'App::JobLog::Log::Event';
  2         4  
  2         41  
15 2     2   412 use DateTime;
  2         5  
  2         14  
16 2     2   59 use App::JobLog::Log::Line;
  2         3  
  2         25  
17 2     2   106 use App::JobLog::Time qw(tz);
  2         5  
  2         106  
18 2     2   10 use Carp qw(carp);
  2         18  
  2         106  
19              
20 2     2   29 use overload '""' => \&to_string;
  2         4  
  2         20  
21 2     2   164 use overload 'bool' => sub { 1 };
  2     0   5  
  2         13  
  0         0  
22              
23 2     2   100 use constant FLEX => 1;
  2         4  
  2         110  
24 2     2   10 use constant FIXED => 2;
  2         4  
  2         94  
25 2     2   15 use constant ANNUAL => 1;
  2         4  
  2         92  
26 2     2   10 use constant MONTHLY => 2;
  2         4  
  2         4644  
27              
28             sub new {
29 0     0 1   my ( $class, $log_line, %opts ) = @_;
30 0   0       $class = ref $class || $class;
31 0           bless {
32             log => $log_line,
33             type => 0,
34             repeats => 0,
35             tags => [],
36             events => [],
37             vacation => [],
38             %opts
39             },
40             $class;
41             }
42              
43              
44 0     0 1   sub flex { $_[0]->{type} == FLEX }
45              
46              
47 0     0 1   sub fixed { $_[0]->{type} == FIXED }
48              
49              
50 0     0 1   sub annual { $_[0]->{repeats} == ANNUAL }
51              
52              
53 0     0 0   sub monthly { $_[0]->{repeats} == MONTHLY }
54              
55              
56 0     0 1   sub repeats { $_[0]->{repeats} }
57              
58              
59             sub description : lvalue {
60 0     0 1   $_[0]->data->description;
61             }
62              
63              
64             sub clone {
65 0     0 1   my ($self) = @_;
66 0           my $clone = $self->SUPER::clone;
67 0           $clone->{type} = $self->{type};
68 0           $clone->{repeats} = $self->{repeats};
69 0           return $clone;
70             }
71              
72              
73             sub cmp {
74 0     0 1   my ( $self, $other ) = @_;
75              
76             # when mixed with ordinary events
77 0 0         if ( ref $other eq 'App::JobLog::Log::Event' ) {
78              
79             # treat as an ordinary event if fixed
80 0 0         return $self->SUPER::cmp($other) if $self->fixed;
81              
82             # put after ordinary events
83 0           return 1;
84             }
85 0 0         if ( $self->monthly ) {
    0          
86 0 0         return -1 unless $other->monthly;
87             }
88             elsif ( $self->annual ) {
89 0 0         return 1 if $other->monthly;
90 0 0         return -1 unless $other->annual;
91             }
92 0           return $self->SUPER::cmp($other);
93             }
94              
95             # some global variables for use in BNF regex
96             our ( @dates, $type, @tags, $description );
97              
98             # log line parser
99             my $re = qr{
100             ^ (?&ts) : (?&non_ts) $
101             (?(DEFINE)
102             (? (?&date) : (?&date) )
103             (? (\d{4}\s++\d++\s++\d++\s++\d++\s++\d++\s++\d++) (?{push @dates, $^N}) )
104             (? (?&flex) : (?&tags) : (?&description))
105             (? ([012]{2}) (?{$type = $^N}))
106             (? (?:(?&tag)(\s++(?&tag))*+)?)
107             (? ((?:[^\s:\\]|(?&escaped))++) (?{push @tags, $^N}))
108             (? \\.)
109             (? (.++) (?{$description = $^N}))
110             )
111             }xi;
112              
113              
114             sub parse {
115 0     0 1   my ( $class, $text ) = @_;
116 0   0       $class = ref $class || $class;
117 0           local ( @dates, $type, @tags, $description );
118 0 0         if ( $text =~ $re ) {
119 0           my $start = _parse_time( $dates[0] );
120 0           my $end = _parse_time( $dates[1] );
121 0           my %tags = map { $_ => 1 } @tags;
  0            
122 0           my $tags = [ map { s/\\(.)/$1/g; $_ } sort keys %tags ];
  0            
  0            
123 0           $description = [ map { s/\\(.)/$1/g; $_ } ($description) ];
  0            
  0            
124 0           my ( $type, $repeats ) = split //, $type;
125 0           $obj = $class->new(
126             App::JobLog::Log::Line->new(
127             description => $description,
128             time => $start,
129             tags => $tags
130             ),
131             type => $type,
132             repeats => $repeats,
133             end => $end
134             );
135 0           return $obj;
136             }
137             else {
138 0           carp "malformed line in vacation file: '$text'";
139             }
140 0           return;
141             }
142              
143             sub _parse_time {
144 0     0     my @time = split /\s++/, $_[0];
145 0           $date = DateTime->new(
146             year => $time[0],
147             month => $time[1],
148             day => $time[2],
149             hour => $time[3],
150             minute => $time[4],
151             second => $time[5],
152             time_zone => tz,
153             );
154 0           return $date;
155             }
156              
157              
158             sub to_string {
159 0     0 1   my ($self) = @_;
160 0           my $text = $self->data->time_stamp( $self->start );
161 0           $text .= ':';
162 0           $text .= $self->data->time_stamp( $self->end );
163 0           $text .= ':';
164 0 0         if ( $self->flex ) {
    0          
165 0           $text .= FLEX;
166             }
167             elsif ( $self->fixed ) {
168 0           $text .= FIXED;
169             }
170             else {
171 0           $text .= 0;
172             }
173 0 0         if ( $self->annual ) {
    0          
174 0           $text .= ANNUAL;
175             }
176             elsif ( $self->monthly ) {
177 0           $text .= MONTHLY;
178             }
179             else {
180 0           $text .= 0;
181             }
182 0           $text .= ':';
183 0   0       $self->tags ||= [];
184 0           my %tags = map { $_ => 1 } @{ $self->tags };
  0            
  0            
185 0           $text .= join ' ', map { s/([:\\\s])/\\$1/g; $_ } sort keys %tags;
  0            
  0            
186 0           $text .= ':';
187 0   0       $self->description ||= [];
188             $text .= join ';',
189 0           map { ( my $d = $_ ) =~ s/([;\\])/\\$1/g; $d } @{ $self->description };
  0            
  0            
  0            
190             }
191              
192              
193             sub conflicts {
194 0     0 1   my ( $self, $other ) = @_;
195 0 0         return 1 if $self->intersects($other);
196 0           my $other_is_period = ref $other eq __PACKAGE__;
197 0 0 0       if ( $self->annual || $other_is_period && $other->annual ) {
    0 0        
      0        
      0        
198 0 0         if ( $self->start->year != $other->start->year ) {
199 0 0         if ( !$self->annual ) {
200 0           my $t = $self;
201 0           $self = $other;
202 0           $other = $t;
203             }
204 0           $self = $self->clone;
205 0           my $d = $self->start->year - $other->start->year;
206 0           $self->start->subtract( years => $d );
207 0           $self->end->subtract( years => $d );
208 0           return $self->intersects($other);
209             }
210             }
211             elsif ( $self->monthly || $other_is_period && $other->monthly ) {
212 0 0 0       if ( $self->start->year != $other->start->year
213             || $self->start->month != $other->start->month )
214             {
215 0 0         if ( !$self->monthly ) {
216 0           my $t = $self;
217 0           $self = $other;
218 0           $other = $t;
219             }
220 0           $self = $self->clone;
221 0           my $d = $self->start->year - $other->start->year;
222 0           $self->start->subtract( years => $d );
223 0           $self->end->subtract( years => $d );
224 0           $d = $self->start->month - $other->start->month;
225 0           $self->start->subtract( months => $d );
226 0           $self->end->subtract( months => $d );
227 0           return $self->intersects($other);
228             }
229             }
230 0           return 0;
231             }
232              
233              
234             sub parts {
235 0     0 1   my ($self) = @_;
236 0           return $self->_time, $self->_properties, $self->_tags, $self->_description;
237             }
238              
239              
240             sub single_day {
241 0     0 1   my ($self) = @_;
242 0           my ( $s, $e ) = ( $self->start, $self->end );
243 0   0       return $s->year == $e->year && $s->month == $e->month && $s->day == $e->day;
244             }
245              
246             # time part of summary
247             sub _time {
248 0     0     my ($self) = @_;
249 0           my $fmt;
250 0 0         if ( $self->annual ) {
    0          
251 0           $fmt = '%b %d';
252             }
253             elsif ( $self->monthly ) {
254 0           $fmt = '%d';
255             }
256             else {
257 0           $fmt = '%F';
258             }
259 0 0         $fmt .= ' %H:%M:%S' if $self->fixed;
260 0           my $s;
261 0 0         if ( $self->single_day ) {
262 0           $s = $self->start->strftime($fmt);
263             }
264             else {
265 0           $s = $self->start->strftime($fmt) . ' -- ' . $self->end->strftime($fmt);
266             }
267 0           return $s;
268             }
269              
270             # properties part of summary
271             sub _properties {
272 0     0     my ($self) = @_;
273 0           my $s;
274 0 0         if ( $self->fixed ) {
    0          
275 0           $s = 'fixed';
276             }
277             elsif ( $self->flex ) {
278 0           $s = 'flex';
279             }
280             else {
281 0           $s = '';
282             }
283 0 0         if ( $self->annual ) {
    0          
284 0 0         $s .= ' ' if $s;
285 0           $s .= 'annual';
286             }
287             elsif ( $self->monthly ) {
288 0 0         $s .= ' ' if $s;
289 0           $s .= 'monthly';
290             }
291 0           return $s;
292             }
293              
294              
295             sub overlap {
296 0     0 1   my ( $self, $start, $end ) = @_;
297 0 0 0       if ( $self->annual || $self->monthly ) {
298              
299             # cloning here should be duplicated work, but better safe than sorry
300 0           my $cloned = 0;
301 0 0 0       if ( $self->annual
      0        
302             || $self->monthly && $self->start->year != $start->year )
303             {
304 0           $self = $self->clone;
305 0           $cloned = 1;
306 0           my $delta = $start->year - $self->start->year;
307 0           $self->start->add( years => $delta );
308 0           $self->end->add( years => $delta );
309             }
310 0 0 0       if ( $self->monthly && $self->start->month != $start->month ) {
311 0 0         $self = $self->clone unless $cloned;
312 0           my $delta = $start->month - $self->start->month;
313 0           $self->start->add( months => $delta );
314 0           $self->end->add( months => $delta );
315             }
316             }
317 0           return $self->SUPER::overlap( $start, $end );
318             }
319              
320             # tag part of summary
321             sub _tags {
322 0     0     my ($self) = @_;
323 0           return join ', ', @{ $self->tags };
  0            
324             }
325              
326             # description part of summary
327             sub _description {
328 0     0     my ($self) = @_;
329 0           return join '; ', @{ $self->description };
  0            
330             }
331              
332             1;
333              
334             __END__