コード例 #1
0
ファイル: Blackout.py プロジェクト: mmccarty/nell
    def generateDates(self, calstart, calend, local_timezone = False):
        """
        Takes two UTC datetimes representing a period of time on the calendar.
        Returns a list of (datetime, datetime) tuples representing all the
        events generated by this blackout in that period.
        calstart       : the start datetime
        calend         : the end datetime
        local_timezone : boolean, if True results are in the local timezone;
                         if False, in UTC.
        """

        # short-circuit if calstart-calend outside of range
        start = self.getStartDate()
        end = self.getEndDate() if self.getRepeat() == 'Once' else self.getUntil()

        if end < calstart or start > calend:
            return []

        dates = []

        for seq in self.blackout_sequence_set.order_by("start_date"):
            start       = seq.start_date
            end         = seq.end_date
            until       = min(seq.until, calend) if seq.until else calend
            periodicity = seq.repeat.repeat

            # take care of simple scenarios first
            if start is None or end is None:
                continue  # ignore this sequence

            if periodicity == "Once":
                # A 'Once' sequence may belong to a 'Once' blackout,
                # or may be a DST transition sequence in a repeat
                # blackout.  The date test is for the latter, but will
                # work in either case.
                if not (end < calstart or start > calend):
                    dates.append((start, end))                    
            else:
                # Check to see if this *sequence* (not the entire
                # blackout) is outside the calstart-calend range.  If
                # so, skip to next sequence.
                if until < calstart or start > calend:
                    continue
                # Otherwise, get the dates that are within the range.
                while start <= until:
                    if start >= calstart:
                        dates.append((start, end))

                    start, end = self.get_next_period(start, end, periodicity)

        if local_timezone:
            return map(lambda x: (tz_to_tz(x[0], 'UTC', self.timeZone, True),\
                                      tz_to_tz(x[1], 'UTC', self.timeZone, True)), dates)
        else:
            return dates
