- Guides
Arabic digits, dates, and currencies
Arabic digits, dates, and currencies need explicit field grammar, calendar, currency, and separator policies before conversion.
- Author
Udit Goenka- Editorial date
- Reading time
- 25 min

TL;DR: Select the field type, accepted digit set and punctuation grammar before converting Arabic-script numeric text. Preserve identifier zeros, require explicit calendar/date order and currency context, and return clarification when the input cannot support one typed candidate.
A caller supplies a code containing a leading zero, a date and an amount. The application applies one numeric-cleanup function to all three. It removes punctuation, guesses a calendar from Arabic text and converts the code to an integer.
The values now look easier to store, but their meaning has changed. A code is not an amount, a date is not an instant, and the language of a string cannot identify its currency or calendar.
Begin with a typed capture grammar. Keep the raw literal, select the field contract explicitly, map only an approved decimal-digit set and interpret separators before constructing a candidate.
The multilingual task guide separates text and task evidence. This article provides a small standard-library parser for three bounded field types, with negative cases that preserve ambiguity instead of guessing.
All examples are synthetic text. They do not parse spoken Arabic number phrases, recognize speech, convert Hijri dates, determine a caller's locale or establish business authorization for 60db Agent or another system.
Table of contents
- Choose the field before changing the text
- Declare the accepted digit set
- Interpret separators before removing anything
- Keep calendar and currency explicit
- Run the bounded typed parser
- Read the printed candidates
- Preserve codes as strings
- Use decimal text for bounded amounts
- Keep a date separate from an instant
- Bind confirmation to the complete candidate
- Test the recognition and display adapters
- Review a policy change
- Audit the digit repertoire at the boundary
- Review amount grammar before arithmetic
- Keep temporal context out of digit inference
- Trace the candidate across serialization
- Exercise the clarification path as useful behavior
- Frequently asked questions
- Begin with three ambiguous inputs
Choose the field before changing the text
A sequence of digit glyphs does not identify the kind of value it represents. The same surface can be a literal identifier, a quantity, a calendar date or one part of a larger address.
Select the field from the current task contract, then validate its supported grammar. Do not guess the field from whether an integer conversion succeeds. That conversion answers a representation question after meaning should already be established.
For an identifier, the literal sequence can matter, including leading zeros. For an amount, the value needs a currency and scale policy. For a date, the components need an order and calendar before a date candidate can be constructed.
CLDR's number specification describes numbering systems, symbols and formatting. It does not supply the caller's intended business field or resolve ambiguous spoken meaning on its own.
The money and quantity guide examines the next task boundary. A mathematically valid decimal can still be the wrong amount when the caller's unit or intended interpretation remains uncertain.
Keep raw input with every candidate and failure disposition. A parser that returns only a cleaned value can erase the evidence needed to explain how punctuation or a zero disappeared.

Declare the accepted digit set
Unicode distinguishes characters with decimal values, broader digit values and broader numeric values. Those categories are useful properties, but accepting every numeric-looking character is not a field grammar.
The Python Unicode database documentation exposes decimal, digit and numeric APIs separately. A superscript or fraction should not automatically become part of a decimal identifier merely because some numeric API supplies a value.
This reference accepts one explicitly selected set per field: ASCII digits, Arabic-Indic digits or Extended Arabic-Indic digits. The sets are spelled out in the code so the accepted repertoire is inspectable and does not expand silently with an unrelated Unicode category.
A different digit set inside the same input produces clarification under this intentionally strict policy. That is a teaching choice for a narrow task contract, not a claim that every mixed-script number is invalid in every application.
Record the numbering-system choice as metadata rather than deriving it from Arabic text, nationality or a browser language setting. The Emirati task guide explains why locale and speaker/task evidence remain separate.
The parser maps approved digits while retaining punctuation for the next grammar step. It does not normalize the entire input with a compatibility transformation and hope that the result still has the original field meaning.
Unicode normalization defines canonical and compatibility forms. A normalization form is not a universal numeric-cleanup policy, and its textual equivalences do not establish identifier identity or currency intent.
Interpret separators before removing anything
A comma or decimal-looking mark can have different meanings under different field policies. Removing it before choosing a grammar discards the distinction the parser needs to evaluate.
The reference amount grammar supports one explicitly selected decimal separator and no grouping separators. An input containing another punctuation pattern clarifies rather than guessing whether it represents grouping, a fractional part or something else.
This narrow grammar is useful because it can be tested completely for its supported cases. It is not a full CLDR parser, a spoken-number interpreter or a general financial input library.
The Python regular-expression documentation distinguishes Unicode patterns and explicit character classes. The example uses full-string matching with explicit ASCII digit classes after the approved mapping, so a matching substring cannot hide unsupported surrounding characters.
For an identifier, punctuation is rejected by this subset instead of stripped. A task that legitimately supports separators needs a declared serialization rule and fixtures proving which characters belong to the literal identifier.
The order-ID and email guide provides a related boundary. Punctuation can be part of the value; treating it as decoration can silently change the target.

