(StartA <= EndB) and (EndA >= StartB)

Let ConditionA Mean that DateRange A Completely After DateRange B
_ |---- DateRange A ------| |---Date Range B -----| _
(True if StartA > EndB)

Let ConditionB Mean that DateRange A is Completely Before DateRange B
|---- DateRange A -----| _ _ |---Date Range B ----|
(True if EndA < StartB)

Then Overlap exists if Neither A Nor B is true -
(If one range is neither completely after the other,
nor completely before the other, then they must overlap.)

Now one of De Morgan's laws says that:

Not (A Or B) <=> Not A And Not B

Which translates to: (StartA <= EndB) and (EndA >= StartB)

NOTE: This includes conditions where the edges overlap exactly. If you wish to exclude that,
change the >= operators to >, and <= to <

NOTE2. Thanks to @Baodad, see this blog, the actual overlap is least of:
{ endA-startA, endA - startB, endB-startA, endB - startB }

(StartA <= EndB) and (EndA >= StartB) (StartA <= EndB) and (StartB <= EndA)

NOTE3. Thanks to @tomosius, a shorter version reads:
DateRangesOverlap = max(start1, start2) < min(end1, end2)
This is actually a syntactical shortcut for what is a longer implementation, which includes extra checks to verify that the start dates are on or before the endDates. Deriving this from above:

If start and end dates can be out of order, i.e., if it is possible that startA > endA or startB > endB, then you also have to check that they are in order, so that means you have to add two additional validity rules:
(StartA <= EndB) and (StartB <= EndA) and (StartA <= EndA) and (StartB <= EndB) or:
(StartA <= EndB) and (StartA <= EndA) and (StartB <= EndA) and (StartB <= EndB) or,
(StartA <= Min(EndA, EndB) and (StartB <= Min(EndA, EndB)) or:
(Max(StartA, StartB) <= Min(EndA, EndB)

But to implement Min() and Max(), you have to code, (using C ternary for terseness),:
(StartA > StartB? Start A: StartB) <= (EndA < EndB? EndA: EndB)