def _get_remaining_time_series_for_ticket( self, start, end, today, get_remaining_time, append_current_time, interval_duration=timedelta(days=1)): """Return the remaining time series for this ticket within the specified interval. Today is the date of today so that his does not have to be computed every time (prevents tz-related errors). get_remaining_time is a callable which returns the remaining time as number for the passed datetime.""" faked_start = midnight(start) rt_series = self._remaining_times_for(faked_start, end, get_remaining_time, interval_duration) if len(rt_series) > 0: rt_series[0] = (start, get_remaining_time(start)) if append_current_time and (midnight(today) < end) and (start != end): # dates are used as keys later so we have to use a datetime # with exactly the same microsecond attribute! rt_series.append((today, get_remaining_time())) return rt_series
def test_return_capacity_information_until_end_of_sprint_even_if_last_values_are_zero(self): self.teh.disable_sprint_date_normalization() team, member = self.team_with_one_member() self._set_default_capacity_for_member(0, member) sprint = self.teh.create_sprint('Foo Sprint') sprint.start = midnight(yesterday()) sprint.end = midnight(day_after_tomorrow()) capacities = team.capacity().hourly_capacities_in_sprint(sprint) # +1 because the last item is from 22:00-23:00 self.assert_length(24*3+1, capacities)
def test_can_sum_remaining_capacities_in_sprint(self): self.teh.disable_sprint_date_normalization() team, member = self.team_with_one_member() self._set_default_capacity_for_member(9*2, member) sprint = self.teh.create_sprint(name="SprintWithContingent", team=team, start=midnight(yesterday()), end=midnight(tomorrow())) summed = team.capacity().summed_hourly_capacities_in_sprint(sprint) # 2*9 (no. working hours), +8 (additional hours after zero), +1 (00:00-01:00 on the last day) self.assert_length(9*2+8+1, summed) self.assert_equals(2*(9*2), summed[0].capacity) self.assert_equals(0, summed[-1].capacity)
def testUTCDayTimeConversions(self): """Tests the UTC Datetime conversion, using timezone and utcoffsets""" bangkok_tz = get_timezone('GMT +7:00') now_in_bangkok = to_datetime(datetime(2009, 7, 11, 2, 30, tzinfo=bangkok_tz)) midnight_in_bangkok = dt.midnight(now_in_bangkok) midnight_in_bangkok_as_utc = dt.midnight_with_utc_shift(now_in_bangkok) self.assert_equals(midnight_in_bangkok, midnight_in_bangkok_as_utc)
def get_remaining_time(self, day=None): """Returns the remaining time on a specific day, passed as a date or ordinal value. If none, returns remaining time for today""" if day is None: # if is today, just return the current remaining time return float(self.task[Key.REMAINING_TIME] or 0) timestamp = self._get_timestamp(day) available_timestamps = sorted(self.history) remaining_time = None if len(available_timestamps) > 0: if timestamp < available_timestamps[0]: return 0.0 elif timestamp >= available_timestamps[-1]: return self.history[available_timestamps[-1]] else: last_timestamp = available_timestamps[0] for a_timestamp in available_timestamps: if a_timestamp > timestamp: # the last one was the good one remaining_time = self.history[last_timestamp] break last_timestamp = a_timestamp else: # In case timestamp was built from an ordinal, we must use 0:00 to # check instead of the current time if timestamp >= to_timestamp(midnight(today(), tz=localtz)): remaining_time = float(self.task[Key.REMAINING_TIME] or 0) else: remaining_time = 0.0 return remaining_time
def _get_remaining_time_series_for_ticket(self, start, end, today, get_remaining_time, append_current_time, interval_duration=timedelta(days=1)): """Return the remaining time series for this ticket within the specified interval. Today is the date of today so that his does not have to be computed every time (prevents tz-related errors). get_remaining_time is a callable which returns the remaining time as number for the passed datetime.""" faked_start = midnight(start) rt_series = self._remaining_times_for(faked_start, end, get_remaining_time, interval_duration) if len(rt_series) > 0: rt_series[0] = (start, get_remaining_time(start)) if append_current_time and (midnight(today) < end) and (start != end): # dates are used as keys later so we have to use a datetime # with exactly the same microsecond attribute! rt_series.append((today, get_remaining_time())) return rt_series
def test_today_is_relative_to_the_users_timezone(self): self.assert_true(self.sprint.is_currently_running) bangkok_tz = get_timezone('GMT +7:00') times = self.get_chart_data('utc_today_data', tz=bangkok_tz, storage='data')[0] self.assert_equals(2, len(times)) # Now we test that midnight in bangkok is normalized as UTC data with # offset now_in_bangkok = self.now.astimezone(bangkok_tz) today_start_in_bangkok = midnight(now_in_bangkok) self.assert_equals(today_start_in_bangkok, times[0]) self.assert_equals(today_start_in_bangkok + timedelta(days=1), times[1])
def test_cuts_off_times_that_would_be_on_previous_day_for_the_viewer(self): team, member1 = self.team_with_one_member() set_user_attribute_in_session(self.env, 'tz', 'GMT +11:00', member1.name) viewer_timezone = utc # don't want to get values from tomorrow self.set_hours_for_day_on_team_member(0, tomorrow(viewer_timezone), member1) # 11:00 at his place is 0:00 here, so two values should be lost capacities = team.capacity(viewer_timezone).hourly_capacities_for_day(today()) self.assert_length(8, capacities) self.assert_equals(1, capacities[0].capacity) self.assert_equals(1, capacities[-2].capacity) start_of_day_for_member1 = midnight(now(tz=viewer_timezone)) self.assert_equals(start_of_day_for_member1, capacities[0].when)
def _execute(self, sp_controller, date_converter, as_key): # AT: I am not sure how much more readable the code is by duplicating # variables... commitment = self.commitment env = sp_controller.env today = now(tz=utc) sprint = self.sprint start = sprint.start end = sprint.end # FIXME: (AT) I am not sure that cut_to_today is True by default, # as stated in the comment of the method. As far as I can see it is # rather False by default, the validator checks for not None so if # it would be None would be evaluated as False, and not as True if self.cut_to_today: end = min(end, midnight(today + timedelta(days=1))) actual_burndown = self._get_remaining_times_for_interval( env, start, end, sprint, sp_controller, self.cut_to_today) self._inject_commitment(actual_burndown, commitment) return self._transform_to_old_structure(actual_burndown)
def _execute(self, sp_controller, date_converter, as_key): # AT: I am not sure how much more readable the code is by duplicating # variables... commitment = self.commitment env = sp_controller.env today = now(tz=utc) sprint = self.sprint start = sprint.start end = sprint.end # FIXME: (AT) I am not sure that cut_to_today is True by default, # as stated in the comment of the method. As far as I can see it is # rather False by default, the validator checks for not None so if # it would be None would be evaluated as False, and not as True if self.cut_to_today: end = min(end, midnight(today + timedelta(days=1))) actual_burndown = self._get_remaining_times_for_interval(env, start, end, sprint, sp_controller, self.cut_to_today) self._inject_commitment(actual_burndown, commitment) return self._transform_to_old_structure(actual_burndown)
def test_midnight_uses_localtz_if_no_other_tz_given(self): self.assert_equals(utc, midnight(date.today(), tz=utc).tzinfo) self.assert_equals(localtz, midnight(date.today()).tzinfo) self.assert_equals(utc, midnight(datetime.now(), tz=utc).tzinfo) self.assert_equals(localtz, midnight(datetime.now()).tzinfo)
def midnight_in_timezone(timestamp): return midnight( date_to_datetime(timestamp, self.viewer_timezone).astimezone( self.viewer_timezone))
def test_midnight_keeps_timezone_if_already_specified(self): los_angeles = get_timezone('GMT -8:00') self.assert_equals(los_angeles, midnight(datetime.now(tz=los_angeles)).tzinfo)
class BurndownChartCanShowWorkingDaysOnly(BurndownChartTestCase): now = now() today = midnight(now) yesterday = today - timedelta(days=1) tomorrow = today + timedelta(days=1) def setUp(self): self.super() self.env.config.set(AgiloConfig.AGILO_GENERAL, 'burndown_should_show_working_days_only', True) self.now = datetime(2010, 5, 7, 9, 0, 0, tzinfo=localtz) # creates sprint with 20 days, of which 15 are working days # This sprint starts at a specific day, which means that it will soon # end before the real now. Other parts of the superclass test # infrastructure don't expec this, which may lead to errors as stuff is # created after the actual sprint has ended. self._create_remaining_times(self.now) def _dates_from_chart(self, param_name='remaining_times'): times = self.get_times_from_chart(param_name) all_dates = set() for time in times: all_dates.add(time.date()) return sorted(list(all_dates)) def test_weekends_are_hidden(self): all_dates = self._dates_from_chart('capacity_data') self.assert_length(15, all_dates) self.assert_contains(date(2010, 5, 7), all_dates) # this is actually the monday, shifted to the saturday by the compactor self.assert_contains(date(2010, 5, 8), all_dates) self.assert_contains((self.sprint.end - timedelta(days=5)).date(), all_dates) self.assert_not_contains((self.sprint.end - timedelta(days=4)).date(), all_dates) def test_sprint_without_team_does_not_hide_any_day(self): self.sprint.team = None self.sprint.save() self._remaining_time(5, self.sprint.end - timedelta(hours=5)) all_dates = self._dates_from_chart('remaining_times') self.assert_contains(self.sprint.end.date(), all_dates) def test_weekend_marker_is_empty_when_hiding_weekends(self): all_dates = self._dates_from_chart('weekend_data') self.assert_length(0, all_dates) def test_today_marker_is_empty_when_hiding_today(self): member = self.sprint.team.members[0] member.capacity = [0] * 7 member.save() self.sprint.start = self.yesterday self.sprint.end = self.tomorrow self.sprint.save() all_dates = self._dates_from_chart('today_data') self.assert_length(0, all_dates) self.env.config.set(AgiloConfig.AGILO_GENERAL, 'burndown_should_show_working_days_only', False) all_dates = self._dates_from_chart('today_data') self.assert_length(1, all_dates) def test_trendline_extends_to_shifted_sprint_end(self): all_times = self.get_times_from_chart('trend_data') self.assert_length(2, all_times) first, last = all_times virtual_end = self.sprint.end - timedelta(days=5) self.assert_equals(virtual_end, last)
def yesterday_midnight(self): return midnight(yesterday(tz=utc), tz=utc)
class TimeAggregationTest(AgiloTestCase): now = now() today = midnight(now) one_week_ago = today - timedelta(days=7) yesterday = today - timedelta(days=1) tomorrow = today + timedelta(days=1) def _generate_ticks_in_interval(self, start, end, viewer_timezone=localtz, days_to_remove=()): return TickGenerator(start, end, viewer_timezone, days_to_remove=days_to_remove).generate_ticks() def test_can_generate_ticks_for_one_week(self): ticks = self._generate_ticks_in_interval(self.one_week_ago, self.now) self.assert_length(8, ticks) for number_of_day in range(0, 8): self.assert_equals( ticks[number_of_day], self.one_week_ago + timedelta(days=number_of_day)) def test_can_generate_ticks_for_one_day(self): ticks = self._generate_ticks_in_interval(self.yesterday, self.now) self.assert_length(2, ticks) for number_of_day in range(0, 2): self.assert_equals(ticks[number_of_day], self.yesterday + timedelta(days=number_of_day)) def test_generate_no_ticks_for_negative_timeframe(self): ticks = self._generate_ticks_in_interval(self.now, self.yesterday) self.assert_length(0, ticks) def test_can_generate_ticks_for_two_weeks(self): two_weeks_ago = self.today - timedelta(days=14) ticks = self._generate_ticks_in_interval(two_weeks_ago, self.now) self.assert_length(8, ticks) for number_of_day in range(0, 8): self.assert_equals( ticks[number_of_day], two_weeks_ago + timedelta(days=2 * number_of_day)) def test_can_generate_ticks_for_sprint(self): self.teh.disable_sprint_date_normalization() sprint = self.teh.create_sprint('fnord', start=self.now, duration=4) ticks = TickGenerator.for_sprint(sprint, localtz).generate_ticks() self.assert_length(3, ticks) self.assert_equals(ticks[0].date(), sprint.start.date() + timedelta(days=1)) self.assert_equals(ticks[-1].date(), sprint.end.date()) def test_can_generate_tick_labels(self): some_friday = datetime(day=30, month=4, year=2010, tzinfo=localtz) ticks = TickGenerator(some_friday, some_friday + timedelta(3), localtz).generate_tick_labels() first_label = ticks[0] self.assert_contains('4', first_label.label) self.assert_contains('30', first_label.label) self.assert_contains('10', first_label.label) def test_ticks_are_at_midnight_in_viewers_timezone(self): viewer_timezone = get_timezone("GMT -4:00") ticks = self._generate_ticks_in_interval(self.yesterday, self.now, viewer_timezone) for tick in ticks: self.assert_equals(0, tick.hour) # should all be on midnight self.assert_equals(0, tick.minute) self.assert_equals(viewer_timezone, tick.tzinfo) def test_can_leave_out_specified_days(self): days_to_remove = [self.today] ticks = self._generate_ticks_in_interval(self.yesterday, self.tomorrow, localtz, days_to_remove) self.assert_length(1, ticks) self.assert_equals(self.yesterday, ticks[0]) def test_leaving_out_specific_days_works_with_tick_interval_bigger_than_one( self): days_to_remove = [self.today] start = self.today - timedelta(days=20) ticks = self._generate_ticks_in_interval(start, self.tomorrow, localtz, days_to_remove) self.assert_length(10, ticks)
def midnight_in_timezone(timestamp): return midnight(date_to_datetime(timestamp, self.viewer_timezone).astimezone(self.viewer_timezone))
def _normalized_start(self): normalized_start = self.start.astimezone(self.viewer_timezone) normalized_start = midnight(normalized_start) if normalized_start < self.start: normalized_start = normalized_start + timedelta(days=1) return normalized_start
class CompactorTest(AgiloTestCase): now = now() today = midnight(now) one_week_ago = today - timedelta(days=7) yesterday = today - timedelta(days=1) tomorrow = today + timedelta(days=1) next_week = today + timedelta(days=7) def _compact_values(self, timed_values, days_to_remove): compactor = ValuesPerTimeCompactor(timed_values, days_to_remove) return compactor.compact_values() def _remove_today(self, timed_values): return self._compact_values(timed_values, [self.today]) def test_hiding_a_day_does_not_remove_previous_days(self): timed_values = [ValuePerTime('fnord', self.yesterday), ValuePerTime('fnord', self.now) ] compacted_days = self._remove_today(timed_values) self.assert_length(1, compacted_days) self.assert_equals(self.yesterday, compacted_days[0].when) def test_does_nothing_when_no_days_are_hidden(self): timed_values = [ValuePerTime('fnord', self.yesterday), ValuePerTime('fnord', self.now), ValuePerTime('fnord', self.tomorrow), ] compacted_days = self._compact_values(timed_values, []) self.assert_length(3, compacted_days) self.assert_equals(timed_values, compacted_days) def test_returns_empty_list_when_values_are_empty(self): timed_values = [] compacted_days = self._remove_today(timed_values) self.assert_length(0, compacted_days) def test_hiding_a_day_does_not_remove_future_days(self): timed_values = [ValuePerTime('fnord', self.today), ValuePerTime('fnord', self.now), ValuePerTime('fnord', self.tomorrow),] compacted_days = self._remove_today(timed_values) self.assert_length(1, compacted_days) #self.assert_equals(self.tomorrow, compacted_days[0].when) def test_hiding_one_day_shifts_future_values_by_one_day(self): timed_values = [ValuePerTime('fnord', self.tomorrow),] compacted_days = self._remove_today(timed_values) self.assert_length(1, compacted_days) self.assert_equals(self.today, compacted_days[0].when) # tomorrow now becomes today def test_does_not_blow_up_if_no_values_are_present_on_day(self): timed_values = [ValuePerTime('fnord', self.yesterday), ValuePerTime('fnord', self.tomorrow), ] compacted_days = self._remove_today(timed_values) self.assert_length(2, compacted_days) self.assert_equals(timed_values, compacted_days) def test_works_with_several_days_to_remove(self): timed_values = [ValuePerTime(1, self.one_week_ago), ValuePerTime(2, self.yesterday), ValuePerTime(3, self.today), ValuePerTime(4, self.now), ValuePerTime(5, self.yesterday), ValuePerTime(6, self.next_week), ] compacted_days = self._compact_values(timed_values, [self.yesterday, self.tomorrow]) self.assert_length(4, compacted_days) self.assert_equals([1,3,4,6], [value.value for value in compacted_days]) self.assert_equals(self.one_week_ago - timedelta(days=0), compacted_days[0].when) self.assert_equals(self.today - timedelta(days=1), compacted_days[1].when) self.assert_equals(self.now - timedelta(days=1), compacted_days[2].when) self.assert_equals(self.next_week - timedelta(days=2), compacted_days[3].when) def test_compactor_can_generate_final_shift(self): self.assert_equals(timedelta(), ValuesPerTimeCompactor.final_shift([])) self.assert_equals(timedelta(days=1), ValuesPerTimeCompactor.final_shift([self.today])) self.assert_equals(timedelta(days=2), ValuesPerTimeCompactor.final_shift([self.yesterday, self.tomorrow]))