File Coverage

blib/lib/HTML/FormHandler/Field/Date.pm
Criterion Covered Total %
statement 53 53 100.0
branch 20 26 76.9
condition 3 6 50.0
subroutine 8 8 100.0
pod 1 4 25.0
total 85 97 87.6


line stmt bran cond sub pod time code
1             package HTML::FormHandler::Field::Date;
2             # ABSTRACT: a date field with formats
3             $HTML::FormHandler::Field::Date::VERSION = '0.40068';
4 3     3   2668 use Moose;
  3         11  
  3         32  
5             extends 'HTML::FormHandler::Field::Text';
6 3     3   26498 use DateTime;
  3         894769  
  3         118  
7 3     3   2136 use DateTime::Format::Strptime;
  3         174853  
  3         36  
8              
9              
10             has '+html5_type_attr' => ( default => 'date' );
11             has 'format' => ( is => 'rw', isa => 'Str', default => "%Y-%m-%d" );
12             has 'locale' => ( is => 'rw', isa => 'Str' ); # TODO
13             has 'time_zone' => ( is => 'rw', isa => 'Str' ); # TODO
14             has 'date_start' => ( is => 'rw', isa => 'Str|CodeRef', clearer => 'clear_date_start' );
15             has 'date_end' => ( is => 'rw', isa => 'Str|CodeRef', clearer => 'clear_date_end' );
16             has '+size' => ( default => '10' );
17             has '+deflate_method' => ( default => sub { \&date_deflate } );
18              
19             # translator for Datepicker formats to DateTime strftime formats
20             my $dp_to_dt = {
21             "d" => "\%e", # day of month (no leading zero)
22             "dd" => "\%1", # day of month (2 digits) "%d"
23             "o" => "\%4", # day of year (no leading zero) "%{day_of_year}"
24             "oo" => "\%j", # day of year (3 digits)
25             "D" => "\%a", # day name long
26             "DD" => "\%A", # day name short
27             "m" => "\%5", # month of year (no leading zero) "%{day_of_month}"
28             "mm" => "\%3", # month of year (two digits) "%m"
29             "M" => "\%b", # Month name short
30             "MM" => "\%B", # Month name long
31             "y" => "\%2", # year (2 digits) "%y"
32             "yy" => "\%Y", # year (4 digits)
33             "@" => "\%s", # epoch
34             };
35              
36             our $class_messages = {
37             'date_early' => 'Date is too early',
38             'date_late' => 'Date is too late',
39             };
40             sub get_class_messages {
41 3     3 0 6 my $self = shift;
42             return {
43 3         6 %{ $self->next::method },
  3         17  
44             %$class_messages,
45             }
46             }
47              
48             sub date_deflate {
49 5     5 0 12 my ( $self, $value ) = @_;
50              
51             # if not a DateTime, assume correctly formatted string and return
52 5 100 66     44 return $value unless blessed $value && $value->isa('DateTime');
53 3         13 my $format = $self->get_strf_format;
54 3         16 my $string = $value->strftime($format);
55 3         176 return $string;
56             }
57              
58             sub validate {
59 14     14 1 31 my $self = shift;
60              
61 14         52 my $format = $self->get_strf_format;
62 14         31 my @options;
63 14 100       408 push @options, ( time_zone => $self->time_zone ) if $self->time_zone;
64 14 50       376 push @options, ( locale => $self->locale ) if $self->locale;
65 14         75 my $strp = DateTime::Format::Strptime->new( pattern => $format, @options );
66              
67 14         28381 my $dt = eval { $strp->parse_datetime( $self->value ) };
  14         61  
68 14 100       8004 unless ($dt) {
69 5   33     25 $self->add_error( $strp->errmsg || $@ );
70 5         296 return;
71             }
72 9         78 $self->_set_value($dt);
73 9         33 my $val_strp = DateTime::Format::Strptime->new( pattern => "%Y-%m-%d", @options );
74 9 100       9615 if ( my $date_start = $self->date_start ) {
75 1 50       5 $date_start = $date_start->() if ref $date_start eq 'CODE';
76 1         4 $date_start = $val_strp->parse_datetime( $date_start );
77 1 50       548 die "date_start: " . $val_strp->errmsg unless $date_start;
78 1         10 my $cmp = DateTime->compare( $date_start, $dt );
79 1 50       93 $self->add_error($self->get_message('date_early')) if $cmp eq 1;
80             }
81 9 100       235 if ( my $date_end = $self->date_end ) {
82 3 50       15 $date_end = $date_end->() if ref $date_end eq 'CODE';
83 3         17 $date_end = $val_strp->parse_datetime( $date_end );
84 3 50       1945 die "date_end: " . $val_strp->errmsg unless $date_end;
85 3         24 my $cmp = DateTime->compare( $date_end, $dt );
86 3 100       354 $self->add_error($self->get_message('date_late')) if $cmp eq -1;
87             }
88             }
89              
90             sub get_strf_format {
91 17     17 0 30 my $self = shift;
92              
93             # if contains %, then it's a strftime format
94 17 100       464 return $self->format if $self->format =~ /\%/;
95 6         140 my $format = $self->format;
96 6         11 foreach my $dpf ( reverse sort keys %{$dp_to_dt} ) {
  6         48  
97 78         146 my $strf = $dp_to_dt->{$dpf};
98 78         435 $format =~ s/$dpf/$strf/g;
99             }
100 6         41 $format =~ s/\%1/\%d/g,
101             $format =~ s/\%2/\%y/g,
102             $format =~ s/\%3/\%m/g,
103             $format =~ s/\%4/\%{day_of_year}/g,
104             $format =~ s/\%5/\%{day_of_month}/g,
105             return $format;
106             }
107              
108             before 'get_tag' => sub {
109             my $self = shift;
110              
111             if (
112             $self->form
113             && $self->form->is_html5
114             && $self->html5_type_attr eq 'date' # subclass may be using different input type
115             && not( $self->format =~ /^(yy|%Y)-(mm|%m)-(dd|%d)$/ )
116             ) {
117             warn "Form is HTML5, but date field '" . $self->full_name
118             . "' has a format other than %Y-%m-%d, which HTML5 requires for date "
119             . "fields. Either correct the date format or set the is_html5 flag to false.";
120             }
121             };
122              
123             __PACKAGE__->meta->make_immutable;
124 3     3   3641 use namespace::autoclean;
  3         12  
  3         42  
