Year to date (YTD) adds up a metric from 1 January to each day; month to date (MTD) from the first of the month. In the Semantic Layer both are cumulative metrics with grain_to_date: the running total restarts at the start of every year, quarter or month. They need the time spine.

Copy and paste

# models/metrics/time.yml
metrics:
  - name: revenue_ytd
    label: Revenue year to date
    type: cumulative
    input_metric: revenue
    grain_to_date: year
    period_agg: last          # value at the end of each month or quarter
  - name: revenue_qtd
    type: cumulative
    input_metric: revenue
    grain_to_date: quarter
    period_agg: last
  - name: revenue_mtd
    type: cumulative
    input_metric: revenue
    grain_to_date: month
    period_agg: last

As of a date:

mf query --metrics revenue_ytd,revenue_qtd,revenue_mtd --group-by metric_time__day \
  --start-time 2026-05-15 --end-time 2026-05-15
metric_time__day      revenue_ytd    revenue_qtd    revenue_mtd
-------------------  -------------  -------------  -------------
2026-05-15T00:00:00            590            250            170

By month:

mf query --metrics revenue,revenue_ytd --group-by metric_time__month --start-time 2025-10-01

The monthly view

Month revenue revenue_ytd YTD without period_agg revenue_qtd revenue_mtd
2025-10 140 990 850 140 140
2025-11 150 1,140 990 290 150
2025-12 200 1,340 1,140 490 200
2026-01 110 110 110 110 110
2026-02 130 240 110 240 130
2026-03 100 340 240 340 100
2026-04 80 420 340 80 80
2026-05 470 890 420 550 470

The running total restarts in January, and QTD in January and April. December 2025 shows 1,340, the total of the year.

period_agg

A cumulative metric is computed for every day. Grouped by month, MetricFlow has to pick one day’s value per month, and period_agg decides which: first, last or average. The default is first, the value on the first day of the month, which for a YTD metric is roughly last month’s YTD: the “without period_agg” column above lags one month behind. Set period_agg: last for month-end values, which is what a report means by “YTD in March”.

As of a date

Queried by month, the current month shows its value on the last day in the time spine, which may be after today. In the example, May shows 890, including an order of 20 May, although “today” is 15 May. Two ways to stop at a date:

mf query --metrics revenue_ytd,revenue_mtd --group-by metric_time__month \
  --where "{{ TimeDimension('metric_time', 'day') }} <= '2026-05-15'"

With the filter, May’s last day is 15 May and revenue_ytd returns 590. --end-time alone does not do it: it limits the rows returned, and the May row is within range.

End the time spine shortly after the last date with data, or the last month keeps repeating the same YTD until the end of the spine.

Week to date and fiscal years

grain_to_date: week gives week to date. MetricFlow truncates dates with the warehouse’s date_trunc, so weeks started on Monday on DuckDB; on Snowflake the WEEK_START parameter decides, as explained in how to calculate week to date in Snowflake. For a year that starts in April, see how to use a fiscal calendar in the time spine.

Tested with dbt Core 1.12 and MetricFlow 0.213 on DuckDB.

References