@dhis2/multi-calendar-dates

中文文档
3.2.1发布于 2026/10/20BSD-3-ClauseTypeScriptESM + CJS近 30 天下载 1.7万

codecov

原始使用文档
文档目录4 节
  1. Helpers
  2. Fixed period utils
  3. Hooks and others
  4. Hooks for Calendar UI

codecov

multi-calendar-engine

This library is used to work with dates in multiple calendrical system (i.e. Ethiopic, Nepali etc..) across DHIS-2 applications. It mainly exposes two components:

  1. Hooks like useDatePicker used to build UI components (part of @dhis/ui) such as the Calendar and CalendarInput components.
  2. Helper methods, like generateFixedPeriods and getNowInCalendar to deal with period generations, date manipulation and arithmetic across multiple calendrical systems.

The idea behind this library is to abstract the complicated details of dealing with dates in DHIS2, and to centralise them in one place for consumption in different apps and libraries.

Internally, the library relies on the Temporal API which aims to achieve, among other goals, full-support for non-Gregorian calendars.

This document has some of the requirements and design decisions for this project.

This Jira epic lists the app that moved to using the library, and what comes next.

API

Everything below is exported from @dhis2/multi-calendar-dates. See API and examples for arguments, return values and examples.

Helpers

NameDescription
getNowInCalendarThe current date and time of day in a calendar and time zone
convertFromIso8601Converts an ISO 8601 (Gregorian) date to a calendar
convertToIso8601Converts a date in a calendar to ISO 8601 (Gregorian)
addToCalendarDateAdds or subtracts years, months and days in a calendar, clamping the day to the end of the month
formatCalendarDateFormats a date as the yyyy-MM-dd string the DHIS2 backend expects, optionally with the time of day
getDaysInMonthThe number of days in a month in a calendar
validateDateStringValidates a date string against the format, a min/max range and the calendar's rules. Returns a DateValidationResult code when invalid

Fixed period utils

Used by apps like Data Entry, and by the analytics library, to generate the list of fixed periods (daily, weekly, monthly, …) in different calendar systems.

NameDescription
generateFixedPeriodsAll periods of a period type in a year, in the given calendar and locale
getFixedPeriodByDateThe period of a period type that contains a date
createFixedPeriodFromPeriodIdRebuilds a period from its id, e.g. 202301
getAdjacentFixedPeriodsThe N periods before or after a period

Hooks and others

The hooks are exported, but they're mostly used internally: by @dhis2/ui's Calendar and CalendarInput. Most apps use those components rather than the hooks directly.

NameDescription
useDatePickerHook that returns the week grid, day labels and navigation state for a calendar picker
useResolvedDirectionHook that resolves the text direction (LTR/RTL) from the locale, or an explicit override
periodTypesAll supported period type ids
constants.calendarsThe supported calendar ids
constants.numberingSystemsThe supported CLDR numbering system codes
DateValidationResultThe codes validateDateString returns when a date is invalid
CalendarDate, CalendarDateTime (types){ year, month, day, eraYear? }, and the same plus hour, minute and second. Read the year as eraYear ?? year

Hooks for Calendar UI

The library provides a hook useDatePicker that's consumed by the dhis/ui calendar components. There are two components that currently use it:

  • Calendar: a calendar component supporting non-Greogorian calendars
  • CalendarInput: a wrapper around the Calendar component to support the most common use case for a calendar when we want to display it next to an input.

useDatePicker takes a DatePickerOptions object, and returns UseDatePickerReturn that contains information needed to render a UI component, i.e. the localised day names, and the weeks in the current view (month).

// the options passed to useDatePicker
type DatePickerOptions = {
    date: string
    options: PickerOptions
    onDateSelect: (payload: OnDateSelectPayload) => void
    minDate?: string
    maxDate?: string
    format?: 'YYYY-MM-DD' | 'DD-MM-YYYY'
    strictValidation?: boolean
}
type OnDateSelectPayload = { calendarDateString: string } | null
// the return type of the hook
type UseDatePickerReturn = UseNavigationReturnType & {
    weekDayLabels: string[]
    calendarWeekDays: {
        dateValue: string
        label: string | number
        onClick: () => void
        isSelected: boolean | undefined
        isToday: boolean
        isInCurrentMonth: boolean
    }[][]
}

Special cases and considerations with periods logic

