4

In our ASP.NET MVC 5 application we have user profiles with a Timezone ID (IANA).

public class User
{
    public int Id { get; set; }
    public string TimeZoneId { get; set; }
}

The task is to send all users an email at 6 AM local time. Our plan is to send out emails every hour to all timezones where the local time is 6 AM at that point in time (calculated based on the UTC time of the server).

Since we only have the Timezone ID to go by, I was hoping to get the corresponding Timezone IDs for, let's say, offset -4:00:00 -- taking into account DST.

string[] timezoneIds;

I could then query my database like so:

db.Users.Where(x => timezoneIds.Contains(x.TimeZoneId));

My question is, obviously, is this a decent idea or are there best practices for this problem that I am not aware of?

Thanks.

Fred Fickleberry III
  • 2,439
  • 4
  • 34
  • 50

2 Answers2

7

Simpler code for the "building a dictionary" that Serge shows: use LINQ's lookup, DateTimeZone.GetUtcOffset, and Offset keys instead of TimeSpan values:

var now = SystemClock.Instance.GetCurrentInstant();
var provider = DateTimeZoneProviders.Tzdb;
var idsByCurrentOffset = provider.Ids.ToLookup(id => provider[id].GetUtcOffset(now));

Then you can use:

var offset = Offset.FromHours(5);
foreach (var id in idsByCurrentOffset[offset])
{
    ...
}

(You don't even need to check for existence first, as the lookup indexer returns an empty sequence when presented with a key that doesn't exist in the lookup.)

Sam Carlson
  • 1,891
  • 1
  • 17
  • 44
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
3

You could try the following:

First create a dictionary with all the offsets and the timezones belonging to that offset

Instant now = SystemClock.Instance.Now;
IDateTimeZoneProvider timeZoneProvider = DateTimeZoneProviders.Tzdb;
Dictionary<TimeSpan, List<string>> timezonesForOffset = new Dictionary<TimeSpan, List<string>>();
foreach(var id in timeZoneProvider.Ids){
    ZonedDateTime zdt = now.InZone(timeZoneProvider[id]);
    var timespan = zdt.Offset.ToTimeSpan();
    if(timezonesForOffset.ContainsKey(timespan)){
        timezonesForOffset[timespan].Add(id);
    } else {
        timezonesForOffset[timespan] = new List<string> {id, };
    }
}

After you have done that you could use the following snippet to get all users within a certain timezone

var timespan2 = new TimeSpan(1,0,0);
var timezonesWithOffset = timezonesForOffset[timespan2];

var usersinTimezone = db.Users.Where(x=> timezonesWithOffset.Contains(x.TimezoneId)).ToList();

That would get all users in the timezone utc+1

Serge Bekenkamp
  • 416
  • 4
  • 13
  • This approach is fine, though consider the generated SQL may be quite large, depending on how many time zones that match. Also, if the code is run ahead of time, then be sure to pass the correct `Instant` for the actual time of execution, rather than `now`. – Matt Johnson-Pint Nov 05 '15 at 21:46
  • Oh, and I'm sure it would work just as well with `Offset` keys in the dictionary, not that it really matters. ;) – Matt Johnson-Pint Nov 05 '15 at 21:47