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