Skip Menu |

This queue is for tickets about the DateTime-Event-ICal CPAN distribution.

Report information
The Basics
Id: 97318
Status: resolved
Priority: 0/
Queue: DateTime-Event-ICal

People
Owner: Nobody in particular
Requestors: hanenkamp [...] cpan.org
Cc:
AdminCc:

Bug Information
Severity: (no value)
Broken in: (no value)
Fixed in: (no value)



Subject: Broken handling of hourly, minutely, and secondly frequency with bymonth or bymonthday
There is a problem with the way this module uses intersection to apply various BYMONTH, BYMONTHDAY constraints to FREQ=MINUTELY, FREQ=HOURLY, and FREQ=SECONDLY. For example, if you have an ICAL recurrence defined like so: RRULE:FREQ=MINUTELY;INTERVAL=4;BYHOUR=4;BYMONTH=4;BYMONTHDAY=4 The expectation is that it will output 15 dates each year (April 4th @ 4:00, 4:04, 4:08, ... 11 more ..., 4:56). Instead, if given this input, it outputs only the first one. The workaround is to add this to the line: BYMINUTE=0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59 It's pretty obscure and I don't know if I have ever run into a need for this, but I ran across it well trying to write up some test cases for some code I'm writing. If I have time, I will try to send a patch.
Okay, here's a quick patch that handles a couple of the issues. I don't think it's a complete fix, but it's a start. On Thu Jul 17 13:23:19 2014, HANENKAMP wrote: Show quoted text
> There is a problem with the way this module uses intersection to apply > various BYMONTH, BYMONTHDAY constraints to FREQ=MINUTELY, FREQ=HOURLY, > and FREQ=SECONDLY. For example, if you have an ICAL recurrence defined > like so: > > RRULE:FREQ=MINUTELY;INTERVAL=4;BYHOUR=4;BYMONTH=4;BYMONTHDAY=4 > > The expectation is that it will output 15 dates each year (April 4th @ > 4:00, 4:04, 4:08, ... 11 more ..., 4:56). Instead, if given this > input, it outputs only the first one. The workaround is to add this to > the line: > > BYMINUTE=0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59 > > It's pretty obscure and I don't know if I have ever run into a need > for this, but I ran across it well trying to write up some test cases > for some code I'm writing. > > If I have time, I will try to send a patch.
Subject: minutely-fix.patch
diff --git a/lib/DateTime/Event/ICal.pm b/lib/DateTime/Event/ICal.pm index a79cc3f..41941dc 100644 --- a/lib/DateTime/Event/ICal.pm +++ b/lib/DateTime/Event/ICal.pm @@ -598,7 +598,16 @@ sub recur { if ( exists $args{bymonthday} || exists $args{bymonth} ) { - $by_month_day = _yearly_recurrence($dtstart, \%args); + my %by = %args; + $by{byhour} = $args_backup{byhour} if $args_backup{byhour}; + $by{byhour} = [ 0 .. 23 ] if $args{freq} eq 'hourly'; + $by{byminute} = $args_backup{byminute} if $args_backup{byminute}; + $by{byminute} = [ 0 .. 59 ] if $args{freq} eq 'minutely'; + $by{bysecond} = $args_backup{bysecond} if $args_backup{bysecond}; + $by{bysecond} = [ 0 .. 59 ] if $args{freq} eq 'secondly'; + $by_month_day = _yearly_recurrence($dtstart, \%by); + delete $args{bymonthday}; + delete $args{bymonth}; } my $by_week_day; @@ -606,7 +615,16 @@ sub recur { if ( exists $args{byday} || exists $args{byweekno} ) { - $by_week_day = _weekly_recurrence($dtstart, \%args); + my %by = %args; + $by{byhour} = $args_backup{byhour} if $args_backup{byhour}; + $by{byhour} = [ 0 .. 23 ] if $args{freq} eq 'hourly'; + $by{byminute} = $args_backup{byminute} if $args_backup{byminute}; + $by{byminute} = [ 0 .. 59 ] if $args{freq} eq 'minutely'; + $by{bysecond} = $args_backup{bysecond} if $args_backup{bysecond}; + $by{bysecond} = [ 0 .. 59 ] if $args{freq} eq 'secondly'; + $by_week_day = _weekly_recurrence($dtstart, \%by); + delete $args{byday}; + delete $args{byweekno}; } my $by_hour; diff --git a/t/06hms-byetc.t b/t/06hms-byetc.t new file mode 100644 index 0000000..1455c3b --- /dev/null +++ b/t/06hms-byetc.t @@ -0,0 +1,101 @@ +#!/usr/bin/env perl +use strict; +use warnings; + +use DateTime; +use DateTime::Event::ICal; +use DateTime::Span; +use Test::More; + +my $dt20140101 = DateTime->new( + year => 2014, month => 1, day => 1, hour => 0, minute => 0, second => 0, +); + +my $dt20140404a4 = DateTime->new( + year => 2014, month => 4, day => 4, hour => 4, minute => 0, second => 0, +); + +my $dt20140101a9 = $dt20140101->clone->set( hour => 9 ); + +my @test_cases = ( + { + name => 'Every 4 minutes of the 4th hour of the 4th of April', + start => $dt20140101, + recurrence => { + freq => 'minutely', + interval => 4, + byhour => [ 4 ], + bymonth => [ 4 ], + bymonthday => [ 4 ], + }, + expect => [ + map { $dt20140404a4->clone->set( minute => $_ ) } + map { $_ * 4 } 0 .. 14 + ], + }, + { + name => 'Every 20 minutes in January from 9:00 to 10:00', + start => $dt20140101a9, + recurrence => { + freq => 'minutely', + interval => 20, + bymonth => [ 1 ], + byday => [ qw( su mo tu we th fr sa ) ], + byhour => [ 9 ], + }, + expect => [ + map { ( + $_->clone->set( minute => 0 ), + $_->clone->set( minute => 20 ), + $_->clone->set( minute => 40 ), + ) } + map { $dt20140101a9->clone->set( day => $_ ) } 1 .. 31 + ], + }, + { + name => 'Every 8 hours on January 10th through 12th', + start => $dt20140101, + recurrence => { + freq => 'hourly', + interval => 8, + bymonth => [ 1 ], + bymonthday => [ 10, 11, 12 ], + }, + expect => [ + map { ( + $_->clone->set( hour => 0 ), + $_->clone->set( hour => 8 ), + $_->clone->set( hour => 16 ), + ) } map { $dt20140101->clone->set( day => $_ ) } 10 .. 12 + ], + }, +); + +my $test_count = 0; +$test_count += @{ $_->{expect} } for @test_cases; + +plan tests => $test_count; + +my $span = DateTime::Span->new( + start => DateTime->new( + year => 2014, month => 1, day => 1, hour => 0, minute => 0, second => 0, + ), + end => DateTime->new( + year => 2014, month => 12, day => 31, hour => 23, minute => 59, second => 59, + ), +); + +for my $case (@test_cases) { + my $set = DateTime::Event::ICal->recur( + dtstart => $case->{start}, + %{ $case->{recurrence} }, + ); + + my @all_expected = @{ $case->{expect} }; + my $iter = $set->iterator( span => $span ); + while (my $got = $iter->next) { + my $expected = shift @all_expected; + is($got, $expected, "$case->{name}: $expected matches"); + } + fail("$case->{name}: $_ matches") for @all_expected; +}
Subject: Re: [rt.cpan.org #97318] Broken handling of hourly, minutely, and secondly frequency with bymonth or bymonthday
Date: Thu, 17 Jul 2014 22:28:37 +0200
To: bug-DateTime-Event-ICal [...] rt.cpan.org
From: "Flavio S. Glock" <fglock [...] gmail.com>
Andrew: Thank you for looking into this! I've applied the patch and pushed DateTime-Event-ICal-0.12 to CPAN. I've edited the test to reduce the running time. I've added this to the TODO list: * high frequency FREQ can be optimized by precomputing larger steps of INTERVAL - Flávio S. Glock 2014-07-17 19:58 GMT+02:00 Andrew Sterling Hanenkamp via RT < bug-DateTime-Event-ICal@rt.cpan.org>: Show quoted text
> > Queue: DateTime-Event-ICal > Ticket <URL: https://rt.cpan.org/Ticket/Display.html?id=97318 > > > Okay, here's a quick patch that handles a couple of the issues. I don't
think it's a complete fix, but it's a start. Show quoted text
> > On Thu Jul 17 13:23:19 2014, HANENKAMP wrote:
> > There is a problem with the way this module uses intersection to apply > > various BYMONTH, BYMONTHDAY constraints to FREQ=MINUTELY, FREQ=HOURLY, > > and FREQ=SECONDLY. For example, if you have an ICAL recurrence defined > > like so: > > > > RRULE:FREQ=MINUTELY;INTERVAL=4;BYHOUR=4;BYMONTH=4;BYMONTHDAY=4 > > > > The expectation is that it will output 15 dates each year (April 4th @ > > 4:00, 4:04, 4:08, ... 11 more ..., 4:56). Instead, if given this > > input, it outputs only the first one. The workaround is to add this to > > the line: > > > >
BYMINUTE=0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59 Show quoted text
> > > > It's pretty obscure and I don't know if I have ever run into a need > > for this, but I ran across it well trying to write up some test cases > > for some code I'm writing. > > > > If I have time, I will try to send a patch.
> > >
Em Qui Jul 17 16:28:47 2014, fglock@gmail.com escreveu: Show quoted text
> Andrew: > > Thank you for looking into this! > > I've applied the patch and pushed DateTime-Event-ICal-0.12 to CPAN. > > I've edited the test to reduce the running time. > > I've added this to the TODO list: > * high frequency FREQ can be optimized by precomputing larger steps of > INTERVAL > > - Flávio S. Glock > > 2014-07-17 19:58 GMT+02:00 Andrew Sterling Hanenkamp via RT < > bug-DateTime-Event-ICal@rt.cpan.org>:
> > > > Queue: DateTime-Event-ICal > > Ticket <URL: https://rt.cpan.org/Ticket/Display.html?id=97318 > > > > > Okay, here's a quick patch that handles a couple of the issues. I > > don't
> think it's a complete fix, but it's a start.
> > > > On Thu Jul 17 13:23:19 2014, HANENKAMP wrote:
> > > There is a problem with the way this module uses intersection to > > > apply > > > various BYMONTH, BYMONTHDAY constraints to FREQ=MINUTELY, > > > FREQ=HOURLY, > > > and FREQ=SECONDLY. For example, if you have an ICAL recurrence > > > defined > > > like so: > > > > > > RRULE:FREQ=MINUTELY;INTERVAL=4;BYHOUR=4;BYMONTH=4;BYMONTHDAY=4 > > > > > > The expectation is that it will output 15 dates each year (April > > > 4th @ > > > 4:00, 4:04, 4:08, ... 11 more ..., 4:56). Instead, if given this > > > input, it outputs only the first one. The workaround is to add this > > > to > > > the line: > > > > > >
> BYMINUTE=0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59
> > > > > > It's pretty obscure and I don't know if I have ever run into a need > > > for this, but I ran across it well trying to write up some test > > > cases > > > for some code I'm writing. > > > > > > If I have time, I will try to send a patch.
> > > > > >