File Coverage

blib/lib/Org/Element/Timestamp.pm
Criterion Covered Total %
statement 65 79 82.2
branch 29 56 51.7
condition 21 27 77.7
subroutine 8 9 88.8
pod 3 3 100.0
total 126 174 72.4


line stmt bran cond sub pod time code
1              
2             use 5.010;
3 12     12   1374 use locale;
  12         37  
4 12     12   55 use utf8;
  12         41  
  12         64  
5 12     12   4896 use Moo;
  12         112  
  12         86  
6 12     12   313 no if $] >= 5.021_006, warnings => "locale";
  12         23  
  12         60  
7 12     12   3523 extends 'Org::Element';
  12         23  
  12         94  
8             with 'Org::ElementRole';
9             with 'Org::ElementRole::Inline';
10              
11             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
12             our $DATE = '2022-06-23'; # DATE
13             our $DIST = 'Org-Parser'; # DIST
14             our $VERSION = '0.558'; # VERSION
15              
16             my @attrs = (qw/datetime has_time event_duration recurrence is_active/);
17             for (@attrs) {
18             has $_ => (is => 'rw', clearer=>"clear_$_");
19             before $_ => sub {
20             my $self = shift;
21             return unless defined $self->_is_parsed; # never been parsed
22             $self->_parse_timestamp($self->_str)
23             unless $self->_is_parsed; # has been reset, re-set
24             };
25             }
26              
27             has _repeater => (is => 'rw'); # stores the raw repeater spec, for as_string
28             has _warning_period => (is => 'rw'); # raw warning period spec, for as_string
29             has _is_parsed => (is => 'rw');
30              
31             my $self = shift;
32             return unless defined $self->_is_parsed; # never been parsed
33 1     1 1 978 for (@attrs) { my $m = "clear_$_"; $self->$m }
34 1 50       5 $self->_is_parsed(0);
35 1         4 }
  5         44  
  5         86  
36 1         9  
37             our @dow = (undef, qw(Mon Tue Wed Thu Fri Sat Sun));
38              
39             my ($self) = @_;
40             return $self->_str if $self->_str;
41             my $dt = $self->datetime;
42 18     18 1 8448 my ($hour2, $min2);
43 18 50       119 if ($self->event_duration) {
44 0         0 my $hour = $dt->hour;
45 0         0 my $min = $dt->minute;
46 0 0       0 my $mins = $self->event_duration / 60;
47 0         0 $min2 = $min + $mins;
48 0         0 my $hours = int ($min2 / 60);
49 0         0 $hour2 = $hour + $hours;
50 0         0 $min2 = $min2 % 60;
51 0         0 }
52 0         0 join("",
53 0         0 $self->is_active ? "<" : "[",
54             $dt->ymd, " ",
55 0 0       0 $dow[$dt->day_of_week],
    0          
    0          
    0          
    0          
    0          
56             $self->has_time ? (
57             " ",
58             sprintf("%02d:%02d", $dt->hour, $dt->minute),
59             defined($hour2) ? (
60             "-",
61             sprintf("%02d:%02d", $hour2, $min2),
62             ) : (),
63             $self->_repeater ? (
64             " ",
65             $self->_repeater,
66             ) : (),
67             $self->_warning_period ? (
68             " ",
69             $self->_warning_period,
70             ) : (),
71             ) : (),
72             $self->is_active ? ">" : "]",
73             );
74             }
75              
76             goto \&as_string;
77             }
78              
79             require DateTime;
80 0     0 1 0 require DateTime::Event::Recurrence;
81             my ($self, $str, $opts) = @_;
82             $self->_is_parsed(undef); # to avoid deep recursion
83             $opts //= {};
84 70     70   7752 $opts->{allow_event_duration} //= 1;
85 70         3965746 $opts->{allow_repeater} //= 1;
86 70         507669  
87 70         219 my $num_re = qr/\d+(?:\.\d+)?/;
88 70   100     318  
89 70   100     299 my $dow_re = qr/\w{1,3} | # common, chinese 四, english thu
90 70   100     240 \w{2,3}\. # french mer., german Mi.
91             /x;
92 70         220  
93             $str =~ /^(?<open_bracket> \[|<)
94 70         147 (?<year> \d{4})-(?<mon> \d{2})-(?<day> \d{2})
95             (?:
96             (?:\s* (?<dow> $dow_re) \s*)?
97             (?:\s+
98 70 50       2310 (?<hour> \d{2}):(?<min> \d{2})
99             (?:-
100             (?<event_duration>
101             (?<hour2> \d{2}):(?<min2> \d{2}))
102             )?
103             )?
104             (?:\s+(?<repeater>
105             (?<repeater_prefix> \+\+|\.\+|\+)
106             (?<repeater_interval> $num_re)
107             (?<repeater_unit> [dwmy])
108             (?:\/(?<repeater_interval_max> $num_re)
109             (?<repeater_unit_max> [dwmy]))?
110             )
111             )?
112             (?:\s+(?<warning_period>
113             -
114             (?<warning_period_interval> $num_re)
115             (?<warning_period_unit> [dwmy])
116             )
117             )?
118             )?
119             \s* (?<close_bracket> \]|>)
120             $/x
121             or die "Can't parse timestamp string: $str";
122             # just for sanity. usually doesn't happen though because Document gives us
123             # either "[...]" or "<...>"
124             die "Mismatch open/close brackets in timestamp: $str"
125             if $+{open_bracket} eq '<' && $+{close_bracket} eq ']' ||
126             $+{open_bracket} eq '[' && $+{close_bracket} eq '>';
127             die "Duration not allowed in timestamp: $str"
128             if !$opts->{allow_event_duration} && $+{event_duration};
129             die "Repeater ($+{repeater}) not allowed in timestamp: $str"
130             if !$opts->{allow_repeater} && $+{repeater};
131 70 50 66     1106  
      66        
      33        
