npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

chrono-fa

v0.1.0

Published

Deterministic natural-language date and time parser for Persian (Farsi)

Readme

chrono-fa

chrono-fa is a deterministic, offline, TypeScript-first parser for dates and times written in Persian (Farsi), including common conversational forms. It produces JavaScript Date values together with Jalali and Gregorian wall-clock components, source text, confidence, and known/implied fields.

import { parseDate } from "chrono-fa";

parseDate("فردا ساعت ۸ شب");
parseDate("۲۵ شهریور ۱۴۰۵");
parseDate("سه روز دیگر");
parseDate("next Friday"); // null

The parser performs no network requests, has no telemetry, and does not require a server or AI service. See README.fa.md for a Persian overview.

Features

  • Persian, Arabic-Indic, and ASCII digits
  • Persian number words from zero through 999, including common ordinals
  • Relative dates and elapsed times (فردا, سه روز دیگر, نیم ساعت دیگه)
  • Persian weekdays and configurable forward-date behavior
  • Named Jalali months with validated leap years and month lengths
  • Persian Gregorian month names and ISO dates
  • Conversational clock forms (هفت و نیم, یک ربع به هشت, ۷ شب)
  • Multiple matches and opt-in experimental ranges
  • Original source indices preserved through length-changing normalization
  • IANA time zones, ESM, CommonJS, declarations, and a separate React hook

Installation

npm install chrono-fa

React is an optional peer dependency. Install React only when using chrono-fa/react.

JavaScript

import { parse, parseDate } from "chrono-fa";

const referenceDate = new Date("2026-08-02T10:00:00+03:30");
const date = parseDate("فردا ساعت هفت و نیم شب", referenceDate, {
  timeZone: "Asia/Tehran",
  forwardDate: true,
});

console.log(date?.toISOString()); // 2026-08-03T16:00:00.000Z
console.log(parse("جلسه فردا ساعت ۱۰ و جمعه ساعت ۶ عصر", referenceDate));

There is no default export. All APIs use named exports.

TypeScript

import { parseOne, type FaParseResult, type ParseFaOptions } from "chrono-fa";

const options: ParseFaOptions = {
  timeZone: "Asia/Kabul",
  calendar: "auto",
  strict: true,
};

const result: FaParseResult | null = parseOne("۱۵ آگوست ۲۰۲۶", undefined, options);

The published package includes declarations for both public entry points.

React

import { useState } from "react";
import { useChronoFa } from "chrono-fa/react";

export function DateField() {
  const [value, setValue] = useState("فردا ساعت ۸ شب");
  const { results, result, date, isMatch } = useChronoFa(value, {
    timeZone: "Asia/Tehran",
    forwardDate: true,
  });

  return (
    <>
      <input value={value} onChange={(event) => setValue(event.target.value)} />
      <output>{isMatch ? date?.toISOString() : "No date"}</output>
      <small>
        {results.length} match(es), first: {result?.text}
      </small>
    </>
  );
}

The hook is synchronous and memoized with useMemo. It has no effects, state, browser-global access, or SSR-only branches. referenceDate is accepted by the hook in addition to the parser options. The main chrono-fa entry never imports React.

Node.js

ES modules:

import { parseDate } from "chrono-fa";
console.log(parseDate("فردا"));

CommonJS:

const { parseDate } = require("chrono-fa");
console.log(parseDate("فردا"));

Node.js 22 or newer is supported. CI covers Node.js 22 and 24, the supported LTS lines at the time of the 0.1.0 release.

Browser

The ESM output works with modern bundlers and directly in browsers that support ES2022, modules, Unicode property escapes, and Intl.DateTimeFormat IANA zones. A local import-map demo is provided in examples/vanilla.

<script type="importmap">
  { "imports": { "chrono-fa": "/node_modules/chrono-fa/dist/index.js" } }
</script>
<script type="module">
  import { parseOne } from "chrono-fa";
  console.log(parseOne("فردا ساعت ۸ شب"));
