{"id":19004884,"url":"https://github.com/dhis2/multi-calendar-dates","last_synced_at":"2025-10-11T20:49:20.270Z","repository":{"id":62982465,"uuid":"549594116","full_name":"dhis2/multi-calendar-dates","owner":"dhis2","description":"Tools to help working with dates, times and periods in non-Gregorian calendars","archived":false,"fork":false,"pushed_at":"2025-06-05T22:21:02.000Z","size":1169,"stargazers_count":2,"open_issues_count":12,"forks_count":4,"subscribers_count":20,"default_branch":"main","last_synced_at":"2025-06-05T23:24:58.797Z","etag":null,"topics":["calendar","ethiopic","islamic-calendar","nepali","nepali-calendar","team-platform","temporal","web-lib"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dhis2.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-10-11T12:34:13.000Z","updated_at":"2025-06-05T22:21:03.000Z","dependencies_parsed_at":"2024-01-09T12:25:27.108Z","dependency_job_id":"51a8addd-0a5f-4fc1-8a9c-b6ec08ac392c","html_url":"https://github.com/dhis2/multi-calendar-dates","commit_stats":null,"previous_names":[],"tags_count":78,"template":false,"template_full_name":null,"purl":"pkg:github/dhis2/multi-calendar-dates","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dhis2%2Fmulti-calendar-dates","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dhis2%2Fmulti-calendar-dates/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dhis2%2Fmulti-calendar-dates/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dhis2%2Fmulti-calendar-dates/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dhis2","download_url":"https://codeload.github.com/dhis2/multi-calendar-dates/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dhis2%2Fmulti-calendar-dates/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279008636,"owners_count":26084480,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-11T02:00:06.511Z","response_time":55,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["calendar","ethiopic","islamic-calendar","nepali","nepali-calendar","team-platform","temporal","web-lib"],"created_at":"2024-11-08T18:25:05.291Z","updated_at":"2025-10-11T20:49:20.257Z","avatar_url":"https://github.com/dhis2.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![codecov](https://codecov.io/github/dhis2/multi-calendar-dates/graph/badge.svg?token=VZBHOLXYP1)](https://codecov.io/github/dhis2/multi-calendar-dates)\n\n# multi-calendar-engine\n\nThis library is used to work with dates in multiple calendrical system (i.e.\nEthiopic, Nepali etc..) across DHIS-2 applications. It mainly exposes two\ncomponents:\n\n1. Hooks like\n   [useDatePicker](https://github.com/dhis2/multi-calendar-dates/blob/multi-calendar-docs/src/hooks/useDatePicker.ts)\n   used to build UI components (part of @dhis/ui) such as the\n   [Calendar](https://ui.dhis2.nu/components/calendar) and\n   [CalendarInput](https://ui.dhis2.nu/components/calendar-input) components.\n1. Helper methods, like `generateFixedPeriods` and `getNowInCalendar` to deal\n   with period generations, date manipulation and arithmetic across multiple\n   calendrical systems.\n\nThe idea behind this library is to abstract the complicated details of dealing\nwith dates in DHIS2, and to centralise them in one place for consumption in\ndifferent apps and libraries.\n\nInternally, the library relies on the [Temporal\nAPI](https://tc39.es/proposal-temporal) which aims to achieve, among other\ngoals, full-support for non-Gregorian calendars.\n\nThis\n[document](https://docs.google.com/document/d/19zjyB45oBbqC5KeubaU8E7cw9fGhFc3tOXY0GkzZKqc/edit?userstoinvite=hendrik%40dhis2.org#heading=h.rjt0etsbsqh6)\nhas some of the requirements and design decisions for this project.\n\nThis [Jira epic](https://dhis2.atlassian.net/browse/DHIS2-14051) lists the app\nthat moved to using the library, and what comes next.\n\n# Periods and Dates helpers\n\nThe library also provides helper methods to work with periods and dates in\nmultiple calendars. This allows doing date arithmetic, comparisons in a\nstandard timezone-safe way. It currently relies on the Temporal API.\n\n\u003e The library is built on top of Temporal API, but this is an implementation\n\u003e detail, and one of the goals of the library is to hide that detail to allow\n\u003e replacing Temporal with a different implementation in the future, i.e. a\n\u003e Kotlin multi-platform implementation to achieve consistency between web,\n\u003e android and backend.\n\n## generateFixedPeriods\n\n`generateFixedPeriods` returns the periods of a specific type in a specific\nyear. This is used, for example in [Data\nVisualizer](https://github.com/dhis2/data-visualizer-app/pull/2233), to display\nthe list of periods of different types (i.e. monthly, weekly, yearly) in a\nspecific year, according to the user's calendar and locale.\n\n### Examples\n\nCalling `generateFixedPeriods({year: 2015, periodType: \"FYNOV\", calendar:\n\"gregory\"})` will return all the November financial year periods (FYNOV) from\nthe year 2015 (defaults to 10 years). Here are some more examples:\n\n```js\n// some examples\nimport { generateFixedPeriods } from '@dhis2/multi-calendar-dates'\n\ndescribe('generateFixedPeriods', () =\u003e {\n    it('should generate financial years periods (starting November) for the specified', () =\u003e {\n        const result = generateFixedPeriods({\n            year: 2015,\n            calendar: 'gregory',\n            locale: 'en',\n            periodType: 'FYNOV',\n        })\n\n        expect(result).toEqual(\n            expect.arrayContaining([\n                {\n                    id: '2015Nov',\n                    name: 'November 2015 - October 2016',\n                    startDate: '2015-11-01',\n                    endDate: '2016-10-31',\n                },\n                {\n                    id: '2014Nov',\n                    name: 'November 2014 - October 2015',\n                    startDate: '2014-11-01',\n                    endDate: '2015-10-31',\n                },\n                // .... up to\n                {\n                    id: '2006Nov',\n                    name: 'November 2006 - October 2007',\n                    startDate: '2006-11-01',\n                    endDate: '2007-10-31',\n                },\n            ])\n        )\n    })\n    it('should generate weekly periods (weeks starting on Sunday) for the specified year', () =\u003e {\n        const result = generateFixedPeriods({\n            year: 2015,\n            calendar: 'gregory',\n            locale: 'en',\n            periodType: 'WEEKLYSUN',\n        })\n\n        expect(result).toEqual(\n            expect.arrayContaining([\n                {\n                    id: '2015SunW1',\n                    iso: '2015SunW1',\n                    name: 'Week 1 - 2015-01-04 - 2015-01-10',\n                    startDate: '2015-01-04',\n                    endDate: '2015-01-10',\n                },\n                // .... up to\n                {\n                    id: '2015SunW52',\n                    iso: '2015SunW52',\n                    name: 'Week 52 - 2015-12-27 - 2016-01-02',\n                    startDate: '2015-12-27',\n                    endDate: '2016-01-02',\n                },\n            ])\n        )\n    })\n\n    it('should generate monthly periods (localised in Spanish) for the specified year', () =\u003e {\n        const result = generateFixedPeriods({\n            year: 2015,\n            calendar: 'gregory',\n            locale: 'es',\n            periodType: 'MONTHLY',\n        })\n\n        expect(result).toEqual(\n            expect.arrayContaining([\n                {\n                    id: '201501',\n                    iso: '201501',\n                    name: 'Enero de 2015',\n                    startDate: '2015-01-01',\n                    endDate: '2015-01-31',\n                },\n                // .... up to\n                {\n                    id: '201512',\n                    iso: '201512',\n                    name: 'Diciembre de 2015',\n                    startDate: '2015-12-01',\n                    endDate: '2015-12-31',\n                },\n            ])\n        )\n    })\n\n    it('should generate bi-monthly periods for the Ethiopic calendar for the specified year', () =\u003e {\n        const result = generateFixedPeriods({\n            year: 2015,\n            calendar: 'ethiopic',\n            locale: 'en',\n            periodType: 'BIMONTHLY',\n        })\n\n        expect(result).toEqual(\n            expect.arrayContaining([\n                {\n                    id: '201501B',\n                    iso: '201501B',\n                    name: 'Meskerem - Tekemt 2015',\n                    startDate: '2015-01-01',\n                    endDate: '2015-02-30',\n                },\n                // .... up to\n                {\n                    endDate: '2015-12-30',\n                    id: '201506B',\n                    iso: '201506B',\n                    name: 'Hamle - Nehasse 2015',\n                    startDate: '2015-11-01',\n                },\n            ])\n        )\n    })\n    it('should generate six-monthly periods for the Nepali calendar for the specified year', () =\u003e {\n        const result = generateFixedPeriods({\n            year: 2078,\n            calendar: 'nepali',\n            locale: 'en',\n            periodType: 'SIXMONTHLY',\n        })\n\n        expect(result).toEqual([\n            {\n                id: '2078S1',\n                iso: '2078S1',\n                name: 'Baisakh - Ashwin 2078',\n                startDate: '2078-01-01',\n                endDate: '2078-06-31',\n            },\n            {\n                id: '2078S2',\n                iso: '2078S2',\n                name: 'Kartik - Chaitra 2078',\n                startDate: '2078-07-01',\n                endDate: '2078-12-30',\n            },\n        ])\n    })\n})\n```\n\n### Types\n\nThe method takes a single `options` parameter and returns an\n`Array\u003cFixedPeriod\u003e`.\n\n#### The `options` param\n\nThe `options` param is an object of type `GeneratedPeriodParams`:\n\n```ts\ntype GeneratedPeriodParams = {\n    year: number\n    periodType: PeriodIdentifier\n    calendar: SupportedCalendar\n    locale?: string\n    startingDay?: number /** 1 is Monday */\n    yearsCount?: number\n}\n```\n\nFor convenience, `locale` can be passed in the Java-like style (i.e. `ar_SD`\nrather than `ar-SD`) and it will be converted to the JS-style. `yearsCount` is\nused when generating yearly periods to know how many years to generate\n(defaults to 10). `\n\n`periodType` can be one of the period identifiers defined\n[here](https://github.com/dhis2/multi-calendar-dates/blob/multi-calendar-docs/src/period-calculation/fixed-periods.ts#L10).\nAlthough the library internally is flexible and can accept, for example,\nquarterly periods starting any month (i.e. `QUARTERLYMAY`), support for such\nperiods might not be available in the backend, so they should be used\ncarefully. The flexibility, though, means that a new period could be added to\nthe backend and used without changes in the frontend.\n\n#### The return value\n\nThe returned value is an array of `FixedPeriod`s:\n\n```ts\ntype FixedPeriod = {\n    id: string\n    iso?: string\n    name: string\n    startDate: string\n    endDate: string\n}\n```\n\n-   `id` (and `iso`) are the same right now, and return a period identifier,\n    i.e. `2015Q1` (quarter one of the year 2015) or `2015SunW1` (the first week\n    -   starting Sunday - of the year 2015). The full list of values are\n        documented in\n        [periodValues](https://github.com/dhis2/multi-calendar-dates/blob/main/features/fixed-periods.ethiopic.feature)\n        in the feature files.\n\n\u003e please use `id` to identify the period. `iso` was added for backwards\n\u003e compatibility with existing implementations\n\n-   `startDate` and `endDate` are the start and end dates of the specific\n    period. This is provided for convenience, but the backend currently only\n    makes use of the ID to work out what each period means.\n-   `name` is the human readable name of the period, according to what we\n    dispaly in DHIS2 Data Visualizer, i.e. `Week 1 - 2014-13-03 - 2015-01-04`\n    for a weekly period or `2015` for a yearly period.\n\n## getNowInCalendar\n\n`getNowInCalendar` returns today's date in the specified calendrical system.\n\n### Examples\n\n```js\n// Assuming today's date is 13th October 2021\nbeforeEach(() =\u003e {\n    // 13 October 2021 UTC\n    jest.spyOn(Date, 'now').mockReturnValue(1634089600000)\n})\nit('should get today date in Gregorian', () =\u003e {\n    const { day, month, year } = getNowInCalendar('gregory', 'UTC')\n    expect({ day, month, year }).toEqual({ day: 13, month: 10, year: 2021 })\n})\nit('should get today date in Ethiopic', () =\u003e {\n    const { day, month, eraYear: year } = getNowInCalendar('ethiopic', 'UTC')\n    expect({ day, month, year }).toEqual({\n        day: 3,\n        month: 2,\n        year: 2014,\n    })\n})\n```\n\n## convertFromIso8601 and convertToIso8601\n\n`convertFromIso8601` and `convertToIso8601` are used to convert between Iso8601 (gregorian) dates and specific calendars (i.e. Ethiopic or Nepali). It accepts either a string in the format `yyyy-MM-dd` or an object representing the date properties (`year`, `month` and `day`).\n\n### Examples\n\n```js\nit('should convert a gregorian date to ethiopic', () =\u003e {\n    const result = convertFromIso8601('2024-05-23', 'ethiopic')\n    expect(result).toMatchObject({\n        year: 7516,\n        eraYear: 2016,\n        month: 9,\n        day: 15,\n    })\n})\nit('should convert a Nepali date to gregorian', () =\u003e {\n    const result = convertToIso8601('2081-02-10', 'nepali')\n    expect(result).toMatchObject({ year: 2024, month: 5, day: 23 })\n})\nit('should convert an ethiopic date to gregorian', () =\u003e {\n    const result = convertToIso8601('2016-09-15', 'ethiopic')\n    expect(result).toMatchObject({ year: 2024, month: 5, day: 23 })\n})\nit('should accept a date object instead of a string', () =\u003e {\n    const result = convertToIso8601(\n        {\n            year: 2081,\n            month: 2,\n            day: 10,\n        },\n        'nepali'\n    )\n    expect(result).toMatchObject({ year: 2024, month: 5, day: 23 })\n})\n```\n\n### Types\n\nThe method takes two positional arguments:\n\n-   `calendarToUse` which can be one of the calendars specified\n    [here](https://github.com/dhis2/multi-calendar-dates/blob/multi-calendar-docs/src/constants/calendars.ts)\n    (defaults to `gregory` calendar if not specied.\n-   `timeZone`: a string representing the time zone (defaults to `UTC`) of the\n    user.\n\nand returns a\n[ZonedDateTime](https://tc39.es/proposal-temporal/docs/zoneddatetime.html#properties)\nobject, which can be destructured to `.year` or `.eraYear` (`eraYear` preferred\nto avoid an issue with Ethiopic calendar), `.month`, `.day` which returns the\nvalues in the specified calendar, or you can `.getISOFields()` to return the\nunderlying iso8601 (gregory) date.\n\n\u003e ToDo: we should also return the date stringified into `yyyy-MM-dd` since this\n\u003e is the most common usecase and it would help clients not to have to do the\n\u003e conversion manually.\n\n## `createFixedPeriodFromPeriodId`\n\n-   [API](#createFixedPeriodFromPeriodId-api)\n-   [Examples](#createFixedPeriodFromPeriodId-examples)\n\n\u003ca name=\"createFixedPeriodFromPeriodId-api\"\u003e\u003c/a\u003e\n\n### API\n\n#### Arguments\n\nThe function expects an options object with the following properties:\n\n| Option     | type                | Required | Default value | Description                                    |\n| ---------- | ------------------- | -------- | ------------- | ---------------------------------------------- |\n| `periodId` | `string`            | yes      | -             | A period id, e.g. `2020` or `2020April`        |\n| `calendar` | `SupportedCalendar` | yes      | -             | A calendar sytem, e.g. `gregory` or `ethiopic` |\n| `locale`   | `string`            | no       | `\"en\"`        | A language locale for the displayed labels     |\n\n#### Return value\n\nReturns a `FixedPeriod` whos `id` equals the provided `periodId`, see `src/period-calculation/types.ts`\n\n\u003ca name=\"createFixedPeriodFromPeriodId-examples\"\u003e\u003c/a\u003e\n\n### Examples\n\n```ts\ncreateFixedPeriodFromPeriodId({\n    periodId: '2023',\n    calendar: 'gregory',\n})\n```\n\nwill return:\n\n```ts\n{\n    periodType: 'YEARLY',\n    name: '2023',\n    displayName: '2023',\n    id: '2023',\n    iso: '2023',\n    startDate: '2023-01-01',\n    endDate: '2023-12-31',\n}\n```\n\n## `getAdjacentFixedPeriods`\n\n-   [API](#getAdjacentFixedPeriods-api)\n-   [Examples](#getAdjacentFixedPeriods-examples)\n\n\u003ca name=\"getAdjacentFixedPeriods-api\"\u003e\u003c/a\u003e\n\n### API\n\n#### Arguments\n\nThe function expects an options object with the following properties:\n\n| Option     | Type                | Required | Default value | Description                                                       |\n| ---------- | ------------------- | -------- | ------------- | ----------------------------------------------------------------- |\n| `period`   | `FixedPeriod`       | yes      | -             | A period id, e.g. `2020` or `2020April`                           |\n| `calendar` | `SupportedCalendar` | yes      | -             | A calendar sytem, e.g. `gregory` or `ethiopic`                    |\n| `steps`    | `integer`           | no       | `1`           | Amount of adjacent fixed period forward/backward, can be negative |\n| `locale`   | `string`            | no       | `\"en\"`        | A language locale for the displayed labels                        |\n\n#### Return value\n\nReturns a collection with fixed periods, see `src/period-calculation/types.ts`\n\n\u003ca name=\"getAdjacentFixedPeriods-examples\"\u003e\u003c/a\u003e\n\n### Examples\n\n**With `steps: 1` (default):**\n\n```ts\nconst period = createFixedPeriodFromPeriodId({\n    periodId: '20230101',\n    calendar: 'gregory',\n})\ngetAdjacentFixedPeriods({\n    period,\n    calendar: 'gregory',\n})\n```\n\nwill return:\n\n```ts\n{\n    periodType: 'YEARLY',\n    name: '2023',\n    displayName: '2023',\n    id: '2023',\n    iso: '2023',\n    startDate: '2023-01-01',\n    endDate: '2023-12-31',\n}\n```\n\n**With `steps: 2`:**\n\n```ts\nconst period = createFixedPeriodFromPeriodId({\n    periodId: '20221230',\n    calendar: 'gregory',\n})\ngetAdjacentFixedPeriods({\n    period,\n    calendar: 'gregory',\n    steps: 2,\n})\n```\n\nwill return:\n\n```ts\n;[\n    {\n        periodType: 'DAILY',\n        name: '2022-12-31',\n        displayName: 'December 31, 2022',\n        id: '20221231',\n        iso: '20221231',\n        startDate: '2022-12-31',\n        endDate: '2022-12-31',\n    },\n    {\n        periodType: 'DAILY',\n        name: '2023-01-01',\n        displayName: 'January 1, 2023',\n        id: '20230101',\n        iso: '20230101',\n        startDate: '2023-01-01',\n        endDate: '2023-01-01',\n    },\n]\n```\n\n**With `steps: -3`:**\n\n```ts\nconst period = createFixedPeriodFromPeriodId({\n    periodId: '20230102',\n    calendar: 'gregory',\n})\ngetAdjacentFixedPeriods({\n    period,\n    calendar: 'gregory',\n    steps: -3,\n})\n```\n\nwill return:\n\n```ts\n;[\n    {\n        periodType: 'DAILY',\n        name: '2022-12-30',\n        displayName: 'December 30, 2022',\n        id: '20221230',\n        iso: '20221230',\n        startDate: '2022-12-30',\n        endDate: '2022-12-30',\n    },\n    {\n        periodType: 'DAILY',\n        name: '2022-12-31',\n        displayName: 'December 31, 2022',\n        id: '20221231',\n        iso: '20221231',\n        startDate: '2022-12-31',\n        endDate: '2022-12-31',\n    },\n    {\n        periodType: 'DAILY',\n        name: '2023-01-01',\n        displayName: 'January 1, 2023',\n        id: '20230101',\n        iso: '20230101',\n        startDate: '2023-01-01',\n        endDate: '2023-01-01',\n    },\n]\n```\n\n## `getFixedPeriodByDate`\n\n-   [API](#getFixedPeriodByDate-api)\n-   [Examples](#getFixedPeriodByDate-examples)\n\n\u003ca name=\"getFixedPeriodByDate-api\"\u003e\u003c/a\u003e\n\n### API\n\n#### Arguments\n\nThe function expects an options object with the following properties:\n\n| Option                                    | Type                | Required | Default value | Description                                    |\n| ----------------------------------------- | ------------------- | -------- | ------------- | ---------------------------------------------- |\n| `periodType`                              | `PeriodType`        | yes      | -             | E.g. `'YEARLY'` (see                           |\n| `src/period-calculation/period-types.ts`) |\n| `calendar`                                | `SupportedCalendar` | yes      | -             | A calendar sytem, e.g. `gregory` or `ethiopic` |\n| `date`                                    | `string`            | yes      | -             | E.g. `'2020-10-04'`                            |\n| `locale`                                  | `string`            | no       | `\"en\"`        | A language locale for the displayed labels     |\n\n#### Return value\n\nReturns a fixed period, see `src/period-calculation/types.ts`\n\n\u003ca name=\"getFixedPeriodByDate-examples\"\u003e\u003c/a\u003e\n\n### Examples\n\n```ts\ngetFixedPeriodByDate({\n    periodType: 'DAILY',\n    date: '2022-01-01',\n    calendar: 'gregory',\n})\n```\n\nwill return:\n\n```ts\n{\n    periodType: 'DAILY',\n    id: '20220101',\n    iso: '20220101',\n    displayName: 'January 1, 2022',\n    name: '2022-01-01',\n    startDate: '2022-01-01',\n    endDate: '2022-01-01'\n}\n```\n\n## `periodTypes`\n\nSome functions require a period type to be passed. Instead of hardcoding string\nor manually replicating the constants used in the library, they're being\nexposed. `periodTypes` is an array with string, which can easily be converted\nto an object:\n\n```ts\nconst obj = periodTypes.reduce(\n    (acc, periodType) =\u003e ({ ...acc, [periodType]: periodType }),\n    {}\n)\n```\n\n### API\n\nFor all available period types, see: `src/period-calculation/period-types.ts`\n\n```ts\ntype periodTypes = string[]\n```\n\n# Special cases and considerations with periods logic\n\nThere are some special DHIS2-specific cases when doing period calculations that\nwe handle. The specs for these are documented in the tests and cucumber feature\nfiles. Some of these are:\n\n-   Dealing with the 13th month in the Ethiopian calendar: the Ethiopic\n    calendar has 13 months: 12 months of 30 days, then the 13th month has 5 or\n    6 days depending on whether it's a leap year or not. When generating\n    periods for bi-weekly or less (bi-weekly, weekly, daily) then the 13th\n    month is displayed. But when we are generating periods for monthly or\n    larger, we do not display the 13th month. The backend then makes the\n    decision about where to include any data for the 13th month, so when doing\n    monthly analytics, the 13th month will be lumped with the following month's\n    data.\n-   The period IDs are dictated by the backend, and they use the Gregorian\n    month names, for example, `QuarterlyNov` or `QuarterlyApr`. In the\n    frontend, in a non-Gregorian calendar, `Nov` will be \"translated\" into the\n    11th month of the current calendar system (Hamle in Ethiopic). Ideally, we\n    will have period IDs that are calendar-agnostic in the future, but this\n    works for now as a solution.\n-   From the frontend perspective, we always send a date in the calendar\n    system. It is formatted in an ISO-like format (`yyyy-MM-dd`). It is\n    \"ISO-like\", because the date is actually in the calendar system rather than\n    the Gregorian ISO8601 calendar. So when a system set to Ethiopic, sends\n    `2015-01-01` to the backend, it means the year 2015 in Ethiopic (which is\n    2022 in Gregorian). This is what the backend expects, and if there is a\n    need for a conversion, then it happens in the backend.\n    -   Given we have the Temporal object in the frontend, we have the ability\n        to send the date converted into Gregorian, but that's what the backend\n        expects right now.\n-   Nepali is implemented as a custom calendar: see\n    [nepaliCalendar.ts](https://github.com/dhis2/multi-calendar-dates/blob/multi-calendar-docs/src/custom-calendars/nepaliCalendar.ts).\n    Nepali is luni-solar calendar which means that the length of months can\n    vary from year to year. The way Temporal does date arithmetic, is that it\n    converts the day to gregorian, performs the arithmetic, then converts it\n    back to the calendar. This leads to weird situations where you do\n    `nepaliDate.add({month: 1})` for example, but the result is still in the\n    current month (since the month has 30 days in Gregorian, but 32 in Nepali\n    for example). To get around that, we [set the day to the\n    14th](https://github.com/dhis2/multi-calendar-dates/blob/73057360c4720d995370779b02fe6e3784b326ab/src/period-calculation/getYearlyPeriods.ts#L23)\n    before doing arithmetic (in the context of period generation) and this\n    leads to results that make sense in the context of DHIS2.\n-   Nepali is also not supported as a locale in major browsers. So we rely on\n    localising it with a hardcoded map, meaning we don't have the same\n    flexibility showing the day or month names as short or long for example\n    with `.toLocaleString`. That means that the consumer can only use one of\n    `ne-NP` or `en-NP` (nepali months and days transliterated into English) as\n    a locale with the Nepali calendar, and they can't use browser localisations\n    (so they can't display Nepali month names in French for example, but they\n    would be able to do so with Ehtiopic or Islamic calendars).\n-   In Ethiopic calendar, when consuming a date, use `.eraYear` rather than\n    `.year` to get the year part of a date. There is an ongoing discussion on\n    Temporal to decide which era of the Ethiopic calendar should be the\n    default, but it's likely to be browser-dependent at the end, so it's safer\n    to use `eraYear` as this is what users in Ethiopia would expect to see. We\n    looked at abstracting this difference away in the library, but there was no\n    easy solution for it.\n-   The library also abstracts some DHIS2-specific inconsistencies with the\n    backend to make it easier for consumers of the library. For example, it has\n    a map to convert from the calendar IDs used in DHIS2 to the ones expected\n    by the library:\n    [dhis2CalendarsMap.ts](https://github.com/dhis2/multi-calendar-dates/blob/multi-calendar-docs/src/constants/dhis2CalendarsMap.ts).\n    This maps \"ethiopian\" (used in DHIS2) to \"ethiopic\" which is the standard\n    ID used in JavaScript (in [Unicode CLDR](https://cldr.unicode.org/)). It\n    also maps the Java locale names with an underscore to ones with a dash, to\n    make them easier to use in a JavaScript context (i.e from `ar_SD` to\n    `ar-SD`).\n\n# Hooks for Calendar UI\n\nThe library provides a hook `useDatePicker` that's consumed by the dhis/ui\ncalendar components. There are two components that currently use it:\n\n-   [Calendar](https://ui.dhis2.nu/components/calendar): a calendar component\n    supporting non-Greogorian calendars\n-   [CalendarInput](https://ui.dhis2.nu/components/calendar-input): a wrapper\n    around the Calendar component to support the most common use case for a\n    calendar when we want to display it next to an input.\n\n`useDatePicker` takes a\n[DatePickerOptions](https://github.com/dhis2/multi-calendar-dates/blob/multi-calendar-docs/src/hooks/useDatePicker.ts#L16)\nobject, and returns\n[UseDatePickerReturn](https://github.com/dhis2/multi-calendar-dates/blob/multi-calendar-docs/src/hooks/useDatePicker.ts#LL28C13-L28C32)\nthat contains information needed to render a UI component, i.e. the localised\nday names, and the weeks in the current view (month).\n\n```ts\n// the options passed to useDatePicker\ntype DatePickerOptions = {\n    date: string\n    options: PickerOptions\n    onDateSelect: ({\n        calendarDate,\n        calendarDateString,\n    }: {\n        calendarDate: Temporal.ZonedDateTime\n        calendarDateString: string\n    }) =\u003e void\n}\n```\n\n```ts\n// the return type of the hook\ntype UseDatePickerReturn = UseNavigationReturnType \u0026 {\n    weekDayLabels: string[]\n    calendarWeekDays: {\n        zdt: Temporal.ZonedDateTime\n        label: string | number\n        calendarDate: string\n        onClick: () =\u003e void\n        isSelected: boolean | undefined\n        isToday: boolean\n        isInCurrentMonth: boolean\n    }[][]\n}\n```\n\n# Architecture Design Records\n\n-   [Use Temporal API as the backbone of the\n    engine](./doc/architecture/decisions/0002-use-temporal-api-as-the-backbone-for-the-engine.md)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdhis2%2Fmulti-calendar-dates","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdhis2%2Fmulti-calendar-dates","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdhis2%2Fmulti-calendar-dates/lists"}