1=head1 NAME 2 3DateTime::Format::Builder::Tutorial - Quick class on using Builder 4 5=head1 CREATING A CLASS 6 7As most people who are writing modules know, you start a 8package with a package declaration and some indication of 9module version: 10 11 package DateTime::Format::ICal; 12 our $VERSION = '0.04'; 13 14After that, you call Builder with some options. There are 15only a few (detailed later). Right now, we're only interested 16in I<parsers>. 17 18 use DateTime::Format::Builder 19 ( 20 parsers => { 21 ... 22 } 23 ); 24 25The I<parsers> option takes a reference to a hash of method 26names and specifications: 27 28 parsers => { 29 parse_datetime => ... , 30 parse_datetime_with_timezone => ... , 31 ... 32 } 33 34Builder will create methods in your class, each method being 35a parser that follows the given specifications. It is 36B<strongly> recommended that one method is called 37I<parse_datetime>, be it a Builder created method or one of 38your own. 39 40In addition to creating any of the parser methods it also 41creates a C<new()> method that can instantiate (or clone) 42objects of this class. This behaviour can be modified with 43the I<constructor> option, but we don't need to know that 44yet. 45 46Each value corresponding to a method name in the parsers 47list is either a single specification, or a list of 48specifications. We'll start with the simple case. 49 50 parse_briefdate => { 51 params => [ qw( year month day ) ], 52 regex => qr/^(\d\d\d\d)(\d\d)(\d\d)$/, 53 }, 54 55This will result in a method named I<parse_briefdate> which 56will take strings in the form C<20040716> and return 57DateTime objects representing that date. A user of the class 58might write: 59 60 use DateTime::Format::ICal; 61 my $date = "19790716"; 62 my $dt = DateTime::Format::ICal->parse_briefdate( $date ); 63 print "My birth month is ", $dt->month_name, "\n"; 64 65The C<regex> is applied to the input string, and if it 66matches, then C<$1>, C<$2>, ... are mapped to the I<params> 67given and handed to C<< DateTime->new() >>. Essentially: 68 69 my $rv = DateTime->new( year => $1, month => $2, day => $3 ); 70 71There are more complicated things one can do within a single 72specification, but we'll cover those later. 73 74Often, you'll want a method to be able to take one string, 75and run it against multiple parser specifications. It would 76be very irritating if the user had to work out what format 77the datetime string was in and then which method was most 78appropriate. 79 80So, Builder lets you specify multiple specifications: 81 82 parse_datetime => [ 83 { 84 params => [ qw( year month day hour minute second ) ], 85 regex => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)(\d\d)$/, 86 }, 87 { 88 params => [ qw( year month day hour minute ) ], 89 regex => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)$/, 90 }, 91 { 92 params => [ qw( year month day hour ) ], 93 regex => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)$/, 94 }, 95 { 96 params => [ qw( year month day ) ], 97 regex => qr/^(\d\d\d\d)(\d\d)(\d\d)$/, 98 }, 99 ], 100 101It's an arrayref of specifications. A parser will be created 102that will try each of these specifications sequentially, in 103the order you specified. 104 105There's a flaw with this though. In this example, we're 106building a parser for ICal datetimes. One can place a 107timezone id at the start of an ICal datetime. You might 108extract such an id with the following code: 109 110 if ( $date =~ s/^TZID=([^:]+):// ) 111 { 112 $time_zone = $1; 113 } 114 # Z at end means UTC 115 elsif ( $date =~ s/Z$// ) 116 { 117 $time_zone = 'UTC'; 118 } 119 else 120 { 121 $time_zone = 'floating'; 122 } 123 124C<$date> would end up without the id, and C<$time_zone> would 125contain something appropriate to give to DateTime's 126I<set_time_zone> method, or I<time_zone> argument. 127 128But how to get this scrap of code into your parser? You 129might be tempted to call the parser something else and build 130a small wrapper. There's no need though because an option is 131provided for preprocesing dates: 132 133 parse_datetime => [ 134 [ preprocess => \&_parse_tz ], # Only changed line! 135 { 136 params => [ qw( year month day hour minute second ) ], 137 regex => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)(\d\d)$/, 138 }, 139 { 140 params => [ qw( year month day hour minute ) ], 141 regex => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)$/, 142 }, 143 { 144 params => [ qw( year month day hour ) ], 145 regex => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)$/, 146 }, 147 { 148 params => [ qw( year month day ) ], 149 regex => qr/^(\d\d\d\d)(\d\d)(\d\d)$/, 150 }, 151 ], 152 153It will necessitate I<_parse_tz> to be written, and that 154routine looks like this: 155 156 sub _parse_tz 157 { 158 my %args = @_; 159 my ($date, $p) = @args{qw( input parsed )}; 160 if ( $date =~ s/^TZID=([^:]+):// ) 161 { 162 $p->{time_zone} = $1; 163 } 164 # Z at end means UTC 165 elsif ( $date =~ s/Z$// ) 166 { 167 $p->{time_zone} = 'UTC'; 168 } 169 else 170 { 171 $p->{time_zone} = 'floating'; 172 } 173 return $date; 174 } 175 176On input it is given a hash containing two items: the input 177date and a hashref that will be used in the parsing. The 178return value from the routine is what the parser 179specifications will run against, and anything in the 180I<parsed> hash (C<$p> in the example) will be put in the 181call to C<< DateTime->new(...) >>. 182 183So, we now have a happily working ICal parser. It parses the 184assorted formats, and can also handle timezones. Is there 185anything else it needs to do? No. But we can make it work 186more efficiently. 187 188At present, the specifications are tested sequentially. 189However, each one applies to strings of particular lengths. 190Thus we could be efficient and have the parser only test the 191given strings against a parser that handles that string 192length. Again, Builder makes it easy: 193 194 parse_datetime => [ 195 [ preprocess => \&_parse_tz ], 196 { 197 length => 15, # We handle strings of exactly 15 chars 198 params => [ qw( year month day hour minute second ) ], 199 regex => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)(\d\d)$/, 200 }, 201 { 202 length => 13, # exactly 13 chars... 203 params => [ qw( year month day hour minute ) ], 204 regex => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)$/, 205 }, 206 { 207 length => 11, # 11.. 208 params => [ qw( year month day hour ) ], 209 regex => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)$/, 210 }, 211 { 212 length => 8, # yes. 213 params => [ qw( year month day ) ], 214 regex => qr/^(\d\d\d\d)(\d\d)(\d\d)$/, 215 }, 216 ], 217 218Now the created parser will create a parser that only runs 219specifications against appropriate strings. 220 221So our complete code looks like: 222 223 package DateTime::Format::ICal; 224 use strict; 225 our $VERSION = '0.04'; 226 227 use DateTime::Format::Builder 228 ( 229 parsers => { 230 parse_datetime => [ 231 [ preprocess => \&_parse_tz ], 232 { 233 length => 15, 234 params => [ qw( year month day hour minute second ) ], 235 regex => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)(\d\d)$/, 236 }, 237 { 238 length => 13, 239 params => [ qw( year month day hour minute ) ], 240 regex => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)$/, 241 }, 242 { 243 length => 11, 244 params => [ qw( year month day hour ) ], 245 regex => qr/^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)$/, 246 }, 247 { 248 length => 8, 249 params => [ qw( year month day ) ], 250 regex => qr/^(\d\d\d\d)(\d\d)(\d\d)$/, 251 }, 252 ], 253 }, 254 ); 255 256 sub _parse_tz 257 { 258 my %args = @_; 259 my ($date, $p) = @args{qw( input parsed )}; 260 if ( $date =~ s/^TZID=([^:]+):// ) 261 { 262 $p->{time_zone} = $1; 263 } 264 # Z at end means UTC 265 elsif ( $date =~ s/Z$// ) 266 { 267 $p->{time_zone} = 'UTC'; 268 } 269 else 270 { 271 $p->{time_zone} = 'floating'; 272 } 273 return $date; 274 } 275 276 1; 277 278And that's an ICal parser. The actual 279L<DateTime::Format::ICal> module also includes formatting 280methods and parsing for durations, but Builder doesn't 281support those yet. A drop in replacement (at the time of 282writing the replacement) can be found in the F<examples> 283directory of the Builder distribution, along with similar 284variants of other common modules. 285 286=head1 SUPPORT 287 288Any errors you see in this document, please log them with 289CPAN RT system via the web or email: 290 291 http://perl.dellah.org/rt/dtbuilder 292 bug-datetime-format-builder@rt.cpan.org 293 294This makes it much easier for me to track things and thus means 295your problem is less likely to be neglected. 296 297=head1 LICENSE AND COPYRIGHT 298 299Copyright E<copy> Iain Truskett, 2003. All rights reserved. 300 301You can redistribute this document and/or modify 302it under the same terms as Perl itself. 303 304The full text of the licenses can be found in the F<Artistic> and 305F<COPYING> files included with this document. 306 307=head1 AUTHOR 308 309Iain Truskett <spoon@cpan.org> 310 311=head1 SEE ALSO 312 313C<datetime@perl.org> mailing list. 314 315http://datetime.perl.org/ 316 317L<perl>, L<DateTime>, L<DateTime::Format::Builder> 318 319=cut 320 321