</script>

API reference

parse(text, referenceDate?, options?)

Returns every non-overlapping match, ordered by its index in the original input.

parseOne(text, referenceDate?, options?)

Returns the first FaParseResult, or null.

parseDate(text, referenceDate?, options?)

Returns a new Date for the first match, or null.

normalizePersianText(text, options?)

Returns { text, originalText, sourceMap }. Normalization converts Arabic letter variants, digits, spacing, zero-width joiners, common punctuation, and optional diacritics. It is idempotent. Every normalized code unit maps back to an original range, so parser indices and result.text always refer to the caller's string.

NormalizeOptions contains collapseWhitespace, removeDiacritics, convertDigits, and normalizePunctuation; all default to true.

Number and calendar helpers

The package also exports parsePersianNumber, parsePersianOrdinal, jalaliToGregorian, gregorianToJalali, isValidJalaliDate, isJalaliLeapYear, and getJalaliMonthLength.

Result shape

interface FaParseResult {
  index: number;
  text: string;
  start: Date;
  end?: Date;
  calendar: "jalali" | "gregorian";
  confidence: number;
  jalali: CalendarDateTime;
  gregorian: CalendarDateTime;
  knownValues: Partial<DateTimeValues>;
  impliedValues: Partial<DateTimeValues>;
}

knownValues reflects explicit source components in the source calendar. impliedValues records defaults and reference-derived fields. For a relative phrase, the resolved calendar fields are implied; an attached explicit time moves its hour, minute, and second into knownValues. Returned Date instances and result objects are newly allocated.

Parse options

| Option | Default | Meaning | | -------------------- | --------- | -------------------------------------------------------------------- | | timeZone | host zone | IANA time-zone name such as Asia/Tehran | | calendar | "auto" | Accept both calendars, or filter to one source calendar | | forwardDate | false | Prefer future weekdays/dates and roll past time-only values forward | | strict | false | Reject risky incomplete numeric dates and ambiguous time-only values | | defaultHour | 0 | Local hour for date-only matches | | defaultMinute | 0 | Local minute for date-only matches | | weekStartsOn | 6 | Saturday (6) or Sunday (0) for week-qualified weekdays | | experimentalRanges | false | Enable the bounded range grammar described below |

Options and referenceDate are never mutated. An invalid date, clock default, or IANA zone throws RangeError rather than silently producing a wrong instant.

Jalali calendar

Named Persian months are interpreted as Jalali in auto mode. Common years such as ۱۴۰۵ are Jalali; values such as ۲۰۲۶ in named Gregorian or ISO forms are Gregorian. Conversion and validation use jalaali-js, including accurate Esfand leap-year rules.

Examples:

  • ۲۵ شهریور ۱۴۰۵
  • بیست و پنجم شهریور
  • اول فروردین سال بعد
  • آخر اسفند

Jalali months 1 through 6 have 31 days, so ۳۱ شهریور is valid. ۳۲ فروردین and ۳۰ اسفند in a non-leap year are invalid.

Time zones

Relative interpretation begins from the supplied referenceDate instant. Calendar dates and clocks are created in options.timeZone using @date-fns/tz; elapsed phrases such as دو ساعت دیگر add actual elapsed time. If no zone is supplied, the JavaScript host zone is used.

FaParseResult.start is an absolute Date. The jalali and gregorian objects are wall-clock representations in the selected zone. This keeps results deterministic in CI when both a reference date and zone are provided.

Ambiguous expressions

Rules in calendar: "auto" are intentionally explicit:

  • Named Jalali and Gregorian months determine the calendar.
  • YYYY-MM-DD is Gregorian.
  • Complete slash dates use DD/MM/YYYY; years 1200–1600 select Jalali and years 1700 or later select Gregorian.
  • In casual mode, DD/MM is Jalali and uses the reference Jalali year.
  • In strict mode, incomplete DD/MM is rejected.
  • A named month without a year uses the reference year; with forwardDate: true, a past month/day rolls to the next year.
  • Relative, weekday, and time-only expressions report calendar: "jalali" in auto mode because they have Persian-language rather than explicit Gregorian provenance.