Keep calendar and currency explicit
Arabic digits do not identify a calendar. Neither does an Arabic sentence or a regional locale selection. Require a calendar field from the task's approved interpretation before constructing a date candidate.
The reference supports a Gregorian date subset only. Missing calendar, Hijri input or another unsupported calendar produces clarification. It does not invent a conversion algorithm or substitute the system's local calendar.
CLDR's date specification distinguishes calendar and date-formatting concerns. Formatting data cannot supply an unspecified calendar or determine a unique instant from a date-only string.
Currency needs the same discipline. A symbol such as a dollar sign, or a surface such as riyal or dirham, cannot choose a unique currency code merely because the surrounding language is Arabic.
The amount subset accepts an explicit supported currency code under its declared scale rule. The owning task must obtain that code through its actual context or clarification process. A parser recognizing digits cannot certify that the context is correct.
The date and timezone guide develops the separate instant-resolution contract. This parser returns a calendar date only and never supplies midnight, a timezone or an appointment instant by default.

Run the bounded typed parser
Save the full fence as typed_digits.py and run Python 3.12 or later. It prints successful synthetic identifier, amount and date candidates together with their raw literals, selected policy and clarification state.
The supported subset is nonnegative digit-only identifiers, nonnegative amounts with at most two fractional digits under an explicit separator and currency, and slash-separated Gregorian dates under an explicit order. No grouping, signs, exponents, spoken phrases or calendar conversion are supported.
from dataclasses import dataclass, asdict
from datetime import date
from decimal import Decimal
import json
import re
import unicodedata
DIGITS = {
"ascii": "0123456789",
"arab": "٠١٢٣٤٥٦٧٨٩",
"arabext": "۰۱۲۳۴۵۶۷۸۹",
}
@dataclass(frozen=True)
class Candidate:
raw: str
field: str
numbering: str
disposition: str
reason: str
canonical: str | None
currency: str | None
calendar: str | None
separator: str | None
order: str | None
def parse(raw, field, numbering, *, separator=None, currency=None,
calendar=None, order=None):
if type(raw) is not str or type(field) is not str or type(numbering) is not str:
raise ValueError("text input and explicit field policy required")
if field not in ("identifier", "amount", "date"):
raise ValueError("unsupported field kind")
for value in (separator, currency, calendar, order):
if value is not None and type(value) is not str:
raise ValueError("optional policy fields must be text or None")
def outcome(reason, canonical=None):
return Candidate(raw, field, numbering,
"candidate" if canonical is not None else "clarify",
reason, canonical, currency, calendar, separator, order)
if numbering not in DIGITS:
return outcome("unsupported_numbering")
table = {char: str(i) for i, char in enumerate(DIGITS[numbering])}
mapped = []
for char in raw:
if char in table:
mapped.append(table[char])
elif char.isdecimal():
return outcome("digit_set_mismatch")
else:
mapped.append(char)
value = "".join(mapped)
if field == "identifier":
if re.fullmatch(r"[0-9]{1,32}", value) is None:
return outcome("identifier_grammar")
return outcome("literal_digits", value)
if field == "amount":
if currency not in ("AED", "EGP", "USD"):
return outcome("currency_required_or_unsupported")
if separator not in (".", "٫"):
return outcome("separator_required_or_unsupported")
pattern = r"[0-9]{1,16}(?:" + re.escape(separator) + r"[0-9]{1,2})?"
if re.fullmatch(pattern, value) is None:
return outcome("amount_grammar_or_scale")
decimal_text = value.replace(separator, ".")
return outcome("exact_decimal_text", format(Decimal(decimal_text), "f"))
if calendar != "gregorian":
return outcome("calendar_required_or_unsupported")
if order not in ("DMY", "MDY", "YMD"):
return outcome("date_order_required")
pattern = r"([0-9]{4})/([0-9]{2})/([0-9]{2})" if order == "YMD" else r"([0-9]{2})/([0-9]{2})/([0-9]{4})"
match = re.fullmatch(pattern, value)
if match is None:
return outcome("date_grammar")
fields = dict(zip(order, map(int, match.groups())))
try:
parsed = date(fields["Y"], fields["M"], fields["D"])
except ValueError:
return outcome("invalid_gregorian_date")
return outcome("date_only_not_instant", parsed.isoformat())
def tests():
identifier = parse("٠١٢٣", "identifier", "arab")
assert identifier.canonical == "0123" and identifier.raw == "٠١٢٣"
amount = parse("١٢٫٣٠", "amount", "arab", separator="٫", currency="AED")
assert amount.canonical == "12.30"
day = parse("٠٣/٠٤/٢٠٢٦", "date", "arab", calendar="gregorian", order="DMY")
assert day.canonical == "2026-04-03" and day.reason == "date_only_not_instant"
negatives = [
(parse("²", "identifier", "ascii"), "identifier_grammar"),
(parse("½", "identifier", "ascii"), "identifier_grammar"),
(parse("٠1", "identifier", "arab"), "digit_set_mismatch"),
(parse("123", "identifier", "unknown"), "unsupported_numbering"),
(parse("1,234", "amount", "ascii", currency="USD"), "separator_required_or_unsupported"),
(parse("1,234", "amount", "ascii", currency="USD", separator="."), "amount_grammar_or_scale"),
(parse("12", "amount", "ascii", currency="dirham", separator="."), "currency_required_or_unsupported"),
(parse("12", "amount", "ascii", currency="riyal", separator="."), "currency_required_or_unsupported"),
(parse("12", "amount", "ascii", currency="$", separator="."), "currency_required_or_unsupported"),
(parse("12.345", "amount", "ascii", currency="AED", separator="."), "amount_grammar_or_scale"),
(parse("03/04/2026", "date", "ascii", order="DMY"), "calendar_required_or_unsupported"),
(parse("03/04/2026", "date", "ascii", calendar="hijri", order="DMY"), "calendar_required_or_unsupported"),
(parse("03/04/2026", "date", "ascii", calendar="gregorian"), "date_order_required"),
(parse("31/02/2026", "date", "ascii", calendar="gregorian", order="DMY"), "invalid_gregorian_date"),
]
for result, reason in negatives:
assert result.disposition == "clarify" and result.canonical is None
assert result.reason == reason
assert parse("۰۱۲۳", "identifier", "arabext").canonical == "0123"
assert parse("03/04/2026", "date", "ascii", calendar="gregorian", order="MDY").canonical == "2026-03-04"
assert parse("", "identifier", "ascii").disposition == "clarify"
assert parse("0", "amount", "ascii", currency="USD", separator=".").canonical == "0"
for bad in (lambda: parse(123, "identifier", "ascii"),
lambda: parse("12", "amount", "ascii", currency=True)):
try:
bad()
except ValueError:
pass
else:
raise AssertionError("malformed metadata accepted")
print(json.dumps({"evidence": "synthetic_text", "unicode_database": unicodedata.unidata_version,
"candidates": [asdict(x) for x in (identifier, amount, day)],
"clarifications": [asdict(x) for x, _ in negatives]},
ensure_ascii=False, sort_keys=True))
print("synthetic digit, field, separator, currency and calendar fixtures passed")
if __name__ == "__main__":
tests()
The amount scale and accepted currency subset are explicit teaching constraints. This program performs no rounding and does not claim to implement every currency's minor-unit rule. A field outside the subset needs a different approved grammar or clarification.
Read the printed candidates
Each result retains its original literal and selected numbering policy. A successful candidate carries a canonical string; a clarification carries no canonical value and a reason that identifies the unsupported or missing boundary.
The identifier result preserves a leading zero after the explicit digit mapping. The amount result preserves a decimal textual value without converting through binary floating point. The date result is an ISO calendar date with a reason that explicitly excludes instant semantics.
Read the negative rows too. They show missing calendar, unsupported calendar, absent date order, ambiguous currency surfaces and unsupported amount scale. Those results are useful outputs, not test cases that the parser should force into convenient values.
The task-outcome guide explains the next boundary. A parsed candidate is weaker than confirmation or completed business action. Do not set an external success flag merely because the parser returned a value.
The program does not retain mutable approved policy state. Its frozen output is a supported value snapshot, while a caller still owns any later confirmation or authorization controller. That controller must bind approval to the complete candidate and its policy revision.
Preserve codes as strings
A digit-only identifier is a literal sequence under a task contract. Converting it to an integer can remove information that the caller supplied and that the owning service expects.
RFC 8259 distinguishes JSON strings and numbers, with number grammar that excludes leading zeros. Use a string field when the value is an identifier rather than relying on a numeric serialization to preserve its surface.
The postal-code guide develops this distinction for another field type. A syntactically valid code is also weaker than evidence that the code is assigned or appropriate for the intended address.
The phone-number capture guide explains why country codes, literal zeros and context need their own representation. This tutorial's identifier parser is not a phone-number parser and does not infer missing international context.
Preserve zeros through display, copying, database storage and tool submission. A parser can return the correct string while a later UI component or schema converts it to a number. Test those adapters at their actual boundary rather than assuming the first correct conversion settles the workflow.

