There are indeed two solutions:
You could make a trigger that checks the data for 'gaps' or what-not and have it throw a custom error when you find that the insert / update should fail:
RAISERROR("Cannot do silly things",16,1)
(Looks like a misprint but there really is only one 'E')
This occurs as part of the insert / update batch so (A) it will cancel the insert and (B) the batch will take as long as the insert plus
the trigger take.
You could set up a constraint to do the same thing. The simplest was to set up a constraint is to write a function that returns a bit true or false and have a constraint that checks that this is true. This will have the same delay on insert as a trigger would but you cannot specify a custom error. It will have a default error message if the function throws an error so make sure it doesn't, or, when the function returns false the error will be a generic 547 'The INSERT statement conflicted with the CHECK constraint ...[constraint details]'
The constraint is better when setting up data relationships. Working with date 'Gaps' within a single table is a grey area. I guess you can decide how you want to use it yourself. Roughly speaking: Is it an integral relationship in the data (constraint) or is it a safety check to help the user (trigger)