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