File Coverage

blib/lib/Net/ICal/Duration.pm
Criterion Covered Total %
statement 108 142 76.0
branch 36 56 64.2
condition 36 52 69.2
subroutine 16 24 66.6
pod 13 13 100.0
total 209 287 72.8


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2             # -*- Mode: perl -*-
3             #======================================================================
4             #
5             # This package is free software and is provided "as is" without
6             # express or implied warranty. It may be used, redistributed and/or
7             # modified under the same terms as perl itself. ( Either the Artistic
8             # License or the GPL. )
9             #
10             # $Id: Duration.pm,v 1.22 2001/07/09 12:11:00 lotr Exp $
11             #
12             # (C) COPYRIGHT 2000-2001, Reefknot developers.
13             #
14             # See the AUTHORS file included in the distribution for a full list.
15             #======================================================================
16              
17             =head1 NAME
18              
19             Net::ICal::Duration -- represent a length of time
20              
21             =cut
22              
23             package Net::ICal::Duration;
24 3     3   8650 use strict;
  3         7  
  3         114  
25              
26 3     3   15 use base qw(Net::ICal::Property);
  3         6  
  3         1220  
27              
28 3     3   84 use Carp;
  3         7  
  3         204  
29              
30 3     3   16 use constant DEBUG => 0;
  3         16  
  3         7292  
