Quantization

Quantization

Sometimes nanosecond precision is more than you need or is even too much. Quantization reduces the precision of a datetime by rounding down (or up, if that’s your thing) to a given amount. For example, quantizing 12:36 to 5 minutes gives 12:35, quantizing to 10 minutes gives 12:30, and quantizing to 1 hour gives 12:00.

This is useful for grouping datetimes into buckets such as hours, weeks or months, and for answering questions like “what quarter does this date land in?”

Date and time objects have a quantize() method that takes either a Timespan or Period. Normally you use the former for quantizing to less than one day, and the latter for a day or more. Quantizing by a positive amount rounds down, and a negative amount rounds up.

Less Than a Day

Quantizing by timespans less than a day works pretty much like you would expect. Some examples:

final date = LocalDateTime(2021, 2, 3, 13, 17, 46, 123456789);
date.quantize(Timespan(minutes: 5)) == LocalDateTime(2021, 2, 3, 13, 15);
date.quantize(-Timespan(minutes: 5)) == LocalDateTime(2021, 2, 3, 13, 20);
date.quantize(Timespan(hours: 12)) == LocalDateTime(2021, 2, 3, 12);
date.quantize(-Timespan(hours: 12)) == LocalDateTime(2021, 2, 4, 0);

Days

Quantizing by days uses the total number of days since a fixed epoch beginning on Sunday and not the beginning of the month. This ensures a consistent amount of time between quantized dates. It’s also useful for quantizing to the beginning of the week. For example:

final date = LocalDate(2026, 2, 10); // (A Tuesday)
date.quantize(Period(days: 7)) == hasDate(2026, 2, 8); // (Sunday)
While quantizing a Zoned or OffsetDateTime to a Timespan of days works nearly the same as a Period of days, there is one important difference: The Timespan will quantize to a date in UTC rather than the date in the given zone or offset. In general, it’s best to use a Period of days instead, which uses the correct zone or offset.

Quantizing to a week that starts on a different day is a bit trickier. The basic idea is to subtract the number of days from Sunday to the target weekday, quantize, and then add those days back:

final adjustment = Period(days: Weekday.thursday.us);
final mostRecentThursday =
    (date - adjustment).quantize(Period(days: 7)) + adjustment;
mostRecentThursday == hasDate(2026, 2, 5);

Months

Quantizing by month uses the number of months since the beginning of the year. For example, quantizing by Period(months: 3) rounds down to the nearest start of the quarter (January 1, April 1, July 1, or October 1).

final date = LocalDate(2025, 5, 3);
final startOfMonth = date.quantize(Period(months: 1));
startOfMonth == hasDate(2025, 5, 1);
final startOfQuarter = date.quantize(Period(months: 3));
startOfQuarter == hasDate(2025, 4, 1);
final endOfQuarter = date.quantize(-Period(months: 3)) - Period(days: 1);
endOfQuarter == hasDate(2025, 6, 30);

Years

Year quantization is straightforward. It rounds down (or up) to the beginning of a year. Some examples:

final date = LocalDate(2027, 7, 24);
final startOfDecade = date.quantize(Period(years: 10));
startOfDecade == LocalDate(2020, 1, 1);
final turnOfCentury = date.quantize(Period(years: 100));
turnOfCentury == LocalDate(2000, 1, 1);
There is no year zero in common era year numbering, so technically new decades and centuries start on years ending in one, not zero. But nobody thinks about years that way. If you want to be technically correct (which is the best kind), then the same trick I used above for starting a week on a non-Sunday will work here, too.