|
Packit |
9002b2 |
package DateTime::Format::Builder::Tutorial;
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
# ABSTRACT: Quick class on using Builder
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
__END__
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
=pod
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
=head1 NAME
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
DateTime::Format::Builder::Tutorial - Quick class on using Builder
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
=head1 VERSION
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
version 0.81
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
=head1 CREATING A CLASS
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
As most people who are writing modules know, you start a
|
|
Packit |
9002b2 |
package with a package declaration and some indication of
|
|
Packit |
9002b2 |
module version:
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
package DateTime::Format::ICal;
|
|
Packit |
9002b2 |
our $VERSION = '0.04';
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
After that, you call Builder with some options. There are
|
|
Packit |
9002b2 |
only a few (detailed later). Right now, we're only interested
|
|
Packit |
9002b2 |
in I<parsers>.
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
use DateTime::Format::Builder
|
|
Packit |
9002b2 |
(
|
|
Packit |
9002b2 |
parsers => {
|
|
Packit |
9002b2 |
...
|
|
Packit |
9002b2 |
}
|
|
Packit |
9002b2 |
);
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
The I<parsers> option takes a reference to a hash of method
|
|
Packit |
9002b2 |
names and specifications:
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
parsers => {
|
|
Packit |
9002b2 |
parse_datetime => ... ,
|
|
Packit |
9002b2 |
parse_datetime_with_timezone => ... ,
|
|
Packit |
9002b2 |
...
|
|
Packit |
9002b2 |
}
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
Builder will create methods in your class, each method being
|
|
Packit |
9002b2 |
a parser that follows the given specifications. It is
|
|
Packit |
9002b2 |
B<strongly> recommended that one method is called
|
|
Packit |
9002b2 |
I<parse_datetime>, be it a Builder created method or one of
|
|
Packit |
9002b2 |
your own.
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
In addition to creating any of the parser methods it also
|
|
Packit |
9002b2 |
creates a C<new()> method that can instantiate (or clone)
|
|
Packit |
9002b2 |
objects of this class. This behaviour can be modified with
|
|
Packit |
9002b2 |
the I<constructor> option, but we don't need to know that
|
|
Packit |
9002b2 |
yet.
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
Each value corresponding to a method name in the parsers
|
|
Packit |
9002b2 |
list is either a single specification, or a list of
|
|
Packit |
9002b2 |
specifications. We'll start with the simple case.
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
parse_briefdate => {
|
|
Packit |
9002b2 |
params => [ qw( year month day ) ],
|
|
Packit |
9002b2 |
regex => qr/^(\d\d\d\d)(\d\d)(\d\d)$/,
|
|
Packit |
9002b2 |
},
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
This will result in a method named I<parse_briefdate> which
|
|
Packit |
9002b2 |
will take strings in the form C<20040716> and return
|
|
Packit |
9002b2 |
DateTime objects representing that date. A user of the class
|
|
Packit |
9002b2 |
might write:
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
use DateTime::Format::ICal;
|
|
Packit |
9002b2 |
my $date = "19790716";
|
|
Packit |
9002b2 |
my $dt = DateTime::Format::ICal->parse_briefdate( $date );
|
|
Packit |
9002b2 |
print "My birth month is ", $dt->month_name, "\n";
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
The C<regex> is applied to the input string, and if it
|
|
Packit |
9002b2 |
matches, then C<$1>, C<$2>, ... are mapped to the I<params>
|
|
Packit |
9002b2 |
given and handed to C<< DateTime->new() >>. Essentially:
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
my $rv = DateTime->new( year => $1, month => $2, day => $3 );
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
There are more complicated things one can do within a single
|
|
Packit |
9002b2 |
specification, but we'll cover those later.
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
Often, you'll want a method to be able to take one string,
|
|
Packit |
9002b2 |
and run it against multiple parser specifications. It would
|
|
Packit |
9002b2 |
be very irritating if the user had to work out what format
|
|
Packit |
9002b2 |
the datetime string was in and then which method was most
|
|
Packit |
9002b2 |
appropriate.
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
So, Builder lets you specify multiple specifications:
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
parse_datetime => [
|
|
Packit |
9002b2 |
{
|
|
Packit |
9002b2 |
params => [ qw( year month day hour minute second ) ],
|
|
Packit |
9002b2 |
regex => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)(\d\d)$/,
|
|
Packit |
9002b2 |
},
|
|
Packit |
9002b2 |
{
|
|
Packit |
9002b2 |
params => [ qw( year month day hour minute ) ],
|
|
Packit |
9002b2 |
regex => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)$/,
|
|
Packit |
9002b2 |
},
|
|
Packit |
9002b2 |
{
|
|
Packit |
9002b2 |
params => [ qw( year month day hour ) ],
|
|
Packit |
9002b2 |
regex => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)$/,
|
|
Packit |
9002b2 |
},
|
|
Packit |
9002b2 |
{
|
|
Packit |
9002b2 |
params => [ qw( year month day ) ],
|
|
Packit |
9002b2 |
regex => qr/^(\d\d\d\d)(\d\d)(\d\d)$/,
|
|
Packit |
9002b2 |
},
|
|
Packit |
9002b2 |
],
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
It's an arrayref of specifications. A parser will be created
|
|
Packit |
9002b2 |
that will try each of these specifications sequentially, in
|
|
Packit |
9002b2 |
the order you specified.
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
There's a flaw with this though. In this example, we're
|
|
Packit |
9002b2 |
building a parser for ICal datetimes. One can place a
|
|
Packit |
9002b2 |
timezone id at the start of an ICal datetime. You might
|
|
Packit |
9002b2 |
extract such an id with the following code:
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
if ( $date =~ s/^TZID=([^:]+):// )
|
|
Packit |
9002b2 |
{
|
|
Packit |
9002b2 |
$time_zone = $1;
|
|
Packit |
9002b2 |
}
|
|
Packit |
9002b2 |
# Z at end means UTC
|
|
Packit |
9002b2 |
elsif ( $date =~ s/Z$// )
|
|
Packit |
9002b2 |
{
|
|
Packit |
9002b2 |
$time_zone = 'UTC';
|
|
Packit |
9002b2 |
}
|
|
Packit |
9002b2 |
else
|
|
Packit |
9002b2 |
{
|
|
Packit |
9002b2 |
$time_zone = 'floating';
|
|
Packit |
9002b2 |
}
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
C<$date> would end up without the id, and C<$time_zone> would
|
|
Packit |
9002b2 |
contain something appropriate to give to DateTime's
|
|
Packit |
9002b2 |
I<set_time_zone> method, or I<time_zone> argument.
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
But how to get this scrap of code into your parser? You
|
|
Packit |
9002b2 |
might be tempted to call the parser something else and build
|
|
Packit |
9002b2 |
a small wrapper. There's no need though because an option is
|
|
Packit |
9002b2 |
provided for preprocesing dates:
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
parse_datetime => [
|
|
Packit |
9002b2 |
[ preprocess => \&_parse_tz ], # Only changed line!
|
|
Packit |
9002b2 |
{
|
|
Packit |
9002b2 |
params => [ qw( year month day hour minute second ) ],
|
|
Packit |
9002b2 |
regex => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)(\d\d)$/,
|
|
Packit |
9002b2 |
},
|
|
Packit |
9002b2 |
{
|
|
Packit |
9002b2 |
params => [ qw( year month day hour minute ) ],
|
|
Packit |
9002b2 |
regex => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)$/,
|
|
Packit |
9002b2 |
},
|
|
Packit |
9002b2 |
{
|
|
Packit |
9002b2 |
params => [ qw( year month day hour ) ],
|
|
Packit |
9002b2 |
regex => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)$/,
|
|
Packit |
9002b2 |
},
|
|
Packit |
9002b2 |
{
|
|
Packit |
9002b2 |
params => [ qw( year month day ) ],
|
|
Packit |
9002b2 |
regex => qr/^(\d\d\d\d)(\d\d)(\d\d)$/,
|
|
Packit |
9002b2 |
},
|
|
Packit |
9002b2 |
],
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
It will necessitate I<_parse_tz> to be written, and that
|
|
Packit |
9002b2 |
routine looks like this:
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
sub _parse_tz
|
|
Packit |
9002b2 |
{
|
|
Packit |
9002b2 |
my %args = @_;
|
|
Packit |
9002b2 |
my ($date, $p) = @args{qw( input parsed )};
|
|
Packit |
9002b2 |
if ( $date =~ s/^TZID=([^:]+):// )
|
|
Packit |
9002b2 |
{
|
|
Packit |
9002b2 |
$p->{time_zone} = $1;
|
|
Packit |
9002b2 |
}
|
|
Packit |
9002b2 |
# Z at end means UTC
|
|
Packit |
9002b2 |
elsif ( $date =~ s/Z$// )
|
|
Packit |
9002b2 |
{
|
|
Packit |
9002b2 |
$p->{time_zone} = 'UTC';
|
|
Packit |
9002b2 |
}
|
|
Packit |
9002b2 |
else
|
|
Packit |
9002b2 |
{
|
|
Packit |
9002b2 |
$p->{time_zone} = 'floating';
|
|
Packit |
9002b2 |
}
|
|
Packit |
9002b2 |
return $date;
|
|
Packit |
9002b2 |
}
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
On input it is given a hash containing two items: the input
|
|
Packit |
9002b2 |
date and a hashref that will be used in the parsing. The
|
|
Packit |
9002b2 |
return value from the routine is what the parser
|
|
Packit |
9002b2 |
specifications will run against, and anything in the
|
|
Packit |
9002b2 |
I<parsed> hash (C<$p> in the example) will be put in the
|
|
Packit |
9002b2 |
call to C<< DateTime->new(...) >>.
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
So, we now have a happily working ICal parser. It parses the
|
|
Packit |
9002b2 |
assorted formats, and can also handle timezones. Is there
|
|
Packit |
9002b2 |
anything else it needs to do? No. But we can make it work
|
|
Packit |
9002b2 |
more efficiently.
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
At present, the specifications are tested sequentially.
|
|
Packit |
9002b2 |
However, each one applies to strings of particular lengths.
|
|
Packit |
9002b2 |
Thus we could be efficient and have the parser only test the
|
|
Packit |
9002b2 |
given strings against a parser that handles that string
|
|
Packit |
9002b2 |
length. Again, Builder makes it easy:
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
parse_datetime => [
|
|
Packit |
9002b2 |
[ preprocess => \&_parse_tz ],
|
|
Packit |
9002b2 |
{
|
|
Packit |
9002b2 |
length => 15, # We handle strings of exactly 15 chars
|
|
Packit |
9002b2 |
params => [ qw( year month day hour minute second ) ],
|
|
Packit |
9002b2 |
regex => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)(\d\d)$/,
|
|
Packit |
9002b2 |
},
|
|
Packit |
9002b2 |
{
|
|
Packit |
9002b2 |
length => 13, # exactly 13 chars...
|
|
Packit |
9002b2 |
params => [ qw( year month day hour minute ) ],
|
|
Packit |
9002b2 |
regex => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)$/,
|
|
Packit |
9002b2 |
},
|
|
Packit |
9002b2 |
{
|
|
Packit |
9002b2 |
length => 11, # 11..
|
|
Packit |
9002b2 |
params => [ qw( year month day hour ) ],
|
|
Packit |
9002b2 |
regex => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)$/,
|
|
Packit |
9002b2 |
},
|
|
Packit |
9002b2 |
{
|
|
Packit |
9002b2 |
length => 8, # yes.
|
|
Packit |
9002b2 |
params => [ qw( year month day ) ],
|
|
Packit |
9002b2 |
regex => qr/^(\d\d\d\d)(\d\d)(\d\d)$/,
|
|
Packit |
9002b2 |
},
|
|
Packit |
9002b2 |
],
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
Now the created parser will create a parser that only runs
|
|
Packit |
9002b2 |
specifications against appropriate strings.
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
So our complete code looks like:
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
package DateTime::Format::ICal;
|
|
Packit |
9002b2 |
use strict;
|
|
Packit |
9002b2 |
our $VERSION = '0.04';
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
use DateTime::Format::Builder
|
|
Packit |
9002b2 |
(
|
|
Packit |
9002b2 |
parsers => {
|
|
Packit |
9002b2 |
parse_datetime => [
|
|
Packit |
9002b2 |
[ preprocess => \&_parse_tz ],
|
|
Packit |
9002b2 |
{
|
|
Packit |
9002b2 |
length => 15,
|
|
Packit |
9002b2 |
params => [ qw( year month day hour minute second ) ],
|
|
Packit |
9002b2 |
regex => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)(\d\d)$/,
|
|
Packit |
9002b2 |
},
|
|
Packit |
9002b2 |
{
|
|
Packit |
9002b2 |
length => 13,
|
|
Packit |
9002b2 |
params => [ qw( year month day hour minute ) ],
|
|
Packit |
9002b2 |
regex => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)$/,
|
|
Packit |
9002b2 |
},
|
|
Packit |
9002b2 |
{
|
|
Packit |
9002b2 |
length => 11,
|
|
Packit |
9002b2 |
params => [ qw( year month day hour ) ],
|
|
Packit |
9002b2 |
regex => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)$/,
|
|
Packit |
9002b2 |
},
|
|
Packit |
9002b2 |
{
|
|
Packit |
9002b2 |
length => 8,
|
|
Packit |
9002b2 |
params => [ qw( year month day ) ],
|
|
Packit |
9002b2 |
regex => qr/^(\d\d\d\d)(\d\d)(\d\d)$/,
|
|
Packit |
9002b2 |
},
|
|
Packit |
9002b2 |
],
|
|
Packit |
9002b2 |
},
|
|
Packit |
9002b2 |
);
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
sub _parse_tz
|
|
Packit |
9002b2 |
{
|
|
Packit |
9002b2 |
my %args = @_;
|
|
Packit |
9002b2 |
my ($date, $p) = @args{qw( input parsed )};
|
|
Packit |
9002b2 |
if ( $date =~ s/^TZID=([^:]+):// )
|
|
Packit |
9002b2 |
{
|
|
Packit |
9002b2 |
$p->{time_zone} = $1;
|
|
Packit |
9002b2 |
}
|
|
Packit |
9002b2 |
# Z at end means UTC
|
|
Packit |
9002b2 |
elsif ( $date =~ s/Z$// )
|
|
Packit |
9002b2 |
{
|
|
Packit |
9002b2 |
$p->{time_zone} = 'UTC';
|
|
Packit |
9002b2 |
}
|
|
Packit |
9002b2 |
else
|
|
Packit |
9002b2 |
{
|
|
Packit |
9002b2 |
$p->{time_zone} = 'floating';
|
|
Packit |
9002b2 |
}
|
|
Packit |
9002b2 |
return $date;
|
|
Packit |
9002b2 |
}
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
1;
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
And that's an ICal parser. The actual
|
|
Packit |
9002b2 |
L<DateTime::Format::ICal> module also includes formatting
|
|
Packit |
9002b2 |
methods and parsing for durations, but Builder doesn't
|
|
Packit |
9002b2 |
support those yet. A drop in replacement (at the time of
|
|
Packit |
9002b2 |
writing the replacement) can be found in the F<examples>
|
|
Packit |
9002b2 |
directory of the Builder distribution, along with similar
|
|
Packit |
9002b2 |
variants of other common modules.
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
=head1 SUPPORT
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
Any errors you see in this document, please log them with
|
|
Packit |
9002b2 |
CPAN RT system via the web or email:
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
http://perl.dellah.org/rt/dtbuilder
|
|
Packit |
9002b2 |
bug-datetime-format-builder@rt.cpan.org
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
This makes it much easier for me to track things and thus means
|
|
Packit |
9002b2 |
your problem is less likely to be neglected.
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
=head1 LICENSE AND COPYRIGHT
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
Copyright E<copy> Iain Truskett, 2003. All rights reserved.
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
You can redistribute this document and/or modify
|
|
Packit |
9002b2 |
it under the same terms as Perl itself.
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
The full text of the licenses can be found in the F<Artistic> and
|
|
Packit |
9002b2 |
F<COPYING> files included with this document.
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
=head1 SEE ALSO
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
C<datetime@perl.org> mailing list.
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
http://datetime.perl.org/
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
L<perl>, L<DateTime>, L<DateTime::Format::Builder>
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
=head1 AUTHORS
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
=over 4
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
=item *
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
Dave Rolsky <autarch@urth.org>
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
=item *
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
Iain Truskett
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
=back
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
=head1 COPYRIGHT AND LICENSE
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
This software is Copyright (c) 2013 by Dave Rolsky.
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
This is free software, licensed under:
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
The Artistic License 2.0 (GPL Compatible)
|
|
Packit |
9002b2 |
|
|
Packit |
9002b2 |
=cut
|