1DateTime::Format::BuildUesre:r:TCuotnotrriiablu(t3e)d PeDralteDToicmuem:e:nFtoartmiaotn::Builder::Tutorial(3)
2
3
4

NAME

6       DateTime::Format::Builder::Tutorial - Quick class on using Builder
7

VERSION

9       version 0.83
10

CREATING A CLASS

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

SEE ALSO

264       "datetime@perl.org" mailing list.
265
266       http://datetime.perl.org/
267
268       perl, DateTime, DateTime::Format::Builder
269

SUPPORT

271       Bugs may be submitted at
272       <https://github.com/houseabsolute/DateTime-Format-Builder/issues>.
273
274       I am also usually active on IRC as 'autarch' on "irc://irc.perl.org".
275

SOURCE

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

AUTHORS

281       •   Dave Rolsky <autarch@urth.org>
282
283       •   Iain Truskett <spoon@cpan.org>
284
286       This software is Copyright (c) 2020 by Dave Rolsky.
287
288       This is free software, licensed under:
289
290         The Artistic License 2.0 (GPL Compatible)
291
292       The full text of the license can be found in the LICENSE file included
293       with this distribution.
294
295
296
297perl v5.34.0                      2022-01D-a2t1eTime::Format::Builder::Tutorial(3)
Impressum