31              
32             =head1 SYNOPSIS
33              
34             use Net::ICal;
35              
36             # 3 days 6 hours 15 minutes 10 seconds
37             $d = Net::ICal::Duration->new("P3DT6H15M10S");
38              
39             # 1 hour, in seconds
40             $d = Net::ICal::Duration->new(3600);
41              
42             =head1 DESCRIPTION
43              
44             I represents a length of time, such a 3 days, 30 seconds or
45             7 weeks. You would use this for representing an abstract block of
46             time; "I want to have a 1-hour meeting sometime." If you want a
47             calendar- and timezone-specific block of time, see L.
48              
49             =head1 CONSTRUCTOR
50              
51             =head2 new(SECONDS | DURATION )
52              
53             Create a new DURATION object. It can be constructed from an integer
54             (the number of seconds since the UNIX epoch), a DURATION string (ala
55             2445 section 4.3.6), or the individual components (i.e., weeks, days,
56             hours, minutes and seconds). See the component update methods below
57             for caveats on updating these values individually.
58              
59             =begin testing
60              
61             use Net::ICal::Duration;
62              
63             my $d1 = Net::ICal::Duration->new(3600);
64             ok(defined($d1), "Simple creation from seconds");
65              
66             ok($d1->as_ical_value eq 'PT1H', "Simple creation from seconds, ical output");
67              
68             $d1 = undef;
69             $d1 = Net::ICal::Duration->new("PT10H");
70              
71             ok(defined($d1), "Simple creation from ical");
72             ok($d1->as_ical_value eq 'PT10H', "Simple creation from ical, ical output");
73              
74             # TODO: more elaborate creation tests, and some normalization tests, and
75             # tests that include days in them
76              
77             =end testing
78              
79             =cut
80              
81             # TODO: clarify what we use %args for.
82             sub new {
83 13     13 1 46 my ($class, $value, %args) = @_;
84              
85 13         157 my $self = $class->SUPER::new('DURATION',
86             {
87             # Dummy stub for Property.pm
88             content => { },
89              
90             sign => { type => 'volatile',
91             doc => 'Positive or negative',
92             },
93             nsecs => { type => 'volatile',
94             doc => 'Number of seconds',
95             },
96             ndays => { type => 'volatile',
97             doc => 'Number of days',
98             },
99             });
100              
101             # If a number is given, convert it to hours, minutes, and seconds,
102             # but *don't* extract days -- we want it to represent an absolute
103             # amount of time, regardless of timezone
104 13 50       49 if ($value) {
105 13 100       92 if ($value =~ /^([-+])?\d+$/) { # if we were given an integer time_t
    50          
106 1         6 $self->_set_from_seconds($value);
107             } elsif ($value =~ /^(?:[\-\+])?P/) { # A standard duration string
108 12         30 $self->_set_from_ical($value);
109             }
110             } else { # Individual attributes
111 0         0 $self->set(%args);
112 0         0 _debug("set method called with args");
113             }
114              
115 13         52 bless $self, $class;
116             }
117              
118              
119             #---------------------------------------------------------------------------
120             # _set_from_ical ($self, $duration_string)
121             #
122             # Converts a RFC2445 DURATION format string to an integer number of
123             # seconds.
124             #---------------------------------------------------------------------------
125             sub _set_from_ical {
126 12     12   18 my ($self, $str) = @_;
127              
128             # RFC 2445 section 4.3.6
129             #
130             # dur-value = (["+"] / "-") "P" (dur-date / dur-time / dur-week)
131             # dur-date = dur-day [dur-time]
132             # dur-time = "T" (dur-hour / dur-minute / dur-second)
133             # dur-week = 1*DIGIT "W"
134             # dur-hour = 1*DIGIT "H" [dur-minute]
135             # dur-minute = 1*DIGIT "M" [dur-second]
136             # dur-second = 1*DIGIT "S"
137             # dur-day = 1*DIGIT "D"
138              
139 12         90 my ($sign, $magic, $weeks, $days, $hours, $mins, $secs) =
140             $str =~ m{
141             ([\+\-])? (?# Sign)
142             (P) (?# 'P' for period? This is our magic character)
143             (?:
144             (?:(\d+)W)? (?# Weeks)
145             (?:(\d+)D)? (?# Days)
146             )?
147             (?:T (?# Time prefix)
148             (?:(\d+)H)? (?# Hours)
149             (?:(\d+)M)? (?# Minutes)
150             (?:(\d+)S)? (?# Seconds)
151             )?
152             }x;
153              
154 12 50       33 if (!defined($magic)) {
155 0         0 carp "Invalid duration: $str";
156 0         0 return undef;
157             }
158              
159 12 100 66     148 $self->sign((defined($sign) && $sign eq '-') ? -1 : 1);
160              
161 12 100 100     405 if (defined($weeks) or defined($days)) {
162 8   100     60 $self->_wd([$weeks || 0, $days || 0]);
      100        
163             }
164              
165 12 100 100     111 if (defined($hours) || defined($mins) || defined($secs)) {
      66        
166 7   100     89 $self->_hms([$hours || 0, $mins || 0, $secs || 0]);
      100        
      100        
167             }
168              
169 12         29 return $self;
170             }
171              
172             #---------------------------------------------------------------------------
173             # _set_from_seconds ($self, $num_seconds)
174             #
175             # sets internal data storage properly if we were given seconds as a parameter.
176             #---------------------------------------------------------------------------
177             sub _set_from_seconds {
178 1     1   2 my ($self, $secs) = @_;
179            
180 1 50       8 $self->sign(($secs < 0) ? -1 : 1);
181             # find the number of days, if any
182 1         27 my $ndays = int ($secs / (24*60*60));
183             # now, how many hours/minutes/seconds are there, after
184             # days are taken out?
185 1         2 my $nsecs = $secs % (24*60*60);
186 1         7 $self->ndays(abs($ndays));
187 1         26 $self->nsecs(abs($nsecs));
188              
189              
190 1         22 return $self;
191             }
192              
193              
194             =head1 METHODS
195              
196             =head2 weeks([WEEKS])
197              
198             Return (or set) the number of weeks in the duration.
199              
200             =cut
201              
202             sub weeks (;$) {
203 0     0 1 0 my ($self, $args) = @_;
204            
205 0         0 $self->_ret_or_set(\&_wd, 0, @_)
206             }
207              
208             =head2 days([DAYS])
209              
210             Return (or set) the number of days in the duration.
211              
212             NOTE: If more than six (6) days are specified, the value will be
213             normalized, and the weeks portion of the duration will be updated (and
214             the old value destroyed). See the I method if you want more
215             exact control over the "calendar" portion of the duration.
216              
217             =cut
218              
219             sub days (;$) {
220 0     0 1 0 my ($self, $args) = @_;
221              
222 0         0 $self->_ret_or_set(\&_wd, 1, $args)
223              
224             }
225              
226             =head2 hours([HOURS])
227              
228             Return (or set) the number of hours in the duration.
229              
230             NOTE: Specifying more than 24 hours will NOT increment or update the
231             number of days in the duration. The day portion and time portion are
232             considered separate, since the time portion specifies an absolute amount
233             of time, whereas the day portion is not absolute due to daylight savings
234             adjustments. See the I method if you want more exact control
235             over the "absolute" portion of the duration.
236              
237             =cut
238              
239             sub hours (;$) {
240 0     0 1 0 my ($self, $args) = @_;
241            
242 0         0 $self->_ret_or_set(\&_hms, 0, $args);
243             }
244              
245             =head2 minutes([MINUTES])
246              
247             Return (or set) the number of minutes in the duration. If more than
248             59 minutes are specified, the value will be normalized, and the hours
249             portion of the duration will be updated (and the old value destroyed).
250             See the I method if you want more exact control over the
251             "absolute" portion of the duration.
252              
253             =cut
254              
255             sub minutes (;$) {
256 0     0 1 0 my ($self, $args) = @_;
257              
258 0         0 $self->_ret_or_set(\&_hms, 1, $args);
259             }
260              
261             =head2 seconds([SECONDS])
262              
263             Return (or set) the number of seconds in the duration. If more than 59
264             seconds are specified, the value will be normalized, and the minutes
265             AND hours portion of the duration will be updated (and the old values
266             destroyed). See the I method if you want more exact control
267             over the "absolute" portion of the duration.
268              
269             =cut
270              
271             sub seconds (;$) {
272 0     0 1 0 my ($self, $args) = @_;
273              
274 0         0 $self->_ret_or_set(\&_hms, 2, $args);
275             }
276              
277             =head2 nsecs([SECONDS])
278              
279             Retrieve or set the number of seconds in the duration. This sets the
280             entire "absolute" portion of the duration.
281              
282             =cut
283              
284             =head2 ndays([DAYS])
285              
286             Retrieve or set the number of days in the duration. This sets the entire
287             "calendar" portion of the duration.
288              
289             =cut
290              
291             =head2 clone()
292              
293             Return a new copy of the duration.
294              
295             =cut
296              
297             sub clone {
298 1     1 1 2 my $self = shift;
299              
300 1         8 return bless( {%$self},ref($self));
301              
302             }
303              
304              
305              
306             =head2 is_valid()
307              
308             Returns a truth value indicating whether the current object is a valid
309             duration.
310              
311             =cut
312              
313             sub is_valid {
314 0     0 1 0 my ($self) = @_;
315              
316 0 0 0     0 return (defined($self->nsecs) || defined($self->ndays)) ? 1 : 0;
317             }
318              
319             =head2 as_int()
320              
321             Return the length of the duration as seconds. WARNING -- this folds
322             in the number of days, assuming that they are always 86400 seconds
323             long (which is not true twice a year in areas that honor daylight
324             savings time). If you're using this for date arithmetic, consider using
325             the I method from a L object, as this will
326             behave better. Otherwise, you might experience some error when working
327             with times that are specified in a time zone that observes daylight
328             savings time.
329              
330             =cut
331              
332             sub as_int {
333 7     7 1 10 my ($self) = @_;
334              
335 7   100     26 my $nsecs = $self->{nsecs}->{value} || 0;
336 7   100     23 my $ndays = $self->{ndays}->{value} || 0;
337 7   50     17 my $sign = $self->{sign}->{value} || 1;
338 7         38 print "$sign, $ndays, $nsecs \n";
339 7         33 return $sign*($nsecs+($ndays*24*60*60));
340             }
341              
342              
343             =head2 as_ical()
344              
345             Return the duration as a fragment of an iCal property-value string (e.g.,
346             ":PT2H0M0S").
347              
348             =begin testing
349              
350             # This is a really trivial test, I know.
351             $d = Net::ICal::Duration->new('PT2H');
352              
353             ok($d->as_ical eq ':PT2H',
354             "as_ical is correct with hour-only durations");
355              
356             =end testing
357              
358             =cut
359              
360             sub as_ical {
361 0     0 1 0 my ($self) = @_;
362              
363             # This is an evil hack.
364 0         0 return ":" . $self->as_ical_value;
365             }
366              
367             =head2 as_ical_value()
368              
369             Return the duration in an RFC2445 format value string (e.g., "PT2H0M0S")
370              
371             =begin testing
372              
373             $d = Net::ICal::Duration->new('PT2H');
374              
375             ok($d->as_ical_value eq 'PT2H',
376             "as_ical_value is correct with hour-only durations");
377              
378             =end testing
379              
380             =cut
381              
382             sub as_ical_value {
383 17     17 1 56 my ($self) = @_;
384              
385             #print ("entering as_ical_value\n");
386 17         39 my $tpart = '';
387             #print "nsecs is " . $self->{nsecs}->{value} ."\n";
388             #print "self hms is " . $self->_hms() ."\n" ;
389              
390 17 100       35 if (my $ar_hms = $self->_hms) {
391 13         78 $tpart = sprintf('T%dH%dM%dS', @$ar_hms);
392             }
393              
394 17         240 my $ar_wd = $self->_wd();
395            
396             #print "ar_wd is $ar_wd\n";
397 17         25 my $dpart = '';
398 17 100       35 if (defined $ar_wd) {
399 13         21 my ($weeks, $days) = @$ar_wd;
400             #print "$weeks weeks, $days days\n";
401 13 100 100     50 if ($weeks && $days) {
    100          
402 6         18 $dpart = sprintf('%dW%dD', $weeks, $days);
403             } elsif ($weeks) { # (if days = 0)
404 1         4 $dpart = sprintf('%dW', $weeks);
405             } else {
406 6         15 $dpart = sprintf('%dD', $days);
407             }
408             }
409              
410             #_debug ("self sign value is " . $self->{sign}->{value} . "\n");
411 17 100       63 my $value = join('', (($self->{sign}->{value} < 0) ? '-' : ''),
412             'P', $dpart, $tpart);
413              
414             # remove any zero components from the time string (-P10D0H -> -P10D)
415 17         61 $value =~ s/(?<=[^\d])0[WDHMS]//g;
416              
417             # return either the time value or PT0S (if the time value is zero).
418 17 100       172 return (($value !~ /PT?$/) ? $value : 'PT0S');
419             }
420              
421             #---------------------------------------------------------------------------
422             # $self->_add_or_subtract($duration, $add_or_subtract_flag
423             #
424             # Add or subtract $duration from this duration. If $add_or_subtract_flag
425             # is 1, add; if it's -1, subtract.
426             #---------------------------------------------------------------------------
427             sub _add_or_subtract {
428 4     4   7 my ($self, $dur2, $dur2mult) = @_;
429             #$dur2mult ||= 1; # Add by default
430              
431 4 50       20 unless (UNIVERSAL::isa($dur2, 'Net::ICal::Duration')) {
432 0         0 my $durstring = $dur2;
433 0   0     0 $dur2 = new Net::ICal::Duration($durstring) ||
434             warn "Couldn't turn string $durstring into a Net::ICal::Duration";
435             #print "making $durstring a duration\n";
436             }
437              
438             # do math in raw seconds to minimize headaches with units.
439             # these conversions could probably be abstracted out.
440 4   100     20 my $dur1_nsecs = $self->nsecs || 0;
441 4   50     190 my $dur1_ndays = $self->ndays || 0;
442 4   50     124 my $dur1_totalsecs = ($dur1_nsecs + ($dur1_ndays * 60*60*24) ) || 0;
443            
444 4   50     19 my $dur2_nsecs = $dur2->nsecs || 0;
445 4   50     138 my $dur2_ndays = $dur2->ndays || 0;
446 4   50     114 my $dur2_totalsecs = ($dur2_nsecs + ($dur2_ndays * 60*60*24) ) || 0;
447              
448 4         6 my ($resultsecs);
449 4 50 33     11 if (defined($dur1_totalsecs) || defined($dur2_totalsecs)) {
450 4         8 $resultsecs = $dur1_totalsecs + $dur2mult*$dur2_totalsecs;
451             }
452              
453             # find the number of days, if any
454 4         7 my $ndays = int ($resultsecs / (24*60*60));
455             # now, how many hours/minutes/seconds are there, after
456             # days are taken out?
457 4         6 my $nsecs = $resultsecs % (24*60*60);
458            
459             # The spec is ambiguous here. For purposes of determine whether
460             # the sign should be positive or negative, we use the sign on the
461             # days or (if no days are defined) the seconds. FIXME when the
462             # RFC is clarified on durations
463 4         5 my $sign;
464 4 50       9 if (defined($ndays)) {
    0          
465 4 50       10 $sign = $ndays < 0 ? -1 : 1;
466             } elsif (defined($nsecs)) {
467 0 0       0 $sign = $nsecs < 0 ? -1 : 1;
468             } else {
469             # How would we get here? Answer: if both of the times were null.
470 0         0 $sign = undef;
471             }
472              
473 4         21 $self->sign($sign);
474 4         116 $self->nsecs($nsecs);
475 4         109 $self->ndays($ndays);
476              
477 4         102 return $self;
478             }
479              
480             =head2 add(DURATION)
481              
482             Return a new duration that is the sum of this and DURATION. Does not
483             modify this object. Note that the day and time components are added
484             separately, and the resulting sign is taken only from the days. RFC 2445
485             is unclear on this, so you may want to avoid this method, and do your own
486             calendar/absolute time calculations to ensure that you get the behavior
487             that you expect in your application.
488              
489             =begin testing
490              
491             $d1 = Net::ICal::Duration->new('3600');
492              
493             #=========================================================================
494             # first, tests of adding seconds
495             $d1->add('3600');
496             ok($d1->as_ical_value eq 'PT2H', "Addition of hours (using seconds) works");
497              
498             $d1->add('300');
499             ok($d1->as_ical_value eq 'PT2H5M', "Addition of minutes (using seconds) works");
500              
501             $d1->add('30');
502             ok($d1->as_ical_value eq 'PT2H5M30S', "Addition of seconds works");
503              
504             # I know 1 day != 24 hours, but something like this should be in here.
505             # perhaps add should warn() on this. --srl
506             $d1->add(3600*24*3);
507             ok($d1->as_ical_value eq 'P3DT2H5M30S', "Addition of days (using seconds) works");
508              
509             #TODO: there should probably be some mixed-unit testing here
510              
511             #=========================================================================
512             # now, test adding with iCal strings
513              
514             $d1 = Net::ICal::Duration->new('3600');
515              
516             $d1->add('PT1H');
517             ok($d1->as_ical_value eq 'PT2H', "Addition of hours (using ical period) works");
518              
519             $d1->add('PT5M');
520             ok($d1->as_ical_value eq 'PT2H5M', "Addition of minutes (using ical period) works");
521              
522             $d1->add('PT30S');
523             ok($d1->as_ical_value eq 'PT2H5M30S', "Addition of seconds (using ical period) works");
524              
525             # I know 1 day != 24 hours, but something like this should be in here.
526             # perhaps add should warn() on this. --srl
527             $d1->add('P3D');
528             ok($d1->as_ical_value eq 'P3DT2H5M30S', "Addition of days (using ical period) works");
529              
530             #=========================================================================
531             # now, test adding with Duration objects
532              
533             $d1 = Net::ICal::Duration->new('3600');
534              
535             $d1->add(Net::ICal::Duration->new('PT1H'));
536             ok($d1->as_ical_value eq 'PT2H', "Addition of hours (using ical period) works");
537              
538             $d1->add(Net::ICal::Duration->new('PT5M'));
539             ok($d1->as_ical_value eq 'PT2H5M', "Addition of minutes (using ical period) works");
540              
541             $d1->add(Net::ICal::Duration->new('PT30S'));
542             ok($d1->as_ical_value eq 'PT2H5M30S', "Addition of seconds (using ical period) works");
543              
544             $d1->add(Net::ICal::Duration->new('P3D'));
545             ok($d1->as_ical_value eq 'P3DT2H5M30S', "Addition of days (using ical period) works");
546              
547              
548              
549             =end testing
550              
551             =cut
552              
553             sub add {
554 2     2 1 9 my ($self, $arg) = (@_);
555              
556 2         6 $self->_add_or_subtract($arg, 1);
557             }
558              
559             =head2 subtract($duration)
560              
561             Return a new duration that is the difference between this and DURATION.
562             Does not modify this object. Note that the day and time components are
563             added separately, and the resulting sign is taken only from the days.
564             RFC 2445 is unclear on this.
565              
566             =begin testing
567              
568              
569             =end testing
570              
571             =cut
572              
573             sub subtract {
574 2     2 1 6 my ($self, $arg) = (@_);
575            
576 2         7 $self->_add_or_subtract($arg, -1);
577             }
578              
579             #-------------------------------------------------------------------------
580             # $self->_hms();
581             #
582             # Return an arrayref to hours, minutes, and second components, or undef
583             # if nsecs is undefined. If given an arrayref, computes the new number
584             # of seconds for the duration.
585             #-------------------------------------------------------------------------
586             sub _hms {
587 24     24   36 my ($self, $ar_newval) = @_;
588              
589 24 100       114 if (defined($ar_newval)) {
590 7         20 my $new_sec_value = $ar_newval->[0]*3600 +
591             $ar_newval->[1]*60 + $ar_newval->[2];
592 7         74 $self->nsecs($new_sec_value);
593             }
594              
595             #print "nsecs is now " . $self->{nsecs}->{value} . "\n";
596 24         256 my $nsecs = $self->{nsecs}->{value};
597 24 100       41 if (defined($nsecs)) {
598 20         32 my $hours = int($nsecs/3600);
599 20         36 my $mins = int(($nsecs-$hours*3600)/60);
600 20         24 my $secs = $nsecs % 60;
601 20         98 return [ $hours, $mins, $secs ];
602             } else {
603 4         26 print "returning undef\n";
604 4         10 return undef;
605             }
606             }
607              
608             #---------------------------------------------------------------------------
609             # $self->_wd()
610             #
611             # Return an arrayref to weeks and day components, or undef if ndays
612             # is undefined. If Given an arrayref, computs the new number of
613             # days for the duration.
614             #---------------------------------------------------------------------------
615             sub _wd {
616 25     25   33 my ($self, $ar_newval) = @_;
617              
618             #print "entering _wd\n";
619            
620 25 100       50 if (defined($ar_newval)) {
621            
622 8         22 my $new_ndays = $ar_newval->[0]*7 + $ar_newval->[1];
623 8         27 _debug("trying to set ndays to $new_ndays");
624            
625 8         41 $self->ndays($new_ndays);
626 8         256 _debug("set ndays to " . $self->{ndays}->{value});
627            
628             }
629            
630             #use Data::Dumper; print Dumper $self->{ndays};
631            
632 25 100       97 if (defined(my $ndays= $self->{ndays}->{value})) {
633 21         34 my $weeks = int($ndays/7);
634 21         32 my $days = $ndays % 7;
635 21         54 return [ $weeks, $days ];
636             } else {
637 4         9 return undef;
638             }
639             }
640              
641             #---------------------------------------------------------------------------
642             # $self->_ret_or_set ($code_ref, $position, $new_value)
643             #
644             # Generic function to return or set one of the duration elements
645             # Specifying an undef for any element will undef the entire portion
646             # (either days or seconds)
647             #
648             #---------------------------------------------------------------------------
649             sub _ret_or_set (;$) {
650 0     0   0 my ($self, $code, $pos, $newval) = @_;
651              
652             # get an arrayref to some bit of data in $self, usually either
653             # _wd or _hms
654 0         0 my $ar_cur = &{$code}($self);
  0         0  
655            
656             # if we were given a new value for the data in position $pos,
657             # set that data.
658 0 0       0 if (defined($newval)) {
659 0 0       0 $ar_cur = [ ] unless defined($ar_cur);
660 0         0 $ar_cur->[$pos] = $newval;
661 0         0 &{$code}($self, $ar_cur);
  0         0  
662             } else {
663 0         0 undef $ar_cur;
664 0         0 &{$code}($self, undef);
  0         0  
665             }
666            
667 0 0       0 return defined($ar_cur) ? $ar_cur->[$pos] : undef;
668             }
669              
670              
671             #---------------------------------------------------------------------------
672             # _debug ($debug_message)
673             #
674             # prints a debug message if DEBUG is set to 1.
675             #---------------------------------------------------------------------------
676             sub _debug {
677 16     16   19 my ($debug_message) = @_;
678              
679 16         17 print "$debug_message\n" if DEBUG;
680 16         23 return 1;
681             }
682              
683             =head1 BUGS
684              
685             * RFC 2445 is unclear about how days work in durations.
686             This code might need to be modified if/when section 4.3.6
687             is updated
688              
689             * According to the RFC, there's no limit on how many
690             seconds, minutes, etc. can be in a duration. However,
691             this implementation always normalizes the day and time
692             components, and always reports hours, minutes, and seconds
693             (even if one or more of them are zero or weren't initially
694             defined).
695              
696             =head1 AUTHOR
697              
698             See the Reefknot AUTHORS file, or
699              
700             http://reefknot.sourceforge.net/
701              
702             =cut
703              
704             1;
705              
706             =head1 SEE ALSO
707              
708             More documentation pointers can be found in L.
709              
710             =cut