Filter content by year range when start and end dates span multiple years

8 replies
Last updated: Nov 24, 2023
I have content that have a duration (example: 2022–02–03 to 2023–01–05), how can I filter these on year? Imagine a front-end having a yearly archive, so the example should not show up if you're on
2024
but if you're on
2022
or
2023
it should.
AI Update

Great question! When you have content with date ranges (like 2022-02-03 to 2023-01-05) and want to filter them by year for a yearly archive, you need to check if the date range overlaps with the selected year.

Here's the approach: a content item should appear in a year's archive if either its start date OR end date falls within that year, or if the range spans across that entire year.

Solution

Assuming you have startDate and endDate fields in your documents:

// Filter for year 2022
*[_type == "yourType" && (
  dateTime(startDate) >= dateTime("2022-01-01T00:00:00Z") && 
  dateTime(startDate) < dateTime("2023-01-01T00:00:00Z")
) || (
  dateTime(endDate) >= dateTime("2022-01-01T00:00:00Z") && 
  dateTime(endDate) < dateTime("2023-01-01T00:00:00Z")
) || (
  dateTime(startDate) < dateTime("2022-01-01T00:00:00Z") && 
  dateTime(endDate) >= dateTime("2023-01-01T00:00:00Z")
)]

This checks three conditions:

  1. Start date is in 2022
  2. End date is in 2022
  3. The range spans the entire year (starts before 2022 and ends after 2022)

With a parameter

For a reusable query with a year parameter:

*[_type == "yourType" && (
  dateTime(startDate) >= dateTime($year + "-01-01T00:00:00Z") && 
  dateTime(startDate) < dateTime(string(count([$year]) + $year) + "-01-01T00:00:00Z")
) || (
  dateTime(endDate) >= dateTime($year + "-01-01T00:00:00Z") && 
  dateTime(endDate) < dateTime(string(count([$year]) + $year) + "-01-01T00:00:00Z")
) || (
  dateTime(startDate) < dateTime($year + "-01-01T00:00:00Z") && 
  dateTime(endDate) >= dateTime(string(count([$year]) + $year) + "-01-01T00:00:00Z")
)]

Then pass { year: 2022 } as your parameter.

Simpler approach with string comparison

If your dates are stored in ISO 8601 format (YYYY-MM-DD), you can actually use string comparison which is simpler:

*[_type == "yourType" && (
  (startDate >= "2022-01-01" && startDate < "2023-01-01") ||
  (endDate >= "2022-01-01" && endDate < "2023-01-01") ||
  (startDate < "2022-01-01" && endDate >= "2023-01-01")
)]

Your example (2022-02-03 to 2023-01-05) would correctly appear in both 2022 and 2023 archives, but not in 2024.

The key insight is checking for range overlap rather than trying to extract the year from each date. This is more reliable and works well with GROQ's capabilities, as GROQ doesn't have built-in functions for extracting year components like JavaScript's getFullYear().

Show original thread
8 replies
You could use the
string::split()
function to split on
-
and get the first index:

*[_type == 'post' && string::split(date, "-")[0] == $year]

// params
{
  'year': '2023',
}
Potential downside is that the year param probably needs to be handled as a string.
You can also replace the
string::split()
predicate with:

&& date >= "2023" && date < "2024"
This would filter the year 2023.
thanks for your reply geoff! I'm curious, since it's a duration rather than just a simple date:
duration:{…} 3 properties
  _type:duration
  start:2022-11-05T12:16:00.000Z
  end:2023-01-08T12:17:00.000Z
isn't it a bit more complicated?
🤔
That may be different from the question I thought I was answering, which was about filtering by year, yearly archives, etc. I may have misunderstood you.
I think you could still use the comparison operators, but the
>=
would be against
duration.start
and
<
would be against
duration.end
. Does that accomplish what you’re after?
*[
  _type == 'exhibition'
 && (
  // Condition 1: Posts that started within the specified year
  (duration.start >= $startOfYear && duration.start <= $endOfYear) || 
  
  // Condition 2: Posts that ended within the specified year
  (duration.end >= $startOfYear && duration.end <= $endOfYear) ||
  
  // Condition 3: Posts that spanned the entire specified year
  (duration.start < $startOfYear && duration.end > $endOfYear)
)]
chat gpt helped me out 🙂
GROQ string comparisons are using lexicographic ordering rules, just as in JavaScript.
That means that timestamps in the standard RFC 3339 and ISO 8601 formats, such as:

2022-11-05T12:16:00.000Z
are lexicographically ordered, if they're in the same time zone.

In other words,
laterDateTime > newerDateTime
is always true. The same goes for
>=
,
<
,
<=
.
This is because lexicographic ordering ensures
2
&gt;
1
and so on. Since such timestamps are written with the most significant part (year) first, and each part is always equally long, they are comparable:
This also means you can omit any trailing part when using
<=
and
>=
. For example,
"2023-03-02T00:00:00Z" >= "2023"
will be true, and also true for any date past the year 2023.
It seems hacky, but it actually isn't when you think about it. And it's fast. The
string::split()
trick also works, but is not optimized to an efficient filter operation.

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?