Summary#
Group by the month (or day, or week) in the zone the team works in:
class PerMonth {
public DateTime Month;
public int Jobs;
}
entity Job {
[Required, MaxLength(20)] string Team;
DateTime At;
}
List<PerMonth> Monthly() {
var stockholm = TimeZoneInfo.FindSystemTimeZoneById("Europe/Stockholm");
return Job.GroupBy(j => j.At.Bucket(TimeBucket.Month, stockholm))
.Select(g => new PerMonth { Month = g.Key, Jobs = g.Count() })
.OrderBy(m => m.Month)
.ToList();
}
List<PerMonth> WeeklyFromSunday() {
var stockholm = TimeZoneInfo.FindSystemTimeZoneById("Europe/Stockholm");
return Job.GroupBy(j => j.At.Bucket(TimeBucket.Week, stockholm, DayOfWeek.Sunday))
.Select(g => new PerMonth { Month = g.Key, Jobs = g.Count() })
.OrderBy(m => m.Month)
.ToList();
}A job at 22:30 UTC on 31 March is in April in Stockholm (00:30 on 1 April, summer time), and is counted there.
Signature#
DateTime Bucket(this DateTime at, TimeBucket unit, TimeZoneInfo zone)
DateTime Bucket(this DateTime at, TimeBucket unit, TimeZoneInfo zone, DayOfWeek weekStart) // a week from this day
public enum TimeBucket { Hour, Day, Week, Month, Quarter, Year }The answer is an instant (UTC): the moment the bucket starts in zone. Two instants share a bucket exactly when they
fall in the same local hour, day, week, month, quarter or year there.
Description#
A calendar, not a clock#
A day in Stockholm is 23 hours on the last Sunday of March and 25 on the last Sunday of October; a bucket follows the calendar, so each day is one bar and none is split. Counting in UTC instead moves every evening's work after 22:00 (or 23:00 in winter) into the next day.
Weeks start on the day you say#
Without a weekStart a week starts on Monday (ISO). DayOfWeek.Sunday or DayOfWeek.Saturday starts it there.
Same answer in memory#
list.GroupBy(j => j.At.Bucket(TimeBucket.Month, zone)) over a List<T> gives the same buckets, so a test over a list
and a report over the table agree.
See also#
- GroupBy (and HAVING) — grouping and HAVING.
- Median and percentiles — g.Median(…), g.Percentile(…, 0.9) — medians and percentiles per bucket.
- Time zones — TimeZoneInfo —
Zone.Of, and instants in a zone.