diff --git a/lib/WebGUI/Asset/Event.pm b/lib/WebGUI/Asset/Event.pm index 38b11ac22..79ff42be8 100644 --- a/lib/WebGUI/Asset/Event.pm +++ b/lib/WebGUI/Asset/Event.pm @@ -391,6 +391,28 @@ sub getDateTimeEnd { } } +#################################################################### + +=head2 getDateTimeEndNI + +Since the iCal standard is that ending dates are non-inclusive (they +do not include the second at the end of the time period), this method +provide a copy of the DateTime object that is 1 second earlier than +the set ending time. + +It's just one line of DateTime code to adjust this on any object, but +this is encapsulated here to make sure that the same amount of time +is used EVERYWHERE. + +=cut + +sub getDateTimeEndNI { + my $self = shift; + my $dt = $self->getDateTimeEnd; + $dt->subtract(seconds => 1); + return $dt; +} + @@ -1277,7 +1299,8 @@ sub getTemplateVars { $var{ "startDateEpoch" } = $dtStart->epoch; # End date/time - my $dtEnd = $self->getDateTimeEnd; + my $dtEnd = $self->getDateTimeEnd; + my $dtEndNI = $self->getDateTimeEndNI; $var{ "endDateSecond" } = sprintf "%02d", $dtEnd->second; $var{ "endDateMinute" } = sprintf "%02d", $dtEnd->minute; diff --git a/lib/WebGUI/Asset/Wobject/Calendar.pm b/lib/WebGUI/Asset/Wobject/Calendar.pm index fe03fc82d..bf5abfc1c 100644 --- a/lib/WebGUI/Asset/Wobject/Calendar.pm +++ b/lib/WebGUI/Asset/Wobject/Calendar.pm @@ -674,11 +674,11 @@ This is the main API method to get events from a calendar, so it must be flexibl =head3 startDate -A date with optional time in MySQL format. +A date, with optional time, in UTC, in MySQL format. =head3 endDate -A date with optional time in MySQL format. +A date, with optional time, in UTC, in MySQL format. =head3 options @@ -700,8 +700,6 @@ sub getEventsIn { $params->{order} = '' if $params->{order} !~ /^(?:time|sequencenumber)/i; my $order_by_type = $params->{order} ? lc($params->{order}) : $self->get('sortEventsBy'); - my $tz = $self->session->datetime->getTimeZone; - # Warn and return undef if no startDate or endDate unless ($start && $end) { $self->session->errorHandler->warn("WebGUI::Asset::Wobject::Calendar->getEventsIn() called with not enough arguments at ".join('::',(caller)[1,2])); @@ -710,14 +708,9 @@ sub getEventsIn { # Create objects and adjust for timezone - my ($startDate,$startTime) = split / /, $start; - my ($endDate,$endTime) = split / /, $end; - - #use Data::Dumper; - #$self->session->errorHandler->warn( Dumper [caller(1), caller(2), caller(3)] ); - my $startTz = WebGUI::DateTime->new($self->session, mysql => $start, time_zone => $tz)->set_time_zone("UTC")->toMysql; - my $endTz = WebGUI::DateTime->new($self->session, mysql => $end, time_zone => $tz)->set_time_zone("UTC")->toMysql; - + my ($startDate) = split / /, $start; + my ($endDate) = split / /, $end; + my $where = qq{ ( @@ -730,8 +723,8 @@ sub getEventsIn { ) ) || ( - CONCAT(Event.startDate,' ',Event.startTime) >= '$startTz' - && CONCAT(Event.startDate,' ',Event.startTime) < '$endTz' + CONCAT(Event.startDate,' ',Event.startTime) >= '$start' + && CONCAT(Event.startDate,' ',Event.startTime) < '$end' ) }; @@ -749,7 +742,6 @@ sub getEventsIn { my $orderby = join ',', @order_priority; - my $events = $self->getLineage(["descendants"], { returnObjects => 1, @@ -758,9 +750,9 @@ sub getEventsIn { orderByClause => $orderby, whereClause => $where, }); - + #? Perhaps use Stow to cache Events ?# - + return @{$events}; } @@ -1441,6 +1433,7 @@ sub viewWeek { #### Get all the events in this time period # Get the range of the epoch of this week my $dt = WebGUI::DateTime->new($self->session, $params->{start}); + $dt->set_time_zone($tz); $dt->truncate( to => "day"); # Apply First Day of Week settings @@ -1479,7 +1472,7 @@ sub viewWeek { # Get the week this event is in, and add it to that week in # the template variables my $dt_event_start = $event->getDateTimeStart; - my $dt_event_end = $event->getDateTimeEnd; + my $dt_event_end = $event->getDateTimeEndNI; #Handle events that start before this week or end after this week. if ($dt_event_start < $dt) { @@ -1491,7 +1484,7 @@ sub viewWeek { } my $start_dow = ($dt_event_start->day_of_week - $first_dow) % 7; - my $end_dow = ($dt_event_end->day_of_week - $first_dow) % 7; + my $end_dow = ($dt_event_end->day_of_week - $first_dow) % 7; my $sequence_number = $session->db->dbh->selectcol_arrayref( "SELECT sequenceNumber FROM Event WHERE assetId = ? ORDER BY revisionDate desc LIMIT 1", @@ -1645,26 +1638,26 @@ sub viewWeek { # Get the week this event is in, and add it to that week in # the template variables my $dt_event_start = $event->getDateTimeStart; - my $dt_event_end = $event->getDateTimeEnd; + my $dt_event_end = $event->getDateTimeEndNI; #Handle events that start before this week or end after this week. if ($dt_event_start < $dt) { - $dt_event_start = $dt; + $dt_event_start = $dt->clone; } if ($dt_event_end > $dtEnd) { - $dt_event_end = $dtEnd; + $dt_event_end = $dtEnd->clone; } my $start_dow = ($dt_event_start->day_of_week - $first_dow) % 7; - my $end_dow = ($dt_event_end->day_of_week - $first_dow) % 7; + my $end_dow = ($dt_event_end->day_of_week - $first_dow) % 7; my %eventTemplateVariables = $self->getEventVars($event); foreach my $weekDay ($start_dow .. $end_dow) { my $eventAssetId = $event->get( 'assetId' ); - my %hash = %eventTemplateVariables; + my %hash = %eventTemplateVariables; if ($sort_by_sequence && $can_edit_order) { if (1) { diff --git a/t/Asset/Event.t b/t/Asset/Event.t index d50b5c2ef..a907a2041 100644 --- a/t/Asset/Event.t +++ b/t/Asset/Event.t @@ -19,7 +19,7 @@ use WebGUI::Asset::Event; use Test::More; # increment this value for each test you create use Test::Deep; -plan tests => 9; +plan tests => 10; my $session = WebGUI::Test->session; @@ -81,3 +81,5 @@ is($event3->isAllDay, 1, 'isAllDay is zero since it has no start or end time, ev %templateVars = $event3->getTemplateVars(); is($templateVars{dateSpan}, 'Wednesday, August 16 • Thursday, August 17 ', 'getTemplateVars: dateSpan with no times, across two days'); is($templateVars{isOneDay}, 0, 'getTemplateVars: isOneDay with different start and end dates'); + +cmp_ok($event3->getDateTimeEnd, '>', $event3->getDateTimeEndNI, 'getDateTimeEndNI is less than getDateTimeEnd'); diff --git a/t/Asset/Wobject/Calendar.t b/t/Asset/Wobject/Calendar.t index 168cfbbfa..b7fca03b0 100644 --- a/t/Asset/Wobject/Calendar.t +++ b/t/Asset/Wobject/Calendar.t @@ -30,6 +30,11 @@ my @icalWrapTests = ( out => '\;Escape more than one\; multiple\; semicolons\;', comment => 'escape semicolons', }, + { + in => '\\Escape more than one\\ multiple\\ backslashes\\', + out => '\\\\Escape more than one\\\\ multiple\\\\ backslashes\\\\', + comment => 'escape backslashes', + }, { in => "lots\nand\nlots\nof\nnewlines\n", out => 'lots\\nand\\nlots\\nof\\nnewlines\\n', @@ -48,10 +53,11 @@ use WebGUI::Test; use WebGUI::Session; use Test::More; use Test::Deep; +use Data::Dumper; use WebGUI::Asset::Wobject::Calendar; use WebGUI::Asset::Event; -plan tests => 5 + scalar @icalWrapTests; +plan tests => 10 + scalar @icalWrapTests; my $session = WebGUI::Test->session; @@ -60,6 +66,7 @@ my $node = WebGUI::Asset->getImportNode($session); my $versionTag = WebGUI::VersionTag->getWorking($session); $versionTag->set({name=>"Calendar Test"}); +WebGUI::Test->tagsToRollback($versionTag); my $cal = $node->addChild({className=>'WebGUI::Asset::Wobject::Calendar'}); $versionTag->commit(); @@ -71,11 +78,9 @@ isa_ok($cal, 'WebGUI::Asset::Wobject::Calendar'); my $event = $cal->addChild({className=>'WebGUI::Asset::Event'}); isa_ok($event, 'WebGUI::Asset::Event','Can add Events as a child to the calendar.'); -# Calendars create and autocommit a version tag when a child is added. Lets get the name so we can roll it back. -my $secondVersionTag = WebGUI::VersionTag->new($session, $event->get("tagId")); - my $article = $cal->addChild({className=>"WebGUI::Asset::Wobject::Article"}); isnt(ref $article, 'WebGUI::Asset::Wobject::Article', "Can't add an article as a child to the calendar."); +ok(! defined $article, '... addChild returned undef'); my $dt = WebGUI::DateTime->new($session, mysql => '2001-08-16 8:00:00', time_zone => 'America/Chicago'); @@ -107,6 +112,166 @@ cmp_deeply( 'Variables returned by appendTemplateVarsDateTime' ); +###################################################################### +# +# getEventsIn +# +###################################################################### + +my $windowCal = $node->addChild({ + className => 'WebGUI::Asset::Wobject::Calendar', + title => 'Calendar for doing event window testing', +}); + +my $tz = $session->datetime->getTimeZone(); +my $bday = WebGUI::DateTime->new($session, WebGUI::Test->webguiBirthday); +my $dt = $bday->clone->truncate(to => 'day'); + +my $startDt = $dt->cloneToUserTimeZone->subtract(days => 1); +my $endDt = $dt->cloneToUserTimeZone->add(days => 1); + +my $inside = $windowCal->addChild({ + className => 'WebGUI::Asset::Event', + title => 'Inside window, no times, same day', + startDate => $bday->toDatabaseDate, + endDate => $bday->toDatabaseDate, + timeZone => $tz, +}, undef, undef, {skipAutoCommitWorkflows => 1}); + +my $inside2 = $windowCal->addChild({ + className => 'WebGUI::Asset::Event', + title => 'Inside window, with times', + startDate => $bday->toDatabaseDate, + endDate => $bday->toDatabaseDate, + startTime => $bday->toDatabaseTime, + endTime => $bday->clone->add(hours => 1)->toDatabaseTime, + timeZone => $tz, +}, undef, undef, {skipAutoCommitWorkflows => 1}); + +my $outsideHigh = $windowCal->addChild({ + className => 'WebGUI::Asset::Event', + title => 'Outside window, after time', + startDate => $endDt->clone->add(days => 2)->toDatabaseDate, + endDate => $endDt->clone->add(days => 3)->toDatabaseDate, + timeZone => $tz, +}, undef, undef, {skipAutoCommitWorkflows => 1}); + +my $outsideLow = $windowCal->addChild({ + className => 'WebGUI::Asset::Event', + title => 'Outside window, before time', + startDate => $startDt->clone->subtract(days => 3)->toDatabaseDate, + endDate => $startDt->clone->subtract(days => 2)->toDatabaseDate, + timeZone => $tz, +}, undef, undef, {skipAutoCommitWorkflows => 1}); + +my $straddle = $windowCal->addChild({ + className => 'WebGUI::Asset::Event', + title => 'Straddles the window, inclusive', + startDate => $startDt->clone->subtract(days => 1)->toDatabaseDate, + endDate => $endDt->clone->add(days => 1)->toDatabaseDate, + timeZone => $tz, +}, undef, undef, {skipAutoCommitWorkflows => 1}); + +my $straddleLow = $windowCal->addChild({ + className => 'WebGUI::Asset::Event', + title => 'Straddles the window, lower side', + startDate => $startDt->clone->subtract(hours => 12)->toDatabaseDate, + endDate => $startDt->clone->add(hours => 12)->toDatabaseDate, + startTime => $startDt->clone->subtract(hours => 12)->toDatabaseTime, + endTime => $startDt->clone->add(hours => 12)->toDatabaseTime, + timeZone => $tz, +}, undef, undef, {skipAutoCommitWorkflows => 1}); + +my $straddleHigh = $windowCal->addChild({ + className => 'WebGUI::Asset::Event', + title => 'Straddles the window, higher side', + startDate => $endDt->clone->subtract(hours => 12)->toDatabaseDate, + endDate => $endDt->clone->add(hours => 12)->toDatabaseDate, + startTime => $endDt->clone->subtract(hours => 12)->toDatabaseTime, + endTime => $endDt->clone->add(hours => 12)->toDatabaseTime, + timeZone => $tz, +}, undef, undef, {skipAutoCommitWorkflows => 1}); + +my $justBefore = $windowCal->addChild({ + className => 'WebGUI::Asset::Event', + title => 'Just before the window. Ending time coincident with window start', + startDate => $startDt->clone->subtract(hours => 1)->toDatabaseDate, + endDate => $startDt->toDatabaseDate, + startTime => $startDt->clone->subtract(hours => 1)->toDatabaseTime, + endTime => $startDt->toDatabaseTime, + timeZone => $tz, +}, undef, undef, {skipAutoCommitWorkflows => 1}); + +my $justAfter = $windowCal->addChild({ + className => 'WebGUI::Asset::Event', + title => 'Just after the window. Start time coincident with window end', + startDate => $endDt->toDatabaseDate, + endDate => $endDt->clone->add(hours => 1)->toDatabaseDate, + startTime => $endDt->toDatabaseTime, + endTime => $endDt->clone->add(hours => 1)->toDatabaseTime, + timeZone => $tz, +}, undef, undef, {skipAutoCommitWorkflows => 1}); + +my $tag2 = WebGUI::VersionTag->getWorking($session); +$tag2->commit; +WebGUI::Test->tagsToRollback($tag2); + +is(scalar @{ $windowCal->getLineage(['children'])}, 9, 'added events to the window calendar'); + +my @window = $windowCal->getEventsIn($startDt->toDatabase, $endDt->toDatabase); + +#diag $startDt->toDatabase; +#diag join "\n", map { join ' ', $_->get('title'), $_->get('startDate'), $_->get('startTime')} @window; +#diag $endDt->toDatabase; + +is(scalar @window, 4, 'getEventsIn returned 4 events'); +cmp_bag( + [ map { $_->get('title') } @window ], + [ map { $_->get('title') } ($inside, $inside2, $straddle, $straddleHigh)], + '..returns correct 4 events' +); + +###################################################################### +# +# viewWeek +# +###################################################################### + +my $weekCal = $node->addChild({ + className => 'WebGUI::Asset::Wobject::Calendar', + title => 'Calendar for doing event span testing, week', +}); + +my $allDayDt = $bday->cloneToUserTimeZone; + +my $allDay = $weekCal->addChild({ + className => 'WebGUI::Asset::Event', + title => 'An event with explicit times that lasts all day', + startDate => $allDayDt->toDatabaseDate, + endDate => $allDayDt->clone->add(days => 1)->toDatabaseDate, + startTime => $allDayDt->clone->truncate(to => 'day')->toDatabaseTime, + endTime => $allDayDt->clone->add(days => 1)->truncate(to => 'day')->toDatabaseTime, + timeZone => $tz, +}, undef, undef, {skipAutoCommitWorkflows => 1}); + +my $tag3 = WebGUI::VersionTag->getWorking($session); +$tag3->commit; +WebGUI::Test->tagsToRollback($tag3); + +my $allVars = $weekCal->viewWeek({ start => $bday }); +my @eventBins = (); +foreach my $day (@{ $allVars->{days} }) { + if (exists $day->{events} and scalar @{ $day->{events} } > 0) { + push @eventBins, $day->{dayOfWeek}; + } +} + +cmp_deeply( + \@eventBins, + [ 4 ], + 'viewWeek: all day event is only in 1 day when time zones line up correctly' +); + ################################################################ # # wrapIcal @@ -125,10 +290,3 @@ TODO: { local $TODO = "Tests to make later"; ok(0, 'Lots more to test'); } - -END { - # Clean up after thy self - $versionTag->rollback(); - $secondVersionTag->rollback(); -} -