132             $self->is_active($+{open_bracket} eq '<' ? 1:0)
133 70 100 100     318 unless defined $self->is_active;
134              
135 69 100 100     197 if ($+{event_duration} && !defined($self->event_duration)) {
136             $self->event_duration(
137 68 100       1418 ($+{hour2}-$+{hour})*3600 +
    50          
138             ($+{min2} -$+{min} )*60
139             );
140 68 100 66     641 }
141              
142             my %dt_args = (year => $+{year}, month=>$+{mon}, day=>$+{day});
143 3         77 if (defined($+{hour})) {
144             $dt_args{hour} = $+{hour};
145             $dt_args{minute} = $+{min};
146             $self->has_time(1);
147 68         590 } else {
148 68 100       372 $self->has_time(0);
149 20         65 }
150 20         67 if ($self->document->time_zone) {
151 20         371 $dt_args{time_zone} = $self->document->time_zone;
152             }
153 48         806 #use Data::Dump; dd \%dt_args;
154             my $dt = DateTime->new(%dt_args);
155 68 100       636  
156 1         5 if ($+{repeater} && !$self->recurrence) {
157             my $r;
158             my $i = $+{repeater_interval};
159 68         418 my $u = $+{repeater_unit};
160             if ($u eq 'd') {
161 67 100 66     30922 $r = DateTime::Event::Recurrence->daily(
162 12         73 interval=>$i, start=>$dt);
163 12         41 } elsif ($u eq 'w') {
164 12         39 $r = DateTime::Event::Recurrence->weekly(
165 12 100       43 interval=>$i, start=>$dt);
    100          
    100          
    50          
166 2         13 } elsif ($u eq 'm') {
167             $r = DateTime::Event::Recurrence->monthly(
168             interval=>$i, start=>$dt);
169 3         12 } elsif ($u eq 'y') {
170             $r = DateTime::Event::Recurrence->yearly(
171             interval=>$i, start=>$dt);
172 4         12 } else {
173             die "BUG: Unknown repeater unit $u in timestamp $str";
174             }
175 3         14 $self->recurrence($r);
176             $self->_repeater($+{repeater});
177             }
178 0         0  
179             if ($+{warning_period}) {
180 12         3755 my $i = $+{warning_period_interval};
181 12         108 my $u = $+{warning_period_unit};
182             if ($u eq 'd') {
183             } elsif ($u eq 'w') {
184 67 100       284 } elsif ($u eq 'm') {
185 1         4 } elsif ($u eq 'y') {
186 1         4 } else {
187 1 50       14 die "BUG: Unknown warning period unit $u in timestamp $str";
    0          
    0          
    0          
188             }
189             $self->_warning_period($+{warning_period});
190             }
191              
192 0         0 $self->_is_parsed(1);
193             $self->datetime($dt);
194 1         9 }
195              
196             1;
197 67         202 # ABSTRACT: Represent Org timestamp
198 67         1282  
199              
200             =pod
201              
202             =encoding UTF-8
203              
204             =head1 NAME
205              
206             Org::Element::Timestamp - Represent Org timestamp
207              
208             =head1 VERSION
209              
210             This document describes version 0.558 of Org::Element::Timestamp (from Perl distribution Org-Parser), released on 2022-06-23.
211              
212             =head1 DESCRIPTION
213              
214             Derived from L<Org::Element>.
215              
216             Supported formats:
217              
218             =over
219              
220             =item * C<[...]> and C<< <...> >> (active) forms
221              
222             =item * basic date: C<[2013-10-27 Sun]>
223              
224             =item * event duration: C<[2011-03-23 Wed 10:12-11:23]>
225              
226             =item * repeater: C<[2011-03-23 Wed +3m]> including C<++> and C<.+>
227              
228             =item * habit-style repeater: C<[2011-03-23 Wed 10:12 +1d/2d]>
229              
230             =item * warning period: C<< <2011-05-25 Wed +17.1m -13.2d> >>
231              
232             =back
233              
234             =head1 BUGS AND LIMITATIONS
235              
236             =over
237              
238             =item * Habit-style repeater (e.g. 2d/3d) is not yet represented in C<recurrence>
239              
240             The recurrence object currently will still only include 2d (without the maximum
241             interval).
242              
243             =back
244              
245             =head1 ATTRIBUTES
246              
247             =head2 datetime => DATETIME_OBJ
248              
249             =head2 has_time => BOOL
250              
251             =head2 event_duration => INT
252              
253             Event duration in seconds, e.g. for event timestamp like this:
254              
255             <2011-03-23 10:15-13:25>
256              
257             event_duration is 7200+600=7800 (2 hours 10 minutes).
258              
259             =head2 recurrence => DateTime::Event::Recurrence object
260              
261             =head2 is_active => BOOL
262              
263             =head1 METHODS
264              
265             =head2 $el->clear_parse_result
266              
267             Clear parse result.
268              
269             Since the DateTime::Set::ICal (recurrence) object contains coderefs (and thus
270             poses problem to serialization), an option is provided to remove parse result.
271             You can do this prior to serializing the object.
272              
273             Timestamp will automatically be parsed again from _str when one of the
274             attributes is accessed.
275              
276             =head2 as_string => str
277              
278             From L<Org::Element>.
279              
280             =head2 as_text => str
281              
282             From L<Org::ElementRole::Inline>.
283              
284             =head1 HOMEPAGE
285              
286             Please visit the project's homepage at L<https://metacpan.org/release/Org-Parser>.
287              
288             =head1 SOURCE
289              
290             Source repository is at L<https://github.com/perlancar/perl-Org-Parser>.
291              
292             =head1 AUTHOR
293              
294             perlancar <perlancar@cpan.org>
295              
296             =head1 CONTRIBUTING
297              
298              
299             To contribute, you can send patches by email/via RT, or send pull requests on
300             GitHub.
301              
302             Most of the time, you don't need to build the distribution yourself. You can
303             simply modify the code, then test via:
304              
305             % prove -l
306              
307             If you want to build the distribution (e.g. to try to install it locally on your
308             system), you can install L<Dist::Zilla>,
309             L<Dist::Zilla::PluginBundle::Author::PERLANCAR>, and sometimes one or two other
310             Dist::Zilla plugin and/or Pod::Weaver::Plugin. Any additional steps required
311             beyond that are considered a bug and can be reported to me.
312              
313             =head1 COPYRIGHT AND LICENSE
314              
315             This software is copyright (c) 2022, 2021, 2020, 2019, 2017, 2016, 2015, 2014, 2013, 2012, 2011 by perlancar <perlancar@cpan.org>.
316              
317             This is free software; you can redistribute it and/or modify it under
318             the same terms as the Perl 5 programming language system itself.
319              
320             =head1 BUGS
321              
322             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Org-Parser>
323              
324             When submitting a bug or request, please include a test-file or a
325             patch to an existing test-file that illustrates the bug or desired
326             feature.
327              
328             =cut