> You still don't know what happens when start1 is greater than end1?
That's DateRange's responsibility, it's not a concern for getOverlap. By the time you get as far as getOverlap you already have a valid DateRange. Small, reusable, compositional pieces.
> What happens when they have no overlap? What does overlap return if it doesn't return null?
It can't "return null", we're talking about using a decent type system here. The types should express what kind of failures are possible, so perhaps getOverlap returns a maybe, or an either where the left hand side is some structured "reason" value that expresses what went wrong.
> What happens if start1 and end1 are in local vs UTC time?
You get a compilation error because that distinction is part of the type.
> What happens if range1 and range 2 are in local vs UTC time?
You get a compilation error because that distinction is part of the type.
> When you get overlapRange.Duration. Days is this days in 24 hr increments or days by date?
That's Duration's responsibility, not getOverlap's; again, small, reusable, compositional pieces. You shouldn't ever be calling overlapRange.duration.days (obvious law of demeter violation); rather you should be calling something that can do the right thing with overlapRange.duration (e.g. display it, compare whether it's longer than some limit).
But to answer the question: check its type, that will tell you. Any value that you actually pass around and use in business logic should have a type that tells you what kind of thing it is; primitives should only ever be used within the very lowest level functions.