def test_day_start_end(self):
        """Tests day_start() and day_end() with time and datetime methods."""
        # The "start-of-day" of any UTC time should be that date, but after
        # forcing the hour:minute:second time to the first second of the day.
        start_dt_epoch = calendar.timegm(self.day_dt.utctimetuple())
        start_st_epoch = calendar.timegm(self.day_st)
        self.assertEquals(start_dt_epoch, start_st_epoch)

        # Make the last second of a UTC day, 23:59:59, by forcing hour, minutes,
        # and seconds to the above limits. THe call to day_start should still
        # results in 00:00:00.
        end_dt = self.now_dt.replace(hour=23, minute=59, second=59)
        end_dt_epoch = calendar.timegm(end_dt.utctimetuple())
        end_st = (
            self.now_st.tm_year,
            self.now_st.tm_mon,
            self.now_st.tm_mday,
            23,
            59,
            59,  # Force to 23:59:59, leaving all else unchanged.
            self.now_st.tm_wday,
            self.now_st.tm_yday,
            self.now_st.tm_isdst)
        end_st_epoch = calendar.timegm(end_st)
        self.assertEquals(end_dt_epoch, end_st_epoch)

        self.assertEquals(utc.day_start(start_dt_epoch), start_dt_epoch)
        self.assertEquals(utc.day_start(self.now_seconds), start_dt_epoch)
        self.assertEquals(utc.day_start(end_dt_epoch), start_dt_epoch)

        self.assertEquals(utc.day_end(start_dt_epoch), end_dt_epoch)
        self.assertEquals(utc.day_end(self.now_seconds), end_dt_epoch)
        self.assertEquals(utc.day_end(end_dt_epoch), end_dt_epoch)
    def test_day_start_end(self):
        """Tests day_start() and day_end() with time and datetime methods."""
        # The "start-of-day" of any UTC time should be that date, but after
        # forcing the hour:minute:second time to the first second of the day.
        start_dt_epoch = calendar.timegm(self.day_dt.utctimetuple())
        start_st_epoch = calendar.timegm(self.day_st)
        self.assertEquals(start_dt_epoch, start_st_epoch)

        # Make the last second of a UTC day, 23:59:59, by forcing hour, minutes,
        # and seconds to the above limits. THe call to day_start should still
        # results in 00:00:00.
        end_dt = self.now_dt.replace(hour=23, minute=59, second=59)
        end_dt_epoch = calendar.timegm(end_dt.utctimetuple())
        end_st = (
            self.now_st.tm_year,
            self.now_st.tm_mon,
            self.now_st.tm_mday,
            23,
            59,
            59,  # Force to 23:59:59, leaving all else unchanged.
            self.now_st.tm_wday,
            self.now_st.tm_yday,
            self.now_st.tm_isdst,
        )
        end_st_epoch = calendar.timegm(end_st)
        self.assertEquals(end_dt_epoch, end_st_epoch)

        self.assertEquals(utc.day_start(start_dt_epoch), start_dt_epoch)
        self.assertEquals(utc.day_start(self.now_seconds), start_dt_epoch)
        self.assertEquals(utc.day_start(end_dt_epoch), start_dt_epoch)

        self.assertEquals(utc.day_end(start_dt_epoch), end_dt_epoch)
        self.assertEquals(utc.day_end(self.now_seconds), end_dt_epoch)
        self.assertEquals(utc.day_end(end_dt_epoch), end_dt_epoch)
    def test_leap_second(self):
        """Points out that Python does not know when the leap seconds are.

        This matters because, for example, StudentLifecycleObserver handlers
        are supplied a datetime.datetime, which includes explicit seconds,
        like what is obtained from the ISO_8601_DATETIME_FORMAT string
        ('%Y-%m-%dT%H:%M:%S.%fZ').

        The (harmless?) outcome is that events occurring during the leap
        second (23:59:60) will be added to the next day tallies.
        """
        # 30 Jun 2015 23:59:60 is the most recent leap second, as of this
        # test. time.strptime() is used here, instead of
        # datetime.datetime.strptime(), because the latter function does
        # not understand leap seconds at all, instead complaining with:
        #   ValueError: second must be in 0..59
        leap_st = time.strptime("2015-06-30T23:59:60.0Z",
                                schema_transforms.ISO_8601_DATETIME_FORMAT)
        self.assertEquals(leap_st.tm_year, 2015)
        self.assertEquals(leap_st.tm_mon, 6)
        self.assertEquals(leap_st.tm_mday, 30)
        self.assertEquals(leap_st.tm_hour, 23)
        self.assertEquals(leap_st.tm_min, 59)
        self.assertEquals(leap_st.tm_sec, 60)  # Not 59, but leap second as 60.
        leap_epoch = long(calendar.timegm(leap_st))

        # 30 Jun 2015 23:59:59 is the last "normal" second in 2015-06-30,
        # just prior to the leap second.
        last_dt = datetime.datetime.strptime(
            "2015-06-30T23:59:59.0Z",
            schema_transforms.ISO_8601_DATETIME_FORMAT)
        last_st = last_dt.utctimetuple()
        self.assertEquals(last_st.tm_year, 2015)
        self.assertEquals(last_st.tm_mon, 6)
        self.assertEquals(last_st.tm_mday, 30)
        self.assertEquals(last_st.tm_hour, 23)
        self.assertEquals(last_st.tm_min, 59)
        self.assertEquals(last_st.tm_sec, 59)
        last_epoch = long(calendar.timegm(last_st))

        # According to Posix, "Unix time" (seconds since the 1970-01-01 epoch
        # also known as a "Posix timestamp") should repeat itself for one
        # second during a leap second, but the following confirms this not to
        # be the case. It should not be necessary to add the `+ 1`.
        self.assertEquals(leap_epoch, last_epoch + 1)

        # The tangible effect of this is that events occurring during the
        # actual leap second end up in the tallies for the next day.
        day_sec = 24 * 60 * 60
        self.assertEquals(utc.day_start(leap_epoch),
                          utc.day_start(last_epoch) + day_sec)
    def test_to_timestamp(self):
        """Confirm precedence of seconds, dt, st, or text in to_timestamp()."""
        # Select now_seconds value, because seconds= was supplied.
        self.assertEquals(
            utc.to_timestamp(seconds=self.now_seconds,
                             dt=self.day_dt,
                             st=self.month_st,
                             text=self.year_text), self.now_seconds)

        # Select day start value, because seconds= was not supplied, and
        # dt was supplied.
        self.assertEquals(
            utc.to_timestamp(dt=self.day_dt,
                             st=self.month_st,
                             text=self.year_text),
            utc.day_start(self.now_seconds))

        # Select month value, because seconds= and dt= were not supplied, and
        # st was supplied.
        self.assertEquals(
            utc.to_timestamp(st=self.month_st, text=self.year_text),
            calendar.timegm(self.month_dt.utctimetuple()))

        # Select year value, because seconds=, dt=, and st= were not supplied,
        # and text was supplied.
        self.assertEquals(utc.to_timestamp(text=self.year_text),
                          calendar.timegm(self.year_dt.utctimetuple()))

        # Select new "now" value, because seconds=, dt=, st=, and text= were
        # all missing.
        self.assertTrue(utc.to_timestamp() >= self.now_seconds)
    def test_to_timestamp(self):
        """Confirm precedence of seconds, dt, st, or text in to_timestamp()."""
        # Select now_seconds value, because seconds= was supplied.
        self.assertEquals(
            utc.to_timestamp(seconds=self.now_seconds, dt=self.day_dt, st=self.month_st, text=self.year_text),
            self.now_seconds,
        )

        # Select day start value, because seconds= was not supplied, and
        # dt was supplied.
        self.assertEquals(
            utc.to_timestamp(dt=self.day_dt, st=self.month_st, text=self.year_text), utc.day_start(self.now_seconds)
        )

        # Select month value, because seconds= and dt= were not supplied, and
        # st was supplied.
        self.assertEquals(
            utc.to_timestamp(st=self.month_st, text=self.year_text), calendar.timegm(self.month_dt.utctimetuple())
        )

        # Select year value, because seconds=, dt=, and st= were not supplied,
        # and text was supplied.
        self.assertEquals(utc.to_timestamp(text=self.year_text), calendar.timegm(self.year_dt.utctimetuple()))

        # Select new "now" value, because seconds=, dt=, st=, and text= were
        # all missing.
        self.assertTrue(utc.to_timestamp() >= self.now_seconds)
    def test_leap_second(self):
        """Points out that Python does not know when the leap seconds are.

        This matters because, for example, StudentLifecycleObserver handlers
        are supplied a datetime.datetime, which includes explicit seconds,
        like what is obtained from the ISO_8601_DATETIME_FORMAT string
        ('%Y-%m-%dT%H:%M:%S.%fZ').

        The (harmless?) outcome is that events occurring during the leap
        second (23:59:60) will be added to the next day tallies.
        """
        # 30 Jun 2015 23:59:60 is the most recent leap second, as of this
        # test. time.strptime() is used here, instead of
        # datetime.datetime.strptime(), because the latter function does
        # not understand leap seconds at all, instead complaining with:
        #   ValueError: second must be in 0..59
        leap_st = time.strptime("2015-06-30T23:59:60.0Z", schema_transforms.ISO_8601_DATETIME_FORMAT)
        self.assertEquals(leap_st.tm_year, 2015)
        self.assertEquals(leap_st.tm_mon, 6)
        self.assertEquals(leap_st.tm_mday, 30)
        self.assertEquals(leap_st.tm_hour, 23)
        self.assertEquals(leap_st.tm_min, 59)
        self.assertEquals(leap_st.tm_sec, 60)  # Not 59, but leap second as 60.
        leap_epoch = long(calendar.timegm(leap_st))

        # 30 Jun 2015 23:59:59 is the last "normal" second in 2015-06-30,
        # just prior to the leap second.
        last_dt = datetime.datetime.strptime("2015-06-30T23:59:59.0Z", schema_transforms.ISO_8601_DATETIME_FORMAT)
        last_st = last_dt.utctimetuple()
        self.assertEquals(last_st.tm_year, 2015)
        self.assertEquals(last_st.tm_mon, 6)
        self.assertEquals(last_st.tm_mday, 30)
        self.assertEquals(last_st.tm_hour, 23)
        self.assertEquals(last_st.tm_min, 59)
        self.assertEquals(last_st.tm_sec, 59)
        last_epoch = long(calendar.timegm(last_st))

        # According to Posix, "Unix time" (seconds since the 1970-01-01 epoch
        # also known as a "Posix timestamp") should repeat itself for one
        # second during a leap second, but the following confirms this not to
        # be the case. It should not be necessary to add the `+ 1`.
        self.assertEquals(leap_epoch, last_epoch + 1)

        # The tangible effect of this is that events occurring during the
        # actual leap second end up in the tallies for the next day.
        day_sec = 24 * 60 * 60
        self.assertEquals(utc.day_start(leap_epoch), utc.day_start(last_epoch) + day_sec)
    def bin(cls, timestamp):
        """Converts POSIX timestamp to daily counter bin in self.binned dict.

        Args:
            timestamp: UTC time, as a POSIX timestamp (seconds since epoch).
        Returns:
            The key of the counter bin (which may or may not actually exist)
            in the self.binned dict associated with the supplied UTC time.
        """
        return utc.day_start(timestamp)
    def bin(cls, timestamp):
        """Converts POSIX timestamp to daily counter bin in self.binned dict.

        Args:
            timestamp: UTC time, as a POSIX timestamp (seconds since epoch).
        Returns:
            The key of the counter bin (which may or may not actually exist)
            in the self.binned dict associated with the supplied UTC time.
        """
        return utc.day_start(timestamp)
    def test_create_announcement_defaults(self):
        key = self._add_announcement()
        data = self._get_announcement(key)

        expected_date = utc.to_text(
            seconds=utc.day_start(utc.now_as_timestamp()))
        self.assertEquals(data['date'], expected_date)
        expected_key = str(
            db.Key.from_path(announcements.AnnouncementEntity.kind(), 1))
        self.assertEquals(data['key'], expected_key)
        self.assertEquals(data['html'], '')
        self.assertEquals(data['is_draft'], True)
        self.assertEquals(
            data['title'],
            announcements.AnnouncementsDashboardHandler.DEFAULT_TITLE_TEXT)
    def test_create_announcement_defaults(self):
        key = self._add_announcement()
        data = self._get_announcement(key)

        expected_date = utc.to_text(
            seconds=utc.day_start(utc.now_as_timestamp()))
        self.assertEquals(data['date'], expected_date)
        expected_key = str(db.Key.from_path(
            announcements.AnnouncementEntity.kind(), 1))
        self.assertEquals(data['key'], expected_key)
        self.assertEquals(data['html'], '')
        self.assertEquals(data['is_draft'], True)
        self.assertEquals(
            data['title'],
            announcements.AnnouncementsDashboardHandler.DEFAULT_TITLE_TEXT)
    def reduce(cls, key, values):
        total = sum(int(value) for value in values)
        ns_name = namespace_manager.get_namespace()

        if key == TotalEnrollmentEntity.COUNTING:
            TotalEnrollmentDAO.set(ns_name, total)
            yield key, total
        else:
            # key is actually a daily 'adds' counter bin seconds since epoch.
            bin_seconds_since_epoch = long(key)
            today = utc.day_start(utc.now_as_timestamp())
            # Avoid race conditions by not updating today's daily bin (which
            # is being updated by student lifecycle events).
            if bin_seconds_since_epoch != today:
                date_time = utc.timestamp_to_datetime(bin_seconds_since_epoch)
                EnrollmentsAddedDAO.set(ns_name, date_time, total)
    def reduce(cls, key, values):
        total = sum(int(value) for value in values)
        ns_name = namespace_manager.get_namespace()

        if key == TotalEnrollmentEntity.COUNTING:
            TotalEnrollmentDAO.set(ns_name, total)
            yield key, total
        else:
            # key is actually a daily 'adds' counter bin seconds since epoch.
            bin_seconds_since_epoch = long(key)
            today = utc.day_start(utc.now_as_timestamp())
            # Avoid race conditions by not updating today's daily bin (which
            # is being updated by student lifecycle events).
            if bin_seconds_since_epoch != today:
                date_time = utc.timestamp_to_datetime(bin_seconds_since_epoch)
                EnrollmentsAddedDAO.set(ns_name, date_time, total)
    def binned(self):
        """Returns the binned counters dict (empty if uninitialized counter).

        Returns:
            If the counter is initialized (has been set() at least once), a
            dict containing a single bin containing the total enrollments count
            is constructed and returned.  The single key in the returned dict
            is the 00:00:00 UTC "start of day" time of last_modified, as
            seconds since epoch. The single value is the get() count.

            Otherwise, if the counter is uninitialized, an empty dict is
            returned (just like the EnrollmentsDTO base class).
        """
        if self.is_empty:
            return super(TotalEnrollmentDTO, self).binned

        return {
            utc.day_start(self.last_modified): self.get(),
        }
    def binned(self):
        """Returns the binned counters dict (empty if uninitialized counter).

        Returns:
            If the counter is initialized (has been set() at least once), a
            dict containing a single bin containing the total enrollments count
            is constructed and returned.  The single key in the returned dict
            is the 00:00:00 UTC "start of day" time of last_modified, as
            seconds since epoch. The single value is the get() count.

            Otherwise, if the counter is uninitialized, an empty dict is
            returned (just like the EnrollmentsDTO base class).
        """
        if self.is_empty:
            return super(TotalEnrollmentDTO, self).binned

        return {
            utc.day_start(self.last_modified): self.get(),
        }