File Coverage

blib/lib/URI/_duri_tdb.pm
Criterion Covered Total %
statement 67 127 52.7
branch 14 64 21.8
condition 6 27 22.2
subroutine 18 21 85.7
pod 1 4 25.0
total 106 243 43.6


line stmt bran cond sub pod time code
1             package URI::_duri_tdb;
2              
3 3     3   46 use 5.010;
  3         11  
  3         109  
4 3     3   16 use strict;
  3         5  
  3         76  
5 3     3   15 use warnings;
  3         3  
  3         76  
6 3     3   3121 use utf8;
  3         70  
  3         15  
7              
8             BEGIN {
9 3     3   164 $URI::_duri_tdb::AUTHORITY = 'cpan:TOBYINK';
10 3         58 $URI::_duri_tdb::VERSION = '0.002';
11             }
12              
13 3     3   16 use Carp;
  3         7  
  3         344  
14 3     3   2947 use DateTime::Incomplete;
  3         921134  
  3         121  
15 3     3   37 use POSIX qw[floor];
  3         6  
  3         37  
16 3     3   344 use Scalar::Util qw[blessed reftype];
  3         7  
  3         173  
17              
18 3     3   15 use base 'URI';
  3         4  
  3         4306  
19              
20             my $re_datetime = qr{
21             (?\d{4})
22             (?:
23             \-(?\d{2})
24             (?:
25             \-(?\d{2})
26             (?:
27             T(?\d{2}):(?\d{2})
28             (?:
29             :(? \d{2} (?:\.\d+)? )
30             )?
31             )?
32             )?
33             )?
34             (?
35             [Z] |
36             [+-]\d{2}:\d{2} |
37             [+-]\d{4} |
38             [+-]\d{2}
39             )?
40             }ix;
41              
42             sub new
43             {
44 2     2 1 757 my $param = $_[1];
45            
46 2 50       5 if (not ref $param)
    0          
47 2         7 { goto \&_new_from_string }
48             elsif (reftype $param eq 'HASH')
49 0         0 { goto \&_new_from_hashref }
50              
51 0         0 croak "cannot construct URI::duri object";
52             }
53              
54             sub _new_from_string
55             {
56 2     2   4 my ($class, $str) = @_;
57 2         4 my $self = bless \$str => $class;
58 2         12 $self->_deconstruct;
59 2         7 return $self;
60             }
61              
62             sub _new_from_hashref
63             {
64 0     0   0 my ($class, $hashref) = @_;
65            
66 0         0 my $str = $class->_preferred_scheme . ':2001:urn:example:1';
67 0         0 my $self = bless \$str => $class;
68              
69 0 0       0 if ($hashref->{datetime_string})
    0          
70 0         0 { $self->datetime_string($self->{datetime_string}) }
71             elsif ($hashref->{datetime})
72 0         0 { $self->datetime($self->{datetime}) }
73             else
74 0         0 { $self->datetime(DateTime->now) }
75            
76 0 0       0 exists $hashref->{embedded_uri}
77             or croak "need embedded_uri hash key";
78 0         0 $self->embedded_uri($hashref->{embedded_uri});
79            
80 0         0 return $self;
81             }
82              
83             sub _parse_datetime
84             {
85 200     200   281 my ($self, $str) = @_;
86            
87 200 50       447 confess "_parse_datetime called with undefined argument" unless defined $str;
88            
89 200 50       1951 if ($str =~ /^$re_datetime$/)
90             {
91 3     3   14511 my %parts = %+;
  3         1966  
  3         8135  
  200         6729  
92 200 100 100     1591 if (defined $parts{time_zone}
    100          
93             and lc $parts{time_zone} eq 'z')
94             {
95 100         182 $parts{time_zone} = 'UTC';
96             }
97             elsif (defined $parts{time_zone})
98             {
99 4         16 $parts{time_zone} =~ s/://;
100 4 50       18 $parts{time_zone} .= '00'
101             if length $parts{time_zone} == 3;
102             }
103            
104 200 100 100     1169 if (defined $parts{second}
105             and $parts{second} > floor $parts{second})
106             {
107 64         186 my $frac = $parts{second} - floor $parts{second};
108 64         314 $parts{second} = floor $parts{second};
109 64         159 $parts{nanosecond} = $frac * 1_000_000_000;
110             }
111            
112 200         1085 return DateTime::Incomplete->new(%parts);
113             }
114            
115 0         0 croak "datetime does not match regular expression";
116             }
117              
118             sub _serialize_datetime
119             {
120 0     0   0 my ($self, $dt) = @_;
121            
122 0 0       0 if ($dt->isa('DateTime::Incomplete'))
    0          
123             {
124 0 0       0 croak "datetime has no year"
125             unless $dt->has_year;
126            
127 0         0 my $str = sprintf('%04d' => $dt->year);
128 0         0 my $tz = '';
129            
130 0 0 0     0 if ($dt->has_time_zone and $dt->time_zone->is_utc)
    0 0        
    0          
131 0         0 { $tz = 'Z' }
132             elsif ($dt->has_time_zone and $dt->time_zone->is_floating)
133 0         0 { $tz = '' }
134             elsif ($dt->has_time_zone)
135 0         0 { croak "non-UTC timezone specified" }
136            
137             $dt->has_month
138 0 0       0 ? do { $str .= sprintf('-%02d' => $dt->month) }
  0         0  
139             : return $str.$tz;
140            
141             $dt->has_day
142 0 0       0 ? do { $str .= sprintf('-%02d' => $dt->day) }
  0         0  
143             : return $str.$tz;
144            
145             $dt->has_hour && $dt->has_minute
146 0 0 0     0 ? do { $str .= sprintf('T%02d:%02d' => $dt->hour, $dt->minute) }
  0         0  
147             : return $str.$tz;
148            
149             $dt->has_second
150 0 0       0 ? do { $str .= sprintf(':%02d' => $dt->second) }
  0         0  
151             : return $str.$tz;
152            
153             $dt->has_nanosecond && $dt->nanosecond > 0
154 0 0 0     0 ? do { $str .= sprintf('.%09d' => $dt->nanosecond); $str =~ s/0+$//; }
  0         0  
  0         0  
155             : return $str.$tz;
156            
157 0         0 return $str.$tz;
158             }
159             elsif ($dt->isa('DateTime'))
160             {
161 0 0 0     0 unless ($dt->time_zone->is_floating or $dt->time_zone->is_utc)
162             {
163 0         0 $dt->set_time_zone('UTC');
164             }
165            
166 0         0 my $str = $dt->strftime('%FT%T.%9N');
167 0         0 $str =~ s/0+$//;
168 0         0 $str =~ s/\.$//;
169            
170 0 0       0 if ($dt->time_zone->is_utc)
171             {
172 0         0 $str .= 'Z';
173             }
174            
175 0         0 return $str;
176             }
177            
178 0         0 confess "can't serialize";
179             }
180              
181             sub datetime
182             {
183 200     200 0 106432 my $self = shift;
184            
185 200 50       557 if (@_)
186             {
187 0         0 my $dt = shift;
188 0 0 0     0 croak "expected DateTime object"
      0        
189             unless (
190             blessed($dt) and
191             $dt->isa('DateTime') || $dt->isa('DateTime::Incomplete')
192             );
193 0         0 my $ser = $self->_serialize_datetime($dt);
194 0         0 $self->datetime_string($ser, 1);
195 0         0 return $ser;
196             }
197            
198 200         415 $self->_parse_datetime($self->datetime_string);
199             }
200              
201             sub datetime_string
202             {
203 200     200 0 237 my $self = shift;
204 200         439 my @parts = $self->_deconstruct;
205            
206 200 50       9397 if (@_)
207             {
208 0         0 my ($dt, $skip_check) = @_;
209 0 0       0 unless ($skip_check)
210             {
211 0 0       0 $dt =~ /^$re_datetime$/
212             or croak "string '$dt' cannot be parsed as a DateTime: $@";
213             }
214 0         0 $parts[1] = $dt;
215 0         0 $self->_reconstruct(@parts);
216             }
217            
218 200         959 return $parts[1];
219             }
220              
221             sub embedded_uri
222             {
223 22     22 0 36062 my $self = shift;
224 22         61 my @parts = $self->_deconstruct;
225            
226 22 50       74 if (@_)
227             {
228 0         0 my $uri = shift;
229 0 0       0 $parts[2] = blessed($uri) ? $uri : URI->new("$uri");
230 0         0 $self->_reconstruct(@parts);
231             }
232            
233 22         87 return URI->new($parts[2]);
234             }
235              
236             sub _reconstruct
237             {
238 0     0   0 my $self = shift;
239 0         0 $$self = sprintf('%s:%s:%s', @_);
240 0         0 return $self;
241             }
242              
243             sub _deconstruct
244             {
245 224     224   283 my $self = shift;
246            
247 224 50       5280 if (my @r = ($$self =~ m{
248             ^
249             (?[A-Za-z][A-Za-z0-9+-]*)
250             \:
251             (?$re_datetime)
252             \:
253             (?.+)
254             $
255             }x))
256             {
257             # NOTE: We cannot just return the hash slice. We need to do
258             # the assignment first. This is a workaround to a bizarro bug
259             # in Perl 5.10 and 5.12 (and maybe 5.14?)
260 224         2693 my @parts = @+{qw< scheme datetime embedded >};
261 224         11891 return @parts;
262             }
263            
264             else
265             {
266 0           confess "couldn't match regexp";
267             }
268             }
269              
270             __PACKAGE__