Hardware FixRecommendedDevice not working? Your driver may be the problemCheck updates for common hardware issues.Fix DriversOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsPC HealthRecommendedCrashes, freezes, slowdowns? Check your PC nowSpot repairable issues before they interrupt work.Check PC×
Skip to content
World desk6 min

Filtering by Numbers and Dates in Whoosh: Range Queries Done Right

Filter numbers and dates in Whoosh by indexing NUMERIC or DATETIME fields and querying them with NumericRange or DateRange. Learn the inclusive defaults, UTC handling, and open-ended range workaround.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

To filter by a number or a date in Whoosh, index the value in a typed field (NUMERIC or DATETIME) and query it with the matching range object (NumericRange or DateRange). Both range objects include their endpoints by default. The guidance below comes from the official Whoosh 2.7.4 documentation. That version is the one the documentation describes, and it has not been checked against current Python releases or current Whoosh maintenance status, so confirm the argument names and behaviour against the version you have installed.

Choose the field type that matches the value

Whoosh stores each field with a type, and the type decides how values are sorted and searched. For numbers and dates, the two types you need are:

As an Amazon Associate I earn from qualifying purchases.

  • NUMERIC for integers and floating-point values. The field converts each value into sortable bytes, which is what makes range searches possible.
  • DATETIME for Python datetime.datetime objects. The writing documentation shows datetime fields accepting datetime objects, not date strings.

A minimal schema looks like this:

from datetime import datetime
from whoosh.fields import Schema, TEXT, NUMERIC, DATETIME
from whoosh.filedb.filestore import RamStorage

schema = Schema(
    title=TEXT(stored=True),
    price=NUMERIC(stored=True),
    published=DATETIME(stored=True),
)
ix = RamStorage().create_index(schema)

writer = ix.writer()
writer.add_document(title="Bridge guide", price=24.5,
                    published=datetime(2007, 3, 14, 9, 30))
writer.commit()

If a value is stored as a string in a TEXT or ID field, it is compared as text. Numeric ordering such as 9 before 10 will not hold there, so do not use text fields for numeric or chronological filters.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Numeric ranges with NumericRange

The direct numeric API is whoosh.query.NumericRange(fieldname, start, end, startexcl=False, endexcl=False, boost=1.0, constantscore=True). Its endpoints must be numbers, not strings. By default, values equal to both boundaries match. Set startexcl=True or endexcl=True to drop the matching endpoint.

from whoosh.query import NumericRange

# prices from 10 up to and including 50
q = NumericRange("price", 10, 50)

# prices strictly above 10 and up to 50
q = NumericRange("price", 10, 50, startexcl=True)

# prices from 10 up to but not including 50
q = NumericRange("price", 10, 50, endexcl=True)

The NUMERIC constructor also takes bits, signed, decimal_places and shift_step. The documentation says a lower shift step stores more index data in exchange for faster searches, and that a shift step of zero disables tiered indexing. Tiered indexing stores high-resolution terms at the range edges and lower-resolution terms in the middle, so large ranges can be matched with fewer terms. The documentation describes this as a speed benefit in general terms; it does not publish a benchmark for any workload, so measure your own index before tuning it. Constant-score matching, which is on by default through constantscore=True, is described as useful for typical filter use.

Date ranges with DateRange

DateRange is a thin subclass of NumericRange. It converts datetime endpoints into numbers and otherwise behaves the same way, including the inclusive default and the exclusion flags.

from datetime import datetime
from whoosh.query import DateRange

# published in 2005 through 2010, both boundaries included
start = datetime(2005, 1, 1)
end = datetime(2010, 6, 2, 23, 59, 59)
q = DateRange("published", start, end)

with ix.searcher() as searcher:
    hits = searcher.search(q)
    for hit in hits:
        print(hit["title"], hit["published"])

Keep the two range types separate. Pass numbers to NumericRange and datetime objects to DateRange. A datetime endpoint passed to a numeric field, or a number passed to a datetime field, is the most common cause of ranges that return nothing.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Time zones: index UTC, not local time

The date indexer ignores the tzinfo attribute of a datetime. Attaching a timezone to an object does not make the stored value timezone-aware. The official “About time zones and basetime” section gives the rule directly: “The best way to deal with time zones is to always index datetimes in native UTC form.” Attribute that sentence to the Whoosh documentation rather than to an individual author.

In practice, convert before you index and convert your query endpoints the same way, so both sides use the same clock:

from datetime import datetime, timezone

def to_utc_naive(dt):
    """Convert an aware local datetime to naive UTC for storage."""
    return dt.astimezone(timezone.utc).replace(tzinfo=None)

