Skip site navigation (1)Skip section navigation (2)

FreeBSD Manual Pages

  
 
  

home | help
DateTime::Format::BuilUser:Contributed PDateTime::Format::Builder::Tutorial(3)

NAME
       DateTime::Format::Builder::Tutorial - Quick class on using Builder

VERSION
       version 0.83

CREATING A CLASS
       As most people who are writing modules know, you	start a	package	with a
       package declaration and some indication of module version:

	   package DateTime::Format::ICal; our $VERSION	= '0.04';

       After that, you call Builder with some options. There are only a	few
       (detailed later). Right now, we're only interested in parsers.

	   use DateTime::Format::Builder ( parsers => {...} );

       The parsers option takes	a reference to a hash of method	names and
       specifications:

	   parsers => {
		   parse_datetime => ... ,
		   parse_datetime_with_timezone	=> ... ,
		   ...
	       }

       Builder will create methods in your class, each method being a parser
       that follows the	given specifications. It is strongly recommended that
       one method is called parse_datetime, be it a Builder created method or
       one of your own.

       In addition to creating any of the parser methods it also creates a
       "new" method that can instantiate (or clone) objects of this class.
       This behaviour can be modified with the constructor option, but we
       don't need to know that yet.

       Each value corresponding	to a method name in the	parsers	list is	either
       a single	specification, or a list of specifications. We'll start	with
       the simple case.

	   parse_briefdate => {	params => [qw( year month day )], regex	=>
	       qr/^(\d\d\d\d)(\d\d)(\d\d)$/, },

       This will result	in a method named parse_briefdate which	will take
       strings in the form 20040716 and	return DateTime	objects	representing
       that date. A user of the	class might write:

	   use DateTime::Format::ICal;
	   my $date = '19790716';
	   my $dt   = DateTime::Format::ICal->parse_briefdate($date);
	   print "My birth month is ", $dt->month_name,	"\n";

       The "regex" is applied to the input string, and if it matches, then $1,
       $2, ... are mapped to the params	given and handed to "DateTime->new".
       Essentially:

	   my $rv = DateTime->new( year	=> $1, month =>	$2, day	=> $3 );

       There are more complicated things one can do within a single
       specification, but we'll	cover those later.

       Often, you'll want a method to be able to take one string, and run it
       against multiple	parser specifications. It would	be very	irritating if
       the user	had to work out	what format the	datetime string	was in and
       then which method was most appropriate.

       So, Builder lets	you specify multiple specifications:

	   parse_datetime => [
	       {
		   params => [qw( year month day hour minute second )],
		   regex  => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)(\d\d)$/,
	       },
	       {
		   params => [qw( year month day hour minute )],
		   regex  => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)$/,
	       },
	       {
		   params => [qw( year month day hour )],
		   regex  => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)$/,
	       },
	       {
		   params => [qw( year month day )],
		   regex  => qr/^(\d\d\d\d)(\d\d)(\d\d)$/,
	       },
	   ],

       It's an arrayref	of specifications. A parser will be created that will
       try each	of these specifications	sequentially, in the order you
       specified.

       There's a flaw with this	though.	In this	example, we're building	a
       parser for ICal datetimes. One can place	a timezone id at the start of
       an ICal datetime. You might extract such	an id with the following code:

	   if (	$date =~ s/^TZID=([^:]+):// ) {
	       $time_zone = $1;
	   }

	   # Z at end means UTC
	   elsif ( $date =~ s/Z$// ) {
	       $time_zone = 'UTC';
	   }
	   else	{
	       $time_zone = 'floating';
	   }

       $date would end up without the id, and $time_zone would contain
       something appropriate to	give to	DateTime's set_time_zone method, or
       time_zone argument.

       But how to get this scrap of code into your parser? You might be
       tempted to call the parser something else and build a small wrapper.
       There's no need though because an option	is provided for	preprocessing
       dates:

	   parse_datetime => [
	       [ preprocess => \&_parse_tz ],	 # Only	changed	line!
	       {
		   params => [qw( year month day hour minute second )],
		   regex  => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)(\d\d)$/,
	       },
	       {
		   params => [qw( year month day hour minute )],
		   regex  => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)$/,
	       },
	       {
		   params => [qw( year month day hour )],
		   regex  => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)$/,
	       },
	       {
		   params => [qw( year month day )],
		   regex  => qr/^(\d\d\d\d)(\d\d)(\d\d)$/,
	       },
	   ],

       It will necessitate _parse_tz to	be written, and	that routine looks
       like this:

	   sub _parse_tz {
	       my %args	= @_;
	       my ( $date, $p )	= @args{qw( input parsed )};
	       if ( $date =~ s/^TZID=([^:]+)://	) {
		   $p->{time_zone} = $1;
	       }

	       # Z at end means	UTC
	       elsif ( $date =~	s/Z$// ) {
		   $p->{time_zone} = 'UTC';
	       }
	       else {
		   $p->{time_zone} = 'floating';
	       }
	       return $date;
	   }

       On input	it is given a hash containing two items: the input date	and a
       hashref that will be used in the	parsing. The return value from the
       routine is what the parser specifications will run against, and
       anything	in the parsed hash ($p in the example) will be put in the call
       to "DateTime->new(...)".

       So, we now have a happily working ICal parser. It parses	the assorted
       formats,	and can	also handle timezones. Is there	anything else it needs
       to do? No. But we can make it work more efficiently.

       At present, the specifications are tested sequentially.	However, each
       one applies to strings of particular lengths.  Thus we could be
       efficient and have the parser only test the given strings against a
       parser that handles that	string length. Again, Builder makes it easy:

	   parse_datetime => [
	       [ preprocess => \&_parse_tz ],
	       {
		   length => 15,    # We handle	strings	of exactly 15 chars
		   params => [qw( year month day hour minute second )],
		   regex  => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)(\d\d)$/,
	       },
	       {
		   length => 13,				# exactly 13 chars...
		   params => [qw( year month day hour minute )],
		   regex => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)$/,
	       },
	       {
		   length => 11,				    # 11..
		   params => [qw( year month day hour )],
		   regex  => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)$/,
	       },
	       {
		   length => 8,					    # yes.
		   params => [qw( year month day )],
		   regex  => qr/^(\d\d\d\d)(\d\d)(\d\d)$/,
	       },
	   ],

       Now the created parser will create a parser that	only runs
       specifications against appropriate strings.

       So our complete code looks like:

	   package DateTime::Format::ICal;
	   use strict;
	   our $VERSION	= '0.04';

	   use DateTime::Format::Builder (
	       parsers => {
		   parse_datetime => [
		       [ preprocess => \&_parse_tz ],
		       {
			   length => 15,
			   params => [qw( year month day hour minute second )],
			   regex  => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)(\d\d)$/,
		       },
		       {
			   length => 13,
			   params => [qw( year month day hour minute )],
			   regex  => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)$/,
		       },
		       {
			   length => 11,
			   params => [qw( year month day hour )],
			   regex  => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)$/,
		       },
		       {
			   length => 8,
			   params => [qw( year month day )],
			   regex  => qr/^(\d\d\d\d)(\d\d)(\d\d)$/,
		       },
		   ],
	       },
	   );

	   sub _parse_tz {
	       my %args	= @_;
	       my ( $date, $p )	= @args{qw( input parsed )};
	       if ( $date =~ s/^TZID=([^:]+)://	) {
		   $p->{time_zone} = $1;
	       }

	       # Z at end means	UTC
	       elsif ( $date =~	s/Z$// ) {
		   $p->{time_zone} = 'UTC';
	       }
	       else {
		   $p->{time_zone} = 'floating';
	       }
	       return $date;
	   }

	   1;

       And that's an ICal parser. The actual DateTime::Format::ICal module
       also includes formatting	methods	and parsing for	durations, but Builder
       doesn't support those yet. A drop in replacement	(at the	time of
       writing the replacement)	can be found in	the examples directory of the
       Builder distribution, along with	similar	variants of other common
       modules.