Use decimal text for bounded amounts
Construct Decimal from validated decimal text rather than from a binary float. The Python Decimal documentation explains exact decimal representation and context-sensitive arithmetic behavior.
The reference performs no arithmetic beyond construction and fixed-format output. Its grammar bounds input length and fractional scale before Decimal sees the value. It does not accept exponents, infinity, NaN or signs as hidden extensions of the amount language.
A historical developer question about floating-point monetary conversion illustrates why representation surprises can reach business code. Use it as a problem statement, while the current Decimal contract and task grammar own the implementation rule.
Currency remains a separate explicit field. The same decimal text can describe different values under different currencies or units. Do not let a successful Decimal construction fill a missing currency from server locale or Arabic-language context.
If the task permits rounding, specify when it occurs, which rule applies and who confirms the rounded candidate. This subset clarifies an unsupported scale instead. Silently rounding before confirmation can change the caller's requested value while preserving a misleading appearance of successful parsing.
The workflow guide provides the approval boundary. Store the approved decimal text and currency together so a later policy or value change cannot reuse an earlier confirmation.
Keep a date separate from an instant
The date grammar requires both a Gregorian calendar selection and an explicit component order. Its successful output describes a date, not a time of day, timezone or timestamp.
A date-only value must not silently become midnight because a general datetime parser can supply one. When the task needs an appointment time, obtain the explicit time and zone through the task's separate interpretation and confirmation contract.
The Nepali-English scheduling guide illustrates why calendar date, local wall time and externally acknowledged appointment state need distinct evidence. Changing the surrounding language does not remove those boundaries.
The two date-order fixtures intentionally produce different calendar dates from the same slash-separated literal. That is a synthetic ambiguity test, not a claim about which order a particular Arabic-speaking caller uses.
Relative dates need a reference time and zone that this subset does not accept. Unsupported calendars need their own qualified interpretation. Do not expand the parser with an unreviewed conversion simply to make every input produce a value.
Bind confirmation to the complete candidate
A confirmation should cover the field kind, canonical value, currency or calendar context and relevant interpretation policy. Confirming only a spoken digit sequence can leave those material dimensions unresolved.
If a caller changes the amount, currency, calendar or date order, the earlier candidate is no longer the same approved proposal. Create a new revision and require the appropriate current confirmation before acting.
The self-correction guide provides the revision boundary. The yes-but guide adds the case where apparent agreement contains a material repair.
The parser itself has no confirmation state or deadline. Its outputs are candidate evidence only. A live controller must enforce expiry when queried and when a replacement proposal arrives, rather than relying on another callback to notice that an old operation has expired.
Keep the raw literal when a caller clarifies the policy. The clarification can change how the same literal is interpreted, so a trace should show both the previous uncertainty and the new explicit context instead of rewriting history as though it was always known.
Test the recognition and display adapters
The parser begins with supplied text. Recognition can already have lost punctuation, chosen an unexpected digit set or omitted a leading zero before this code receives the input.
Retain the recognition observation and parser result separately. A parser rejection can be correct under its grammar while exposing a capture failure. A parser success can still correspond to the wrong spoken value if recognition supplied the wrong literal.
The Arabic-English task guide explains how to review intended entities and corrections without making dialect accuracy claims from synthetic text. Keep the source conditions and reference provenance visible in a real trial.
The display path needs separate checks too. Bidirectional UI guidance distinguishes logical storage from visual order. A value that looks correct can still be copied or serialized incorrectly by an adapter.
The mixed-script TTS guide covers speech preparation. Formatting a value for speech must not overwrite the canonical candidate or make a speech-engine request into proof that the caller heard the intended number.

