import rt 3.2.2
[freeside.git] / rt / lib / RT / Date.pm
1 # {{{ BEGIN BPS TAGGED BLOCK
2
3 # COPYRIGHT:
4 #  
5 # This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC 
6 #                                          <jesse@bestpractical.com>
7
8 # (Except where explicitly superseded by other copyright notices)
9
10
11 # LICENSE:
12
13 # This work is made available to you under the terms of Version 2 of
14 # the GNU General Public License. A copy of that license should have
15 # been provided with this software, but in any event can be snarfed
16 # from www.gnu.org.
17
18 # This work is distributed in the hope that it will be useful, but
19 # WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21 # General Public License for more details.
22
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26
27
28 # CONTRIBUTION SUBMISSION POLICY:
29
30 # (The following paragraph is not intended to limit the rights granted
31 # to you to modify and distribute this software under the terms of
32 # the GNU General Public License and is only of importance to you if
33 # you choose to contribute your changes and enhancements to the
34 # community by submitting them to Best Practical Solutions, LLC.)
35
36 # By intentionally submitting any modifications, corrections or
37 # derivatives to this work, or any other work intended for use with
38 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
39 # you are the copyright holder for those contributions and you grant
40 # Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
41 # royalty-free, perpetual, license to use, copy, create derivative
42 # works based on those contributions, and sublicense and distribute
43 # those contributions and any derivatives thereof.
44
45 # }}} END BPS TAGGED BLOCK
46 =head1 NAME
47
48   RT::Date - a simple Object Oriented date.
49
50 =head1 SYNOPSIS
51
52   use RT::Date
53
54 =head1 DESCRIPTION
55
56 RT Date is a simple Date Object designed to be speedy and easy for RT to use
57
58 The fact that it assumes that a time of 0 means "never" is probably a bug.
59
60 =begin testing
61
62 ok (require RT::Date);
63
64 =end testing
65
66 =head1 METHODS
67
68 =cut
69
70
71 package RT::Date;
72
73 use Time::Local;
74
75 use RT::Base;
76
77 use strict;
78 use vars qw/@ISA/;
79 @ISA = qw/RT::Base/;
80
81 use vars qw($MINUTE $HOUR $DAY $WEEK $MONTH $YEAR);
82
83 $MINUTE = 60;
84 $HOUR   = 60 * $MINUTE;
85 $DAY    = 24 * $HOUR;
86 $WEEK   = 7 * $DAY;
87 $MONTH  = 4 * $WEEK;
88 $YEAR   = 365 * $DAY;
89
90 # {{{ sub new 
91
92 sub new  {
93   my $proto = shift;
94   my $class = ref($proto) || $proto;
95   my $self  = {};
96   bless ($self, $class);
97   $self->CurrentUser(@_);
98   $self->Unix(0);
99   return $self;
100 }
101
102 # }}}
103
104 # {{{ sub Set
105
106 =head2 sub Set
107
108 takes a param hash with the fields 'Format' and 'Value'
109
110 if $args->{'Format'} is 'unix', takes the number of seconds since the epoch 
111
112 If $args->{'Format'} is ISO, tries to parse an ISO date.
113
114 If $args->{'Format'} is 'unknown', require Time::ParseDate and make it figure
115 things out. This is a heavyweight operation that should never be called from
116 within RT's core. But it's really useful for something like the textbox date
117 entry where we let the user do whatever they want.
118
119 If $args->{'Value'}  is 0, assumes you mean never.
120
121 =begin testing
122
123 use_ok(RT::Date);
124 my $date = RT::Date->new($RT::SystemUser);
125 $date->Set(Format => 'unix', Value => '0');
126 ok ($date->ISO eq '1970-01-01 00:00:00', "Set a date to midnight 1/1/1970 GMT");
127
128 =end testing
129
130 =cut
131
132 sub Set {
133     my $self = shift;
134     my %args = ( Format => 'unix',
135                  Value  => time,
136                  @_ );
137     if ( !$args{'Value'}
138          || ( ( $args{'Value'} =~ /^\d*$/ ) and ( $args{'Value'} == 0 ) ) ) {
139         $self->Unix(-1);
140         return ( $self->Unix() );
141     }
142
143     if ( $args{'Format'} =~ /^unix$/i ) {
144         $self->Unix( $args{'Value'} );
145     }
146
147     elsif ( $args{'Format'} =~ /^(sql|datemanip|iso)$/i ) {
148         $args{'Value'} =~ s!/!-!g;
149
150         if (( $args{'Value'} =~ /^(\d{4}?)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/ )
151             || ( $args{'Value'} =~
152                  /^(\d{4}?)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)$/ )
153             || ( $args{'Value'} =~
154                  /^(\d{4}?)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)\+00$/ )
155             || ($args{'Value'} =~ /^(\d{4}?)(\d\d)(\d\d)(\d\d):(\d\d):(\d\d)$/ )
156           ) {
157
158             my $year  = $1;
159             my $mon   = $2;
160             my $mday  = $3;
161             my $hours = $4;
162             my $min   = $5;
163             my $sec   = $6;
164
165             #timegm expects month as 0->11
166             $mon--;
167
168             #now that we've parsed it, deal with the case where everything
169             #was 0
170             if ( $mon == -1 ) {
171                 $self->Unix(-1);
172             }
173             else {
174
175                 #Dateamnip strings aren't in GMT.
176                 if ( $args{'Format'} =~ /^datemanip$/i ) {
177                     $self->Unix(
178                           timelocal( $sec, $min, $hours, $mday, $mon, $year ) );
179                 }
180
181                 #ISO and SQL dates are in GMT
182                 else {
183                     $self->Unix(
184                              timegm( $sec, $min, $hours, $mday, $mon, $year ) );
185                 }
186
187                 $self->Unix(-1) unless $self->Unix;
188             }
189         }
190         else {
191             use Carp;
192             Carp::cluck;
193             $RT::Logger->debug(
194                      "Couldn't parse date $args{'Value'} as a $args{'Format'}");
195
196         }
197     }
198     elsif ( $args{'Format'} =~ /^unknown$/i ) {
199         require Time::ParseDate;
200
201         #Convert it to an ISO format string
202
203         my $date = Time::ParseDate::parsedate($args{'Value'},
204                         UK => $RT::DateDayBeforeMonth,
205                         PREFER_PAST => $RT::AmbiguousDayInPast,
206                         PREFER_FUTURE => !($RT::AmbiguousDayInPast));
207
208         #This date has now been set to a date in the _local_ timezone.
209         #since ISO dates are known to be in GMT (for RT's purposes);
210
211         $RT::Logger->debug( "RT::Date used date::parse to make "
212                             . $args{'Value'}
213                             . " $date\n" );
214
215         return ( $self->Set( Format => 'unix', Value => "$date" ) );
216     }
217     else {
218         die "Unknown Date format: " . $args{'Format'} . "\n";
219     }
220
221     return ( $self->Unix() );
222 }
223
224 # }}}
225
226 # {{{ sub SetToMidnight 
227
228 =head2 SetToMidnight
229
230 Sets the date to midnight (at the beginning of the day) GMT
231 Returns the unixtime at midnight.
232
233 =cut
234
235 sub SetToMidnight {
236     my $self = shift;
237     
238     use Time::Local;
239     my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = gmtime($self->Unix);
240     $self->Unix(timegm (0,0,0,$mday,$mon,$year,$wday,$yday));
241     
242     return ($self->Unix);
243     
244     
245 }
246
247
248 # }}}
249
250 # {{{ sub SetToNow
251 sub SetToNow {
252         my $self = shift;
253         return($self->Set(Format => 'unix', Value => time))
254 }
255 # }}}
256
257 # {{{ sub Diff
258
259 =head2 Diff
260
261 Takes either an RT::Date object or the date in unixtime format as a string
262
263 Returns the differnce between $self and that time as a number of seconds
264
265 =cut
266
267 sub Diff {
268     my $self = shift;
269     my $other = shift;
270
271     if (ref($other) eq 'RT::Date') {
272         $other=$other->Unix;
273     }
274     return ($self->Unix - $other);
275 }
276 # }}}
277
278 # {{{ sub DiffAsString
279
280 =head2 sub DiffAsString
281
282 Takes either an RT::Date object or the date in unixtime format as a string
283
284 Returns the differnce between $self and that time as a number of seconds as
285 as string fit for human consumption
286
287 =cut
288
289 sub DiffAsString {
290     my $self = shift;
291     my $other = shift;
292
293
294     if ($other < 1) {
295         return ("");
296     }
297     if ($self->Unix < 1) {
298         return("");
299     }
300     my $diff = $self->Diff($other);
301
302     return ($self->DurationAsString($diff));
303 }
304 # }}}
305
306 # {{{ sub DurationAsString
307
308
309 =head2 DurationAsString
310
311 Takes a number of seconds. returns a string describing that duration
312
313 =cut
314
315 sub DurationAsString {
316
317     my $self     = shift;
318     my $duration = shift;
319
320     my ( $negative, $s );
321
322     $negative = 1 if ( $duration < 0 );
323
324     $duration = abs($duration);
325
326     my $time_unit;
327     if ( $duration < $MINUTE ) {
328         $s         = $duration;
329         $time_unit = $self->loc("sec");
330     }
331     elsif ( $duration < ( 2 * $HOUR ) ) {
332         $s         = int( $duration / $MINUTE );
333         $time_unit = $self->loc("min");
334     }
335     elsif ( $duration < ( 2 * $DAY ) ) {
336         $s         = int( $duration / $HOUR );
337         $time_unit = $self->loc("hours");
338     }
339     elsif ( $duration < ( 2 * $WEEK ) ) {
340         $s         = int( $duration / $DAY );
341         $time_unit = $self->loc("days");
342     }
343     elsif ( $duration < ( 2 * $MONTH ) ) {
344         $s         = int( $duration / $WEEK );
345         $time_unit = $self->loc("weeks");
346     }
347     elsif ( $duration < $YEAR ) {
348         $s         = int( $duration / $MONTH );
349         $time_unit = $self->loc("months");
350     }
351     else {
352         $s         = int( $duration / $YEAR );
353         $time_unit = $self->loc("years");
354     }
355
356     if ($negative) {
357         return $self->loc( "[_1] [_2] ago", $s, $time_unit );
358     }
359     else {
360         return $self->loc( "[_1] [_2]", $s, $time_unit );
361     }
362 }
363
364 # }}}
365
366 # {{{ sub AgeAsString
367
368 =head2 sub AgeAsString
369
370 Takes nothing
371
372 Returns a string that's the differnce between the time in the object and now
373
374 =cut
375
376 sub AgeAsString {
377     my $self = shift;
378     return ($self->DiffAsString(time));
379     }
380 # }}}
381
382 # {{{ sub AsString
383
384 =head2 sub AsString
385
386 Returns the object\'s time as a string with the current timezone.
387
388 =cut
389
390 sub AsString {
391     my $self = shift;
392     return ($self->loc("Not set")) if ($self->Unix <= 0);
393
394     my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($self->Unix);
395
396     return $self->loc("[_1] [_2] [_3] [_4]:[_5]:[_6] [_7]", $self->GetWeekday($wday), $self->GetMonth($mon), map {sprintf "%02d", $_} ($mday, $hour, $min, $sec), ($year+1900));
397 }
398 # }}}
399
400 # {{{ GetWeekday
401 =head2 GetWeekday DAY
402
403 Takes an integer day of week and returns a localized string for that day of week
404
405 =cut
406
407 sub GetWeekday {
408     my $self = shift;
409     my $dow = shift;
410     
411     return $self->loc('Mon.') if ($dow == 1);
412     return $self->loc('Tue.') if ($dow == 2);
413     return $self->loc('Wed.') if ($dow == 3);
414     return $self->loc('Thu.') if ($dow == 4);
415     return $self->loc('Fri.') if ($dow == 5);
416     return $self->loc('Sat.') if ($dow == 6);
417     return $self->loc('Sun.') if ($dow == 0);
418 }
419
420 # }}}
421
422 # {{{ GetMonth
423 =head2 GetMonth DAY
424
425 Takes an integer month and returns a localized string for that month 
426
427 =cut
428
429 sub GetMonth {
430     my $self = shift;
431    my $mon = shift;
432
433     # We do this rather than an array so that we don't call localize 12x what we need to
434     return $self->loc('Jan.') if ($mon == 0);
435     return $self->loc('Feb.') if ($mon == 1);
436     return $self->loc('Mar.') if ($mon == 2);
437     return $self->loc('Apr.') if ($mon == 3);
438     return $self->loc('May.') if ($mon == 4);
439     return $self->loc('Jun.') if ($mon == 5);
440     return $self->loc('Jul.') if ($mon == 6);
441     return $self->loc('Aug.') if ($mon == 7);
442     return $self->loc('Sep.') if ($mon == 8);
443     return $self->loc('Oct.') if ($mon == 9);
444     return $self->loc('Nov.') if ($mon == 10);
445     return $self->loc('Dec.') if ($mon == 11);
446 }
447
448 # }}}
449
450 # {{{ sub AddSeconds
451
452 =head2 sub AddSeconds
453
454 Takes a number of seconds as a string
455
456 Returns the new time
457
458 =cut
459
460 sub AddSeconds {
461     my $self = shift;
462     my $delta = shift;
463     
464     $self->Set(Format => 'unix', Value => ($self->Unix + $delta));
465     
466     return ($self->Unix);
467     
468
469 }
470
471 # }}}
472
473 # {{{ sub AddDays
474
475 =head2 AddDays $DAYS
476
477 Adds 24 hours * $DAYS to the current time
478
479 =cut
480
481 sub AddDays {
482     my $self = shift;
483     my $days = shift;
484     $self->AddSeconds($days * $DAY);
485     
486 }
487
488 # }}}
489
490 # {{{ sub AddDay
491
492 =head2 AddDay
493
494 Adds 24 hours to the current time
495
496 =cut
497
498 sub AddDay {
499     my $self = shift;
500     $self->AddSeconds($DAY);
501     
502 }
503
504 # }}}
505
506 # {{{ sub Unix
507
508 =head2 sub Unix [unixtime]
509
510 Optionally takes a date in unix seconds since the epoch format.
511 Returns the number of seconds since the epoch
512
513 =cut
514
515 sub Unix {
516     my $self = shift;
517     
518     $self->{'time'} = shift if (@_);
519     
520     return ($self->{'time'});
521 }
522 # }}}
523
524 # {{{ sub ISO
525
526 =head2 ISO
527
528 Takes nothing
529
530 Returns the object's date in ISO format
531
532 =cut
533
534 sub ISO {
535     my $self=shift;
536     my    ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst, $date) ;
537     
538     return ('1970-01-01 00:00:00') if ($self->Unix == -1);
539
540     #  0    1    2     3     4    5     6     7     8
541     ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime($self->Unix);
542     #make the year YYYY
543     $year+=1900;
544
545     #the month needs incrementing, as gmtime returns 0-11
546     $mon++;
547         
548     $date = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $year,$mon,$mday, $hour,$min,$sec);
549     
550     return ($date);
551 }
552
553 # }}}
554
555 # {{{ sub W3CDTF
556
557 =head2 W3CDTF
558
559 Takes nothing
560
561 Returns the object's date in W3C DTF format
562
563 =cut
564
565 sub W3CDTF {
566     my $self = shift;
567     my $date = $self->ISO . 'Z';
568     $date =~ s/ /T/;
569     return $date;
570 };
571
572 # }}}
573
574 # {{{ sub LocalTimezone 
575 =head2 LocalTimezone
576
577   Returns the current timezone. For now, draws off a system timezone, RT::Timezone. Eventually, this may
578 pull from a 'Timezone' attribute of the CurrentUser
579
580 =cut
581
582 sub LocalTimezone {
583     my $self = shift;
584
585     return $self->CurrentUser->Timezone
586         if $self->CurrentUser and $self->CurrentUser->can('Timezone');
587
588     return ($RT::Timezone);
589 }
590
591 # }}}
592
593 eval "require RT::Date_Vendor";
594 die $@ if ($@ && $@ !~ qr{^Can't locate RT/Date_Vendor.pm});
595 eval "require RT::Date_Local";
596 die $@ if ($@ && $@ !~ qr{^Can't locate RT/Date_Local.pm});
597
598 1;