Recursive AI

Recognizers

All onenlp docs

Behaviour of the recognizers for numbers, ordinals, percentages, measures, money, address-like sequences, and dates and times. Language-neutral where it can be; the English rules are the first data set. Rules are data: adding a language means writing rule files (see "Rule data"), not code.

Results

Recognizer.Recognize(text) returns the results in text order without overlaps. Each result has UTF-16 offsets Start and End into the string passed in, its exact Text, a Label, and a Confidence from 0 to 1.

TypeLabelMeaning
Quantity, Kind = CardinalCARDINALA plain number: 42, 3.5 million, two hundred and five, a dozen, 3/4.
Quantity, Kind = OrdinalORDINAL1st, twenty-third. Value is the position.
Quantity, Kind = PercentPERCENT5%, fifty percent. Value is in percent: 5, not 0.05. Unit and Dimension are percent.
Quantity, Kind = MoneyMONEY$5m, five bucks, USD 20, 50 cents. Currency is the ISO 4217 code. Value is in that currency: 50 cents is 0.5.
Quantity, Kind = MeasureQUANTITYA number with a unit: 5 km, 20°C, 3-year-old. Unit is the canonical unit name, Dimension what it measures: length, mass, volume, area, speed, information, temperature, age.
SequenceMatch, EmailEMAIL
SequenceMatch, UrlURL
SequenceMatch, IpAddressIP_ADDRESSIPv4 or IPv6.
SequenceMatch, PhoneLikePHONE_LIKEPhone-like sequences, unvalidated; use a dedicated contact-parsing library for validated, canonical phones.
SequenceMatch, HashtagHASHTAG
SequenceMatch, MentionMENTION
SequenceMatch, UuidGUID
TemporalMatch, Kind = Date or DateRangeDATEA day or a period of days: March 5, 2026, next Friday, spring 2021.
TemporalMatch, Kind = Time or TimeRangeTIMEA time of day or a range of times of day: 3pm, from 9am to 5pm.
TemporalMatch, Kind = DateTime or DateTimeRangeDATETIMEA day and a time, or a part of a day: tomorrow at 3pm, Saturday evening.
TemporalMatch, Kind = DurationDURATIONAn amount of time: two weeks, 1 year and 6 months.
TemporalMatch, Kind = SetSETA recurrence: every Monday at 9am, daily.

CARDINAL, ORDINAL, PERCENT, MONEY and QUANTITY are the names OntoNotes uses for numbers, and spaCy's English models use them too. A NER model that predicts the UNER types (PER, ORG, LOC) does not use these names, so recognizer entities and model entities coexist in one Doc.

Value is a decimal. Money must be exact (19.99 is 19.99), and decimal holds 28 digits. Numbers beyond ±7.9e28 do not fit and are not reported (one octillion, 1e27, still fits). Values are normalized so that 2.50 and 2.5 are the same value. A double would have given a wider range and lost the cents.

Values are not converted between units: 5 km is 5 with unit kilometer.

Component

RecognizerComponent (registered as recognizers) writes the results into Doc.Ents. Entities cover whole tokens; a result that starts or ends inside a token (5km as one token) is widened to the tokens it touches. When two results touch the same token the earlier one stays. Existing entities (from a model) stay and win over overlapping results unless overwrite_ents is set, in which case the recognizer results win and the entities they overlap are removed.

Overlaps

Every rule proposes results independently. Overlaps are settled the same way every time:

  1. the longest result wins;
  2. of equal length, the type earlier in this order: URL, EMAIL, MENTION, HASHTAG, GUID, IP_ADDRESS, DATE/TIME/DATETIME/DURATION/SET, PHONE_LIKE, MONEY, PERCENT, QUANTITY, ORDINAL, CARDINAL;
  3. then the earlier start, then the higher confidence.

The date and time labels rank after IP_ADDRESS and before PHONE_LIKE, so 2031 is a year, not a cardinal, and 30 minutes is a duration, not a measure.

A result overlapping one already taken is dropped. So $5 million (money) beats 5 million (a number), and a@b.com beats the b and com inside it.

Numbers

Digits

  • Integer and decimal numbers with . as the decimal separator and , as the group separator (set in the rule data). A group is exactly three digits after a first run of one to three digits: 1,234,567.89 is one number; 1,2345 is two; 12,34 is two.
  • Indian digit grouping is read too: a first run of one or two digits, then groups of two digits, then a last group of three (1,00,000, 12,34,567, 1,23,45,678). 1,23,45 has no three-digit group at the end, so it is three numbers.
  • Scientific notation: a mantissa followed by e or E and a signed exponent of up to three digits (6.02e23, 2E-3), or by a times sign (×, x, X, *) and 10 with the exponent after ^ or ** (6.02 × 10^23, 1.5×10^3) or in superscript digits (2.5×10³). The exponent must not run into a letter or digit (7e5x is no number). A value that does not fit a decimal, or underflows to zero (9.1×10⁻³¹), is not reported. 3 x 10 apples is two numbers: a times sign counts only with an exponent.
  • A leading decimal point is a number: .5 is 0.5.
  • A dotted run of more than one decimal point (1.2.3, 25.12.2020) is not a number: it is a version or a date.
  • A sign: -5 is negative when the hyphen follows white space, an opening bracket, a quote, :, ;, =, <, >, ~, , or a currency sign, or starts the text. After a digit it is a range (3-5 is 3 and 5). After a letter it is part of a name (COVID-19, B-52: no number). − (U+2212) is treated like -. minus and negative before a number also negate it.
  • A number touching letters is not a number unless the letters are an ordinal ending, a multiplier suffix, or the start of a unit: x86, H2O, 5abc give no number.
  • A proper fraction in digits: 1/2, 3/4 (numerator below denominator, denominator up to 1000). 3/4/2020 is three numbers. 1/2 is also a date to some readers; a date recognizer takes precedence by being longer or earlier in the type order.
  • Multipliers touching digits: 3.5k is 3500. Others are only multipliers next to a currency ($5m, 5m dollars), because 5m is also five metres.
  • A scale word after digits multiplies them: 3.5 million, 2 dozen, 5 hundred.

