Blame lib/DateTime/Format/Builder/Tutorial.pod

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