File Coverage

blib/lib/D/oh/Year.pm
Criterion Covered Total %
statement 74 86 86.0
branch 18 26 69.2
condition 5 13 38.4
subroutine 19 24 79.1
pod n/a
total 116 149 77.8


line stmt bran cond sub pod time code
1             package D'oh::Year;
2              
3             require 5.005; # Need a solid overload
4              
5 1     1   34022 use strict;
  1         3  
  1         80  
6              
7             =pod
8              
9             =head1 NAME
10              
11             D'oh::Year - Catch stupid mistakes when mucking with years, like Y2K bugs
12              
13              
14             =head1 SYNOPSIS
15              
16             use D'oh::Year;
17            
18             ($year) = (localtime)[5];
19             print "We're going to party like its 19$year"; # No you're not.
20            
21             print "Welcome to the year 20$year!"; # Sorry, Buck.
22            
23              
24             =head1 DESCRIPTION
25              
26             NO, PERL DOES NOT HAVE A Y2K BUG! but alot of people seem determined
27             to add it. Perl, and most other languges through various historical
28             reasons, like to return years in the form of the number of years since
29             1900. This has led to the false assumption that its actually
30             returning the last two digits of the current year (1999 => 99) and the
31             mistaken assumption that you can set the current year as "19$year".
32              
33             This is a Y2K bug, the honor is not just given to COBOL progrmamers.
34              
35             Bugs of this nature can easily be detected (most of the time) by an
36             automated process. This is it.
37              
38             When D'oh::Year is used, it provides special versions of localtime() and
39             gmtime() which return a rigged value for the year. When used properly
40             (usually 1900 + $year) you'll notice no difference. But when used for
41             B it will die with a message about misuse of the year.
42              
43             The following things are naughty (where $year is from gmtime() or
44             localtime()):
45              
46             "19$year", 19.$year
47             "20$year", 20.$year
48             "200$year", 200.$year
49             $year -= 100, $year = $year - 100;
50              
51             B
52             Take note, please.
53              
54             $year += 1900; # Get the complete year.
55             $year %= 100; # Get the last two digits of the year.
56             # ie "01" in 2001 and "99" in 1999
57            
58              
59             =head1 USAGE
60              
61             Its simple. Just use (do not require!) the module. If it detects a
62             problem, it will cause your program to abort with an error. If you
63             don't like this, you can use the module with the C<:WARN> tag like so:
64              
65             use D'oh::Year qw(:WARN);
66              
67             and it will warn upon seeing a year mishandling instead of dying.
68              
69             Because there is a I performance loss when using D'oh::Year, you
70             might want to only use it during development and testing. A few
71             suggestions for use...
72              
73             =over 4
74              
75             =item B
76              
77             Set up /usr/bin/perl on your development machine as a shell wrapper around
78             perl which always uses D'oh::Year:
79              
80             #!/bin/sh
81              
82             perl -MD::oh::Year $@
83              
84             This might be a little draconian for normal usage.
85              
86             =item B
87              
88             =item B
89              
90             C
91              
92             =back
93              
94              
95             =head1 CAVEATS
96              
97             This program does its checking at B not compile time. Thus
98             it is not simply enough to slap D'oh::Year on a program, run it once
99             and expect it to find everything. For a thourough scrubbing you must
100             make sure every line of code is excersied... but you already have test
101             harnesses set up to do that, RIGHT?!
102              
103              
104             =head1 TODO
105              
106             =over 4
107              
108             =item B
109              
110             Sorting time()'s as strings is a common mistake. I can't detect it
111             without some XS code to look at the op stack.
112              
113             =item B
114              
115             I can't handle this without being able to override printf(), but can't
116             do that because it has a complex prototype. This could be handled,
117             but it would require a patch to pp_printf. I can do sprintf(), but I
118             don't think its wise to be non-orthoganal and lead non-doc readers on
119             that if sprintf() is handled, printf() should be, too.
120              
121             =back
122              
123              
124             =head1 AUTHOR
125              
126             Original idea by Andrew Langmead
127              
128             Original code by Mark "The Ominous" Dominous
129              
130             Cleaned up and maintained by Michael G Schwern .
131              
132             =cut
133              
134 1     1   6 use vars qw($VERSION);
  1         2  
  1         468  
