use std::collections::HashSet;
use std::collections::BTreeMap;
use std::fmt::Display;
use std::ops::Neg;
use std::ops::Div;
use std::ops::Mul;
use std::ops::Add;
use std::ops::{AddAssign, Sub};
use std::cmp::max;
use std::collections::VecDeque;
use std::cmp::min;
use std::collections::BTreeSet;
use std::cmp::Ordering;
use std::fmt::Debug;

fn read_line() -> String {
    let mut buffer = String::new();
    std::io::stdin().read_line(&mut buffer).expect("No Line");
    buffer
}
fn read_lines<T: std::str::FromStr>(count: usize) -> Vec<T> {
    let mut buffer = String::new();
    let mut vec = Vec::with_capacity(count);
    for _ in 0 .. count {
        std::io::stdin().read_line(&mut buffer).expect("No Line");
        vec.push(buffer.trim().parse().ok().expect("Can't Parse"));
        buffer.clear();
    }
    vec
}
fn read_tabulate<R, T: Fn(&str)->R> (count: usize, transformer: T) -> Vec<R> {
    let mut buffer = String::new();
    let mut vec = Vec::with_capacity(count);
    for _ in 0 .. count {
        std::io::stdin().read_line(&mut buffer).expect("No Line");
        vec.push(transformer(buffer.trim()));
        buffer.clear();
    }
    vec
}
fn read_value<T: std::str::FromStr>() -> T {
    read_line().trim().parse().ok().unwrap()
}
fn read_values<T: std::str::FromStr>() -> Vec<T> {
    read_line().trim().split(' ').map(|x| x.parse().ok().expect("Can't Parse")).collect::<Vec<T>>()
}
macro_rules! freeze {
    ($($id:ident), *) => {
        $(let $id = $id;)*
    };
}
macro_rules! read_lines {
    ($ident: ident: [$block: block; $size: expr]) => {
        let $ident = (0 .. $size).into_iter().map(|_| $block).collect::<Vec<_>>();
    };
    (mut $ident: ident: [$block: block; $size: expr]) => {
        let mut $ident = (0 .. $size).into_iter().map(|_| $block).collect::<Vec<_>>();
    };
}
macro_rules! read {
    (mut $ident: ident: [$ty:ty]) => {
        let mut $ident = read_values::<$ty>();
    };
    ($ident: ident: [$ty:ty]) => {
        let $ident = read_values::<$ty>();
    };
    (mut $ident: ident: [[$ty:ty]; $size: expr]) => {
        let mut $ident = (0 .. $size).into_iter().map(|_| read_values::<$ty>()).collect::<Vec<_>>();
    };
    ($ident: ident: [[$ty:ty]; $size: expr]) => {
        let $ident = (0 .. $size).into_iter().map(|_| read_values::<$ty>()).collect::<Vec<_>>();
    };
    (mut $ident: ident: [$ty:ty; $size:expr]) => {
        let mut $ident = read_lines::<$ty>($size);
    };
    ($ident: ident: [$ty:ty; $size:expr]) => {
        let $ident = read_lines::<$ty>($size);
    };
    ($ident: ident: [$block: block; $size: expr]) => {
        let $ident = (0 .. $size).into_iter().map(|_| $block).collect::<Vec<_>>();
    };
    (mut $ident: ident: [$block: block; $size: expr]) => {
        let mut $ident = (0 .. $size).into_iter().map(|_| $block).collect::<Vec<_>>();
    };
    ($($token: tt)*) => {
        let mut iter = read_values::<String>().into_iter();
        read_from_iter!(iter; $($token)*);
    };
}
macro_rules! read_from_iter {
    ($iter:expr; mut $ident:ident:$ty:ty, $($rest:tt)*) => {
        let mut $ident = $iter.next().unwrap().parse::<$ty>().expect("Can't Parse");
        read_from_iter!($iter; $($rest)*);
    };
    ($iter:expr; $ident:ident:$ty:ty, $($rest:tt)*) => {
        let $ident = $iter.next().unwrap().parse::<$ty>().expect("Can't Parse");
        read_from_iter!($iter; $($rest)*);
    };
    ($iter:expr; mut $ident:ident:$ty:ty) => {
        let mut $ident = $iter.next().unwrap().parse::<$ty>().expect("Can't Parse");
    };
    ($iter:expr; $ident:ident:$ty:ty) => {
        let $ident = $iter.next().unwrap().parse::<$ty>().expect("Can't Parse");
    };
    ($iter: expr; ) => {};
}
struct KeyValue<K, V> {
    key: K,
    value: V
}
impl <K: PartialOrd, V> PartialEq for KeyValue<K, V> {
    fn eq(&self, other: &Self) -> bool {
        self.key.eq(&other.key)
    }
}
impl <K: PartialOrd, V> Eq for KeyValue<K, V> {}
impl <K: PartialOrd, V> PartialOrd for KeyValue<K, V> {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        self.key.partial_cmp(&other.key)
    }
}
impl <K: PartialOrd, V> Ord for KeyValue<K, V> {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.key.partial_cmp(&other.key).ok_or("Can't Compare").unwrap()
    }
}

