diff --git a/.changeset/neat-years-remain.md b/.changeset/neat-years-remain.md new file mode 100644 index 00000000..1fc9ddad --- /dev/null +++ b/.changeset/neat-years-remain.md @@ -0,0 +1,5 @@ +--- +"@razorpay/i18nify-js": patch +--- + +feat: add timezone getter function in dateTime module diff --git a/packages/i18nify-js/README.md b/packages/i18nify-js/README.md index e1f5b317..0eab6a57 100644 --- a/packages/i18nify-js/README.md +++ b/packages/i18nify-js/README.md @@ -1150,6 +1150,48 @@ console.log(parsed3); // Outputs object with date components for January 23, 202 πŸ’‘ Pro Tip: Leverage `parseDateTime` in applications where detailed date analysis and manipulation are key, such as in calendar apps, scheduling tools, or date-sensitive data processing. It's like having a Swiss Army knife for all things related to dates and times! πŸ“…πŸ› οΈ +#### getTimezoneList() + +`getTimezoneList` 🌍 dynamically imports country-specific timezone information πŸ•’, offering a detailed map πŸ—ΊοΈ of timezones and capital city timezones keyed by country codes. Ideal for applications needing timezone data for location-based services πŸ“ or global scheduling features πŸ“…. It returns a Promise πŸ”„ resolving to a map of country codes to timezone information, including each country's timezones 🌐 and the timezone of its capital πŸ›οΈ. + +##### Examples + +```javascript + +console.log(await getTimezoneList()); +/* + { + AF: { + timezone_of_capital: 'Asia/Kabul', + timezones: { + 'Asia/Kabul': { + utc_offset: 'UTC +04:30', + }, + }, + }, + IN: { + timezone_of_capital: 'Asia/Kolkata', + timezones: { + 'Asia/Kolkata': { + utc_offset: 'UTC +05:30', + }, + }, + }, + // rest of the countries + } +*/ +``` + +#### getTimeZoneByCountry(countryCode) + +`getTimeZoneByCountry` asynchronously fetches the time zone ⏰ for the capital city πŸ™οΈ of a given country, using its country code 🌍. It's crafted for applications that require displaying or working with time zone-specific information πŸ•’ across different locations globally 🌎. + +##### Examples + +```javascript +console.log(await getTimeZoneByCountry('AF')); // 'Asia/Kabul' +``` + #### Calendar, CalendarDate, CalendarDateTime, Time, ZonedDateTime Leverage the power of Adobe's @internationalized/date with our module, designed to offer a sophisticated, locale-sensitive approach to managing dates and times. Utilize these advanced tools to create applications that are both intuitive and efficient, ensuring they connect with users worldwide. diff --git a/packages/i18nify-js/src/modules/dateTime/__tests__/getTimeZoneByCountry.spec.ts b/packages/i18nify-js/src/modules/dateTime/__tests__/getTimeZoneByCountry.spec.ts new file mode 100644 index 00000000..7fcfb56b --- /dev/null +++ b/packages/i18nify-js/src/modules/dateTime/__tests__/getTimeZoneByCountry.spec.ts @@ -0,0 +1,20 @@ +import { test } from '@playwright/test'; +import { getTimeZoneByCountry } from '../index'; +import { assertScriptText, injectScript } from '../../../blackbox/utils'; + +test.describe('getTimeZoneByCountry', () => { + test.beforeEach(async ({ page }) => { + await page.exposeFunction('getTimeZoneByCountry', getTimeZoneByCountry); + }); + + test('should print the correct timezone of a country capital from countries meta data', async ({ + page, + }) => { + await injectScript( + page, + `await getTimeZoneByCountry('AF').then(res => res)`, + ); + + await assertScriptText(page, 'Asia/Kabul'); + }); +}); \ No newline at end of file diff --git a/packages/i18nify-js/src/modules/dateTime/__tests__/getTimeZoneByCountry.test.ts b/packages/i18nify-js/src/modules/dateTime/__tests__/getTimeZoneByCountry.test.ts new file mode 100644 index 00000000..04656276 --- /dev/null +++ b/packages/i18nify-js/src/modules/dateTime/__tests__/getTimeZoneByCountry.test.ts @@ -0,0 +1,32 @@ +import { getTimeZoneByCountry } from '../index'; +import { COUNTRIES_METADATA } from '../mocks/country'; +import { CountryMetaType } from '../types'; + +type MockResponse = { + metadata_information: Record; +}; + +global.fetch = jest.fn(() => + Promise.resolve({ + ok: true, + status: 200, + json: () => Promise.resolve(COUNTRIES_METADATA as any), + } as Response), +); + +describe('getTimeZoneByCountry', () => { + it('fetches country metadata correctly', async () => { + const timeZoneAF = await getTimeZoneByCountry('AF'); + expect(timeZoneAF).toBe('Asia/Kabul'); + + const timeZoneIN = await getTimeZoneByCountry('IN'); + expect(timeZoneIN).toBe('Asia/Kolkata'); + }); + + it('handles API errors', async () => { + global.fetch = jest.fn(() => Promise.reject('API Error')); + await expect(getTimeZoneByCountry('XYZ')).rejects.toThrow( + 'Error in API response', + ); + }); +}); diff --git a/packages/i18nify-js/src/modules/dateTime/__tests__/getTimezoneList.spec.ts b/packages/i18nify-js/src/modules/dateTime/__tests__/getTimezoneList.spec.ts new file mode 100644 index 00000000..da94c96d --- /dev/null +++ b/packages/i18nify-js/src/modules/dateTime/__tests__/getTimezoneList.spec.ts @@ -0,0 +1,20 @@ +import { test } from '@playwright/test'; +import { getTimezoneList } from '../index'; +import { assertScriptText, injectScript } from '../../../blackbox/utils'; + +test.describe('getTimezoneList', () => { + test.beforeEach(async ({ page }) => { + await page.exposeFunction('getTimezoneList', getTimezoneList); + }); + + test('should print the correct timezone of a country capital from countries meta data', async ({ + page, + }) => { + await injectScript( + page, + `await getTimezoneList().then(res => res.AF.timezone_of_capital)`, + ); + + await assertScriptText(page, 'Asia/Kabul'); + }); +}); \ No newline at end of file diff --git a/packages/i18nify-js/src/modules/dateTime/__tests__/getTimezoneList.test.ts b/packages/i18nify-js/src/modules/dateTime/__tests__/getTimezoneList.test.ts new file mode 100644 index 00000000..252f1857 --- /dev/null +++ b/packages/i18nify-js/src/modules/dateTime/__tests__/getTimezoneList.test.ts @@ -0,0 +1,45 @@ +import { getTimezoneList } from '../index'; +import { COUNTRIES_METADATA } from '../mocks/country'; +import { CountryMetaType } from '../types'; + +type MockResponse = { + metadata_information: Record; +}; + +global.fetch = jest.fn(() => + Promise.resolve({ + ok: true, + status: 200, + json: () => Promise.resolve(COUNTRIES_METADATA as any), + } as Response), +); + +describe('getTimezoneList', () => { + it('fetches country metadata correctly', async () => { + const expectedTimezoneListObj = { + AF: { + timezone_of_capital: 'Asia/Kabul', + timezones: { + 'Asia/Kabul': { + utc_offset: 'UTC +04:30', + }, + }, + }, + IN: { + timezone_of_capital: 'Asia/Kolkata', + timezones: { + 'Asia/Kolkata': { + utc_offset: 'UTC +05:30', + }, + }, + }, + }; + const timeZoneList = await getTimezoneList(); + expect(timeZoneList).toEqual(expectedTimezoneListObj); + }); + + it('handles API errors', async () => { + global.fetch = jest.fn(() => Promise.reject('API Error')); + await expect(getTimezoneList()).rejects.toThrow('Error in API response'); + }); +}); diff --git a/packages/i18nify-js/src/modules/dateTime/constants.ts b/packages/i18nify-js/src/modules/dateTime/constants.ts index 32def42c..7d1ae848 100644 --- a/packages/i18nify-js/src/modules/dateTime/constants.ts +++ b/packages/i18nify-js/src/modules/dateTime/constants.ts @@ -13,3 +13,6 @@ export const ALLOWED_FORMAT_PARTS_KEYS = [ 'year', 'yearName', ] as const; + +export const I18NIFY_DATA_SOURCE = + 'https://raw.githubusercontent.com/razorpay/i18nify/master/i18nify-data'; diff --git a/packages/i18nify-js/src/modules/dateTime/getTimeZoneByCountry.ts b/packages/i18nify-js/src/modules/dateTime/getTimeZoneByCountry.ts new file mode 100644 index 00000000..d828367d --- /dev/null +++ b/packages/i18nify-js/src/modules/dateTime/getTimeZoneByCountry.ts @@ -0,0 +1,33 @@ +import { withErrorBoundary } from '../../common/errorBoundary'; +import { I18NIFY_DATA_SOURCE } from './constants'; + +/** + * Asynchronously retrieves the timezone of the capital city for a given country code. + * + * @param countryCode The country code for which the timezone of the capital city is requested. + * @returns A promise that resolves to the timezone of the capital city for the specified country code. + * @throws Error if the country code is not found in the dynamically imported dataset or if there's an API response error. + */ +const getTimeZoneByCountry = async (countryCode: string): Promise => { + try { + const response = await fetch( + `${I18NIFY_DATA_SOURCE}/country/metadata/data.json`, + ); + const data = await response.json(); + + const timezone = + data.metadata_information[countryCode]?.timezone_of_capital; + + if (!timezone) { + throw new Error(`Invalid countryCode: ${countryCode}`); + } + + return timezone; + } catch (err) { + throw new Error(`Error in API response: ${(err as Error).message}`); + } +}; + +export default withErrorBoundary( + getTimeZoneByCountry, +); diff --git a/packages/i18nify-js/src/modules/dateTime/getTimezoneList.ts b/packages/i18nify-js/src/modules/dateTime/getTimezoneList.ts new file mode 100644 index 00000000..e6535491 --- /dev/null +++ b/packages/i18nify-js/src/modules/dateTime/getTimezoneList.ts @@ -0,0 +1,36 @@ +import { withErrorBoundary } from '../../common/errorBoundary'; +import { I18NIFY_DATA_SOURCE } from './constants'; +import { CountryDataApiResponse, CountryMetaType } from './types'; + +/** + * Dynamically imports country data and extracts timezone information for each country. + * + * This function transforms the imported COUNTRY_DATA dataset to a map where each key is a country code, + * associated with an object that includes the timezones (an object with timezone names as keys + * and their utc_offset as values) and the timezone_of_capital (the timezone in which the capital city resides). + * + * @returns A Promise that resolves to a map with country codes as keys and their respective timezone information. + */ +const getTimezoneList = async (): Promise> => { + try { + const response = await fetch( + `${I18NIFY_DATA_SOURCE}/country/metadata/data.json`, + ); + const data: CountryDataApiResponse = await response.json(); + + return Object.entries(data.metadata_information).reduce( + (acc, [countryCode, countryMetadata]) => ({ + ...acc, + [countryCode]: { + timezones: countryMetadata.timezones, + timezone_of_capital: countryMetadata.timezone_of_capital, + }, + }), + {}, + ); + } catch (err) { + throw new Error(`Error in API response: ${(err as Error).message}`); + } +}; + +export default withErrorBoundary(getTimezoneList); diff --git a/packages/i18nify-js/src/modules/dateTime/index.ts b/packages/i18nify-js/src/modules/dateTime/index.ts index 157b583a..5680e3c7 100644 --- a/packages/i18nify-js/src/modules/dateTime/index.ts +++ b/packages/i18nify-js/src/modules/dateTime/index.ts @@ -8,7 +8,8 @@ export { default as formatDateTime } from './formatDateTime'; export { default as getRelativeTime } from './getRelativeTime'; export { default as getWeekdays } from './getWeekdays'; export { default as parseDateTime } from './parseDateTime'; - +export { default as getTimezoneList } from './getTimezoneList'; +export { default as getTimeZoneByCountry } from './getTimeZoneByCountry'; // For additional information, refer to the documentation: https://react-spectrum.adobe.com/internationalized/date/index.html /** * Direct exports include Calendar, CalendarDate, CalendarDateTime, Time, and ZonedDateTime. diff --git a/packages/i18nify-js/src/modules/dateTime/mocks/country.ts b/packages/i18nify-js/src/modules/dateTime/mocks/country.ts new file mode 100644 index 00000000..29251b08 --- /dev/null +++ b/packages/i18nify-js/src/modules/dateTime/mocks/country.ts @@ -0,0 +1,136 @@ +export const COUNTRIES_METADATA = { + metadata_information: { + AF: { + country_name: 'Afghanistan', + continent_code: 'AS', + continent_name: 'Asia', + alpha_3: 'AFG', + numeric_code: '004', + flag: 'https://flagcdn.com/af.svg', + sovereignty: 'UN member state', + dial_code: '+93', + supported_currency: ['AFN'], + timezones: { + 'Asia/Kabul': { + utc_offset: 'UTC +04:30', + }, + }, + timezone_of_capital: 'Asia/Kabul', + locales: { + fa_AF: { + name: 'Persian (Afghanistan)', + }, + ps: { + name: 'Pashto', + }, + uz_AF: { + name: 'Uzbek', + }, + tk: { + name: 'Turkmen', + }, + }, + default_locale: 'fa_AF', + default_currency: 'AFN', + }, + IN: { + country_name: 'India', + continent_code: 'AS', + continent_name: 'Asia', + alpha_3: 'IND', + numeric_code: '356', + flag: 'https://flagcdn.com/in.svg', + sovereignty: 'UN member state', + dial_code: '+91', + supported_currency: ['INR'], + timezones: { + 'Asia/Kolkata': { + utc_offset: 'UTC +05:30', + }, + }, + timezone_of_capital: 'Asia/Kolkata', + locales: { + en_IN: { + name: 'English (India)', + }, + hi: { + name: 'Hindi', + }, + bn: { + name: 'Bangla', + }, + te: { + name: 'Telugu', + }, + mr: { + name: 'Marathi', + }, + ta: { + name: 'Tamil', + }, + ur: { + name: 'Urdu', + }, + gu: { + name: 'Gujarati', + }, + kn: { + name: 'Kannada', + }, + ml: { + name: 'Malayalam', + }, + or: { + name: 'Odia', + }, + pa: { + name: 'Punjabi', + }, + as: { + name: 'Assamese', + }, + bh: { + name: 'Bihari languages', + }, + sat: { + name: 'Santali', + }, + ks: { + name: 'Kashmiri', + }, + ne: { + name: 'Nepali', + }, + sd: { + name: 'Sindhi', + }, + kok: { + name: 'Konkani', + }, + doi: { + name: 'Dogri', + }, + mni: { + name: 'Manipuri', + }, + sit: { + name: 'Sino-Tibetan languages', + }, + sa: { + name: 'Sanskrit', + }, + fr: { + name: 'French', + }, + lus: { + name: 'Lushai', + }, + inc: { + name: 'Indic languages', + }, + }, + default_locale: 'en_IN', + default_currency: 'INR', + }, + }, +}; diff --git a/packages/i18nify-js/src/modules/dateTime/types.ts b/packages/i18nify-js/src/modules/dateTime/types.ts index bfd697ab..e9397363 100644 --- a/packages/i18nify-js/src/modules/dateTime/types.ts +++ b/packages/i18nify-js/src/modules/dateTime/types.ts @@ -31,3 +31,36 @@ export interface SupportedDateFormats { secondIndex?: number; format: string; } + +export interface TimezoneInfo { + utc_offset: string; +} + +export interface LocaleInfo { + name: string; +} + +export interface CountryMetaType { + country_name: string; + continent_code: string; + continent_name: string; + alpha_3: string; + numeric_code: string; + flag: string; + sovereignty: string; + dial_code: string; + supportedCurrency: string[]; + timezones: Record; + timezone_of_capital: string; + locales: Record; + default_locale: string; + default_currency: string; +} + +export interface CountryMetadataInformation { + [countryCode: string]: CountryMetaType; +} + +export interface CountryDataApiResponse { + metadata_information: CountryMetadataInformation; +}