Determine Time Offset given Olson TZID and local DateTime?

665 views Asked by At

I need to determine Time Offset given Olson TZID of an event and a DateTime of an event.

I suppose I can do it with a help of Noda Time, but I'm new here and need help - an example of actual API call sequence to perform this task.

A few more details of what we're using and doing.

  1. We're running ASP.NET + SQL Server based Web site. Users of our site enter and save events which are happening at various locations. For each event Lat/Long and DateTime with time offset (of that event) are among required fields.
  2. There are various scenarios of data entry, sometimes Lat/Long is entered first, sometimes DateTime is. System allows to determine Lat/Long from an address or a map point. We want Time Offset to be consistent with Lat/Long in most of the cases because we normally expect DateTime being entered as local to that event.
  3. We use SQL Server DateTimeOffset field to store data.
  4. We don't want to depend on third-party web services to determine an offset, but prefer our system to do that.

I'm free to download and use any necessary tools or data. I already downloaded shapefiles from http://efele.net/maps/tz/ and used Shape2SQL http://goo.gl/u7AUy to convert them to SQL Server table with TZIDs and GEOMs.
I know how to get TZID from Lat/Long by querying that table.
What I need, again, is to determine Time Offset from TZID and DateTime.

1

There are 1 answers

0
vkelman On

Thank you for Jon Skeet and Matt Johnson for providing an answer on Noda Time Google Group http://goo.gl/I9unm0. Jon explained how to get Microsoft BCL DateTimeOffset value given Olson TZID and NodaTime LocalDateTime values. Matt pointed to an example of determining if DST is active given ZonedDateTime: What is the System.TimeZoneInfo.IsDaylightSavingTime equivalent in NodaTime?

I'm going to write a blog post summarizing my experience of getting Olson TZID from Lat/Long, parsing DateTime string into LocalDateTime and determining DateTimeOffset. I'll show there how GetTmzIdByLocation(latitude, longitude) and GetRoughDateTimeOffsetByLocation(latitude, longitude) methods work and why I needed both (first method doesn't work for locations on ocean). Once I write this post I'll add a comment here.

Note, that parsing DateTime string in a code below is not optimal yet; as Matt explained in a Google Group post (link above) it's better to use Noda Time tools than BCL. See a related question at http://goo.gl/ZRZ7XP

My current code:

public object GetDateTimeOffset(string latitude, string longitude, string dateTime)
{
    var tzFound = false;
    var isDST = false;

    var tmpDateTime = new DateTimeOffset(DateTime.Now).DateTime;
    if (!String.IsNullOrEmpty(dateTime))
    {
        try
        {
            // Note: Looks stupid? I need to throw away TimeZone Offset specified in dateTime string (if any).
            // Funny thing is that calling DateTime.Parse(dateTime) would automatically modify DateTime for its value in a system timezone.
            tmpDateTime = DateTimeOffset.Parse(dateTime).DateTime;
        }

        catch (Exception) { }
    }

    try
    {
        var tmzID = GetTmzIdByLocation(latitude, longitude);

        DateTimeOffset result;
        if (String.IsNullOrEmpty(tmzID) || tmzID.ToLower() == "uninhabited")   // TimeZone is unknown, it's probably an ocean, so we would just return time offest based on Lat/Long. 
        {
            var offset = GetRoughDateTimeOffsetByLocation(latitude, longitude);
            result = new DateTimeOffset(tmpDateTime, TimeSpan.FromMinutes(offset * 60));  // This only works correctly if tmpDateTime.Kind = Unspecified, see http://goo.gl/at3Vba
        }                                                                      // A known TimeZone is found, we can adjust for DST using Noda Time calls below.
        else
        {
            tzFound = true;
            // This was provided by Jon Skeet
            var localDateTime = LocalDateTime.FromDateTime(tmpDateTime); // See Noda Time docs at http://goo.gl/XseiSa
            var dateTimeZone = DateTimeZoneProviders.Tzdb[tmzID];
            var zonedDateTime = localDateTime.InZoneLeniently(dateTimeZone);  // See Noda Time docs at http://goo.gl/AqE8Qo
            result = zonedDateTime.ToDateTimeOffset(); // BCL DateTimeOffset
            isDST = zonedDateTime.IsDaylightSavingsTime();
        }

        return new { result = result.ToString(IncidentDateFormat), tzFound, isDST };
    }
    catch (Exception ex)
    {
        IMAPLog.LogEvent(System.Reflection.MethodBase.GetCurrentMethod().Name, "", ex);
        throw new CustomHttpException("Unable to get timezone offset.");
    }
}

An extension method (provided by Matt Johnson) for determining if DST is active What is the System.TimeZoneInfo.IsDaylightSavingTime equivalent in NodaTime?

public static class NodaTimeUtil
{
    // An extension method by Matt Johnson - on Stack Overflow at http://goo.gl/ymy7Wb
    public static bool IsDaylightSavingsTime(this ZonedDateTime zonedDateTime)
    {
        var instant = zonedDateTime.ToInstant();
        var zoneInterval = zonedDateTime.Zone.GetZoneInterval(instant);
        return zoneInterval.Savings != Offset.Zero;
    }
}