Esempio n. 1
0
    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()
Esempio n. 2
0
    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())