For an unqualified weekday with forwardDate: false, the closest occurrence within three days is chosen. With forwardDate: true, the occurrence on or after the reference date is used. آینده, بعد, and پیش رو always choose a future occurrence; هفته بعد and این هفته honor weekStartsOn.

Times without a meridiem use their literal 24-hour value. ۷ صبح is 07:00, ۷ عصر and ۷ شب are 19:00, ۱۲ ظهر is 12:00, and ۱۲ شب/نیمه‌شب are 00:00.

Strict mode

Strict mode rejects:

  • incomplete numeric dates such as ۱۲/۰۵;
  • time-only values at or before 12 without an explicit meridiem, such as جلسه ساعت ۷;
  • calendar years outside the documented auto-detection ranges when the calendar cannot be inferred.

It still accepts unambiguous Persian relative phrases, named dates, ISO dates, 24-hour clocks, and clocks with explicit meridiems. A date plus an attached clock is accepted because the date context is explicit.

Ranges

Range support is experimental and opt-in:

parseOne("از شنبه تا دوشنبه", reference, { experimentalRanges: true });
parseOne("از ۱۰ تا ۱۵ شهریور", reference, { experimentalRanges: true });
parseOne("فردا از ساعت ۸ تا ۱۰", reference, { experimentalRanges: true });

Supported ranges fill start and end. Cross-midnight clock ranges roll their end into the following day. The narrow grammar is deliberate: unsupported range syntax returns ordinary independent matches or no range instead of guessing.

Example output

{
  "index": 0,
  "text": "فردا ساعت ۸ شب",
  "start": "2026-08-03T16:30:00.000Z",
  "calendar": "jalali",
  "confidence": 0.93,
  "jalali": { "year": 1405, "month": 5, "day": 12, "hour": 20, "minute": 0, "second": 0 },
  "gregorian": { "year": 2026, "month": 8, "day": 3, "hour": 20, "minute": 0, "second": 0 },
  "knownValues": { "hour": 20, "minute": 0, "second": 0 },
  "impliedValues": { "year": 2026, "month": 8, "day": 3, "weekday": 1 }
}

Date is shown as an ISO string above only for readability; the runtime value is a Date object.

Dependencies

  • chrono-node: its documented custom Parser pipeline scans each independently maintained Persian date grammar. No internal imports or undocumented APIs are used.
  • jalaali-js: compact, established Jalali conversion, validation, month-length, and leap-year calculations.
  • @date-fns/tz: IANA-aware local date construction and reference-zone access through the public TZDate API.

React is only an optional peer. Runtime code makes no API calls and contains no telemetry.

Current limitations

Version 0.1.0 intentionally limits the grammar:

  • Persian number words are supported through 999; four-digit years must be digits.
  • Only Persian is parsed; English input such as next Friday returns null.
  • Range grammar is opt-in and supports only the documented forms.
  • The input safety limit is 250,000 UTF-16 code units; content after that boundary is ignored.
  • Highly regional idioms, seasons, holidays, durations without a direction, and free-form recurrence rules are not parsed.
  • Ambiguous DST wall-clock transitions follow TZDate/host Intl disambiguation.

Browser compatibility

The package targets ES2022 and relies on standard Intl IANA time-zone data. Current evergreen Chromium, Firefox, and Safari releases are suitable. Applications targeting older browsers must provide their own transpilation and Intl time-zone data strategy.

Development

npm install
npm run check
npm run test:coverage
npm run build
npm run example:react:build
npm pack --dry-run

npm run benchmark runs a small, non-blocking smoke benchmark over 5,000 short phrases.

Contributing

Contributions are welcome. Read CONTRIBUTING.md, add deterministic fixtures with a fixed reference date and IANA zone, and keep public grammar changes documented.

License

MIT © chrono-fa contributors. See LICENSE.