コード例 #2
0
ファイル: Blackout.py プロジェクト: mmccarty/nell
    def initialize(self, tz, start, end, repeat = None, until = None, description = None):
        """
        Initializes a new or current blackout object to the given values.
        tz          : string; time zone for the blackout
        start       : datetime; start date/time, *in UTC*
        end         : datetime; end date/time, *in UTC*
        repeat      : Repeat object; repeat interval.
        until       : datetime; end date for repeat, if used
        description : string; A brief description of the blackout.
        """

        self.save() # the Blackout entry in the database needs an ID
                    # so that blackout sequences can be added.

        self.description = description

        self.clear_sequences()
        self.timeZone = tz

        # Simple case: 'Once'
        if not repeat or repeat.repeat == 'Once':
            if repeat == None:
                repeat = Repeat.objects.get(repeat = 'Once')

            # NOTE: Strip out TZ info, just in case the database has
            # not been altered to use timestamp without time zone.
            # The tzinfo is UTC, as these times are provided as UTC
            # representations of the user's desired local time, and
            # that is what we want.  However, if the tzinfo is not set
            # to None and the database has not been altered then these
            # get set to the local time by the time it is saved to a
            # sequence
            start = start.replace(tzinfo = None)
            end = end.replace(tzinfo = None)
            
            bs = Blackout_Sequence(start_date = start, end_date = end,
                                   repeat = repeat, until = until)
            self.blackout_sequence_set.add(bs)
        else:
            # We're looking at a series of blackouts that may continue
            # past a DST boundary.  In addition, one of the blackouts
            # may itself straddle the DST bound.  The blackout
            # sequences must be represented in the DB as UTC values,
            # since Antioch doesn't have the timezone libraries Python
            # has.  But since blackouts are a user concept, they must
            # be continuous in the local time zone (i.e. 8:00 AM local
            # time, before or after DST starts).  Thus the UTC values
            # will change as the series crosses DST bounds.  In
            # addition we are looking at the possibility that one of
            # the blackout instances itself will cross the DST
            # bound. Through all this we wish to maintain the proper
            # phase and spacing of the repeating blackouts. This
            # diagram represents this idea:
            #
            #                    DST                             DST
            # |---|---------------|-|=|=======================|===|=|----------------|
            # S   E    ...        S   E      ...              S   E         ...      U
            #
            #     Sequence 1
            # |---|---------------|
            # S   E               U
            #                     Seq 2
            #                     |---|
            #                     S   E
            #                        Rep.
            #                     |-------|    Sequence 3
            #                             |---|-------------------|
            #                             S   E   ...             U
            #                                                    Rep.
            #                                                 |-------|   Sequence 4
            #                                                         |---|----------|
            #                                                         S   E   ...    U
            #
            # Naive algorithm:
            #
            # 1) Get DST bounds over desired time range
            # 2) Map blackout instances (SE) over entire range
            # 3) Iterate over SEs and check:
            #    a) Has S crossed over a DST?
            #       i) If so, previous E becomes U of last sequence; save sequence,
            #          start another working sequence, and go to next iteration.
            #    b) Has E crossed over a DST?
            #       i) If so, previous E can become U of last sequence, as above, but:
            #          Create and save a 'Once' sequence with current S and E; next SE
            #          becomes working sequence, and go to next iteration
            #    c) if we got this far, until of current SE becomes until of working
            #       sequence

            dstb =  dst_boundaries(tz, start, until)
            dst_date = None

            if len(dstb):
                dstb.reverse()
                dst_date = tz_to_tz(dstb.pop(), 'UTC', tz, naive = True)

            localstart = tz_to_tz(start, 'UTC', tz, naive = True)
            localend = tz_to_tz(end, 'UTC', tz, naive = True)
            localuntil = tz_to_tz(until, 'UTC', tz, naive = True)
            days = truncateDt(localend) - truncateDt(localstart)
            periodicity = repeat.repeat
            bls = []

            while localstart <= localuntil:
                bls.append([localstart, localend, periodicity, localuntil])
                localstart, localend = self.get_next_period(localstart, localend, periodicity)

            seq = bls[0]
            seqs = []

            for i in range(0, len(bls)):
                if dst_date:  # if we are contending with a DST boundary...
                    # keep DST boundary date current
                    if seq[0] > dst_date:
                        if len(dstb):
                            dst_date = tz_to_tz(dstb.pop(), 'UTC', tz, naive = True)
                        else:
                            dst_date = None
                            continue

                    if bls[i][0] > dst_date:      # start crosses dst_date
                        seq[3] = bls[i - 1][1]    # set until to previous instance's end
                        seqs.append(seq)          # save
                        seq = bls[i]
                        continue
                    if bls[i][1] > dst_date:      # start hasn't, but end has crossed
                        seq[3] = bls[i - 1][1]    # dst_date; set until to previous end
                        seqs.append(seq)
                        seq = bls[i]
                        seq[2] = 'Once'
                        seqs.append(seq)

                        if i < len(bls):
                            seq = bls[i + 1]
                        continue

                seq[3] = bls[i][3]

            seqs.append(seq) # get the last sequence

            for i in seqs:
                bs = Blackout_Sequence(start_date = tz_to_tz(i[0], tz, 'UTC', True),
                                       end_date = tz_to_tz(i[1], tz, 'UTC', True),
                                       repeat = Repeat.objects.get(repeat = i[2]),
                                       until = tz_to_tz(i[3], tz, 'UTC', True))
                self.blackout_sequence_set.add(bs)

        self.save()
コード例 #3
0
ファイル: Blackout.py プロジェクト: mmccarty/nell
 def getEndDateTZ(self, tz = None):
     if not tz:
         tz = self.timeZone
     return tz_to_tz(self.getEndDate(), 'UTC', tz)
