Comparing two different timezone timespans using NodaTime

I have a requirement which I'm getting a little confused about. I started using NodaTime which I think is the best way to go.

I have two users, User1 and User2 both in two different timezones. They are available to meet between 2pm and 5pm for example, in their local timezones. If User2 has an offset of +2 hours from User1, then the overlap is just 1 hour. What I want to get the number of hours overlap (the actual time for User1 and User2 would be a bonus.)

All I have got so far is:

var user1TimeZone = DateTimeZoneProviders.Tzdb.GetZoneOrNull(user1timezone);
var user2TimeZone = DateTimeZoneProviders.Tzdb.GetZoneOrNull(user2timeZone);

Any thoughts on how I should even start tackling this problem?

Thanks,

Jon Skeet
people
quotationmark

Firstly, be aware that it could change each day: don't treat a time zone as a fixed offset.

Secondly, be aware that the local time specified (for each of start/end) may not even happen, or may happen twice. Work out how you want to handle ambiguous and skipped times.

For any particular day, I would just convert they users' start/end times to Instant (via ZonedDateTime) and then you can find the overlap. This does assume that any overlap happens on the same day, however... that isn't the case in reality. I'm having a meeting soon where one of the attendees is in New Zealand - it's March 14th here, but March 15th there. Accounting for that is rather trickier...

Here's code for the relatively simple case though:

using NodaTime;
using System;

class Test
{
    static void Main()
    {
        // My availability: 4pm-7pm in London        
        var jon = new Availability(
            DateTimeZoneProviders.Tzdb["Europe/London"],
            new LocalTime(16, 0, 0),
            new LocalTime(19, 0, 0));
        // My friend Richard's availability: 12pm-4pm in New York
        var richard = new Availability(
            DateTimeZoneProviders.Tzdb["America/New_York"],
            new LocalTime(12, 0, 0),
            new LocalTime(16, 0, 0));

        // Let's look through all of March 2017...
        var startDate = new LocalDate(2017, 3, 1);
        var endDate = new LocalDate(2017, 4, 1);
        for (LocalDate date = startDate; date < endDate; date = date.PlusDays(1))
        {
            var overlap = GetAvailableOverlap(date, jon, richard);
            Console.WriteLine($"{date:yyyy-MM-dd}: {overlap:HH:mm}");
        }
    }

    static Duration GetAvailableOverlap(
        LocalDate date,
        Availability avail1,
        Availability avail2)
    {
        // TODO: Check that the rules of InZoneLeniently are what you want.
        // Be careful, as you could end up with an end before a start...
        var start1 = (date + avail1.Start).InZoneLeniently(avail1.Zone);
        var end1 = (date + avail1.End).InZoneLeniently(avail1.Zone);

        var start2 = (date + avail2.Start).InZoneLeniently(avail2.Zone);
        var end2 = (date + avail2.End).InZoneLeniently(avail2.Zone);

        var latestStart = Instant.Max(start1.ToInstant(), start2.ToInstant());
        var earliestEnd = Instant.Min(end1.ToInstant(), end2.ToInstant());

        // Never return a negative duration... return zero of there's no overlap.
        // Noda Time should have Duration.Max really...
        var overlap = earliestEnd - latestStart;
        return overlap < Duration.Zero ? Duration.Zero : overlap;
    }
}

public sealed class Availability
{
    public DateTimeZone Zone { get; }
    public LocalTime Start { get; }
    public LocalTime End { get; }

    public Availability(DateTimeZone zone, LocalTime start, LocalTime end)
    {
        Zone = zone;
        Start = start;
        End = end;
    }
}

people

See more on this question at Stackoverflow