Dates aren’t only useful for sorting. You can also leverage them to filter search results on a specific date range. Imagine you have a blog, and you want to provide time-based filters. For example, you may want to allow users to filter on recent posts, or only see posts from a certain period.
Modifying the data: an example
Before
Let’s say we have an index called articles
that looks like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| [
{
"title": "Algolia's Global Roadshow",
"author": "Ryan Chen",
"excerpt": "We've heard it from experts, industry surveys, and our most successful customers: search and discovery are key to moving the digital conversation forward.",
"date": "2018-10-17"
},
{
"title": "Black Friday & Site Search: small tips, big difference",
"author": "Matthieu Blandineau",
"excerpt": "It’s no surprise that during the holiday season, Black Friday & Cyber Monday are absolutely critical events for any e-commerce business. According to Adobe, online retailers earned $108.15B between Nov 1 and Dec 31 2017, up 13.8% from 2016. Only in the U.S.",
"date": "2018-10-05"
},
{
"title": "For better school projects, a partnership with GitHub",
"author": "Jessica West",
"excerpt": "Hello GitHubbers and Algolians alike! We have some exciting news we’d like to share with you. Algolia is so pleased to announce that we have partnered with GitHub’s Student Developer Pack to help students build search functionality into their projects freely and effortlessly 🎉.",
"date": "2018-09-18"
}
]
|
Algolia can handle filtering on date, as long as you format them properly. This means you first need to transform your date
attribute into Unix timestamps, as numeric values.
After
Before we can filter on date, we need to add the date as a Unix timestamp. We don’t have to remove or change date
; instead, we can add a date_timestamp
attribute with the proper format.
Note that this attribute needs to be a numeric value for Algolia to be able to sort on it.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| [
{
"title": "Algolia's Global Roadshow",
"author": "Ryan Chen",
"excerpt": "We've heard it from experts, industry surveys, and our most successful customers: search and discovery are key to moving the digital conversation forward.",
"date": "2018-10-17",
"date_timestamp": 1539734400
},
{
"title": "Black Friday & Site Search: small tips, big difference",
"author": "Matthieu Blandineau",
"excerpt": "It’s no surprise that during the holiday season, Black Friday & Cyber Monday are absolutely critical events for any e-commerce business. According to Adobe, online retailers earned $108.15B between Nov 1 and Dec 31 2017, up 13.8% from 2016. Only in the U.S.",
"date": "2018-10-05",
"date_timestamp": 1538697600
},
{
"title": "For better school projects, a partnership with GitHub",
"author": "Jessica West",
"excerpt": "Hello GitHubbers and Algolians alike! We have some exciting news we’d like to share with you. Algolia is so pleased to announce that we have partnered with GitHub’s Student Developer Pack to help students build search functionality into their projects freely and effortlessly 🎉.",
"date": "2018-09-18",
"date_timestamp": 1537228800
}
]
|
Applying a date filter
Now that Algolia can understand our dates, we can use the filters
attribute on them at search time to only retrieve some results.
Recent posts
Imagine we want to let users filter on most recent articles. First, you need to define what recent means for you. It may vary depending on your use case, the frequency at which you add new content, etc.
Let’s say that in our case, a recent article means an article that’s less than a week old. This means we need to set a filter that excludes records which date_timestamp
is greater than now minus one week.
Algolia filters use a SQL-like syntax, which allows you to use comparison operators.
1
2
3
4
5
| $dateTimestamp = strtotime('-1 week');
$results = $index->search('query', [
'filters' => "date_timestamp > $dateTimestamp"
]);
|
1
2
3
4
5
| nowTimestamp = Time.now.to_i # Unix timestamp
results = index.search('query', {
filters: "date_timestamp >= #{nowTimestamp - 60 * 60 * 24 * 7}"
})
|
1
2
3
4
5
6
7
| const d = new Date()
index.search('query', {
filters: `date_timestamp > ${Math.floor(d.setDate(d.getDate() - 7) / 1000)}`
}).then(({ hits }) => {
console.log(hits);
});
|
1
2
3
4
5
6
| date = datetime.datetime.now() - datetime.timedelta(weeks=1)
date_timestamp = int(time.mktime(date.timetuple()))
results = index.search('query', {
'filters': 'date_timestamp > ' + str(date_timestamp)
})
|
1
2
3
4
5
6
7
8
9
| let date = Calendar.current.date(byAdding: .day, value: -7, to: Date())
let dateTimestamp = Int(date!.timeIntervalSince1970)
let query = Query(query: "query")
query.filters = "date_timestamp > \(dateTimestamp)"
index.search(query, completionHandler: { (res, error) in
print(res)
})
|
1
2
| long unixTimeStamp = ZonedDateTime.now(ZoneOffset.UTC).plusWeeks(-1).toEpochSecond();
index.search(new Query().setFilters("date_timestamp > " + unixTimeStamp), null);
|
1
2
3
4
5
6
7
| DateTime date = DateTime.UtcNow.AddDays(-7);
DateTime sTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
long unixTime = (long)(date - sTime).TotalSeconds;
index.Search(new Query<Book>("query") {
Filters = $"date_timestamp > {unixTime}"
});
|
1
2
| long unixTimeStamp = ZonedDateTime.now(ZoneOffset.UTC).plusWeeks(-1).toEpochSecond();
index.search(new Query().setFilters("date_timestamp > " + unixTimeStamp));
|
1
2
3
4
5
6
7
| dateTimestamp := time.Now().AddDate(0, 0, -7).Unix()
filter := fmt.Sprintf("date_timestamp > %d", dateTimestamp)
res, err := index.Search(
"query",
opt.Filters(filter),
)
|
1
2
3
4
5
6
7
8
| val dateTimestamp = LocalDateTime.now().minusWeeks(1).toInstant(ZoneOffset.UTC).getEpochSecond
client.execute {
search into "myIndex" query Query(
query = Some("query"),
filters = Some("date_timestamp > " + dateTimestamp),
)
}
|
1
2
3
4
5
6
7
8
9
| val query = query("query") {
filters {
and {
comparison("date_timestamp", Greater, 1538352000000)
}
}
}
index.search(query)
|
Posts from a particular month
Now imagine we want only to retrieve posts from October 2018. We can achieve this by setting a range filter that spans the full month, by providing Unix timestamps for the first and last day of the month.
1
2
3
| $results = $index->search('query', [
'filters' => 'date_timestamp:1538352000 TO 1540944000'
]);
|
1
2
3
| results = index.search('query', {
filters: 'date_timestamp:1538352000 TO 1540944000'
})
|
1
2
3
4
5
| index.search('query', {
filters: 'date_timestamp:1538352000 TO 1540944000'
}).then(({ hits }) => {
console.log(hits);
});
|
1
2
3
| results = index.search('query', {
'filters': 'date_timestamp:1538352000 TO 1540944000'
})
|
1
2
3
4
5
6
| let query = Query(query: "query")
query.filters = "date_timestamp:1538352000 TO 1540944000"
index.search(query, completionHandler: { (res, error) in
print(res)
})
|
1
2
3
4
| index.search(
new Query("query")
.setFilters("date_timestamp:1538352000 TO 1540944000")
);
|
1
2
3
| index.Search(new Query<Book>("query") {
Filters = "date_timestamp:1538352000 TO 1540944000"
});
|
1
2
3
4
| index.search(
new Query("query")
.setFilters("date_timestamp:1538352000 TO 1540944000")
);
|
1
2
3
4
| res, err := index.Search(
"query",
opt.Filters("date_timestamp:1538352000 TO 1540944000"),
)
|
1
2
3
4
5
6
| client.execute {
search into "myIndex" query Query(
query = Some("query"),
filters = Some("date_timestamp:1538352000 TO 1540944000")
)
}
|
1
2
3
4
5
6
7
8
9
| val query = query("query") {
filters {
and {
range("date_timestamp", 1538352000000..1540944000000)
}
}
}
index.search(query)
|
You may also want to exclude a particular month, or generally search for all posts but a specific range. For this, you can combine numeric ranges with it to the NOT
boolean operator.
1
2
3
| $results = $index->search('query', [
'filters' => 'NOT date_timestamp:1538352000 TO 1540944000'
]);
|
1
2
3
| results = index.search('query', {
filters: 'NOT date_timestamp:1538352000 TO 1540944000'
})
|
1
2
3
4
5
| index.search('query', {
filters: 'NOT date_timestamp:1538352000 TO 1540944000'
}).then(({ hits }) => {
console.log(hits);
});
|
1
2
3
| results = index.search('query', {
'filters': 'NOT date_timestamp:1538352000 TO 1540944000'
})
|
1
2
3
4
5
6
| let query = Query(query: "query")
query.filters = "NOT date_timestamp:1538352000 TO 1540944000"
index.search(query, completionHandler: { (res, error) in
print(res)
})
|
1
2
3
4
| index.search(
new Query("query")
.setFilters("NOT date_timestamp:1538352000 TO 1540944000")
);
|
1
2
3
| index.Search(new Query<Book>("query") {
Filters = "NOT date_timestamp:1538352000 TO 1540944000"
});
|
1
2
3
4
| index.search(
new Query("query")
.setFilters("NOT date_timestamp:1538352000 TO 1540944000")
);
|
1
2
3
4
| res, err := index.Search(
"query",
opt.Filters("NOT date_timestamp:1538352000 TO 1540944000"),
)
|
1
2
3
4
5
6
| client.execute {
search into "myIndex" query Query(
query = Some("query"),
filters = Some("NOT date_timestamp:1538352000 TO 1540944000")
)
}
|
Closest records first
You can combine filters to a sorting strategy to go even further. Imagine you now have an index of concert dates. A nice search experience could be to get search results order from sooner to later, so that the end user would get upcoming concerts first.
First, you need to sort records by ascending date so you keep later events low in search results.
1
2
3
4
5
6
7
8
9
10
11
12
13
| $index->setSettings([
'ranking' => [
'asc(date_timestamp)',
'typo',
'geo',
'words',
'filters',
'proximity',
'attribute',
'exact',
'custom'
]
]);
|
1
2
3
4
5
6
7
8
9
10
11
12
13
| index.set_settings({
ranking: [
'asc(date_timestamp)',
'typo',
'geo',
'words',
'filters',
'proximity',
'attribute',
'exact',
'custom'
]
})
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| index.setSettings({
ranking: [
"asc(date_timestamp)",
"typo",
"geo",
"words",
"filters",
"proximity",
"attribute",
"exact",
"custom"
]
}).then(() => {
// done
});
|
1
2
3
4
5
6
7
8
9
10
11
12
13
| index.set_settings({
'ranking': [
'asc(date_timestamp)',
'typo',
'geo',
'words',
'filters',
'proximity',
'attribute',
'exact',
'custom'
]
})
|
1
2
3
4
5
6
7
8
9
10
11
12
13
| index.setSettings([
"ranking": [
"asc(date_timestamp)",
"typo",
"geo",
"words",
"filters",
"proximity",
"attribute",
"exact",
"custom"
]
])
|
1
2
3
4
5
6
7
8
9
10
11
| index.setSettings(new JSONObject().put("ranking", new JSONArray()
.put("asc(date_timestamp)")
.put("typo")
.put("geo")
.put("words")
.put("filters")
.put("proximity")
.put("attribute")
.put("exact")
.put("custom"))
);
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| IndexSettings settings = new IndexSettings
{
Ranking = new List<string>
{
"asc(date_timestamp)",
"typo",
"geo",
"words",
"filters",
"proximity",
"attribute",
"exact",
"custom"
}
};
index.SetSettings(settings);
// Asynchronous
await index.SetSettingsAsync(settings);
|
1
2
3
4
5
6
7
8
9
10
11
12
13
| index.setSettings(
new IndexSettings().setRanking(Arrays.asList(
"asc(date_timestamp)",
"typo",
"geo",
"words",
"filters",
"proximity",
"attribute",
"exact",
"custom",
))
);
|
1
2
3
4
5
6
7
8
9
10
11
12
13
| res, err := index.SetSettings(search.Settings{
Ranking: opt.Ranking(
"asc(date_timestamp)",
"typo",
"geo",
"words",
"filters",
"proximity",
"attribute",
"exact",
"custom",
),
})
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| client.execute {
changeSettings of "concerts" `with` IndexSettings(
ranking = Some(Seq(
Ranking.asc("date_timestamp"),
Ranking.typo,
Ranking.geo,
Ranking.words,
Ranking.filters,
Ranking.proximity,
Ranking.attribute,
Ranking.exact,
Ranking.custom
))
)
}
|
Then, you can filter out every record with past dates. Combined to the sorting setting, this returns events from closest to the current date, to later in time.
1
2
3
4
5
| $nowTimestamp = time();
$results = $index->search('query', [
'filters' => "date_timestamp >= $nowTimestamp"
]);
|
1
2
3
4
5
| nowTimestamp = Time.now.to_i
results = index.search('query', {
filters: "date_timestamp >= #{nowTimestamp}"
})
|
1
2
3
4
5
6
7
| const nowTimestamp = Date.now();
index.search('query', {
filters: `date_timestamp >= ${nowTimestamp}`
}).then(({ hits }) => {
console.log(hits);
});
|
1
2
3
4
5
| now_timestamp = int(time.time())
results = index.search('query', {
'filters': 'date_timestamp >= ' + str(now_timestamp)
})
|
1
2
3
4
5
6
7
8
| let nowTimestamp = NSDate().timeIntervalSince1970
let query = Query(query: "query")
query.filters = "date_timestamp >= \(nowTimestamp)"
index.search(query, completionHandler: { (res, error) in
print(res)
})
|
1
2
| long nowTimeStamp = OffsetDateTime.now(ZoneOffset.UTC).toEpochSecond();
index.search(new Query().setFilters("date_timestamp >= " + nowTimeStamp), null);
|
1
2
3
4
5
| long nowTimeStamp = new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds();
index.Search(new Query<Book>("query") {
Filters = $"date_timestamp > {nowTimeStamp}"
});
|
1
2
| long nowTimeStamp = OffsetDateTime.now(ZoneOffset.UTC).toEpochSecond();
index.search(new Query().setFilters("date_timestamp >= " + nowTimeStamp));
|
1
2
3
4
5
6
7
| nowTimestamp := time.Now().Unix()
filter := fmt.Sprintf("date_timestamp > %d", nowTimestamp)
res, err := index.Search(
"query",
opt.Filters(filter),
)
|
1
2
3
4
5
6
7
8
| val nowTimestamp = LocalDateTime.now().toInstant(ZoneOffset.UTC).getEpochSecond
client.execute {
search into "myIndex" query Query(
query = Some("query"),
filters = Some("date_timestamp > " + nowTimestamp),
)
}
|
To learn more on how to sort an index by date, see Sort an Index by Date.