#[derive(Copy, Clone, Eq, PartialEq, Debug)]
struct Time {
    hour: i32, minute: i32, second: i32
}
impl Time {
    fn to_int(&self) -> i32 {
        self.hour * 3600 + self.minute * 60 + self.second
    }
    fn to_clock(&self) -> Clock {
        Clock{needle_a: (self.to_int() / 720) % 60, needle_b: (self.to_int() / 60) % 60, needle_c: self.to_int() % 60 }
    }
    fn from_int(second: i32) -> Time {
        let second = (second % 86400 + 86400) % 86400;
        Time{hour: second / 3600, minute: (second % 3600) / 60, second: second % 60}
    }
}
impl Sub for Time {
    type Output = Time;
    fn sub(self, rhs: Time) -> <Self as std::ops::Sub<Time>>::Output { 
        Time::from_int((self.to_int() - rhs.to_int() + 86400) % 86400)
    }
}
impl PartialOrd for Time {
    fn partial_cmp(&self, rhs: &Time) -> std::option::Option<std::cmp::Ordering> { 
        self.to_int().partial_cmp(&rhs.to_int())
     }
}
impl Ord for Time {
    fn cmp(&self, rhs: &Self) -> std::cmp::Ordering { 
        self.to_int().cmp(&rhs.to_int())
    }
}
impl Display for Time {
    fn fmt<'a>(&self, fmt: &mut std::fmt::Formatter<'a>) -> std::result::Result<(), std::fmt::Error> { 
        write!(fmt, "{:02}:{:02}:{:02}", self.hour, self.minute, self.second)
    }
}
#[derive(Copy, Clone, Eq, PartialEq)]
struct Clock {
    needle_a: i32, needle_b: i32, needle_c: i32
}
impl Clock {
    fn all_suspect(&self) -> Vec<Time> {
        self.shuffled().into_iter().flat_map(|clock| (0 .. 60).flat_map(move |offset| clock.rotate(offset).try_time())).collect::<Vec<_>>()
    }
    fn shuffled(&self) -> Vec<Clock> {
        vec![
            *self,
            Clock{needle_a: self.needle_a, needle_b: self.needle_c, needle_c: self.needle_b},
            Clock{needle_a: self.needle_b, needle_b: self.needle_a, needle_c: self.needle_c},
            Clock{needle_a: self.needle_b, needle_b: self.needle_c, needle_c: self.needle_a}, 
            Clock{needle_a: self.needle_c, needle_b: self.needle_a, needle_c: self.needle_b},
            Clock{needle_a: self.needle_c, needle_b: self.needle_b, needle_c: self.needle_a}
        ]
    }
    fn rotate(&self, angle: i32) -> Clock {
        Clock{needle_a: (self.needle_a + angle % 60 + 60) % 60, needle_b: (self.needle_b + angle % 60 + 60) % 60, needle_c: (self.needle_c + angle % 60 + 60) % 60}
    }
    fn try_time(&self) -> Option<Time> {
        if self.needle_b / 12 != self.needle_a % 5 {
            None
        }else {
            Some(Time{hour: self.needle_a / 5, minute: self.needle_b, second: self.needle_c})
        }
    }
}
fn lower_bound<T: Ord>(vec: &Vec<T>, target: &T) -> usize {
    let mut min = 0;
    let mut max = vec.len();
    while min < max {
        let mid = (min + max) >> 1;
        if &vec[mid] < target {
            min = mid + 1;
        }else {
            max = mid;
        }
    }
    max
}
fn main() {
    loop {
        read!(n: usize);
        if n == 0 {break;}
        read_lines! {
            clocks: [{
                read!(s: i32, t: i32, u: i32);
                Clock{needle_a: s, needle_b: t, needle_c: u}
            }; n]
        }
        let times = clocks.iter().map(|c| {
            let mut s = c.all_suspect();
            s.sort();
            s
        }).collect::<Vec<_>>();
        let mut start_times = times.iter().flat_map(|f|f.clone()).collect::<Vec<_>>();
        start_times.sort();
        let mut min_start = Time{hour: 0, minute: 0, second: 0};
        let mut min_end = Time{hour: 11, minute: 59, second: 59};
        for start_time in start_times.into_iter().rev() {
            let end_time: Vec<Time> = times.iter().flat_map(|time| {
                let end = lower_bound(time, &start_time);
                if end == time.len() {
                    None
                }else {
                    Some(time[end])
                }
            }).collect::<Vec<_>>();
            if end_time.len() == times.len() {
                let &max_end = end_time.iter().max().unwrap();
                if (min_end - min_start) >= (max_end - start_time) {
                    min_start = start_time;
                    min_end = max_end;
                }
            }
        }
        println!("{} {}", min_start, min_end);
    }
}



Measured memory_bytes: 5056