File Coverage

blib/lib/DateTime/Format/Genealogy.pm
Criterion Covered Total %
statement 87 91 95.6
branch 49 60 81.6
condition 16 24 66.6
subroutine 10 10 100.0
pod 2 2 100.0
total 164 187 87.7


line stmt bran cond sub pod time code
1             package DateTime::Format::Genealogy;
2              
3             # Author Nigel Horne: njh@bandsman.co.uk
4             # Copyright (C) 2018-2021, Nigel Horne
5              
6             # Usage is subject to licence terms.
7             # The licence terms of this software are as follows:
8             # Personal single user, single computer use: GPL2
9             # All other users (including Commercial, Charity, Educational, Government)
10             # must apply in writing for a licence for use from Nigel Horne at the
11             # above e-mail.
12              
13 3     3   503132 use strict;
  3         26  
  3         92  
14 3     3   18 use warnings;
  3         7  
  3         74  
15             # use diagnostics;
16             # use warnings::unused;
17 3     3   55 use 5.006_001;
  3         11  
18              
19 3     3   1551 use namespace::clean;
  3         51321  
  3         23  
20 3     3   660 use Carp;
  3         8  
  3         171  
21 3     3   1774 use DateTime::Format::Natural;
  3         1625547  
  3         225  
22 3     3   2071 use Genealogy::Gedcom::Date 2.01;
  3         837874  
  3         40  
23              
24             our %months = (
25             'January' => 'Jan',
26             'February' => 'Feb',
27             'March' => 'Mar',
28             'April' => 'Apr',
29             # 'May' => 'May',
30             'June' => 'Jun',
31             'July' => 'Jul',
32             'August' => 'Aug',
33             'September' => 'Sep',
34             'October' => 'Oct',
35             'November' => 'Nov',
36             'December' => 'Dec'
37             );
38              
39             =head1 NAME
40              
41             DateTime::Format::Genealogy - Create a DateTime object from a Genealogy Date
42              
43             =head1 VERSION
44              
45             Version 0.04
46              
47             =cut
48              
49             our $VERSION = '0.04';
50              
51             =head1 SYNOPSIS
52              
53             =head1 SUBROUTINES/METHODS
54              
55             =head2 new
56              
57             Creates a DateTime::Format::Genealogy object.
58              
59             =cut
60              
61             sub new {
62 11     11 1 5156 my $proto = shift;
63 11   33     74 my $class = ref($proto) || $proto;
64              
65 11 50       63 if(defined($class)) {
66 11         64 return bless {}, $class;
67             }
68             }
69              
70             =head2 parse_datetime($string)
71              
72             Given a date,
73             runs it through L<Genealogy::Gedcom::Date> to create a L<DateTime> object.
74             If a date range is given, return a two element array in array context, or undef in scalar context
75              
76             Returns undef if the date can't be parsed, is just a year or if it is an appoximate date starting with "c", "ca" or "abt".
77             Can be called as a class or object method.
78              
79             date: the date to be parsed
80             quiet: set to fail silently if there is an error with the date
81             strict: more strictly enforce the Gedcom standard, for example don't allow long month names
82              
83             =cut
84              
85             sub parse_datetime {
86 43     43 1 279577 my $self = shift;
87 43         100 my %params;
88              
89 43 100 100     406 if(!ref($self)) {
    100          
    100          
    100          
    100          
90 8 100       42 if(scalar(@_)) {
91 5         32 return(__PACKAGE__->new()->parse_datetime(@_));
92             }
93 3         24 return(__PACKAGE__->new()->parse_datetime($self));
94             } elsif(ref($self) eq 'HASH') {
95 1         16 return(__PACKAGE__->new()->parse_datetime($self));
96             } elsif(ref($_[0]) eq 'HASH') {
97 9         25 %params = %{$_[0]};
  9         43  
98             } elsif(ref($_[0])) {
99 1         4 Carp::croak('Usage: ', __PACKAGE__, '::parse_datetime(date => $date)');
100             } elsif(scalar(@_) && (scalar(@_) % 2 == 0)) {
101 8         40 %params = @_;
102             } else {
103 16         60 $params{'date'} = shift;
104             }
105 33         83 my $quiet = $params{'quiet'};
106              
107 33 100       121 if(my $date = $params{'date'}) {
108             # TODO: Needs much more sanity checking
109 29 100 100     355 if(($date =~ /^bef\s/i) || ($date =~ /^aft\s/i) || ($date =~ /^abt\s/i)) {
      66        
110 4 100       21 Carp::carp("$date is invalid, need an exact date to create a DateTime")
111             unless($quiet);
112 4         1090 return;
113             }
114 25 100       125 if($date =~ /^31 Nov/) {
115 1         7 Carp::carp("$date is invalid, there are only 30 days in November");
116 1         401 return;
117             }
118 24         107 my $dfn = $self->{'dfn'};
119 24 100       84 if(!defined($dfn)) {
120 11         88 $self->{'dfn'} = $dfn = DateTime::Format::Natural->new();
121             }
122 24 100       85307 if($date =~ /^\s*(.+\d\d)\s*\-\s*(.+\d\d)\s*$/) {
123 1         12 Carp::carp("Changing date '$date' to 'bet $1 and $2'");
124 1         505 $date = "bet $1 and $2";
125             }
126 24 100       137 if($date =~ /^bet (.+) and (.+)/i) {
127 3 100       17 if(wantarray) {
128 1         9 return $self->parse_datetime($1), $self->parse_datetime($2);
129             }
130 2         14 return;
131             }
132              
133 21 50       159 if($date !~ /^\d{3,4}$/) {
134 21 100       131 if($date =~ /^(\d{1,2})\s+([A-Z]{4,}+)\s+(\d{3,4})$/i) {
135 3         13 my $strict = $params{'strict'};
136              
137 3 100 100     27 if((!$strict) && (my $abbrev = $months{$2})) {
138 1         7 $date = "$1 $abbrev $3";
139             } else {
140 2 50       19 Carp::carp("Unparseable date $date - often because the month name isn't 3 letters") unless($quiet);
141 2         1065 return;
142             }
143             }
144 19 100 66     202 if(($date =~ /^\d/) && (my $d = $self->_date_parser_cached($date))) {
145 14         123 return $dfn->parse_datetime($d->{'canonical'});
146             }
147 5 50 33     51 if(($date !~ /^(Abt|ca?)/i) && ($date =~ /^[\w\s,]+$/)) {
148             # ACOM exports full month names and non-standard format dates e.g. U.S. format MMM, DD YYYY
149             # TODO: allow that when not in strict mode
150 5 50       25 if(my $rc = $dfn->parse_datetime($date)) {
151 5 100       55425 if($dfn->success()) {
152 1         55 return $rc;
153             }
154 4 50       259 Carp::carp($dfn->error()) unless($quiet);
155             } else {
156 0 0       0 Carp::carp("Can't parse date '$date'") unless($quiet);
157             }
158 4         2209 return;
159             }
160             }
161             } else {
162 4         11 Carp::croak('Usage: ', __PACKAGE__, '::parse_datetime(date => $date)');
163             }
164             }
165              
166             # Parse Gedcom format dates
167             # Genealogy::Gedcom::Date is expensive, so cache results
168             sub _date_parser_cached
169             {
170 14     14   34 my $self = shift;
171 14         34 my $date = shift;
172              
173 14 50       49 if(!defined($date)) {
174 0         0 Carp::croak('Usage: _date_parser_cached(date => $date)');
175             }
176              
177 14 100       56 if($self->{'all_dates'}{$date}) {
178 2         15 return $self->{'all_dates'}{$date};
179             }
180 12         34 my $date_parser = $self->{'date_parser'};
181 12 100       47 if(!defined($date_parser)) {
182 10         264 $date_parser = $self->{'date_parser'} = Genealogy::Gedcom::Date->new();
183             }
184              
185 12         2321770 my $d;
186 12         36 eval {
187 12         106 $d = $date_parser->parse(date => $date);
188             };
189 12 50       302435 if(my $error = $date_parser->error()) {
190 0         0 Carp::carp("$date: '$error'");
191 0         0 return;
192             }
193 12 50 33     224 if($d && (ref($d) eq 'ARRAY')) {
194 12         40 $d = @{$d}[0];
  12         45  
195 12         73 $self->{'all_dates'}{$date} = $d;
196             }
197 12         109 return $d;
198             }
199              
200             1;
201              
202             =head1 AUTHOR
203              
204             Nigel Horne, C<< <njh at bandsman.co.uk> >>
205              
206             =head1 BUGS
207              
208             =head1 SEE ALSO
209              
210             L<Genealogy::Gedcom::Date> and
211             L<DateTime>
212              
213             =head1 SUPPORT
214              
215             You can find documentation for this module with the perldoc command.
216              
217             perldoc DateTime::Format::Gedcom
218              
219             You can also look for information at:
220              
221             =over 4
222              
223             =item * RT: CPAN's request tracker
224              
225             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=DateTime-Format-Gedcom>
226              
227             =item * CPAN Ratings
228              
229             L<http://cpanratings.perl.org/d/DateTime-Format-Gedcom>
230              
231             =back
232              
233             =head1 LICENSE AND COPYRIGHT
234              
235             Copyright 2018-2021 Nigel Horne.
236              
237             This program is released under the following licence: GPL2
238              
239             =cut
240              
241             1;