135             $VERSION = '0.06';
136              
137             sub _mk_localtime {
138 3     3   5 my($reaction) = shift;
139            
140             return sub {
141 5 0   5   1840 return @_ ? localtime(@_) : localtime() unless wantarray;
    50          
142 5 50       483 my @t = @_ ? localtime(@_) : localtime();
143 5         37 $t[5] = D'oh::Year::year->new($t[5], $reaction);
144 5         60 @t;
145             }
146 3         27 }
147              
148             sub _mk_gmtime {
149 3     3   4 my($reaction) = shift;
150            
151             return sub {
152 5 0   5   14 return @_ ? gmtime(@_) : gmtime() unless wantarray;
    50          
153 5 50       44 my @t = @_ ? gmtime(@_) : gmtime();
154 5         22 $t[5] = D'oh::Year::year->new($t[5], $reaction);
155 5         70 @t;
156             }
157 3         23 }
158              
159             sub _mk_time {
160 0     0   0 my($reaction) = shift;
161            
162             return sub {
163 0     0   0 return D'oh::Year::time->new(time, $reaction);
164             }
165 0         0 }
166              
167              
168             sub import {
169 3     3   598 () = shift; # Dump the package.
170 3         6 my $reaction = shift;
171 3         6 my $caller = caller;
172            
173 3 100       9 $reaction = ':DIE' unless defined $reaction;
174              
175 3 100       9 $reaction = $reaction =~ /^:WARN/i ? 'warn' : 'die';
176            
177             {
178 1     1   8 no strict 'refs';
  1         15  
  1         124  
  3         4  
179 3         8 *{$caller . '::localtime'} = &_mk_localtime($reaction);
  3         22  
180 3         9 *{$caller . '::gmtime'} = &_mk_gmtime($reaction);
  3         109  
181             # Didn't pan out.
182             # *{$caller . '::time'} = &_mk_time($reaction);
183             }
184             }
185              
186              
187             package D'oh::Year::year;
188              
189 1     1   1224 use fields qw(_Year _Reaction);
  1         2206  
  1         7  
190              
191 1     1   150 use strict;
  1         2  
  1         64  
192              
193 1         9 use overload '.' => \&concat,
194             '""' => \&stringize,
195             '0+' => \&numize,
196             '-' => \&subtract,
197             'fallback' => 'TRUE',
198 1     1   1920 ;
  1         1053  
199              
200              
201             sub new {
202 10     10   18 my $proto = shift;
203 10   33     54 my $class = ref $proto || $proto;
204 10         196 my($year, $reaction) = @_;
205              
206 10         58 my $self = fields::new($class);
207 10         4795 $self->{_Year} = $year;
208 10   50     430 $self->{_Reaction} = $reaction || 'die';
209              
210 10         25 return $self;
211             }
212              
213              
214             sub concat {
215 78     78   80911 my ($self, $a2, $rev) = @_;
216              
217 78 100 100     805 if ($a2 =~ /(19|200?)$/ && $rev) {
218 54         268 require Carp;
219 54 100       163 if ( $self->{_Reaction} eq 'warn' ) {
220 18         1995 Carp::carp("Possible year misuse.");
221             } else {
222 36         5345 Carp::croak("Possible year misuse.");
223             }
224             }
225            
226 24 100       63 if ($rev) {
227 12         324 return $a2 . $self->{_Year};
228             } else {
229 12         357 return $self->{_Year} . $a2;
230             }
231             }
232              
233             sub stringize {
234 2     2   8275 return shift->{_Year};
235             }
236              
237              
238             sub numize {
239 12     12   6520 return shift->{_Year};
240             }
241              
242             sub subtract {
243 18     18   14387 my($self, $num) = @_;
244 18 100       71 if( $num == 100 ) { # Catch $year -= 100
245 12         71 require Carp;
246 12 100       50 if ( $self->{_Reaction} eq 'warn' ) {
247 4         468 Carp::carp("Possible year misuse.");
248             } else {
249 8         904 Carp::croak("Possible year misuse.");
250             }
251             }
252              
253 6         112 return $self->{_Year} - $num;
254             }
255              
256             # I had an idea about catching C, but it didn't pan out.
257             package D'oh::Year::time;
258              
259 1     1   414 use fields qw(_Time _Reaction);
  1         2  
  1         5  
260              
261 1     1   70 use strict;
  1         1  
  1         62  
262              
263 1         6 use overload '""' => \&stringize,
264             '0+' => \&numize,
265             'fallback' => 'TRUE',
266 1     1   5 ;
  1         2  
267              
268              
269             sub new {
270 0     0     my $proto = shift;
271 0   0       my $class = ref $proto || $proto;
272 0           my($time, $reaction) = @_;
273              
274 0           my $self = fields::new($class);
275 0           $self->{_Time} = $time;
276 0   0       $self->{_Reaction} = $reaction || 'die';
277              
278 0           return $self;
279             }
280              
281              
282             sub stringize {
283             # XXX Need code to figure out if we're being called directly from
284             # XXX a sort.
285 0     0     return shift->{_Time};
286             }
287              
288              
289             sub numize {
290 0     0     return shift->{_Time};
291             }
292              
293              
294              
295             return 'sc_current_century';