125             1;
126              
127             __END__
128              
129             =pod
130              
131             =encoding UTF-8
132              
133             =head1 NAME
134              
135             HTML::FormHandler::Field::Date - a date field with formats
136              
137             =head1 VERSION
138              
139             version 0.40068
140              
141             =head1 SUMMARY
142              
143             This field may be used with the jQuery Datepicker plugin.
144              
145             You can specify the format for the date using jQuery formatDate strings
146             or DateTime strftime formats. (Default format is format => '%Y-%m-%d'.)
147              
148             d - "%e" - day of month (no leading zero)
149             dd - "%d" - day of month (two digit)
150             o - "%{day_of_year}" - day of the year (no leading zeros)
151             oo - "%j" - day of the year (three digit)
152             D - "%a" - day name short
153             DD - "%A" - day name long
154             m - "%{day_of_month}" - month of year (no leading zero)
155             mm - "%m" - month of year (two digit) "%m"
156             M - "%b" - month name short
157             MM - "%B" - month name long
158             y - "%y" - year (two digit)
159             yy - "%Y" - year (four digit)
160             @ - "%s" - Unix timestamp (ms since 01/01/1970)
161              
162             For example:
163              
164             has_field 'start_date' => ( type => 'Date', format => "dd/mm/y" );
165              
166             or
167              
168             has_field 'start_date' => ( type => 'Date', format => "%d/%m/%y" );
169              
170             You can also set 'date_end' and 'date_start' attributes for validation
171             of the date range. Use iso_8601 formats for these dates ("yyyy-mm-dd");
172             The dates can be specified either as a string, or as a subref; the subref
173             is evaluated at validation time.
174              
175             has_field 'start_date' => ( type => 'Date', date_start => "2009-12-25", date_end => sub { DateTime->now->ymd } );
176              
177             Customize error messages 'date_early' and 'date_late':
178              
179             has_field 'start_date' => ( type => 'Date,
180             messages => { date_early => 'Pick a later date',
181             date_late => 'Pick an earlier date', } );
182              
183             =head2 Using with HTML5
184              
185             If the field's form has its 'is_html5' flag active, then the field's rendering
186             behavior changes in two ways:
187              
188             =over
189              
190             =item *
191              
192             It will render as <input type="date" ... /> instead of type="text".
193              
194             =item *
195              
196             If the field's format is set to anything other than ISO date format
197             (%Y-%m-%d), then attempting to render the field will result in a warning.
198              
199             (Note that the default value for the field's format attribute is, in fact,
200             the ISO date format.)
201              
202             =back
203              
204             =head1 AUTHOR
205              
206             FormHandler Contributors - see HTML::FormHandler
207              
208             =head1 COPYRIGHT AND LICENSE
209              
210             This software is copyright (c) 2017 by Gerda Shank.
211              
212             This is free software; you can redistribute it and/or modify it under
213             the same terms as the Perl 5 programming language system itself.
214              
215             =cut