コード例 #4
0
ファイル: Blackout.py プロジェクト: mmccarty/nell
 def getUntilTZ(self, tz = None):
     if not tz:
         tz = self.timeZone
     until = self.getUntil()
     return tz_to_tz(until, 'UTC', tz) if until is not None else until
コード例 #5
0
ファイル: TestBlackout.py プロジェクト: mmccarty/nell
    def test_pt_dst(self):
        # dates are given as UTC dates even though the timezone is
        # given as a local timezone.  This is the way the blackout
        # view works. :/

        localstart = datetime(2011, 1, 1, 11)
        localend = datetime(2011, 1, 4, 13)
        localuntil = datetime(2011, 12, 4, 11)
        utcstart = tz_to_tz(localstart, 'US/Pacific', 'UTC', naive = True)
        utcend = tz_to_tz(localend, 'US/Pacific', 'UTC', True)
        utcuntil = tz_to_tz(localuntil, 'US/Pacific', 'UTC', True)
        spring, fall = dst_boundaries('US/Pacific', utcstart, utcuntil)

        my_bo = create_blackout(user     = self.u,
                                repeat   = 'Weekly',
                                start    = utcstart,
                                end      = utcend,
                                until    = utcuntil,
                                timezone = 'US/Pacific')

        # generate 'UTC' sequence of blackout dates for standard time
        # until spring transition.
        dates = my_bo.generateDates(utcstart,
                                    spring,
                                    local_timezone = False)
        self.assertNotEquals(len(dates), 0)

        # All the dates except the last one are in standard time.
        for i in range(0, len(dates) - 1):
            self.assertEquals(dates[i][0].time(), utcstart.time())
            self.assertEquals(dates[i][1].time(), utcend.time())
        # the last one straddles DST, so end should be an hour earlier in UTC.
        self.assertEquals(dates[-1][0].time(), utcstart.time())
        self.assertEquals(dates[-1][1].time(), (utcend - timedelta(hours = 1)).time())

        # generate 'UTC' sequence of blackout dates for spring DST
        # transition until fall transition.  This sequence will
        # include 2 transition blackouts over both DST transitions:
        one_hour = timedelta(hours = 1)
        dates = my_bo.generateDates(spring,
                                    fall,
                                    local_timezone = False)
        self.assertNotEquals(len(dates), 0)

        self.assertEquals(dates[0][0].time(), utcstart.time())
        self.assertEquals(dates[0][1].time(), (utcend - one_hour).time())

        for i in range(1, len(dates) - 1):
            self.assertEquals(dates[i][0].time(), (utcstart - one_hour).time())
            self.assertEquals(dates[i][1].time(), (utcend - one_hour).time())

        self.assertEquals(dates[-1][0].time(), (utcstart - one_hour).time())
        self.assertEquals(dates[-1][1].time(), utcend.time())

        # generate 'UTC' sequence of blackout dates from fall
        # transition until the 'until' time.  Back to standard time.
        # The first blackout in the range will be a transition
        # blackout.
        dates = my_bo.generateDates(fall,
                                    utcuntil,
                                    local_timezone = False)
        self.assertNotEquals(len(dates), 0)

        self.assertEquals(dates[0][0].time(), (utcstart - one_hour).time())
        self.assertEquals(dates[0][1].time(), utcend.time())

        for i in range(1, len(dates)):
            self.assertEquals(dates[i][0].time(), utcstart.time())
            self.assertEquals(dates[i][1].time(), utcend.time())

        # generate local timezone sequence of blackout dates for the
        # entire range.  Despite the complexity of the underlying UTC
        # representation, the local times should all be the same.
        dates = my_bo.generateDates(utcstart,
                                    utcuntil,
                                    local_timezone = True)
        self.assertNotEquals(len(dates), 0)

        for i in dates:
            self.assertEquals(i[0].time(), localstart.time())
            self.assertEquals(i[1].time(), localend.time())