File Coverage

blib/lib/DateTime/Event/Random.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1              
2             package DateTime::Event::Random;
3              
4 1     1   1957 use strict;
  1         3  
  1         43  
5 1     1   486 use DateTime::Set;
  0            
  0            
6             use vars qw( $VERSION @ISA );
7             use Carp;
8              
9             BEGIN {
10             $VERSION = 0.03;
11             }
12              
13             sub new_cached {
14             my $class = shift;
15             my %args = @_; # the parameters are validated by DT::Set
16              
17             my $density = $class->_random_init( \%args );
18              
19             my $cache_set = DateTime::Set->empty_set;
20             my $cache_last;
21             my $cache_first;
22              
23             my $get_cached =
24             sub {
25             my $dt = $_[0];
26             my $prev = $cache_set->previous( $dt );
27             my $next = $cache_set->next( $dt );
28             return ( $prev, $next ) if defined $prev && defined $next;
29              
30             # initialize the cache
31             unless ( defined $cache_last )
32             {
33             $cache_last = $dt - $class->_random_duration( $density );
34             $cache_first = $cache_last->clone;
35             $cache_set = $cache_set->union( $cache_last );
36             };
37              
38             while ( $cache_last <= $dt ) {
39             $cache_last += $class->_random_duration( $density );
40             $cache_set = $cache_set->union( $cache_last );
41             };
42              
43             while ( $cache_first >= $dt ) {
44             $cache_first -= $class->_random_duration( $density );
45             $cache_set = $cache_set->union( $cache_first );
46             };
47              
48             $prev = $cache_set->previous( $dt );
49             $next = $cache_set->next( $dt );
50             return ( $prev, $next );
51             };
52              
53             my $cached_set = DateTime::Set->from_recurrence(
54             next => sub {
55             return $_[0] if $_[0]->is_infinite;
56             my ( undef, $next ) = &$get_cached( $_[0] );
57             return $next;
58             },
59             previous => sub {
60             return $_[0] if $_[0]->is_infinite;
61             my ( $previous, undef ) = &$get_cached( $_[0] );
62             return $previous;
63             },
64             %args,
65             );
66             return $cached_set;
67              
68             }
69              
70             sub new {
71             my $class = shift;
72             my %args = @_; # the parameters will be validated by DT::Set
73             my $density = $class->_random_init( \%args );
74             return DateTime::Set->from_recurrence(
75             next => sub {
76             return $_[0] if $_[0]->is_infinite;
77             $_[0] + $class->_random_duration( $density );
78             },
79             previous => sub {
80             return $_[0] if $_[0]->is_infinite;
81             $_[0] - $class->_random_duration( $density );
82             },
83             %args,
84             );
85             }
86              
87             sub _random_init {
88             my $class = shift;
89             my $args = shift;
90              
91             my $density = 0;
92              
93             if ( exists $args->{duration} )
94             {
95             my %dur = $args->{duration}->deltas;
96             $args->{ $_ } = $dur{ $_ } for ( keys %dur );
97             delete $args->{duration};
98             }
99              
100             $density += ( delete $args->{nanoseconds} ) / 1E9 if exists $args->{nanoseconds};
101             $density += ( delete $args->{seconds} ) if exists $args->{seconds};
102             $density += ( delete $args->{minutes} ) * 60 if exists $args->{minutes};
103             $density += ( delete $args->{hours} ) * 60*60 if exists $args->{hours};
104             $density += ( delete $args->{days} ) * 24*60*60 if exists $args->{days};
105             $density += ( delete $args->{weeks} ) * 7*24*60*60 if exists $args->{weeks};
106             $density += ( delete $args->{months} ) * 365.24/12*24*60*60 if exists $args->{months};
107             $density += ( delete $args->{years} ) * 365.24*24*60*60 if exists $args->{years};
108              
109             $density = 24*60*60 unless $density; # default = 1 day
110              
111             return {
112             density => $density,
113             starting => 1,
114             };
115             }
116              
117             sub _random_duration {
118             my $class = shift;
119             my $param = shift;
120              
121             my $tmp;
122             if ( $param->{starting} )
123             {
124             $param->{starting} = 0;
125              
126             # this is a density function that approximates to
127             # the "duration" in seconds between a random and
128             # a non-random date.
129             $tmp = log( 1 - rand ) * ( - $param->{density} / 2 );
130             }
131             else
132             {
133             # this is a density function that approximates to
134             # the "duration" in seconds between two random dates.
135             $tmp = log( 1 - rand ) * ( - $param->{density} );
136             }
137              
138              
139             # split into "days", "seconds" and "nanoseconds"
140              
141             my $days = int( $tmp / ( 24*60*60 ) );
142             if ( $days > 1000 )
143             {
144             return DateTime::Duration->new(
145             days => $days,
146             seconds => int( rand( 61 ) ),
147             nanoseconds => int( rand( 1E9 ) ) );
148             }
149              
150             my $seconds = int( $tmp );
151             return DateTime::Duration->new(
152             seconds => $seconds,
153             nanoseconds => int( 1E9 * ( $tmp - $seconds ) ) );
154             }
155              
156              
157             sub datetime {
158             my $class = shift;
159             carp "Missing class name in call to ".__PACKAGE__."->datetime()"
160             unless defined $class;
161             my %args = @_;
162              
163             my $locale = delete $args{locale};
164             my $time_zone = delete $args{time_zone};
165              
166             my $dt = $class->_random_datetime_no_locale( %args );
167              
168             $dt->set( locale => $locale ) if defined $locale;
169             $dt->set( time_zone => $time_zone ) if defined $time_zone;
170             return $dt;
171             }
172              
173             sub _random_datetime_no_locale {
174             my $class = shift;
175             my %args = @_;
176             my %span_args;
177             my $span;
178             if ( exists $args{span} )
179             {
180             $span = delete $args{span};
181             }
182             else
183             {
184             for ( qw( start end before after ) )
185             {
186             $span_args{ $_ } = delete $args{ $_ } if exists $args{ $_ };
187             }
188             $span = DateTime::Span->from_datetimes( %span_args )
189             if ( keys %span_args );
190             }
191              
192             if ( ! defined $span ||
193             ( $span->start->is_infinite &&
194             $span->end->is_infinite ) )
195             {
196             my $dt = DateTime->now( %args );
197             $dt->add( months => ( 0.5 - rand ) * 1E6 );
198             $dt->add( days => ( 0.5 - rand ) * 31 );
199             $dt->add( seconds => ( 0.5 - rand ) * 24*60*60 );
200             $dt->add( nanoseconds => ( 0.5 - rand ) * 1E9 );
201             return $dt;
202             }
203              
204             return undef unless defined $span->start;
205              
206             if ( $span->start->is_infinite )
207             {
208             my $dt = $span->end;
209             $dt->add( months => ( - rand ) * 1E6 );
210             $dt->add( days => ( - rand ) * 31 );
211             $dt->add( seconds => ( - rand ) * 24*60*60 );
212             $dt->add( nanoseconds => ( - rand ) * 1E9 );
213             return $dt;
214             }
215              
216             if ( $span->end->is_infinite )
217             {
218             my $dt = $span->start;
219             $dt->add( months => ( rand ) * 1E6 );
220             $dt->add( days => ( rand ) * 31 );
221             $dt->add( seconds => ( rand ) * 24*60*60 );
222             $dt->add( nanoseconds => ( rand ) * 1E9 );
223             return $dt;
224             }
225              
226             my $dt1 = $span->start;
227             my $dt2 = $span->end;
228             my %deltas = $dt2->subtract_datetime( $dt1 )->deltas;
229             # find out the most significant delta
230             if ( $deltas{months} ) {
231             $deltas{months}++;
232             $deltas{days} = 31;
233             $deltas{minutes} = 24*60;
234             $deltas{seconds} = 60;
235             $deltas{nanoseconds} = 1E9;
236             }
237             elsif ( $deltas{days} ) {
238             $deltas{days}++;
239             $deltas{minutes} = 24*60;
240             $deltas{seconds} = 60;
241             $deltas{nanoseconds} = 1E9;
242             }
243             elsif ( $deltas{minutes} ) {
244             $deltas{minutes}++;
245             $deltas{seconds} = 60;
246             $deltas{nanoseconds} = 1E9;
247             }
248             elsif ( $deltas{seconds} ) {
249             $deltas{seconds}++;
250             $deltas{nanoseconds} = 1E9;
251             }
252             else {
253             $deltas{nanoseconds}++;
254             }
255              
256             my %duration;
257             my $dt;
258             while (1)
259             {
260             %duration = ();
261             for ( keys %deltas )
262             {
263             $duration{ $_ } = int( rand() * $deltas{ $_ } )
264             if $deltas{ $_ };
265             }
266             $dt = $dt1->clone->add( %duration );
267             return $dt if $span->contains( $dt );
268              
269             %duration = ();
270             for ( keys %deltas )
271             {
272             $duration{ $_ } = int( rand() * $deltas{ $_ } )
273             if $deltas{ $_ };
274             }
275             $dt = $dt2->clone->subtract( %duration );
276             return $dt if $span->contains( $dt );
277             }
278             }
279              
280             sub duration {
281             my $class = shift;
282             carp "Missing class name in call to ".__PACKAGE__."->duration()"
283             unless defined $class;
284             my $dur;
285             if ( @_ )
286             {
287             if ( $_[0] eq 'duration' )
288             {
289             $dur = $_[1];
290             }
291             else
292             {
293             $dur = DateTime::Duration->new( @_ );
294             }
295             }
296             if ( $dur ) {
297             my $dt1 = DateTime->now();
298             my $dt2 = $dt1 + $dur;
299             my $dt3 = $class->datetime( start => $dt1, before => $dt2 );
300             return $dt3 - $dt1;
301             }
302             return DateTime->now() - $class->datetime();
303             }
304              
305             1;
306              
307             __END__