Words

  • Units, teens, tens, hundred, and the scale words of the short scale (thousand to octillion). A group of hundreds is built by addition inside the group; scale words close a group and must descend (one million two thousand is fine, one thousand million is not read as one number).
  • Words may be joined by white space (at most two white-space characters, none a line break) or a hyphen touching both words. and joins hundred or a scale word to what follows: one hundred and five, two thousand and ten.
  • a/an is one before hundred, a scale word, dozen and a fraction word (a hundred, a dozen, a half). It is not a number elsewhere.
  • twelve hundred is 1200 (a group below 100 times a hundred).
  • zero stands alone.
  • Fractions: three quarters (0.75), a half, two thirds; a fraction word needs a numerator, so half alone is not a number. Mixed numbers: two and three quarters, one and a half.
  • Decimals: three point one four is 3.14: every word after point is one digit. A scale word may follow a decimal or a mixed number: two and a half million.
  • The longest well-formed prefix wins: one two is two numbers; one hundred hundred is 100 followed by an unread hundred.
  • A lone word that is also a pronoun or an idiom is not a number when a neighbour says so (number.not-number): no one, the one, which one, one of them, one another, one's own, and a word ordinal before time (for the first time, for the second time). one apple and I have one stay numbers; one hundred and one is untouched, since only a word standing alone is judged.

Numerals repeated in brackets

Drafted text repeats an amount: sixty (60) days, five thousand dollars ($5,000), fifty percent (50%), ten (10) business days.

  • A number written in words followed (one white-space character at most) by a bracketed number written in digits with the same value is one number: sixty (60) is a CARDINAL of 60, and it takes the unit that follows it (sixty (60) days is a duration of 60 days).
  • When an amount with a unit, a currency or a percent sign is followed by a bracketed result of the same kind and unit or currency (or by a plain number), and the values agree, the result grows to include the brackets (five thousand dollars ($5,000)).
  • When the values disagree (sixty (90) days, five thousand dollars ($4,000)), the words' value stands, its confidence is multiplied by 0.6, and nothing is merged: the bracketed number is reported by itself.
  • Only a number written in words starts this; 60 (sixty) days is not merged.

Ordinals

  • Digits with an ending that fits the number: 1st, 2nd, 3rd, 4th, 11th, 12th, 13th, 21st, 22nd, and grouped integers (1,000th). A wrong ending (21th) is not an ordinal and not a number either.
  • Words: first to nineteenth, twentieth to ninetieth, hundredth, thousandth, ..., and compounds twenty-third, one hundred and first.
  • second is also a unit of time, so on its own right after a number it is not an ordinal (one second is the number one); after tens it is (twenty second is 22nd).

Percent

A number followed (at most one white-space character between) by % or a percent word: 5%, 5 %, fifty percent, 12.5 per cent.

Measures

A number, then (touching it, after one white-space character, or after one hyphen touching both) a unit from the data: 5km, 5 km, 5-foot, two miles per hour, 100 km/h, 20°C, 68 degrees Fahrenheit. The longest unit form wins. A unit must not be followed by a letter or digit touching it (5 kg2 is no unit).

Unit forms are matched without regard to case unless the data marks them case-sensitive (MB is megabytes, Mb megabits, m metres). Short forms that are everyday words (in, a, at, min) are not units.

Compound measures. Two measures of related units written one after the other are one: 5 feet 3 inches, 5 feet and 3 inches, 6 pounds, 4 ounces, 3 miles 200 yards. The pairs and their ratios are data (unit.compound: foot | inch | 12). The result is in the smaller unit (5 feet 3 inches is 63 inches), the first number must be a whole number and the second below the ratio, so 5 feet 15 inches stays two measures.

Age is a number and a time unit followed by old: 5 years old, a 3-year-old boy (Unit year, Dimension age). A bare duration (5 years) is not recognized here.

Money

  • A currency sign or code before the number, touching it or after one white-space character: $5, $ 5, USD 5, €20, US$5; a leading minus before the sign: -$5; a minus after it: $-5.
  • A sign, code or name after the number: 20€, 100 USD, 5 dollars, five bucks, 50 cents, 10 zł.
  • Currency names may have several words (new zealand dollars). A name may carry a factor: cents is 0.01 of its currency.
  • Codes are the alphabetic codes of ISO 4217 in capitals. Lower-case usd is not read because many codes are English words (ALL, TRY, TOP).
  • Ambiguous signs get the common reading and a confidence below 1: $ is USD (0.9), ¥ is JPY (0.7), dollars is USD (0.9). Signs that pin down a currency (US$, A$, CA$, €, £) have confidence 1.
  • pounds is a unit of mass; pound sterling and pounds sterling are currency. A name that is both (currency.context) after a number is money, at confidence 0.8, when a money word (fine, cost, paid, price, ...) stands in the clause within five words before the number or four after the name and no weight word (weighs, lost, of, ...) does; otherwise it is the unit. The window stops at the clause end and at another number. So The fine was 1,000 pounds is GBP, He weighs 180 pounds and 10 pounds of flour are mass.
  • The alphabetic codes were checked on 2026-11-05 against List One of ISO 4217 as published by SIX, the maintenance agency (dated 2026-09-17, 178 codes). All are present except XTS (reserved for testing) and XXX (no currency). ANG and BGN, no longer on List One, are kept because older text uses them.
  • A multiplier touching the number counts next to a currency: $5m is 5,000,000; $1.2 billion; 5m dollars.

Sequences

The shape of each sequence is fixed by a standard, so it lives in code; what varies by deployment (URL schemes, domain endings) is rule data.

E-mail

