Add storydate.
[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     if ($format =~ m/%/) {
97         return strftime($format, $time, $use_gmtime);
98     }
99     elsif ($strftime_formats{ $format }) {
100         return strftime($strftime_formats{ $format }, $time, $use_gmtime);
101     }
102 }
103
104 # strftime wrapper, using the first strftime() it finds in @strftime_modules
105 my %cache = ();
106 my $strftime_module = '';
107 sub strftime {
108     my ($format, $time, $use_gmtime) = @_;
109     return unless $format && $time;
110     $use_gmtime = 0 unless defined $use_gmtime;
111   
112     # Check cache
113     my $cache_key = "$time:$use_gmtime:$format";
114     return $cache{ $cache_key } if exists $cache{ $cache_key };
115
116     # For testing
117     $strftime_module = $ENV{BLOSXOM_STORYDATE_STRFTIME_MODULE};
118
119     # Search for a strftime module, and load
120     if (! $strftime_module) {
121         for my $module (@strftime_modules) {
122             if (eval "require $module") {
123                 $strftime_module = $module;
124                 # debug(2, "strftime_module: $strftime_module");
125                 last;
126             }
127         }
128         if (! $strftime_module) {
129             warn "storydate: cannot find any suitable strftime module\n";
130             return '';
131         }
132     }
133
134     my $datetime = '';
135     if ($strftime_module eq 'Time::Piece') {
136         my $t = $use_gmtime ? Time::Piece->gmtime($time) : Time::Piece->localtime($time);
137  
138         # Seems to be a bug in Time::Piece %z handling - workaround
139         my $tz_offset = sprintf("%+03d%02d", int($t->tzoffset / 3600), ($t->tzoffset % 3600) / 60);
140         $format =~ s/%z/$tz_offset/g;
141
142         $datetime = $t->strftime( $format );
143     }
144
145     elsif ($strftime_module eq 'Date::Format') {
146         my @t = $use_gmtime ? gmtime($time) : localtime($time);
147         $datetime = Date::Format::strftime($format, \@t);
148     }
149
150     elsif ($strftime_module eq 'POSIX') {
151         my @t = $use_gmtime ? gmtime($time) : localtime($time);
152         $datetime = POSIX::strftime($format, @t);
153     }
154
155     $cache{ $cache_key } = $datetime if $datetime;
156     return $datetime;
157 }
158
159
160 1;
161
162 __END__
163
164 =head1 NAME
165
166 storydate - blosxom plugin to provide story dates in various formats 
167 for use by other plugins
168
169
170 =head1 DESCRIPTION
171
172 storydate is a blosxom plugin that provides story dates in various 
173 formats for use by other plugins. It defines the following variables:
174
175 =over 4
176
177 =item $story_rfc822
178
179 The official/publication date of the story in RFC822 format.
180
181 =item $story_iso8601
182
183 The official/publication date of the story in ISO8601 format.
184
185 =item $story_mtime_rfc822
186
187 The last modification time of the story in RFC822 format.
188
189 =item $story_mtime_iso8601
190
191 The last modification time of the story in ISO8601 format.
192
193 =item $now_rfc822
194
195 The current time in RFC822 format.
196
197 =item $now_iso8601
198
199 The current time in ISO8601 format.
200
201 =back
202
203 In addition, storydate defines a few subroutines that might be useful
204 to other plugins:
205
206 =over 4
207
208 =item strftime($format, $time)
209
210 Returns $time, formatted according to the L<strftime(3)> format 
211 $format. Requires one of the following perl modules be available to 
212 provide a strftime() function: Time::Piece, Date::Format, or POSIX.
213
214 =item get_story_dates($format, $path, $filename)
215
216 Returns the publication date and the last modified date of the story 
217 at "$path/$filename.$blosxom::file_extension", formatted according
218 to the L<strftime(3)> format $format.
219
220 =back
221
222
223 =head1 USAGE
224
225 storydate should be loaded early, before any story plugins that want
226 to make use of it.
227
228
229 =head1 SEE ALSO
230
231 lastmodified2
232
233 Blosxom: http://blosxom.sourceforge.net/
234
235
236 =head1 ACKNOWLEDGEMENTS
237
238 storydate is based on Frank Hecker's lastmodified2 plugin, which is 
239 itself based on Bob Schumaker's lastmodified plugin.
240
241 The problem with lastmodified2 is that its HTTP header functionality 
242 requires that it runs very late, which makes it useless for providing
243 dates to story plugins. storydate is basically just all the parts of
244 lastmodified2 that can be run early pulled out into a separate plugin.
245
246
247 =head1 AUTHOR
248
249 Gavin Carr <gavin@openfusion.com.au>, http://www.openfusion.net/
250
251
252 =head1 LICENSE
253
254 Copyright 2007, Gavin Carr.
255
256 This plugin is licensed under the same terms as blosxom itself i.e.
257
258 Permission is hereby granted, free of charge, to any person obtaining a
259 copy of this software and associated documentation files (the "Software"),
260 to deal in the Software without restriction, including without limitation
261 the rights to use, copy, modify, merge, publish, distribute, sublicense,
262 and/or sell copies of the Software, and to permit persons to whom the
263 Software is furnished to do so, subject to the following conditions:
264
265 The above copyright notice and this permission notice shall be included
266 in all copies or substantial portions of the Software.
267
268 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
269 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
270 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
271 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
272 OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
273 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
274 OTHER DEALINGS IN THE SOFTWARE.
275
276 =cut
277
278 # vim:ft=perl:sw=4
279