Dates, you either hate them or you're not paying close enough attention.
Recently I've needed to break a day down into buckets of time for a project, for most days of the year this is fine and the 24 hours of the day (and yes, I know, but I can ignore that part for now) nicely breaks down into equal groups. But for 2 days of the year - the 25th March and 28th October for 2018 - that's not quite true, on the 25th March the day is an hour shorter and on the 28th October it's an hour longer. Taking the longer day as an example, there needs to be a couple of groups which have the same time part because the clocks go back at 2am to 1am.
For most systems this isn't a problem as you can use UTC for dates in your logic and use local time purely at a visualization layer, but in this particular case the logic needs to be done against the GMT time zone.
So the problem is this, if we use UTC then the timestamps don't match up properly and the latter periods go into the following day. Use only local time and you'll have the same problem or the first few periods go into the previous day. So how do you manage this? Well in short what we want to do is start from midnight and keep moving forward in steps of minutes until we get to the next day, if Daylight Savings is applied then we'll get fewer or more periods. So we need to:
Start at midnight in local time
Convert this value into UTC
Keep adding x minutes to that date and time until the date part is no longer for today
For each step, convert the UTC date back into local time
var tz = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time");
var localDate = new DateTime(2018, 3, 25, 0, 0, 0, DateTimeKind.Unspecified);
var utcDate = TimeZoneInfo.ConvertTimeToUtc(localDate, tz);
var dates = new List<DateTime>();
var i = 1;
dates.Add(TimeZoneInfo.ConvertTimeFromUtc(utcDate.AddMinutes(i * 30), tz));
while (dates.Last().Date == localDate.Date)
{
i++;
dates.Add(TimeZoneInfo.ConvertTimeFromUtc(utcDate.AddMinutes(i * 30), tz));
}
foreach (var d in dates)
{
Console.WriteLine($"{d:o}");
}
Console.WriteLine($"Created {dates.Count} periods");
In this example code we're using 30 minute periods, starting at 00:30 and finishing at 00:00 the following day. For the 25th March being run here it will give 46 dates and times, importantly the first 3 will look like this (formatting changed to make it easier to read).
2018-03-25 00:30:00
2018-03-25 02:00:00
2018-03-25 02:30:00
This is because when the clocks go forward time jumps from 1am to 2am.