A simplified addr-spec of RFC 5322 §3.4.1: a dot-atom local part of at most 64 characters (RFC 5321 §4.5.3.1.1) (no quoted strings, comments or folding), @, and a domain of dot-separated labels (up to 63 characters each, 253 in all, RFC 1035 §2.3.1), with at least two labels, the last a known domain ending (or an xn-- ending). Non-ASCII letters and digits are accepted in both parts (RFC 6531). The local part starts at a letter, digit or _, and ends before the @ without a dot; two dots in a row end the match. Trailing dots and hyphens after the domain are not part of it. Normalized lower-cases the domain.

URL

RFC 3986 §3 (scheme://[userinfo@]host[:port]/path?query#fragment) with the WHATWG URL Standard's leniency for addresses written without a scheme:

  • With scheme://: the scheme must be one listed in the rule data (so note://abc in prose is left alone). The host is a registered name, an IPv4 address, or a bracketed IPv6 address; the port is 1 to 5 digits; the authority may be empty (file:///tmp/x).
  • Starting with www.: the host needs at least three labels and a known domain ending.
  • Bare (example.com/page): the last host label must be in the shorter bare list of the rule data (com, org, uk, ...; not md, py, sh, it, which are also file extensions and words), so that README.md, node.js and e.g. are not URLs.
  • Path, query and fragment run over RFC 3986 characters and non-ASCII letters, digits and symbols, up to 2048 characters in all.
  • Trailing ! ' * , . : ; ? are dropped (RFC 3986 Appendix C), and a closing ) or ] is dropped unless its opening bracket is inside the match.
  • An address is not resolved or checked to exist.

IP address

  • IPv4 (RFC 791 dotted decimal): four decimal parts 0–255 without leading zeros (010.1.1.1 and 256.1.1.1 are not addresses), not part of a longer dotted run (1.2.3.4.5).
  • IPv6 (RFC 4291 §2.2): eight 16-bit hex groups, one ::, an IPv4 tail allowed. Normalized is the RFC 5952 §4 form (lower case, no leading zeros, the longest run of at least two zero groups compressed, the first on a tie). Trailing sentence punctuation is dropped. :: alone, text where a hex word touches letters (std::vector) and runs with too many groups are not addresses. Zone identifiers (%eth0) are not read.

Phone-like

Syntax only, nothing verified, confidence 0.7. Normalized is an optional + and the digits. Accepted shapes (digits in all 7 to 15; E.164 caps a number at 15):

  • + then a country code of one to three digits and more groups: +44 20 7183 8750, +1 (555) 123-4567; or + and 8–15 digits together;
  • groups with a parenthesized group: (415) 555-0132 (9 or more digits);
  • groups joined by one hyphen or dot, three to five groups of two to four digits (a leading group of one digit is allowed with four or more groups: 1-800-555-0199): 555-123-4567, 01.23.45.67.89; or exactly ddd-dddd;
  • space-separated groups only when they start with 0 (national prefix): 020 7183 8750.

Not accepted: dates (2020-01-15, 15.01.2020), decimals (3.14159265), plain digit runs, groups of digits separated by spaces without a prefix (4111 1111 1111 1111), anything touching letters.

Hashtag, mention, GUID

  • # and one or more letters, digits or _ (Unicode letters and marks included), not touching a preceding letter, digit, _, &, # or /; all-digit tags (#2024) are numbers, not hashtags; at most 100 characters. Normalized is the tag lower-cased without the #.
  • @ and a user name of letters, digits and _, with . and - inside, not touching a preceding letter, digit, _, @, + or .; an @host.example after the name is part of a fediverse handle. Normalized is the name lower-cased without the @.
  • GUID (RFC 9562 §4): 8-4-4-4-12 hexadecimal digits, optionally in braces or after urn:uuid:. Version and variant bits are not checked. Not touching a letter, digit or hyphen. Normalized is 36 lower-case characters.

Dates and times

Recognizer.Recognize(text, new RecognitionContext(reference) { Region = "us" }) resolves dates and times to values. Nlp.Dates(text, reference) lists them, and Nlp.ParseDate(text, reference) returns the first as a DateRange. The English rules are Data/en/datetime.rules (vocabulary) and Data/en/holidays.rules (holiday table); the grammar that combines the words is code.

Result

TemporalMatch has the Kind (below), a Value string, a Granularity (null for durations), an Approximate flag, the UtcOffset the text names, and a Range (DateRange: Start inclusive, End exclusive, Granularity, Approximate) on the calendar. A day runs from its midnight to the next; an instant such as 3pm runs for its precision (an hour; 3:05pm a minute). A time of day with no date is placed on the reference day; with no reference date its Range is null. Durations and sets have no Range; a fixed-length duration has Amount as a TimeSpan. A duration also has Count and Unit (a DurationUnit): 60 days is 60 Day, 18 months is 18 Month, ten business days is 10 BusinessDay. One term keeps its unit as written, with a quarter as three months, a decade as ten years and a fortnight as two weeks, and 1.5 hours is 1.5 Hour. Several terms are counted in the smallest unit when they are all clock and day units or all month and year units (2 hours and 30 minutes is 150 Minute, 1 year and 6 months is 18 Month); 1 year and 3 days cannot be added, so its Unit and Count are null, as they are for every other kind.

KindExamplesValue
DateMarch 5, 2026, tomorrow, the 4th of July, Easter 2027, in 3 days, a week ago2026-03-05
Time3pm, 7:05 AM, 15:45, noon, 5 in the afternoon, 3.30pm, 9am JSTT15:00, T09:00+09:00; Z for UTC and GMT
DateTimetomorrow at 3pm, 3pm on March 22, in 45 minutes, 2026-06-30T09:15:00Z2026-03-19T15:00
DateRangenext week, last winter, the first quarter of 2026, late 2023, the 1990s, from April 6 to April 10, May 4-8, 1990-2000, the past three days2026-03-23/2026-03-30
TimeRange9am-5pm, from 6 pm until 9 pm, between 12 and 1 pmT09:00/T17:00
DateTimeRangetomorrow morning, Saturday evening, Monday from 9am to 11am, from Monday 8am to Monday 6pm2026-03-23T09:00/2026-03-23T11:00
Durationtwo weeks, 45 seconds, an hour and a half, 2 hours and 30 minutes, sixty (60) days, ten business daysseconds as an integer (1209600); ISO 8601 P1Y6M when it names months or years; P10BD for business days and PT8BH for business hours (not ISO 8601; they have no length in seconds)
Setevery Monday, every Monday at 9am, daily, every 3 hours, every other day, every day at noonTIMEX3: XXXX-WXX-1, XXXX-WXX-1T09:00, P1D, PT3H, P2D, T12:00

Granularity is the unit the expression is stated in: for an instant its precision (Second, Minute, Hour, Day); for a range the kind of period (Week, Month, Quarter, Half, Season, Year, Decade, or Day for explicit day ranges and weekends, Hour or Minute for ranges of times); for a set the recurrence unit. This is the gold-set convention (docs/recognizer-gold.md).

Reference date, zones and region

  • Relative expressions need a reference date (today, next Friday, in 3 days, Christmas with no year, the 25th, March 5 with no year). Without one (RecognitionContext.ReferenceDate null) they are not reported; absolute ones are (March 5, 2027, 3pm, two weeks, every Monday). Wall-clock expressions are read in the reference's offset.
  • Zones are explicit. PST, UTC+2, GMT-05:00 and a trailing Z or +hh:mm after a time are kept in Value and UtcOffset and become the offset of Range. The recognizer carries no time zone database: a named zone is a fixed offset, so there is no daylight-saving arithmetic (PST is always -08:00; PDT is -07:00). A reference date in America/New_York is just its DateTimeOffset. The abbreviation table is time.zone; ambiguous abbreviations (CST, BST, IST) take the reading English text usually means, and IST is left out.
  • Region (RecognitionContext.Region, us default or gb) picks the holiday rows and the order of numeric dates: 04/05/2026 is April 5 in us and 4 May in gb; a first number above 12 and a dotted date are always day first.

How expressions resolve

  • Weekdays. A bare weekday or this weekday is the first such day on or after the reference day (today counts). next Friday is Friday of the next week (weeks run Monday to Sunday). last Friday is the latest one before today. Wednesday of next week is that day in that week. Monday, March 23 is the date; the weekday is not checked.
  • Missing year. A month and day, a holiday or the 25th is the first such day on or after the reference day, so a date that has passed this year is next year's.
  • Ranges are half-open. The end of a range of days or longer periods is the start of the period after the stated end (from April 6 to April 10 ends at 2026-04-11; through 2027 at 2028-01-01). A clock end point is the instant stated (from 3pm to 5pm is T15:00/T17:00). A second weekday or month-day counts from the first (from Monday to Friday is the week of the Monday it names; December 28 to January 3 crosses the year).
  • Times of day. 3pm and 3:30 pm have am/pm; 15:45 and 09:00 are 24-hour; H:MM with no am/pm is read as a clock time; a bare hour (at 9) is not a time, because it has no am/pm. In a range the first end takes the second's am/pm (between 12 and 1 pm, from 9 to 5 pm), moving to am when that would put it after the end (between 11 and 1 pm). 5 in the afternoon and 10 at night use the day part. noon and midnight are 12:00 and 00:00.
  • Parts of a day are approximate ranges: morning 06:00-12:00, afternoon 12:00-18:00, evening 18:00-24:00, night 20:00-24:00. tonight is the night of today, last night the night of yesterday, this morning the morning of today.
  • Periods. Weeks run Monday to Sunday; this weekend is Saturday and Sunday of the current week and next weekend those of the next week. Seasons are meteorological, northern hemisphere (spring March-May, summer June-August, fall September-November, winter December-February), approximate, and winter 2024 starts in December 2024. early, mid and late split a year into January-April, May-August and September-December and a month into days 1-10, 11-20 and 21-end, approximate. H1/Q3 and the second half of ... give halves and quarters; with no year they are in the reference year. last winter is the latest season that has ended; this summer the one that contains today or comes next.
  • Offsets. in 3 days, 3 days ago, a year from now, the day after tomorrow, two days before Christmas, the third Thursday of April, the first Monday after Easter 2027. Units of a day or more give a date; hours, minutes and seconds give a date-time at that precision.
  • Durations are an amount and a unit (two weeks, a day, half an hour, 1.5 hours, an hour and a half); several terms join with and or a comma when each is smaller than the one before (2 hours and 30 minutes; seven days a week is not one duration). a second is a duration of one second only when what follows cannot be the thing that is second: the end of the text, punctuation, or a word listed as secondfollow (wait a second, please, a second ago, give me a second to think); otherwise it is an ordinal (a second opinion). the second floor, second place and the second half of 2026 are ordinals and a half-year, as before.
  • Business and calendar units. business day(s), working day(s), work day(s), workday(s) and business hour(s) are units with no length in seconds: a duration of business days stands alone (it does not add to other terms) and its Value is P10BD or PT8BH. calendar day(s), week(s), month(s) and year(s) are the plain units. in 3 business days and 5 business days ago are dates, counting Monday to Friday and skipping Saturdays and Sundays; public holidays are not considered. Counting from a weekend day starts from the weekday on the other side of the weekend (Saturday plus one business day is Monday).
  • Sets are daily, weekly, monthly, quarterly, yearly, every/each with a weekday or an optional count and a unit, and every other. With a time (every Monday at 9am, daily at noon) the value carries it.

Holidays

The table is Data/en/holidays.rules: forms | rule | regions (us, gb, all). Rules are fixed MM-DD, nth N|last WEEKDAY MONTH [+-DAYS], easter +-DAYS and nearest WEEKDAY MM-DD. Easter is the Western (Gregorian) date from the "Anonymous Gregorian" computus (Meeus, Astronomical Algorithms ch. 8, after Butcher 1876), valid for every Gregorian year; the other moveable feasts count from it (Good Friday is -2, Ash Wednesday -46, Pentecost +49). The dates are the calendar days of the observances, not the weekday a holiday is observed in lieu of a weekend.

RegionHolidays
usthe United States federal holidays of 5 U.S.C. 6103 (New Year's Day, Martin Luther King Jr. Day, Washington's Birthday/Presidents' Day, Memorial Day, Juneteenth, Independence Day, Labor Day, Columbus Day, Veterans Day, Thanksgiving, Christmas), Election Day, Tax Day, Flag Day, Groundhog Day, Mother's Day (second Sunday of May)
gbthe bank holidays of England and Wales as published at gov.uk/bank-holidays (New Year's Day, Good Friday, Easter Monday, Early May, Spring and Summer bank holidays, Christmas Day, Boxing Day), Mothering Sunday (three weeks before Easter, also "Mother's Day"), Remembrance Sunday, Bonfire Night, St David's, St George's and St Andrew's days, Burns Night, Whit Monday
allValentine's Day, St Patrick's Day, April Fools' Day, Halloween, Thanksgiving, Black Friday, Cyber Monday, Christmas Eve and Day, Boxing Day, New Year's Eve, Easter and its feasts, Father's Day, Pi Day, May Day, Cinco de Mayo, Earth Day, Epiphany, Armistice Day, Leap Day

Holidays that follow a lunar calendar (Eid, Diwali, Passover, Hanukkah, Ramadan, Lunar New Year) are not in the table: they need a calendar table per year, which is data a later set of rules can add.

Rule data for dates and times

SectionFieldsMeaning
time.month1-12 | formsMonth names and abbreviations. Forms starting = are case-sensitive, which keeps May and March from matching the verbs
time.weekday1-7 | forms [| plural forms]Monday is 1; plurals make a weekly set
time.unitname | forms | seconds | monthsday | day, days | 86400 | 0
time.seasonname | forms | first month | months
time.daypartname | forms | start hour | end hourEnd 24 is midnight
time.meridiemam or pm | formsa.m., p.m. included
time.wordrole | formsThe words the grammar uses: today, next, ago, from, rangeto (to, until, -), early, approx, zonebase, ... The roles are listed in datetime.rules
time.recurrenceTIMEX3 period | unit | formsP1W | week | weekly
time.zoneforms | offset minutes [| z]z writes the zone as Z
time.holidayforms | rule | regionsSee above

A malformed temporal row throws RecognizerRulesException with the line, like the others.

Limits

  • English only. Day-first order for slashes is by region and by the numbers (a first number above 12), not by learned usage.
  • No time zone database, no daylight saving, no calendars other than Gregorian, and no lunar holidays.
  • No fuzzy or vague quantities (a couple of days, a few weeks), no end of the month, the weekend of March 21, Friday the 13th, every second Tuesday, twice a week, or fiscal periods.
  • A bare weekday is the next one, even in past-tense text (said on Tuesday resolves forward); a bare month or season (in March, in summer) and M/D without a year are not reported, because they need context to mean one thing.
  • A bare four-digit number from 1000 to 2999 is read as a year (2031, 1969) at confidence 0.8, so a count such as 2000 people is also reported as a year; a measure or money amount of the same length wins when the number has a unit.
  • in 45 minutes is a time ahead, also in text such as ran 5 miles in 45 minutes.

Rule data

Rule files are UTF-8 text. # starts a comment. [section] starts a section; each other line is a rule whose fields are separated by |. A section may appear in several files; a language is a set of files (Recognizers/Data/<code>/*.rules embedded in the assembly, or files given to Recognizer.FromFiles).

In a list of forms, forms are separated by commas, and may have several words. A form starting with = is matched case-sensitively; others are matched without regard to case. Forms are cut into words, digit runs and single symbols by the same lexer as the text, so km/h in the data is three items touching each other, and matches km/h but not km / h.

SectionFieldsMeaning
number.digitskey | valuedecimal, group (one character each), negative (characters read as a minus)
number.cardinalword | kind | valuekind is unit (0–9), teen (10–19), tens, scale (100 and up; 100 is "hundred") or multiplier ("dozen")
number.ordinalword | value [| not-after-number]An ordinal word; the flag keeps second out right after a number
number.fractionword | denominatorhalf, thirds, ...
number.article, number.connector, number.negative, number.pointworda/an; and; minus; point
number.suffixform | multiplier | any or moneyk, m, bn touching digits
number.ordinal-suffixending | last digits | any, teen or not-teenst | 1 | not-teen: 1st, not 11st
percentforms%, percent, per cent
unitcanonical | dimension | forms [| confidence]
unit.compoundlarger | smaller | ratiofoot | inch | 12: 5 feet 3 inches is one length
number.not-numberword | before or after | wordsWords that, next to the lone word, show it is no number: one | before | no, the, which
currency.contextforms | ISO code | value of one | money words | weight wordspounds is money or mass by its neighbours
age.unit, age.suffixcanonical | forms; formsCombined as 5 years old and 5-year-old
currency.symbolform | ISO code | prefix, suffix or both [| confidence]
currency.nameforms | ISO code [| factor [| confidence]]
currency.codecodesAlphabetic ISO 4217 codes, in capitals, separated by commas or spaces
url.scheme, url.tld, url.tld.bareitemsSchemes read with ://; all domain endings; the endings allowed for bare addresses

Limits, checked while loading: 8 MB per file, 200,000 lines, 8192 characters per line, 8 fields per rule, 16 words or symbols per form. A malformed file throws RecognizerRulesException naming the line: invalid UTF-8, a rule before any section, an unknown section, a wrong field count, a number that does not parse, an unknown kind or position.

Words and symbols (not numbers or dates) are what the rules recognize; the sequence syntaxes and the arithmetic of the number grammar are code. A language whose numbers are not built from additive and multiplicative words in the short scale (French quatre-vingts, German einundzwanzig) needs engine support in addition to data; that is a known limit, and a language of the same shape as English needs data only.

Limits and decisions

  • Compound measures cover pairs in the data only (two terms); 1 yard 2 feet 3 inches gives the first pair and a separate 3 inches.
  • A number whose exponent leaves the range of a decimal (9.1×10⁻³¹) is not reported.
  • Separators are per language: English takes . as the decimal point and , as the group separator; French and German read 18.000 EUR as eighteen thousand and 3,5 as three and a half.
  • Business-day arithmetic ignores public holidays.
  • Number words glued to digits (5 hundred thousand) read the first scale word only.
  • Phone-like detection will miss shapes outside the list above and will accept some number groups that are not phones: it is a guess, with confidence 0.7.
  • The engine takes a RecognitionContext with a reference date and region; the number, unit, currency and sequence rules ignore it, the date and time rules use it.

French and German

French (fr-FR) and German (de-DE) are data sets on the same engine: Data/<lang>/{numbers,units,currency,datetime,holidays}.rules, with the language-neutral sequence rules in Data/shared/. Use Recognizer.For(Language.French), or Nlp.Quantities, Numbers, Sequences, Dates and ParseDate with a Language argument. English results are unchanged.

Numbers

  • number.digits also takes system (english, french, german) and ordinal-dot (yes makes 3. an ordinal). group takes several separators by name or character: space nbsp nnbsp . for French (space, no-break space, narrow no-break space, dot), . for German. A group separator counts only between groups of exactly three digits, so 3.5 stays a decimal in English and 2 400 is 2400 in French.
  • French counting is the french system: quatre-vingt-dix-sept (four twenties and seventeen), soixante et onze, vingt et un, septante, huitante, octante and nonante as plain tens, hundreds with a plural cents, mille without a count. Cardinals of kind bare-scale (cent, mille, hundert, tausend) may stand for one of themselves.
  • German counting is the german system: a whole number below a million is one word. The word is cut into the parts listed in number.cardinal (units, teens, tens, hundert, tausend) and the connector und, in the order units before tens (dreihundertvierundzwanzig). Ordinals are stems (number.ordinal rows whose word ends in ~, as dritt~) plus the endings of number.ordinal-ending; einundzwanzigste is 21. number.ordinal-dot-word lists capitalized nouns that make the dot after a number an ordinal (5. Stock); a month name or a lower-case word does it already.
  • number.lone-skip lists words that count only inside a longer number (un, ein), so the article is not a number but vingt et un is.
  • number.ordinal-suffix rows serve French endings (1er, 1re, 2e, 2nd).
  • age.prefix (French âgé de) puts words before the number into the span of an age; age.word (German jährig, 4-jähriges) is an age unit that needs no alt.

Dates and times

Dates are day first wherever time.region is not us. The extra word roles of time.word:

RoleMeaning
determineran article that belongs to the expression when it starts it: le 3 mars, la semaine prochaine
agoprefixthe word before an amount that means "ago": il y a, vor
dayaftertomorrow, daybeforeyesterdayaprès-demain, übermorgen, avant-hier, vorgestern
nextpost, lastpostnext and last after the noun: samedi prochain, mardi dernier
hourmark, hourwordthe mark of the 24-hour clock: 15h30, 15 Uhr (strong), and heures (weak: also a length of time, a time only after an at-word or clockcue, with minutes or a part of the day, or with a clock tail)
clockcuea word before an hour that makes it a time: il est huit heures
plus15, plus30, minus15, minusthe tail of an hour: et quart, et demie, moins le quart, moins dix
halfbefore, plusquarter, minusquarterwords before an hour: halb drei (2:30), Viertel nach drei, Viertel vor drei
andhalfet demi after a unit: trois jours et demi
daypartprefixwords between an hour and a part of the day: huit heures du soir
decadeprefix, decadesuffix, decadewordles années 1990, 1990er Jahre

half also covers demi-heure and eine halbe Stunde as 30 minutes. time.flag switches on behaviours: daypart-direct (German acht Uhr abends), no-lone-day (la 2e place is an ordinal, not the next 2nd), bare-hour-24 (von 16 bis 18:30 Uhr), determiner-inside-only (German: a determiner stays out of the span except in Dienstag, den 24. März). time.region names the default region (fr, de).

Holidays: fr is the French public holidays of the Code du travail L3133-1 (1 January, Easter Monday, 1 May, 8 May, Ascension, Whit Monday, 14 July, 15 August, 1 November, 11 November, 25 December) with Easter itself and the customary feasts; de is the nationwide German holidays (New Year, Good Friday, Easter Monday, 1 May, Ascension, Whit Monday, 3 October, Christmas Day and Boxing Day) with the customary and regional feasts (Rosenmontag, Fronleichnam, Allerheiligen, Reformationstag) listed without regional limits.

Limits (French and German)

  • Written-out numbers glued to an age word (sechsjährig) and zweieinhalb, anderthalb, une quinzaine are not read.
  • midi, Mittag, minuit and Mitternacht are not times on their own; the gold sets do not label them.
  • ce mois-ci is not read.
  • Belgian and Swiss counting (septante, nonante, huitante) is read; Swiss Franken are francs.
  • The French and German unit tables are shorter than the English one.

Spanish and Portuguese

Spanish (es, Spain by default) and Portuguese (pt, Brazil by default) are data sets on the same engine: Data/<lang>/{numbers,units,currency,datetime}.rules, the holidays inside datetime.rules, and the shared sequence rules. Use Recognizer.For(Language.Spanish) or the Nlp recognizer verbs with a Language argument. English, French and German results are unchanged.

Numbers

  • number.digits takes system | iberian and per-region separators: decimal.mx | . and group.mx | space nbsp nnbsp , replace the defaults (decimal | ,, group | space nbsp nnbsp .) when RecognitionContext.Region is mx, so 1,234.56 is read in Mexican Spanish and 1.234,56 in Spain's.
  • Iberian counting. Tens above twenty join a unit with the connector (treinta y cinco, trinta e cinco; Spanish veintiuno to veintinueve are teens of their own). Hundreds are words of kind hundreds (doscientos, quinhentos, cien, ciento, cento) that open a group; mil is a bare-scale. In Portuguese the connector also links a hundred to what follows (cento e vinte) and a thousand to its rest (dois mil e quinhentos). mil millones as one scale word is not read; write 7.900 millones.
  • Ordinals. An ordinal tens word may be followed by an ordinal unit (vigésimo tercero, décimo quinto, décima segunda); one-word forms (decimoquinto) are rows. number.ordinal-suffix rows may start with a dot: .º, .ª, .er read 1.º, 3.ª, 1.er. segundo after por (por segundo) and before an object pronoun (Portuguese segundo ele) is not an ordinal, and primera hora is not one (number.not-number).
  • age.suffix makes 82 años de edad / 82 anos de idade an age; 82 años alone is a length of time.
  • phone.space-start | digits | min | max lets a phone-like number be written as space-separated groups when it starts with one of the digits and has that many in all (913 45 67 89 in Spain: 6789 | 9 | 9). Without the section only a leading 0 and 9 to 11 digits count.

Dates and times

Dates are day first. New data for time.word and time.flag:

ItemMeaning
time.flag of-before-yearthe of word may stand between a month and its year or a holiday and its year: 12 de septiembre de 2026, abril de 1987, Navidad de 2026; and between the last day and the month of a day range: del 12 al 20 de agosto
hourarticlethe article or fused preposition and article that opens a clock time and is part of it: las 15:00, la una, às 14:30. With it (or a determiner) a spoken or digit hour is a time when a part of the day or a tail follows (las diez de la mañana, las tres y media, às oito da noite), or when it is above twelve (a las 15)
decadeprefixalso reads two digits: los años 80 is 1980-1990, anos 20 is 2020-2030 (30 to 90 are the 1900s, 00 to 20 the 2000s)
daypartprefixalso joins a date and a part of the day when it is one word: amanhã à noite

noche and noite are 18:00 to 24:00, tarde 12:00 to 18:00, mañana and manhã 06:00 to 12:00 (mañana alone is tomorrow). Regions for time.region and RecognitionContext.Region: es (default) and mx for Spanish, br (default) and pt for Portuguese; the region also picks the holiday rows. Holiday sources are statute facts: Spain's national holidays (Estatuto de los Trabajadores art. 37.2 and the yearly BOE list), Mexico's mandatory rest days (Ley Federal del Trabajo art. 74; the Monday holidays are nth rules), Brazil's (Leis 662/1949, 10.607/2002, 14.759/2023) and Portugal's (Código do Trabalho art. 234), with the Easter-based feasts (Carnaval -48, Corpus Christi +60) from the computus.

Limits (Spanish and Portuguese)

  • Weekdays in Portuguese are the full segunda-feira forms and the dotted abbreviations; a bare quarta or sexta is not a weekday.
  • mil millones, una docena y media and compound measures past two terms are not read.
  • The seasons are the northern-hemisphere meteorological ones, as for English.

Italian and Dutch

Italian (it) and Dutch (nl) are data sets on the same engine: Data/<lang>/{numbers,units,currency,datetime}.rules, the holidays inside datetime.rules, and the shared sequence rules. Use Recognizer.For(Language.Italian) or the Nlp recognizer verbs with a Language argument. The other languages' results are unchanged.

Numbers

  • number.digits | system takes italian and dutch besides the earlier names. dutch is an alias of german (one word below a million, units before tens: vierentwintig, tweeduizend vijfhonderd). italian is one word too, but the tens come before the units (ventitré, trecentoventiquattro).
  • [number.part] lists the elided stems that only occur inside a compound: vent (twenty, in ventuno, ventotto), cent (in centotto), trent, quarant, and so on, each with its kind and value. A stem is never read alone.
  • A compound word may follow a round thousand (tweeduizend vijfhonderd, duemilacinquecento), and a teen may precede honderd (twaalfhonderd is 1200).
  • Decimal comma and dot or space grouping, as for French and German: the CLDR conventions, written as data.

Dates and times

Dates are day first. New sections and flags:

ItemMeaning
[time.glue]splits glued words before reading them: zaterdagavond is zaterdag + avond. Rows are first words | second words
[time.decade]names decades (Trenta | 30); a two-digit decade after anni '90 or anni 90 is read too
time.flag bare-range-24a range of bare hours is a time range when the hours are plausible clock times: dalle 8 alle 12
time.flag word-houra spoken hour with a part of the day is a time: sei del mattino
hourwordalso after the second end of a range: van 8 tot 12 uur
holiday rule fixed-not-sunday MM-DDthe date, moved to the day before when it falls on a Sunday. Used for Koningsdag (27 April; 26 April when 27 April is a Sunday)

Regions: it and nl (they pick the holiday rows). Holiday sources are statute facts: the Italian public holidays (Legge 27 maggio 1949, n. 260, as amended by later laws) and the Dutch general holidays (Algemene termijnenwet art. 3), with the Easter-based feasts from the computus. Customary dates (Ferragosto, Sinterklaasavond, Festa della mamma) are fixed or nth rows.

Limits (Italian and Dutch)

  • di N anni always reads as an age in Italian, so un contratto di 5 anni is an age; write per 5 anni for a duration.
  • Dutch uur is a weak hour word: 8 uur is a time only with a part of the day, a tail or a range around it.
  • vanmiddag, vanavond and vannacht are cut as van (this) plus middag, avond, nacht.
  • Compound measures past two terms are not read.

Chinese

Chinese (zh, Mainland conventions, simplified characters) is a data set on the same engine plus a scanner for text without spaces: Data/zh/{numbers,units,currency,datetime}.rules (the zh.* sections), the shared sequence rules, and the code in Scan.Chinese*.cs. Use Recognizer.Chinese, Recognizer.For(Language.Chinese) or the Nlp recognizer verbs with Language.Chinese. The other languages' results are unchanged.

Numbers, measures and money

  • number.digits | system chinese reads numerals built from digits (一二三, 零, 〇, 两) and place words (十, 百, 千, 万, 亿), with 零 as the filler (一百零一, 两千零二十), mixed forms (3万, 2.5亿) and decimals (零点零五). Fractions and percentages: 百分之五, 三分之一, 5%.
  • Ordinals are 第 plus a number (第三, 第一百零一).
  • A lone numeral needs a counting word. 一个, 一杯 and 一次 are not numbers on their own; a numeral is a number when it is two or more characters, carries a digit, or stands in a construct (a measure, money, a date). The measure word stays outside the span.
  • Measures (zh.unit): length, weight, area, volume, speed, temperature (零下二十度, 摄氏), age (岁, 周岁, inside the span). 斤 is the jin and 亩 the mu (Chinese market units, named as such in the values).
  • Money (zh.currency): 元, 块, 角, 分, 人民币, 美元, symbols and ISO codes, and compounds (三块五, 两元五角).
  • Sequences are found in a masked pass: text outside ASCII word characters is treated as a boundary, so e-mail, URL, IP and phone numbers are found directly beside Chinese characters. A hashtag is #x# (a pair) or #x.

Dates and times

Rules in zh.* sections of datetime.rules: day words (今天, 后天), weekdays (周一, 星期天, 礼拜三), dates in 2026年3月18日, 3月18号 and 2026-03-18 forms, relative periods (下个月, 去年, 三天前, 两周后), parts of the day (早上 is 06:00-09:00, 下午, 晚上), clock times (3点半, 下午三点十分, 15:30), ranges (从周一到周五, 3点到5点; the 从 is inside the span), durations (两年零三个月 is P2Y3M; 零 between terms is a gap word, 半 is a half), sets (每天, 每周三, 每个月) and holidays. 北京时间 is inside a time span and gives the +08:00 zone.

Holidays ([zh.holiday]): fixed ones (国庆节, 劳动节, 元旦, 儿童节), nth-weekday ones (母亲节), 复活节 from the computus, and the lunar ones (春节, 大年初一, 除夕, 元宵节, 端午节, 中秋节) plus 清明节, which read the pinned table [zh.lunar] of Gregorian dates for 2022 to 2030; other years do not resolve. Statutory holidays follow the State Council's Measures for National Holidays; the compensatory working days of a year's notice are not modelled.

Limits (Chinese)

  • 5号线 (line 5) and 三块 (three pieces or three yuan) are ambiguous; the readings follow the context words in the rules, and money is read only with a currency construct.
  • A bare 3点 is read as a clock hour as written, with no part of the day.
  • 年初 and 年底 are not read for years; 90年代 is read, a two-digit decade (九十年代 as a number of years) is not.
  • Lunar festivals and 清明节 resolve only for 2022-2030. The 2028-2030 rows come from the astronomical calendar, not from a published notice.
  • Only simplified characters are covered by the rules and the gold set.

    Turkish and Hindi

Turkish (tr) and Hindi (hi) are data sets on the same engine (Data/<lang>/{numbers,units,currency,datetime}.rules). Use Recognizer.For(Language.Turkish) or Language.Hindi, or the Nlp recognizer verbs with a Language argument. The other languages' results are unchanged (their test predictions are byte-identical to before).

Numbers

  • number.digits | system takes devanagari digits (read as ASCII digits of the same value; Indian grouping 12,34,567, lakh and crore as scale words) and native-digits. case | turkic folds İ/I to i/ı before matching, with offsets unchanged.
  • Number words follow the language: Turkish compounds are separate words (yüz yirmi üç bin dört yüz elli); on bir (tens plus unit) is accepted. Hindi has an irregular table for 1 to 100 (generated into numbers.rules) and the scale words सौ, हज़ार, लाख, करोड़, अरब, खरब. A decimal number before a scale word multiplies (1.5 करोड़).
  • Digit ordinals take an apostrophe ending in Turkish (5'inci); the ending is outside the number.
  • [percent.prefix]: percent before the number (Turkish %12, yüzde on iki).

Dates and times

Day first. Case endings after an apostrophe (3 Mart'ta, 20.03.2026'da) are outside the span. New items:

ItemMeaning
holiday rule table YYYY-MM-DD ...a pinned list of dates, for observances that follow the lunar calendar; years not listed have no date
regions tr, inpick the holiday rows (national days; Ramazan and Kurban Bayramı 2025-2030 from the Diyanet lists; Hindi gazetted holidays 2026 and 2027)
roles earlypost, midpost, latepostthe part of a period named after it: 2021 başında, Mayıs ortasında, 2022 के मध्य, मई की शुरुआत: a year or month, an optional of word, then the part. They give an approximate range, as early 2021 does
a season after the year2021 baharında, 2025 के पतझड़
role nodatephrases that must not be read as a weekday (pazar yerinde, the market)
roles plushalf, plusquarter, minusquarterHindi साढ़े तीन बजे, सवा चार, पौने पाँच
role rangetailthe words that close a range after its second end (10 Nisan'a kadar, 16:00 तक, arasında)
flags daypart-first, dot-clock, bare-hour-range, spoken-hour-markakşam 7, saat 15.30, a range of bare hours (9-17), a spoken hour with its mark
phone.space-startHindi: ten-digit mobile numbers in two groups, and landlines with a trunk 0 (0141 234 5678)

Limits (Turkish and Hindi)

  • Ramazan and Kurban Bayramı are listed for 2025-2030 only. Hindi lunar holidays are listed for 2026 and 2027 only (Bakrid is left out: the published lists disagree).
  • Inflected forms of Turkish month and weekday names are listed in the rule data, not derived: a rare case ending is not read.
  • Hindi रात reads as evening to midnight (18:00-24:00) and कल as tomorrow unless a past word is nearby.
  • Early, middle and late parts are only read after a four-digit year or a month name.