Add xml entity escape code to rss20.
[matthijs/upstream/blosxom-plugins.git] / gavinc / storydate
1 # Blosxom Plugin: storydate
2 # Author(s): Gavin Carr <gavin@openfusion.com.au>
3 # (based on work by Frank Hecker <hecker@hecker.org>
4 #  and Bob Schumaker <cobblers@pobox.com>)
5 # Version: 0.001000
6 # Documentation: See the bottom of this file or type: perldoc storydate
7
8 package storydate;
9
10 use strict;
11
12 use File::stat;
13
14 # Uncomment next line to enable debug output (don't uncomment debug() lines)
15 #use Blosxom::Debug debug_level => 1;
16
17 # --- Configuration variables -----
18
19 # Perl modules to use for strftime, in search order
20 my @strftime_modules = qw(Time::Piece Date::Format POSIX);
21
22 # ---------------------------------
23
24 # Package variables
25 use vars qw(
26   $story_rfc822
27   $story_iso8601
28   $story_mtime_rfc822
29   $story_mtime_iso8601
30   $now_rfc822
31   $now_iso8601
32   %strftime_formats
33 );
34
35 %strftime_formats = (
36     rfc822      => "%a, %d %b %Y  %T %z",
37     # ISO 8601 format (localtime, including time zone offset)
38     # Format is YYYY-MM-DDThh:mm:ssTZD per http://www.w3.org/TR/NOTE-datetime
39     iso8601     => "%Y-%m-%dT%T%z",
40 );
41
42 sub start { 
43     my $now = time();
44     $now_rfc822  = format_date('rfc822', $now);
45     $now_iso8601 = format_date('iso8601', $now);
46     # debug(1, "now_rfc822: $now_rfc822");
47     # debug(1, "now_iso8601: $now_iso8601");
48     1;
49 }
50
51 sub story {
52     my ($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_;
53
54     ($story_rfc822, $story_mtime_rfc822) 
55         = get_story_dates('rfc822', $path, $filename);
56     ($story_iso8601, $story_mtime_iso8601) 
57         = get_story_dates('iso8601', $path, $filename);
58
59     return 1;
60 }
61
62 # Get the 'file' (publish/official) and 'mtime' formatted dates for the given story file
63 sub get_story_dates {
64     my ($format, $path, $filename) = @_; 
65     my ($file_dt, $mtime_dt) = ('', '');
66     $path ||= '';
67
68     my $story_file = "$blosxom::datadir$path/$filename.$blosxom::file_extension";
69     my $timestamp = $blosxom::files{ $story_file };
70     # debug(2, "$path/$filename timestamp: $timestamp");
71
72     if (my $stat = stat( $story_file )) {
73         if (my $mtime = $stat->mtime) {
74             # debug(2, "$path/$filename mtime: = $mtime");
75
76             $mtime_dt = format_date($format, $mtime);
77
78             $timestamp ||= $mtime;
79         }
80         else {
81             warn "storydate: cannot get mtime on story file '$story_file'\n";
82         }
83     }
84     else {
85         warn "storydate: cannot stat story file '$story_file'\n";
86     }
87
88     $file_dt = format_date($format, $timestamp) if $timestamp;
89
90     return wantarray ? ( $file_dt, $mtime_dt ) : $file_dt;
91 }
92
93 sub format_date {
94     my ($format, $time, $use_gmtime) = @_;
95     return unless $format;
96
97     my $date = '';
98     if ($format =~ m/%/) {
99         $date = strftime($format, $time, $use_gmtime);
100     }
101     elsif ($strftime_formats{ $format }) {
102         $date = strftime($strftime_formats{ $format }, $time, $use_gmtime);
103     }
104     # Hack to handle the fact that ISO8601 dates require a : in the timezone, which strftime doesn't support
105     if ($format eq 'iso8601') {
106         $date =~ s/(\d)(\d{2})$/$1:$2/;
107     }
108
109     return $date;
110 }
111
112 # strftime wrapper, using the first strftime() it finds in @strftime_modules
113 my %cache = ();
114 my $strftime_module = '';
115 sub strftime {
116     my ($format, $time, $use_gmtime) = @_;
117     return unless $format && $time;
118     $use_gmtime = 0 unless defined $use_gmtime;
119   
120     # Check cache
121     my $cache_key = "$time:$use_gmtime:$format";
122     return $cache{ $cache_key } if exists $cache{ $cache_key };
123
124     # For testing
125     $strftime_module = $ENV{BLOSXOM_STORYDATE_STRFTIME_MODULE};
126
127     # Search for a strftime module, and load
128     if (! $strftime_module) {
129         for my $module (@strftime_modules) {
130             if (eval "require $module") {
131                 $strftime_module = $module;
132                 # debug(2, "strftime_module: $strftime_module");
133                 last;
134             }
135         }
136         if (! $strftime_module) {
137             warn "storydate: cannot find any suitable strftime module\n";
138             return '';
139         }
140     }
141
142     my $datetime = '';
143     if ($strftime_module eq 'Time::Piece') {
144         my $t = $use_gmtime ? Time::Piece->gmtime($time) : Time::Piece->localtime($time);
145  
146         # Seems to be a bug in Time::Piece %z handling - workaround
147         my $tz_offset = sprintf("%+03d%02d", int($t->tzoffset / 3600), ($t->tzoffset % 3600) / 60);
148         $format =~ s/%z/$tz_offset/g;
149
150         $datetime = $t->strftime( $format );
151     }
152
153     elsif ($strftime_module eq 'Date::Format') {
154         my @t = $use_gmtime ? gmtime($time) : localtime($time);
155         $datetime = Date::Format::strftime($format, \@t);
156     }
157
158     elsif ($strftime_module eq 'POSIX') {
159         my @t = $use_gmtime ? gmtime($time) : localtime($time);
160         $datetime = POSIX::strftime($format, @t);
161     }
162
163     $cache{ $cache_key } = $datetime if $datetime;
164     return $datetime;
165 }
166
167
168 1;
169
170 __END__
171
172 =head1 NAME
173
174 storydate - blosxom plugin to provide story dates in various formats 
175 for use by other plugins
176
177
178 =head1 DESCRIPTION
179
180 storydate is a blosxom plugin that provides story dates in various 
181 formats for use by other plugins. It defines the following variables:
182
183 =over 4
184
185 =item $story_rfc822
186
187 The official/publication date of the story in RFC822 format.
188
189 =item $story_iso8601
190
191 The official/publication date of the story in ISO8601 format.
192
193 =item $story_mtime_rfc822
194
195 The last modification time of the story in RFC822 format.
196
197 =item $story_mtime_iso8601
198
199 The last modification time of the story in ISO8601 format.
200
201 =item $now_rfc822
202
203 The current time in RFC822 format.
204
205 =item $now_iso8601
206
207 The current time in ISO8601 format.
208
209 =back
210
211 In addition, storydate defines a few subroutines that might be useful
212 to other plugins:
213
214 =over 4
215
216 =item strftime($format, $time)
217
218 Returns $time, formatted according to the L<strftime(3)> format 
219 $format. Requires one of the following perl modules be available to 
220 provide a strftime() function: Time::Piece, Date::Format, or POSIX.
221
222 =item get_story_dates($format, $path, $filename)
223
224 Returns the publication date and the last modified date of the story 
225 at "$path/$filename.$blosxom::file_extension", formatted according
226 to the L<strftime(3)> format $format.
227
228 =back
229
230
231 =head1 USAGE
232
233 storydate should be loaded early, before any story plugins that want
234 to make use of it.
235
236
237 =head1 SEE ALSO
238
239 lastmodified2
240
241 Blosxom: http://blosxom.sourceforge.net/
242
243
244 =head1 ACKNOWLEDGEMENTS
245
246 storydate is based on Frank Hecker's lastmodified2 plugin, which is 
247 itself based on Bob Schumaker's lastmodified plugin.
248
249 The problem with lastmodified2 is that its HTTP header functionality 
250 requires that it runs very late, which makes it useless for providing
251 dates to story plugins. storydate is basically just all the parts of
252 lastmodified2 that can be run early pulled out into a separate plugin.
253
254
255 =head1 AUTHOR
256
257 Gavin Carr <gavin@openfusion.com.au>, http://www.openfusion.net/
258
259
260 =head1 LICENSE
261
262 Copyright 2007, Gavin Carr.
263
264 This plugin is licensed under the same terms as blosxom itself i.e.
265
266 Permission is hereby granted, free of charge, to any person obtaining a
267 copy of this software and associated documentation files (the "Software"),
268 to deal in the Software without restriction, including without limitation
269 the rights to use, copy, modify, merge, publish, distribute, sublicense,
270 and/or sell copies of the Software, and to permit persons to whom the
271 Software is furnished to do so, subject to the following conditions:
272
273 The above copyright notice and this permission notice shall be included
274 in all copies or substantial portions of the Software.
275
276 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
277 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
278 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
279 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
280 OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
281 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
282 OTHER DEALINGS IN THE SOFTWARE.
283
284 =cut
285
286 # vim:ft=perl:sw=4
287