local = datetime(2024, 5, 1, 9, 0, tzinfo=timezone.utc).astimezone()
stored = to_utc_naive(local)

The helper above returns a naive datetime holding UTC wall-clock time. This avoids any dependence on the indexer’s handling of tzinfo. Use the same helper for the start and end values of every DateRange query.

Open-ended date ranges

The Whoosh 2.7.4 date guide states that DATETIME fields do not currently support open-ended ranges. Its suggested workaround is to use an endpoint far in the past or future. Choose that bound from the valid data domain of your application, so it sits safely outside any real value:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
from datetime import datetime
from whoosh.query import DateRange

# "published on or after 2015-01-01", with a far-future upper bound
q = DateRange("published", datetime(2015, 1, 1), datetime(9999, 12, 31))

The guide presents this as a workaround for that version. Later releases may handle open ends differently, which is one more reason to check the installed version.

Range syntax in the query language

The default query parser has its own range syntax. Square brackets include the endpoints, and curly braces exclude them. You can mix delimiters so that one endpoint is inclusive and the other exclusive:

  • [apple TO bear] matches terms from apple to bear, including both.
  • {apple TO bear} excludes both endpoints.
  • [apple TO bear} includes the start and excludes the end.

The guide gives date-shaped examples such as date:[20050101 TO 20090715] and date:[20050101 to 20100602]. These work because the dates are stored in a lexically sorted form, such as YYYYMMDD, where text order matches chronological order. This is term-range behaviour, not the typed DateRange object. Do not assume a parser range on a NUMERIC or DATETIME field behaves identically to the typed API. If you expose parser syntax to users on typed fields, test the exact strings you accept against your index before relying on them.

Parser-side single-date forms are also documented. date:2005 and date:20050624 are examples of this lexical style.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Comparison operators with GtLtPlugin

The optional GtLtPlugin lets users write comparisons such as field:>apple or date:>='31 march 2001', which the parser translates into ranges. It is a plugin, not a default. Add it explicitly to the parser you use, for example with parser.add_plugin(GtLtPlugin()), and check the output of a few sample queries.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Human-readable dates with DateParserPlugin

If users type dates in natural language, the DateParserPlugin can convert that text into a date query. Three limits matter before you adopt it:

  • The Whoosh 2.7.4 documentation labels it experimental.
  • It supports English dates.
  • Relative expressions, such as “last week”, depend on a base datetime. Pass a fixed base when you need repeatable results, or the same input will match different ranges on different days.

The free=True setting allows unquoted date text after a field prefix. That makes input more forgiving, and it also makes the parser more likely to accept text you did not intend as a date. Enable it only after testing with realistic input.

Choosing an approach

Choice Use it when Main considerations
NumericRange The field is NUMERIC and your code builds the query Endpoints must be numbers. Both ends are inclusive unless startexcl or endexcl is set (Whoosh 2.7.4 query API).
DateRange The field is DATETIME and your code builds the query Pass datetime objects. Store and query in UTC. Open-ended ranges need a far-past or far-future bound (Whoosh 2.7.4 date guide).
Query parser range syntax Users type query strings and your schema supports the form Brackets are inclusive and braces exclusive. Ranges compare lexical term order, so verify numeric and date behaviour on your own index.
GtLtPlugin Users write comparisons such as >= Optional plugin that must be added to the parser. Translates comparisons into range queries.
DateParserPlugin Users enter natural-language dates Experimental in the 2.7.4 documentation, English only, and relative terms depend on a base datetime.

Common pitfalls

  • Strings where numbers belong. A numeric filter on a text field sorts lexically, so 10 falls between 1 and 2.
  • Local time in the index. Mixed time zones make date boundaries shift for some records. Normalize to UTC on both write and query.
  • Assuming exclusive ends by default. A range ending at 50 includes 50 unless you set endexcl=True or use a closing brace in query syntax.
  • Trusting an open end. A DATETIME range with no real upper or lower limit needs an explicit sentinel bound, not an empty value.
  • Using old version examples unchanged. Confirm argument names and defaults in the documentation for your installed release before copying code.

Verify before you ship

  1. Run pip show whoosh and confirm the installed version and the Python interpreter you are using.
  2. Index three test records: one at each boundary and one just outside each boundary. Query each range and confirm which records match.
  3. Repeat the boundary test with a UTC value and a local-time value to confirm your normalization.

The official Whoosh documentation is the reference for every behaviour described above. Check it against your installed version whenever you upgrade.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

“

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
Crashes, No Sound, or Screen Glitches?Free driver scan
Windows Errors? Fix Them Before They SpreadFree repair scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.