1DateTime::Format::BuildUesre:r:TCuotnotrriiablu(t3e)d PeDralteDToicmuem:e:nFtoartmiaotn::Builder::Tutorial(3)
2
3
4
6 DateTime::Format::Builder::Tutorial - Quick class on using Builder
7
9 version 0.83
10
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
264 "datetime@perl.org" mailing list.
265
266 http://datetime.perl.org/
267
268 perl, DateTime, DateTime::Format::Builder
269
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
277 The source code repository for DateTime-Format-Builder can be found at
278 <https://github.com/houseabsolute/DateTime-Format-Builder>.
279
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)