def start(self): w = self.contained.get_week() # assume start time of 8:00 ET, to be converted to UT below, # if necessary. dt = datetime(w.year, w.month, w.day, 8) if self.TZ != 'ET': dt = TimeAgent.est2utc(dt) return dt
def _get_incidental_events(today, timezone): """ Gathers up all the non-maintenance-period maintenance activities, and returns them in a list of CalEvent objects. """ utc_today = TimeAgent.est2utc(today) if timezone == "ET" else today mas = ( Maintenance_Activity.objects.filter(_start__gte=utc_today) .filter(_start__lt=utc_today + timedelta(days=1)) .filter(group=None) .filter(deleted=False) .order_by("_start") ) if mas.exists(): ev = CalEventIncidental(mas, TZ=timezone) return [ev] return []
def read(self, request, *args, **kws): if len(args) == 1: tz, = args startPeriods = request.GET.get("startPeriods", datetime.now().strftime("%Y-%m-%d")) daysPeriods = request.GET.get("daysPeriods", '14') dt = TimeAgent.str2dt(startPeriods) start = dt if tz == 'UTC' else TimeAgent.est2utc(dt) duration = int(daysPeriods) * 24 * 60 periods = Period.get_periods(start, duration) pjson = [DssPeriodHttpAdapter(p).jsondict(tz) for p in periods] return HttpResponse( json.dumps(dict(total = len(periods) , periods = pjson , success = 'ok')) , content_type = "application/json") else: tz, id = args p = Period.objects.get(id = id) return HttpResponse( json.dumps(dict(DssPeriodHttpAdapter(p).jsondict(tz) , success = 'ok')) , content_type = "application/json")
def transfer_fixed_periods(self, semester): """ We can dump Carl's DB into MySQL tables and use these to suck whatever info we need in addition to what is in the DB that Carl dumped to. Here we will take all 'scheduled dates' and replicate them as periods so that in the simulations they get translated into fixed periods that we pack around. The tricky part is creating the correct projects & sessions: Maintanence & Shutdown is easy, but the different types of tests need the appropriate project & session names, along w/ appropriate observer. """ self.total_periods_before = Period.objects.all() self.period_ids_before = [] for i in self.total_periods_before: self.period_ids_before.append(i.id) testingTypes = ['Tests', 'Calibration', 'Commissioning'] # Only transfer fixed periods from schedtime table that cover start, end = self.get_schedtime_dates(semester) # prepare for transfering over fixed periods by creating the # necessary projects & session we know we'll need self.create_project_and_session( semester , "Maintenance" , "non-science" , "Maintenance" , "maintenance") self.create_project_and_session( semester , "Shutdown" , "non-science" , "Shutdown" , "maintenance") query = """ SELECT `etdate`, `startet`, `stopet`, `lengthet`, `type`, `pcode`, `vpkey`, `desc`, `bands`, `be`, `observers` FROM `schedtime` WHERE `etdate` >= %s AND `etdate` < %s ORDER BY `etdate`, `startet` """ % (start, end) self.cursor.execute(query) rows = self.cursor.fetchall() for row in rows: #print row # translate row: # first, datetime info dt = row[0] year = int(dt[:4]) month = int(dt[4:6]) day = int(dt[6:]) start_time = row[1] hour = int(start_time[:2]) minutesTrue = int(start_time[2:]) # DSS operates on hourly quarters: so minutes must be - # 0, 15, 30, 45 # round down starttime to avoid overlaps! if 0 <= minutesTrue and minutesTrue < 15: minute = 0 elif 15 <= minutesTrue and minutesTrue < 30: minute = 15 elif 30 <= minutesTrue and minutesTrue < 45: minute = 30 else: minute = 45 # raise an alarm? if minute != minutesTrue: print >> sys.stderr, "minutes changed from %d to %d for row: " % (minutesTrue, minute), row durationHrs = float(row[3].strip()) # DSS operates on hourly quarters: we need to truncate these # down to the nearest quarter to avoid overlaps duration = (int((durationHrs * 60) / 15) * 15 ) / 60.0 # raise an alarm? if abs(duration - durationHrs) > 0.01: print >> sys.stderr, "duration changed from %f to %f for row: " % (durationHrs, duration), row # create the starttime - translate from ET to UT start = TimeAgent.est2utc(datetime(year, month, day, hour, minute)) # get other row values that we'll always need type = row[4].strip() pcode = row[5].strip() try: original_id = int(row[6]) except: original_id = None # what session to link this to? if type in testingTypes: s = self.get_testing_session(row, duration, semester) elif type == "Maintenance": # Maintenance is simple s = Sesshun.objects.filter(name = "Maintenance")[0] elif type == "Shutdown": # Shutdown is simple - not a whole lot going on s = Sesshun.objects.filter(name = "Shutdown")[0] elif type == "Astronomy": # Astronomy is only complicated if something's not right # can we use the vpkey? if original_id is not None and original_id != 0: # simple case - we're done #print "looking for original_id: ", original_id s = Sesshun.objects.filter(original_id = original_id)[0] elif pcode is not None and pcode != "": # try getting a session from the project - we rarely see it # print "Getting Session from pcode: ", pcode p = Project.objects.get(pcode = pcode) s = p.sesshun_set.all()[0] # totally arbitrary! else: # failure: this will raise an alarm s = None else: raise "Unknown Type in Schedtime table." # don't save stuff that will cause overlaps causesOverlap = self.findOverlap(start, duration) if s is not None: # check for problems # are we assigning fixed periods for open sessions? if s.session_type.type == 'open': print >> sys.stderr, "Period fixed for Session of type: ",\ s.session_type.type, s, start, duration if duration > s.max_duration or duration < s.min_duration: print >> sys.stderr, "Open Session duration (%f) does not honor min/max: %f/%f"\ % (duration, s.min_duration, s.max_duration) print >> sys.stderr, s # don't save it off if it caused an overlap if causesOverlap: print >> sys.stderr, "Causes Overlap!: ", s, start, duration else: #NOTE: we are no longer using windows # instead, just saves these off as periods pa = Period_Accounting(scheduled = duration) pa.save() pending = Period_State.get_state("P") p = Period(session = s , start = start , duration = duration , score = 0.0 , forecast = datetime.now() , backup = False , state = pending , accounting = pa) p.save() # keep track of this added one so we can # check for subsequent overlaps self.times.append((s, start, duration)) else: # warn the user: print >> sys.stderr, "Schedtime2DSS: could not find session for row: ", row
def set_start(self, start, tzname = None): if tzname == 'ET': self._start = TimeAgent.est2utc(start) else: self._start = start
def get_gbt_schedule_events(start, end, timezone, ignore_non_maint_period_maint_events=False): """ Generate a list of schedule events. The list returned consists of tuples: first element is a datetime, the second element is the list of events for that date. """ days = (end - start).days calendar = [] one_day = timedelta(days=1) utc_start = TimeAgent.est2utc(start) if timezone == "ET" else start old_monday = None for i in range(0, days): daily_events = [] # must use UTC equivalents for database lookups if times given # in Eastern Time today = start + timedelta(days=i) utc_today = TimeAgent.est2utc(today) if timezone == "ET" else today utc_yesterday = utc_today - one_day utc_tomorrow = utc_today + one_day # get the Monday for the week. The maintenance activity # groups are retrieved for the entire week, so we do this only # once per week. monday = today - timedelta(today.weekday()) # if we go from Monday to Monday in ETC then we must convert # to the equivalent of Monday 00:00 ET into UTC to get the right # time range. If already in UT, then it will stay whatever # 'monday' is set to. monday_in_utc = TimeAgent.est2utc(monday) if timezone == "ET" else monday if monday != old_monday: mags = Maintenance_Activity_Group.get_maintenance_activity_groups(monday_in_utc) old_monday = monday # Include previous day's periods because last one may end # today. Perhaps there is a way to do this exclusively via # query, but there aren't that many periods in a day. Exclude # the maintenance periods, because they are obtained above. ps = ( Period.objects.filter(start__gte=utc_yesterday) .filter(start__lt=utc_tomorrow) .filter(state__name="Scheduled") .exclude(session__observing_type__type="maintenance") ) for p in ps: if p.end() > utc_today: # periods can be everything non-maintenance ev = CalEventPeriod( p, p.start < utc_today, p.end() > utc_tomorrow, True if not (p.moc is False) else False, timezone ) daily_events.append(ev) daily_events += _get_fixed_maint_events(mags, today, timezone) # if today is monday, get floating maintenance events for the week if today.weekday() == 0: daily_events += _get_floating_maint_events(mags, timezone) # finally gather up the non-maintenance-period maintenance events if not ignore_non_maint_period_maint_events: daily_events += _get_incidental_events(today, timezone) # now sort the events and add to the calendar list daily_events.sort() calendar.append((today, daily_events)) return calendar
def read(self, request, *args, **kws): tz = args[0] # one or many? if len(args) == 1: # we are getting periods from within a range of dates sortField = jsonMap.get(request.GET.get("sortField", "start"), "start") order = "-" if request.GET.get("sortDir", "ASC") == "DESC" else "" # Either filter by date, or by something else. filterWnd = request.GET.get("filterWnd", None) # make sure we have defaults for dates defStart = datetime.now().strftime("%Y-%m-%d") if filterWnd is None else None defDays = "1" if filterWnd is None else None # Filtering by date involves a pair of keywords filterWnd = request.GET.get("filterWnd", None) filterElc = request.GET.get("filterElc", None) # make sure we have defaults for dates defStart = datetime.now().strftime("%Y-%m-%d") if filterWnd is None and filterElc is None else None defDays = "1" if filterWnd is None and filterElc is None else None startPeriods = request.GET.get("startPeriods", defStart) daysPeriods = request.GET.get("daysPeriods", defDays) if startPeriods is not None and daysPeriods is not None: if startPeriods is None: startPeriods = datetime.now().strftime("%Y-%m-%d") if daysPeriods is None: daysPeriods = "1" dt = TimeAgent.str2dt(startPeriods) start = dt if tz == "UTC" else TimeAgent.est2utc(dt) duration = int(daysPeriods) * 24 * 60 periods = Period.get_periods(start, duration) else: # filter by something else query_set = Period.objects # window id # filterWnd = request.GET.get("filterWnd", None) if filterWnd is not None: wId = int(filterWnd) query_set = query_set.filter(window__id=wId) # elective id # filterElc = request.GET.get("filterElc", None) if filterElc is not None: eId = int(filterElc) query_set = query_set.filter(elective__id=eId) periods = query_set.order_by(order + sortField) return HttpResponse( json.dumps( dict(total=len(periods), periods=[PeriodHttpAdapter(p).jsondict(tz) for p in periods], success="ok") ), content_type="application/json", ) else: # we're getting a single period as specified by ID p_id = int(args[1]) # p = Period.objects.get(id = p_id) p = get_object_or_404(Period, id=p_id) adapter = PeriodHttpAdapter(p) return HttpResponse( json.dumps(dict(period=adapter.jsondict(tz), success="ok")), content_type="application/json" )
def scheduling_email(request, *args, **kwds): address_key = ["observer_address", "changed_address", "staff_address"] subject_key = ["observer_subject", "changed_subject", "staff_subject"] body_key = ["observer_body", "changed_body", "staff_body"] email_key = ["observer", "changed", "staff"] if request.method == 'GET': # Show the schedule from now until 8am eastern 'duration' days from now. start = datetime.utcnow() duration = int(request.GET.get("duration")) end = TimeAgent.est2utc(TimeAgent.utc2est(start + timedelta(days = duration - 1)) .replace(hour = 8, minute = 0, second = 0, microsecond = 0)) # The class that sets up the emails needs the periods in the # scheduling range, and all the periods in the future. currentPs = list(Period.objects.filter(start__gt = start , start__lt = end)) futurePs = list(Period.objects.filter(start__gte = start).order_by("start")) notifier.setPeriods(currentPs, futurePs) return HttpResponse( json.dumps({ 'observer_address' : notifier.getAddresses("observer"), 'observer_subject' : notifier.getSubject("observer"), 'observer_body' : notifier.getBody("observer"), 'changed_address' : notifier.getAddresses("changed"), 'changed_subject' : notifier.getSubject("changed"), 'changed_body' : notifier.getBody("changed"), 'staff_address' : notifier.getAddresses("staff"), 'staff_subject' : notifier.getSubject("staff"), 'staff_body' : notifier.getBody("staff"), 'obs_periods' : [p.id for p in notifier.observingPeriods], 'changed_periods' : [p.id for p in notifier.changedPeriods] }) , mimetype = "text/plain") elif request.method == 'POST': # here we are overriding what/who gets sent for the first round # of emails for i in xrange(3): addr = str(request.POST.get(address_key[i], "")).replace(" ", "").split(",") notifier.setAddresses(email_key[i], addr) notifier.setSubject(email_key[i], request.POST.get(subject_key[i], "")) notifier.setBody(email_key[i], request.POST.get(body_key[i], "")) notifier.notify() # Remember when we did this to allow time-tagging of the schedule sn = Schedule_Notification(date = datetime.utcnow()) sn.save() # Emails for a given period shouldn't be sent more then is # necessary, so here we set the last_notification timestamp. # However, the client can change the recipients and text of the # 'changes' email - this ignores those changes. # See Story: https://www.pivotaltracker.com/story/show/14550249 now = datetime.utcnow() set_periods_last_notification(now, request, "changed_periods") set_periods_last_notification(now, request, "obs_periods") return HttpResponse(json.dumps({'success':'ok'}) , mimetype = "text/plain") else: return HttpResponse( json.dumps({'error': 'request.method is neither GET or POST!'}) , mimetype = "text/plain")
def getUTCHour(self, dt, estHour): dtEst = datetime(dt.year, dt.month, dt.day, estHour) dtUtc = TimeAgent.est2utc(dtEst) return dtUtc.hour
def from_post(self, fdata, tz): # only update the score if something in the period has changed update_score = False if not update_score: update_score = self.period.id is None # if newly created then start with a default if update_score: self.period.reinit_score() handle = fdata.get("handle", "") if handle: new_session = Sesshun.handle2session(handle) if not update_score: update_score = self.period.session != new_session self.period.session = new_session else: try: maintenance = Project.objects.get(pcode='Maintenance') self.period.session = Sesshun.objects.get(project=maintenance) except: self.period.session = Sesshun.objects.get(id=fdata.get("session", 1)) now = TimeAgent.quarter(datetime.utcnow()) date = fdata.get("date", None) time = fdata.get("time", "00:00") if date is None: self.period.start = now else: new_start = TimeAgent.quarter(strStr2dt(date, time + ':00')) if tz == 'ET': new_start = TimeAgent.est2utc(self.period.start) if not update_score: update_score = self.period.start != new_start self.period.start = new_start new_duration = TimeAgent.rndHr2Qtr(float(fdata.get("duration", "1.0"))) if not update_score: update_score = self.period.duration != new_duration self.period.duration = new_duration # if the score needs to be updated, then prepare it for this if update_score and now < self.period.start: self.period.reinit_score() self.period.backup = True if fdata.get("backup", None) == 'true' else False stateAbbr = fdata.get("state", "P") self.period.state = Period_State.objects.get(abbreviation=stateAbbr) self.period.moc_ack = fdata.get("moc_ack", self.period.moc_ack) # elective? eId = fdata.get("elective_id", None) if eId is not None: self.period.elective_id = eId # window? wId = fdata.get("window_id", None) if wId is not None: self.period.window_id = wId try: win = Window.objects.get(id = wId) except Window.DoesNotExist: pass else: end = win.last_date() if end and date is None: self.period.start = datetime(end.year, end.month, end.day) self.period.duration = 1 elif self.period.session.isWindowed() and self.period.window_id is None: # just because the window id wasn't specified doesn't mean # we don't want to assign this a window: # for instance, if this period was created outside of the # Windowed Period Explorer, we'll have to assign a window self.period.assign_a_window() # is this period a default period for a window? default = fdata.get("wdefault", None) if default is not None: # if default == "true" and self.period.window is not None: # assign this period as a default self.period.window.default_period = self.period self.period.window.save() elif default == "false" and self.period.window is not None: # unassign this period as a default self.period.window.default_period = None self.period.window.save() # how to initialize scheduled time? when they get published! # so, only create an accounting object if it needs it. if self.period.accounting is None: schd = self.period.duration if self.period.isScheduled() else 0.0 pa = Period_Accounting(scheduled = schd) pa.save() self.period.accounting = pa self.period.accounting.update_from_post(fdata) self.period.save() # now that we have an id (from saving), we can specify the relation # between this period and assocaited rcvrs self.update_rcvrs_from_post(fdata)