public @CheckForNull String checkSanity() {
        OUTER: for (int i = 0; i < 5; i++) {
            long bitMask = i < 4 ? bits[i] : (long) dayOfWeek;
            for (int j = BaseParser.LOWER_BOUNDS[i]; j <= BaseParser.UPPER_BOUNDS[i]; j++) {
                if (!checkBits(bitMask, j)) {
                    // this rank has a sparse entry.
                    // if we have a sparse rank, one of them better be the left-most.
                    if (i > 0)
                        return Messages.CronTab_do_you_really_mean_every_minute_when_you(spec, "H " + spec.substring(spec.indexOf(' ') + 1));
                    // once we find a sparse rank, upper ranks don't matter
                    break OUTER;
                }
            }
        }

        int daysOfMonth = 0;
        for (int i = 1; i < 31; i++) {
            if (checkBits(bits[2], i)) {
                daysOfMonth++;
            }
        }
        if (daysOfMonth > 5 && daysOfMonth < 28) { // a bit arbitrary
            return Messages.CronTab_short_cycles_in_the_day_of_month_field_w();
        }

        String hashified = hashify(spec);
        if (hashified != null) {
            return Messages.CronTab_spread_load_evenly_by_using_rather_than_(hashified, spec);
        }

        return null;
    }
@Test public void checkSanity() throws Exception {
        assertNull(new CronTab("@hourly").checkSanity());
        assertEquals(Messages.CronTab_do_you_really_mean_every_minute_when_you("* * * * *", "H * * * *"), new CronTab("* * * * *").checkSanity());
        assertEquals(Messages.CronTab_do_you_really_mean_every_minute_when_you("*/1 * * * *", "H * * * *"), new CronTab("*/1 * * * *").checkSanity());
        assertNull(new CronTab("H H(0-2) * * *", Hash.from("stuff")).checkSanity());
        assertEquals(Messages.CronTab_do_you_really_mean_every_minute_when_you("* 0 * * *", "H 0 * * *"), new CronTab("* 0 * * *").checkSanity());
        assertEquals(Messages.CronTab_do_you_really_mean_every_minute_when_you("* 6,18 * * *", "H 6,18 * * *"), new CronTab("* 6,18 * * *").checkSanity());
        // dubious; could be improved:
        assertEquals(Messages.CronTab_do_you_really_mean_every_minute_when_you("* * 3 * *", "H * 3 * *"), new CronTab("* * 3 * *").checkSanity());
        // promote hashes:
        assertEquals(Messages.CronTab_spread_load_evenly_by_using_rather_than_("H/15 * * * *", "*/15 * * * *"), new CronTab("*/15 * * * *").checkSanity());
        assertEquals(Messages.CronTab_spread_load_evenly_by_using_rather_than_("H/15 * * * *", "0,15,30,45 * * * *"), new CronTab("0,15,30,45 * * * *").checkSanity());
        assertEquals(Messages.CronTab_spread_load_evenly_by_using_rather_than_("H * * * *", "0 * * * *"), new CronTab("0 * * * *").checkSanity());
        assertEquals(Messages.CronTab_spread_load_evenly_by_using_rather_than_("H * * * *", "5 * * * *"), new CronTab("5 * * * *").checkSanity());
        // if the user specifically asked for 3:00 AM, probably we should stick to 3:00–3:59
        assertEquals(Messages.CronTab_spread_load_evenly_by_using_rather_than_("H 3 * * *", "0 3 * * *"), new CronTab("0 3 * * *").checkSanity());
        assertEquals(Messages.CronTab_spread_load_evenly_by_using_rather_than_("H 22 * * 6", "00 22 * * 6"), new CronTab("00 22 * * 6").checkSanity());
        assertNull(new CronTab("H/15 * 1 1 *").checkSanity());
        assertNull(new CronTab("0 3 H/15 * *").checkSanity());
        assertEquals(Messages.CronTab_short_cycles_in_the_day_of_month_field_w(), new CronTab("0 3 H/3 * *").checkSanity());
        assertEquals(Messages.CronTab_short_cycles_in_the_day_of_month_field_w(), new CronTab("0 3 */5 * *").checkSanity());
    }