SEE ALSO
       "datetime@perl.org" mailing list.

       http://datetime.perl.org/

       perl, DateTime, DateTime::Format::Builder

SUPPORT
       Bugs may	be submitted at
       <https://github.com/houseabsolute/DateTime-Format-Builder/issues>.

       I am also usually active	on IRC as 'autarch' on "irc://irc.perl.org".

SOURCE
       The source code repository for DateTime-Format-Builder can be found at
       <https://github.com/houseabsolute/DateTime-Format-Builder>.

AUTHORS
       o   Dave	Rolsky <autarch@urth.org>

       o   Iain	Truskett <spoon@cpan.org>

COPYRIGHT AND LICENSE
       This software is	Copyright (c) 2020 by Dave Rolsky.

       This is free software, licensed under:

	 The Artistic License 2.0 (GPL Compatible)

       The full	text of	the license can	be found in the	LICENSE	file included
       with this distribution.

perl v5.32.1			  2020-0DateTime::Format::Builder::Tutorial(3)

NAME | VERSION | CREATING A CLASS | SEE ALSO | SUPPORT | SOURCE | AUTHORS | COPYRIGHT AND LICENSE

Want to link to this manual page? Use this URL:
<https://www.freebsd.org/cgi/man.cgi?query=DateTime::Format::Builder::Tutorial&sektion=3&manpath=FreeBSD+13.0-RELEASE+and+Ports>

home | help