Review a policy change
A new accepted digit set or separator rule changes which inputs produce candidates. Treat that as a behavior change with a version and boundary fixtures, rather than a harmless cleanup of equivalent strings.
Test positive, ambiguous and unsupported inputs before activation. Keep mixed-digit sets, numeric-looking symbols, invalid dates, missing currency and excessive scale in the regression suite.
The regression-testing guide explains how to preserve those cases. Execute the entire published example and inspect its advertised output, not only a test-success line from a simplified helper.
The following criteria compare evidence boundaries. A check means the implementation can establish the property under its declared subset; a cross means it cannot establish it alone.
| Criterion | Digit mapping | Typed candidate grammar | Confirmed task workflow |
|---|---|---|---|
| Maps an approved digit repertoire | ✓ Explicit set | ✓ If included | ✗ Without parser evidence |
| Selects currency or calendar from Arabic language | ✗ | ✗ | ✗ Requires actual context |
| Preserves literal identifier zeros | ✓ If retained as text | ✓ Identifier contract | ✓ If adapters preserve it |
| Resolves unsupported spoken number phrases | ✗ | ✗ | ✗ Needs separate interpretation |
| Establishes completed external action | ✗ | ✗ | ✓ Only supported owning-workflow evidence |
Preserve the previous policy and affected raw fixtures when a change is reviewed. A newly accepted interpretation should not be used to rewrite an earlier uncertain result as though the old parser had already established it.
Audit the digit repertoire at the boundary
The accepted digit table is part of the parser policy. Review it as data whose exact contents affect behavior, rather than treating it as a decorative list of equivalent glyphs.
For each selected set, test every supported digit and verify that the mapped result has the intended decimal character. Then test a decimal digit from another supported set inside the same field. Under this reference's strict single-set policy, that mixture must clarify rather than silently broaden acceptance.
Also test characters that look numeric but are outside the repertoire. Superscripts, fractions and other symbols can have numeric properties without belonging to this field grammar. The parser preserves their raw form and rejects them at the grammar boundary instead of applying a compatibility conversion that makes them appear acceptable.
Keep language labels out of that decision. An Arabic sentence can contain ASCII digits, and an English interface can display Arabic-Indic digits. The field policy must say which input set is accepted; the script of surrounding prose cannot supply a missing policy selection.
A real input component may introduce a mapping before the parser runs. Record that transformation and test its output separately. If an input method has already changed the captured literal, the parser's raw field describes what it received, not necessarily the original keystrokes or spoken words.
The observability guide provides the wider evidence discipline. Name the capture and parser boundaries distinctly so a correct mapping does not conceal an earlier recognition or input-method change.
Retain the runtime Unicode database version with the executed fixture output. This reference uses fixed repertoire strings and a small property check, so it does not claim to implement the newest Unicode specification merely because that specification is cited as background.
Do not make an unsupported security claim from a mismatch. A mixed digit set can be accidental, expected under another application policy or worth further review. The reference's clarify result means the value falls outside its declared subset, not that the caller attempted an attack.
Review amount grammar before arithmetic
An amount parser should establish the permitted text language before constructing a numeric object. That order makes accepted signs, separators, scale and size visible rather than inheriting every syntax a general numeric constructor happens to accept.
This subset accepts nonnegative amounts only. It rejects signs, grouping, exponents and unsupported fractional length. A task involving refunds, signed balances or scientific quantities needs an explicitly different grammar and should not bypass rejection by stripping the character that caused it.
The maximum integer length is a synthetic implementation bound for the reference. It limits the accepted input shape; it is not a business limit, an account balance policy or a recommended provider threshold. Put any real business amount limit in the owning workflow with its own evidence and authorization rule.
Constructing Decimal from an accepted string preserves the decimal representation needed for this candidate. Later arithmetic can still depend on precision and rounding context. If the application multiplies, converts units or allocates fractions, test that later operation separately rather than crediting this parser with guarantees it never executes.
A trailing zero can carry useful display or scale context even when the mathematical value is equal. Keep the validated decimal text and the task's formatting policy available. Do not overwrite the original literal merely because another component prefers a shorter presentation.
The accepted currency codes in the example all use the tutorial's two-fractional-digit subset. That does not establish a universal currency rule. A broader application must consult its actual supported currencies, units and payment contract instead of copying this subset into an unrestricted financial parser.
Separate formatting from conversion. Showing a localized currency label can help a caller understand the proposed amount, while converting between currencies is a different operation with rates, time boundaries and authorization requirements. No exchange-rate feature is implemented here.
The prosody guide for confirmations examines a complementary speech boundary. Making an amount easy to hear supports confirmation, while parsing and external authorization still need their own evidence.
Keep temporal context out of digit inference
A valid date candidate is weaker than a scheduled instant. It identifies a calendar day under the supplied calendar and component order, leaving time of day and zone absent.
The reference never calls a datetime parser that can fill omitted time fields. Its date constructor validates only the supplied year, month and day. The result's reason explicitly says date only, so a downstream adapter cannot reasonably treat the output as proof that midnight was requested.
The Gregorian-only subset is intentional. Missing or unsupported calendar context returns clarification even when the digit groups could form a plausible Gregorian date. Plausibility is not evidence that the caller intended that calendar.
The same rule applies to a currency name. A familiar Arabic word or regional association can motivate a clarification question, but it cannot supply an authoritative currency code for the parser. Obtain the actual task context before constructing the candidate.
Date-order ambiguity must remain visible too. Changing order can produce another valid date rather than an error. A test suite that checks only impossible dates will miss this more dangerous case because both interpretations can pass a date constructor.
Keep relative-date interpretation in a separate adapter. Words such as tomorrow need a reference instant, zone and task-specific boundary. A digit-normalization function has none of that evidence and should not add server-local defaults to fill the gap.
If the task later resolves an instant, retain the original date and explicit local time/zone alongside the resulting timestamp. Equal-looking dates do not establish equal instants, and a timezone conversion should not rewrite the caller's source values.
The scheduling comparison guide provides a useful regression lesson: a replacement proposal cannot revive an expired operation, and a date-only string cannot supply an unstated time. Those are workflow rules to enforce after this parser returns its bounded candidate.
Trace the candidate across serialization
A field can be parsed correctly and still change when it crosses another interface. Inspect the schema used by the database, message bus, tool API and UI component rather than assuming that a string remains a string throughout the path.
For an identifier, test a round trip containing a leading zero and compare the exact logical string. If a downstream schema coerces the value to an integer, fixing the parser alone will not preserve the code the caller supplied.
For an amount, keep currency and value in separate named fields. Concatenating a display symbol and number can make later parsing ambiguous again. The task's serialization contract should carry the explicit currency code and validated decimal text without requiring another component to infer them from prose.
For a date, retain date-only semantics in the schema. A database timestamp column or a general date-time serializer can introduce a time or zone that the candidate never established. Select a representation appropriate to the actual field instead of relying on convenience coercion.
Keep clarification results typed as well. A missing canonical value is not the string null, an empty identifier or a zero amount. Each can be a real value under another contract, so preserve the explicit disposition and reason.
The spoken-answer cache guide adds a related lifetime boundary. A previously parsed or confirmed value can remain available while the current task or context has changed. Reuse needs current ownership and policy evidence, not only matching text.
A live workflow should bind its approval to an owned immutable snapshot. The parser's frozen result supplies one small supported shape, but the full proposal may include more fields. Copy and validate those material fields before approval rather than retaining a caller-owned dictionary that can change afterward.
Record enough raw evidence to explain a mismatch without spreading private values across logs. Public development fixtures can remain synthetic, while actual task records need the application's appropriate access, retention and minimization rules.
Exercise the clarification path as useful behavior
A clarification is a valid outcome when the input lacks context required by the field contract. It protects meaning that would otherwise be lost through a convenient guess.
Test that outcome in the actual workflow. An unsupported calendar should lead to a question about calendar or task context, not a generic recognition failure that invites the caller to repeat the same ambiguous digits indefinitely.
An ambiguous currency should lead to an explicit supported choice or another task-appropriate request for context. Do not ask a caller to confirm a number while leaving the currency unstated, then reuse that confirmation after filling the currency silently.
An unsupported separator pattern should preserve the raw literal. A clarification can establish a new separator policy, but the trace should show that the earlier input was unresolved under the old policy. Do not rewrite it as though the new interpretation had always been known.
A material clarification creates a new candidate revision. Keep older confirmation and tool results attached to their original proposal so they cannot authorize the newly interpreted value. The parser has no asynchronous state, so the owning controller must enforce that revision boundary.
Keep positive controls beside these negative cases. The approved digit repertoire, explicit currency and supported calendar/order should produce candidates when the full grammar is satisfied. An implementation that clarifies every input has not solved capture even though it avoids guessing.
The printed output makes both behaviors inspectable. A reviewer can recompute each canonical value from raw text and policy, or identify the exact reason no value was produced. That is the reference's completed result, while recognition, speech, confirmation and external action remain separately measured boundaries.
Before accepting an exported fixture as reproducible, inspect its policy fields. The reference returns the numbering selection, separator, currency, calendar and date order alongside the raw literal and canonical candidate. A record containing only the final value would be insufficient to explain why another supported interpretation was rejected.
Keep absent fields as typed absence. An identifier does not require a currency, and a date-only candidate does not require a decimal separator. Their absence should not be replaced with a server default merely to make every exported record look complete. Completeness means the material fields for the selected contract are present, not that every possible field has a value.
Ask a reviewer to reconstruct one successful date and one clarification from those fields. They should reach the same date under the selected order and see that an unsupported calendar prevents construction. Repeat the exercise for a monetary value with an explicit separator and for the same raw punctuation without that policy.
This check also catches incomplete instrumentation. If the live adapter forgets to retain the interpretation policy, the parser may have behaved correctly while the archived result becomes impossible to audit. Treat that as missing evidence rather than guessing the policy later from the output's appearance.
When the policy changes, keep a new versioned fixture result. Do not edit the old record in place so its fields describe a decision the earlier run never made. That preserves the distinction between a corrected implementation and a rewritten history.
Frequently asked questions
Which digit sets should an Arabic voice interface accept?
Choose the sets supported by the actual field contract. The reference accepts one explicitly selected ASCII, Arabic-Indic or Extended Arabic-Indic set per input and clarifies other decimal sets rather than guessing.
Are Arabic-Indic and Eastern Arabic-Indic digits identical code points?
No. The reference lists the supported sets explicitly and maps them through the chosen policy. Similar numeric values do not make their original code points or captured literals identical.
Should all Unicode numeric characters become digits?
No. Decimal, digit and numeric properties differ, and the field grammar can be narrower than any broad Unicode category. Superscripts and fractions remain unsupported in this decimal-digit subset.
How do I preserve a leading zero?
Keep identifiers as strings through mapping, storage and serialization. Test later adapters too, because a correct parser result can still lose its zero when another component converts it to a number.
What is the difference between a digit string and an amount?
A digit string can be a literal identifier. An amount needs a value, unit or currency and scale policy; converting both through the same integer-cleanup function can change their meanings.
Why preserve the original transcript?
It explains what the parser received and how policy produced a candidate or clarification. A cleaned value alone can hide lost punctuation, a changed digit set or a removed leading zero.
How are decimal and grouping separators selected?
Use an explicit field/locale interpretation policy supported by the application. This subset accepts one selected decimal separator and no grouping, so unsupported punctuation clarifies instead of being removed.
Can a locale resolve every spoken separator?
No. Locale data supplies formatting conventions, while the caller's intended value may remain ambiguous. Obtain the task-specific context or clarification before converting the literal.
Should dates default to the Gregorian calendar?
Not when the calendar is missing from the task contract. The reference supports Gregorian candidates only after explicit selection and returns clarification for missing or unsupported calendars.
How do I handle an unspecified Hijri date?
Preserve the raw value and ask for the missing calendar/task context. This program does not convert Hijri dates or infer a calendar from Arabic digits or language.
What makes 03/04 ambiguous?
Without component order, the same literal can describe different dates under different supported policies. The example requires an explicit order and preserves the resulting date-only candidate.
Should a currency symbol select a currency automatically?
No. Symbols and currency names can be ambiguous. Require an explicit supported code from the owning task context; language and successful digit parsing cannot supply that evidence.
Why use Decimal for money?
Validated decimal text can be represented without converting through binary floating point. Arithmetic, rounding and supported scale still need explicit policies, which this small parser does not infer.
How should rounding be approved?
Define the rule, application point and confirmation boundary for the actual task. This subset performs no rounding and clarifies values outside its supported fractional scale.
Can number formatting parse spoken Arabic?
Not by itself. Formatting data and a written-digit grammar do not recognize spoken number phrases. That interpretation requires a separate qualified adapter and evidence.
What should mixed digit scripts do?
Follow the declared task policy. The reference's strict single-set rule clarifies a mismatch; another policy would need explicit accepted combinations and regression tests.
How do confirmation revisions work?
Bind confirmation to the complete candidate and material context. Changing value, currency, calendar or interpretation policy requires a new current proposal under the owning controller's contract.
What boundary fixtures are essential?
Include leading zeros, unsupported numeric symbols, mixed sets, ambiguous punctuation, missing currency, missing calendar/order, invalid dates and unsupported scale. Also include positive controls so rejecting every input cannot pass.
Does normalization prove ASR accuracy?
No. This code begins with text and verifies a declared parsing subset. Recognition quality requires real audio, reviewed references and an executed task-specific trial.
When does the example need a real locale library?
When the application must support formatting or parsing beyond this explicitly bounded grammar. A library still needs the correct task context and does not resolve unspecified calendar, currency or spoken intent automatically.
Begin with three ambiguous inputs
Run the complete fence and inspect the clarification rows for an unspecified calendar, an ambiguous currency and an unsupported separator pattern. Confirm that none produces a guessed canonical value.
Then follow the successful identifier, amount and date candidates through the actual adapters. Preserve literal zeros, currency context and date-only semantics instead of letting a downstream schema silently broaden the parser's claim.
The first useful result is a typed value whose meaning and limitations can be recomputed from the raw text and explicit policy. Add broader input support only when its task contract and boundary fixtures are equally clear.
