'Get interval (blocking) rules for persons
@param person_ids: list|tuple|set of person record IDs
@return: rules dict, format:
{person_id:
{event_type_id: (message, earliest_datetime),'

def get_interval_rules(self, person_ids):

    T = current.T
    db = current.db
    s3db = current.s3db
    now = current.request.utcnow
    day_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
    next_day = (day_start + datetime.timedelta(days=1))
    output = {}
    table = s3db.dvr_case_event
    event_type_id = table.type_id
    person_id = table.person_id
    event_types = self.get_event_types()
    etable = s3db.dvr_case_event_exclusion
    query = ((((table.person_id.belongs(person_ids) & (table.date >= day_start)) & (table.deleted == False)) & (etable.excluded_by_id == table.type_id)) & (etable.deleted == False))
    rows = db(query).select(table.person_id, etable.type_id, etable.excluded_by_id)
    collisions = {}
    for row in rows:
        event = row.dvr_case_event
        exclusion = row.dvr_case_event_exclusion
        pid = event.person_id
        if (pid in collisions):
            excluded = collisions[pid]
        else:
            excluded = collisions[pid] = {}
        tid = exclusion.type_id
        if (tid in excluded):
            excluded[tid].append(exclusion.excluded_by_id)
        else:
            excluded[tid] = [exclusion.excluded_by_id]
    for (pid, excluded) in collisions.items():
        if (pid not in output):
            rules = output[pid] = {}
        else:
            rules = output[pid]
        for (tid, excluded_by_ids) in excluded.items():
            event_type = event_types.get(tid)
            if (not event_type):
                continue
            excluded_by_names = []
            seen = set()
            for excluded_by_id in excluded_by_ids:
                if (excluded_by_id in seen):
                    continue
                else:
                    seen.add(excluded_by_id)
                excluded_by_type = event_types.get(excluded_by_id)
                if (not excluded_by_type):
                    continue
                excluded_by_names.append(s3_str(T(excluded_by_type.name)))
            if excluded_by_names:
                msg = (T('%(event)s    already    registered    today,    not    combinable') % {'event': ',    '.join(excluded_by_names)})
                rules[tid] = (msg, next_day)
    def type_query(items):
        if (len(items) == 1):
            return (event_type_id == items[0])
        elif items:
            return event_type_id.belongs(set(items))
        else:
            return None
    check = [tid for (tid, row) in event_types.items() if (row.max_per_day and (tid != '_default'))]
    q = type_query(check)
    if (q is not None):
        cnt = table.id.count()
        query = (((table.person_id.belongs(person_ids) & q) & (table.date >= day_start)) & (table.deleted != True))
        rows = db(query).select(person_id, event_type_id, cnt, groupby=(person_id, event_type_id))
        for row in rows:
            number = row[cnt]
            pid = row[person_id]
            if (pid not in output):
                rules = output[pid] = {}
            else:
                rules = output[pid]
            tid = row[event_type_id]
            if (tid in rules):
                continue
            event_type = event_types[tid]
            limit = event_type.max_per_day
            if (number >= limit):
                if (number > 1):
                    msg = (T('%(event)s    already    registered    %(number)s    times    today') % {'event': T(event_type.name), 'number': number})
                else:
                    msg = (T('%(event)s    already    registered    today') % {'event': T(event_type.name)})
                rules[tid] = (msg, next_day)
    check = [tid for (tid, row) in event_types.items() if (row.min_interval and (tid != '_default'))]
    q = type_query(check)
    if (q is not None):
        query = ((table.person_id.belongs(person_ids) & q) & (table.deleted != True))
        timestamp = table.date.max()
        rows = db(query).select(person_id, event_type_id, timestamp, groupby=(person_id, event_type_id))
        represent = table.date.represent
        for row in rows:
            latest = row[timestamp]
            pid = row[person_id]
            if (pid not in output):
                rules = output[pid] = {}
            else:
                rules = output[pid]
            tid = row[event_type_id]
            if (tid in rules):
                continue
            event_type = event_types[tid]
            interval = event_type.min_interval
            if latest:
                earliest = (latest + datetime.timedelta(hours=interval))
                if (earliest > now):
                    msg = (T('%(event)s    already    registered    on    %(timestamp)s') % {'event': T(event_type.name), 'timestamp': represent(latest)})
                    rules[tid] = (msg, earliest)
    return output