There are some special DHIS2-specific cases when doing period calculations that we handle. The specs for these are documented in the tests and cucumber feature files. Some of these are:

  • Dealing with the 13th month in the Ethiopian calendar: the Ethiopic calendar has 13 months: 12 months of 30 days, then the 13th month has 5 or 6 days depending on whether it's a leap year or not. When generating periods for bi-weekly or less (bi-weekly, weekly, daily) then the 13th month is displayed. But when we are generating periods for monthly or larger, we do not display the 13th month. The backend then makes the decision about where to include any data for the 13th month, so when doing monthly analytics, the 13th month will be lumped with the following month's data.
  • The period IDs are dictated by the backend, and they use the Gregorian month names, for example, QuarterlyNov or QuarterlyApr. In the frontend, in a non-Gregorian calendar, Nov will be "translated" into the 11th month of the current calendar system (Hamle in Ethiopic). Ideally, we will have period IDs that are calendar-agnostic in the future, but this works for now as a solution.
  • From the frontend perspective, we always send a date in the calendar system. It is formatted in an ISO-like format (yyyy-MM-dd). It is "ISO-like", because the date is actually in the calendar system rather than the Gregorian ISO8601 calendar. So when a system set to Ethiopic, sends 2015-01-01 to the backend, it means the year 2015 in Ethiopic (which is 2022 in Gregorian). This is what the backend expects, and if there is a need for a conversion, then it happens in the backend.
    • Given we have the Temporal object in the frontend, we have the ability to send the date converted into Gregorian, but that's what the backend expects right now.
  • Nepali is implemented as a custom calendar: see nepaliCalendar.ts. Nepali is luni-solar calendar which means that the length of months can vary from year to year. The way Temporal does date arithmetic, is that it converts the day to gregorian, performs the arithmetic, then converts it back to the calendar. This leads to weird situations where you do nepaliDate.add({month: 1}) for example, but the result is still in the current month (since the month has 30 days in Gregorian, but 32 in Nepali for example). To get around that, we set the day to the 14th before doing arithmetic (in the context of period generation) and this leads to results that make sense in the context of DHIS2.
  • Nepali is also not supported as a locale in major browsers. So we rely on localising it with a hardcoded map, meaning we don't have the same flexibility showing the day or month names as short or long for example with .toLocaleString. That means that the consumer can only use one of ne-NP or en-NP (nepali months and days transliterated into English) as a locale with the Nepali calendar, and they can't use browser localisations (so they can't display Nepali month names in French for example, but they would be able to do so with Ehtiopic or Islamic calendars).
  • In Ethiopic calendar, when consuming a date, use .eraYear rather than .year to get the year part of a date. There is an ongoing discussion on Temporal to decide which era of the Ethiopic calendar should be the default, but it's likely to be browser-dependent at the end, so it's safer to use eraYear as this is what users in Ethiopia would expect to see. We looked at abstracting this difference away in the library, but there was no easy solution for it.
  • The library also abstracts some DHIS2-specific inconsistencies with the backend to make it easier for consumers of the library. For example, it has a map to convert from the calendar IDs used in DHIS2 to the ones expected by the library: dhis2CalendarsMap.ts. This maps "ethiopian" (used in DHIS2) to "ethiopic" which is the standard ID used in JavaScript (in Unicode CLDR). It also maps the Java locale names with an underscore to ones with a dash, to make them easier to use in a JavaScript context (i.e from ar_SD to ar-SD).

Architecture Decision Records

版本与发布记录

@dhis2/multi-calendar-dates 共发布 59 个版本,首次发布于 2023/01/10;过去 12 个月发布了 11 个版本。

版本发布日期距今
3.2.1latest2026/10/2022 天前
3.2.02026/10/1527 天前
3.1.02026/10/1329 天前
3.0.02026/09/181 个月前
3.0.0-alpha.1alpha2026/07/064 个月前
2.2.0-alpha.32026/07/054 个月前

其他发布通道:beta → 1.0.0-beta.24,alpha → 3.0.0-alpha.1。

关于 @dhis2/multi-calendar-dates 的常见问题

如何安装 @dhis2/multi-calendar-dates?

在项目目录运行 npm install @dhis2/multi-calendar-dates,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(>=16.8)、react-dom(>=16.8)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@dhis2/multi-calendar-dates 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@dhis2/multi-calendar-dates 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@dhis2/multi-calendar-dates 可以商用吗?

@dhis2/multi-calendar-dates 使用 BSD-3-Clause 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。另外不得用原作者名义为衍生产品背书。以上为概括,不构成法律意见,请以许可原文为准。

@dhis2/multi-calendar-dates 的下载量有多少?

根据 npm 官方下载统计,@dhis2/multi-calendar-dates 在 2026/10/12 – 2026/11/10 这 30 天内被下载 16,985 次,日均约 566 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@dhis2/multi-calendar-dates 还在维护吗?

最新版本 3.2.1 发布于 2026/10/20(22 天前);至今共发布 59 个版本,其中过去 12 个月发布了 11 个;首次发布于 2023/01/10。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。