root/branches/release-41/lib/MT/DateTime.pm @ 2747

Revision 2747, 8.8 kB (checked in by bchoate, 17 months ago)

Updated POD.

  • Property svn:keywords set to Author Date Id Revision
Line 
1# Movable Type (r) Open Source (C) 2001-2008 Six Apart, Ltd.
2# This program is distributed under the terms of the
3# GNU General Public License, version 2.
4#
5# $Id$
6
7# Adapted from DateTime package to avoid requirement of DateTime package.
8
9package MT::DateTime;
10
11use Exporter;
12@MT::DateTime::ISA = qw( Exporter );
13use vars qw( @EXPORT_OK );
14@EXPORT_OK = qw( ymd2rd tz_offset_as_seconds );
15
16use MT::Util qw( epoch2ts );
17
18sub new {
19    my $class = shift;
20    my (%param) = @_;
21    my $self = \%param;
22    bless $self, $class || __PACKAGE__;
23}
24
25sub week_year { (shift->week)[0] }
26sub week_number { (shift->week)[1] }
27
28sub year { shift->{year} }
29sub month { shift->{month} }
30sub day { shift->{day} }
31sub hour { shift->{hour} }
32sub minute { shift->{minute} }
33sub second { shift->{second} }
34sub time_zone { shift->{time_zone} }
35
36sub day_of_year {
37    my $self = shift;
38    return $self->{local_c}{day_of_year} if $self->{local_c}{day_of_year};
39
40    my $year = $self->year;
41    my $days = 0;
42
43    require MT::Util;
44    for (my $i = 1; $i < $self->month; $i++) {
45        $days += MT::Util::days_in($i, $year);
46    }
47    $days += $self->day;
48    $self->{local_c}{day_of_year} = $days;
49}
50
51sub week {
52    my $self = shift;
53
54    unless ( defined $self->{local_c}{week_year} ) {
55        my $jan_one_dow_m1 =
56            ( ( $self->ymd2rd( $self->year, 1, 1 ) + 6 ) % 7 );
57
58        $self->{local_c}{week_number} =
59            int( ( ( $self->day_of_year) + $jan_one_dow_m1 ) / 7 );
60        $self->{local_c}{week_number}++ if $jan_one_dow_m1 < 4;
61
62        if ( $self->{local_c}{week_number} == 0 ) {
63            $self->{local_c}{week_year} = $self->year - 1;
64            $self->{local_c}{week_number} =
65                $self->weeks_in_year( $self->{local_c}{week_year} );
66        }
67        elsif ( $self->{local_c}{week_number} == 53 &&
68                $self->weeks_in_year( $self->year ) == 52 )
69        {
70            $self->{local_c}{week_number} = 1;
71            $self->{local_c}{week_year} = $self->year + 1;
72        }
73        else
74        {
75            $self->{local_c}{week_year} = $self->year;
76        }
77    }
78
79    return @{ $self->{local_c} }{ 'week_year', 'week_number' }
80}
81
82sub weeks_in_year {
83    my $self = shift;
84    my $year = shift;
85   
86    my $jan_one_dow =
87        ( ( $self->ymd2rd( $year, 1, 1 ) + 6 ) % 7 ) + 1;
88    my $dec_31_dow =
89        ( ( $self->ymd2rd( $year, 12, 31 ) + 6 ) % 7 ) + 1;
90
91    return $jan_one_dow == 4 || $dec_31_dow == 4 ? 53 : 52;
92}
93
94sub ymd2rd {
95    my $self = shift;
96
97    use integer;
98    my ( $y, $m, $d );
99    if (@_) {
100        ( $y, $m, $d ) = @_;
101    } elsif (ref $self) {
102        ( $y, $m, $d ) = ( $self->{year}, $self->{month}, $self->{day} );
103    }
104
105    my $adj;
106
107    # make month in range 3..14 (treat Jan & Feb as months 13..14 of
108    # prev year)
109    if ( $m <= 2 )
110    {
111        $y -= ( $adj = ( 14 - $m ) / 12 );
112        $m += 12 * $adj;
113    }
114    elsif ( $m > 14 )
115    {
116        $y += ( $adj = ( $m - 3 ) / 12 );
117        $m -= 12 * $adj;
118    }
119
120    # make year positive (oh, for a use integer 'sane_div'!)
121    if ( $y < 0 )
122    {
123        $d -= 146097 * ( $adj = ( 399 - $y ) / 400 );
124        $y += 400 * $adj;
125    }
126
127    # add: day of month, days of previous 0-11 month period that began
128    # w/March, days of previous 0-399 year period that began w/March
129    # of a 400-multiple year), days of any 400-year periods before
130    # that, and 306 days to adjust from Mar 1, year 0-relative to Jan
131    # 1, year 1-relative (whew)
132
133    $d += ( $m * 367 - 1094 ) / 12 + $y % 100 * 1461 / 4 +
134          ( $y / 100 * 36524 + $y / 400 ) - 306;
135}
136
137sub tz_offset_as_seconds {   
138    my $self = shift;
139    my $offset = shift; 
140    if (ref $self) {
141        $offset = $self->{time_zone};
142    }
143
144    return undef unless defined $offset;
145
146    return 0 if $offset eq '0';
147       
148    my ( $sign, $hours, $minutes, $seconds );
149    if ( $offset =~ /^([\+\-])?(\d\d?):(\d\d)(?::(\d\d))?$/ )
150    {
151        ( $sign, $hours, $minutes, $seconds ) = ( $1, $2, $3, $4 );
152    }
153    elsif ( $offset =~ /^([\+\-])?(\d\d)(\d\d)(\d\d)?$/ )
154    {
155        ( $sign, $hours, $minutes, $seconds ) = ( $1, $2, $3, $4 );
156    }   
157    else
158    {       
159        return undef;
160    }
161       
162    $sign = '+' unless defined $sign;
163    return undef unless $hours >= 0 && $hours <= 99;
164    return undef unless $minutes >= 0 && $minutes <= 59;
165    return undef unless ! defined( $seconds ) || ( $seconds >= 0 && $seconds <= 59 );   
166       
167    my $total =  $hours * 3600 + $minutes * 60;
168    $total += $seconds if $seconds;
169    $total *= -1 if $sign eq '-';
170
171    return $total;
172}
173
174sub _param2ts {
175    my ( $param, $blog ) = @_;
176
177    my ( $type, $value );
178    if ( 'HASH' eq ref($param) ) {
179        $type = $param->{type};
180        $value = $param->{value};
181    }
182    else {
183        $type = 'ts';
184        $value = $param;
185    }
186    if ( 'CODE' eq ref($value) ) {
187        $value = $value->();
188    }
189
190    my $ts;
191    if ( 'epoch' eq $type ) {
192        $ts = epoch2ts( $blog, $value );
193    }
194    elsif ( 'datetime' eq $type ) {
195        $ts = sprintf "%04d%02d%02d%02d%02d%02d",
196            $value->year,
197            $value->month,
198            $value->day,
199            $value->hour,
200            $value->minute,
201            $value->second;
202    }
203    else {
204        $ts = $value;
205    }
206    $ts;
207}
208
209sub compare {
210    my $self = shift;
211    my %param = @_;
212    # a => $ts | CODE | { value => CODE|$v, type => ts|epoch|datetime }
213    # b => $ts | CODE | { value => CODE|$v, type => ts|epoch|datetime }
214    # blog => ref|N|undef
215    # comparer => CODE|undef
216
217    my $blog = $param{blog};
218    if ( defined($blog) && !ref($blog) ) {
219        $blog = MT->model('blog')->load( $blog );
220        $blog = undef unless ref($blog);
221    }
222
223    if ( !exists($param{a}) && ref($self) ) {
224        $param{a} = { value => $self, type => 'datetime' };
225    }
226    my $ts_a = _param2ts( $param{a}, $blog );
227
228    if ( !exists($param{b}) && ref($self) ) {
229        $param{b} = { value => $self, type => 'datetime' };
230    }
231    my $ts_b = _param2ts( $param{b}, $blog );
232
233    my $comparer = $param{code};
234    if ( 'CODE' eq ref($comparer) ) {
235        return $comparer->( $ts_a, $ts_b );
236    }
237    else {
238        return $ts_a - $ts_b;
239    }
240}
241
2421;
243__END__
244
245=head1 NAME
246
247MT::DateTime - A utility package for handling date/time values for Movable
248Type.
249
250=head1 METHODS
251
252=head2 MT::DateTime->new(%attr)
253
254Constructs a new C<MT::DateTime> object using the values in C<%attr>.
255C<%attr> may contain:
256
257=over 4
258
259=item * year
260
261A 4-digit year.
262
263=item * month
264
265Month number, where January is 0.
266
267=item * day
268
269Day number, from 1 to 31.
270
271=item * hour
272
273Hour in 24 hour notation (0-23).
274
275=item * minute
276
277Minutes (0-59).
278
279=item * second
280
281Seconds (0-59).
282
283=item * time_zone
284
285Timezone, in '+HH:MM', '-HH:MM', '+HH', or '-HH' notation.
286
287=back
288
289=head2 compare( a => $a, b => $b, blog => $blog )
290
291Compares two timestamp strings and returns negative valye, 0, or
292positive value depending on whether the "a" argument is less than,
293equal to, or greater than the "b" argument.
294
295You can specify scalar value to "a" and "b".  They are treated as
296timestamp values (e.g. 20080405123456).  You can also specify either
297epoch string (e.g the string returned from time method), or C<MT::DateTime>
298object.  In those cases, you must specify both value and type in a hash,
299for example:
300
301    MT::DateTime->compare( blog => $blog,
302        a => '20041231123456', b => { value => time(), type => 'epoch' } );
303
304=head2 $datetime->week_year()
305
306Returns the year for the start of the week for the object.
307
308=head2 $datetime->week_number()
309
310Returns the week number calculated for the object.
311
312=head2 $datetime->year()
313
314Returns the year component of the object.
315
316=head2 $datetime->month()
317
318Returns the month component of the object.
319
320=head2 $datetime->day()
321
322Returns the day component of the object.
323
324=head2 $datetime->hour()
325
326Returns the hours component of the object.
327
328=head2 $datetime->minute()
329
330Returns the minutes component of the object.
331
332=head2 $datetime->second()
333
334Returns the seconds component of the object.
335
336=head2 $datetime->time_zone()
337
338Returns the time zone component of the object.
339
340=head2 $datetime->day_of_year()
341
342Returns the day number of the year of the object.
343
344=head2 $datetime->week()
345
346Returns a list containing the year of the week (C<week_year>) and
347the week number (C<week_number>).
348
349=head2 MT::DateTime->weeks_in_year($year)
350
351Returns the number of weeks that are in the specified C<$year>. Returns
352either 52 or 53, depending on the year.
353
354=head2 $datetime->ymd2rd( [ $year, $month, $day ])
355
356Converts the given C<$year>, C<$month>, C<$day> (or, if unspecified,
357uses the year, month, day elements from C<$datetime>) into a 'Rata Die'
358days value.
359
360=head2 $datetime->tz_offset_as_seconds( [$offset] )
361
362Converts the given C<$offset> (or, if absent, uses the time_zone
363component of C<$datetime>) into an expression of seconds. I.e.,
364an C<$offset> of '-1:30' would yield -5400.
365
366=head1 AUTHOR & COPYRIGHT
367
368Please see L<MT/AUTHOR & COPYRIGHT>.
369
370=cut
Note: See TracBrowser for help on using the browser.