This isn't trivial. A mix of crosstab with overlapping and intersecting ranges, plus  corner cases (merge same start / end dates) on top. Very hard to solve with set-based operations i.e. pure SQL. 
I suggest a procedural solution in PL/pgSQL instead. Should perform nicely, too, as it only needs a single (bitmap-index) scan over the table:
CREATE OR REPLACE FUNCTION f_student_xtab(VARIADIC _student_ids int[])
  RETURNS TABLE (
      student_id int
    , "primary"  text
    , secondary  text
    , start_date date
    , end_date   date) AS
$func$
DECLARE
   r record;
BEGIN
   student_id := -1; -- init with impossible value
   FOR r IN
      SELECT t.student_id, t.site, t.primary_or_secondary = 'Primary' AS prim, l.range_end, l.date
      FROM   tbl t
      CROSS  JOIN LATERAL (
         VALUES (false, t.start_date)
              , (true , t.end_date)
         ) AS l(range_end, date)
      WHERE  t.student_id = ANY (_student_ids)
      ORDER  BY t.student_id, l.date, range_end -- start of range first
   LOOP
      IF r.student_id <> student_id THEN
         student_id := r.student_id;
         IF r.prim THEN "primary" := r.site;
                   ELSE secondary := r.site;
         END IF;
         start_date := r.date;
      ELSIF r.range_end THEN
         IF r.date < start_date THEN
            -- range already reported
            IF r.prim THEN "primary" := NULL;
                      ELSE secondary := NULL;
            END IF;
            start_date := NULL;
         ELSE
            end_date := r.date;
            RETURN NEXT;
            IF r.prim THEN
               "primary" := NULL;
               IF secondary IS NULL THEN start_date := NULL;
                                    ELSE start_date := r.date + 1;
               END IF;
            ELSE
               secondary := NULL;
               IF "primary" IS NULL THEN start_date := NULL;
                                    ELSE start_date := r.date + 1;
               END IF;
            END IF;
            end_date := NULL;
         END IF;
      ELSE  -- range starts
         IF r.date > start_date THEN
            -- range already running
            end_date := r.date - 1;
            RETURN NEXT;
         END IF;
         start_date := r.date;
         end_date := NULL;
         IF r.prim THEN "primary" := r.site;
                   ELSE secondary := r.site;
         END IF;
      END IF;
   END LOOP;
END
$func$  LANGUAGE plpgsql;
Call:
SELECT * FROM f_student_xtab(1,2,3);
Or:
SELECT * FROM f_student_xtab(VARIADIC '{1,2,3}');
db<>fiddle here - with extended test case
About VARIADIC: