Skip to content

Commit

Permalink
Merge pull request #149 from 2wheeh/feature/issue-127/time
Browse files Browse the repository at this point in the history
[#127] Add Time component
  • Loading branch information
2wheeh authored Feb 3, 2024
2 parents 9b2bae7 + 82cd1bf commit 9930e0e
Show file tree
Hide file tree
Showing 8 changed files with 123 additions and 1 deletion.
3 changes: 3 additions & 0 deletions ui/packages/mr-c.app/global-setup.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function globalSetup() {
process.env.TZ = 'Asia/Seoul';
}
3 changes: 2 additions & 1 deletion ui/packages/mr-c.app/jest.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ const createJestConfig = nextJest({
// Add any custom config to be passed to Jest
/** @type {import('jest').Config} */
const config = {
globalSetup: '<rootDir>/global-setup.mjs',
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
// https://github.com/jsdom/jsdom/issues/1724
testEnvironment: './fixed-environment-jsdom.ts',
testEnvironment: '<rootDir>/fixed-environment-jsdom.ts',
};

// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
Expand Down
1 change: 1 addition & 0 deletions ui/packages/mr-c.app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"@mrc/common-components": "workspace:*",
"@mrc/common-utils": "workspace:*",
"clsx": "^2.1.0",
"dayjs": "^1.11.10",
"next": "^14.0.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
Expand Down
19 changes: 19 additions & 0 deletions ui/packages/mr-c.app/src/components/time/client-time.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use client';

import { type Locale, formatDateToLocal } from '@/lib/utils/format-date-to-local';
import Text, { type TextProps } from '@/components/atomic/text';

interface Props extends Omit<TextProps, 'children'> {
dateStr: string;
locale?: Locale;
relative?: boolean;
}

export default function ClientTime({
dateStr,
locale = 'ko-KR',
relative = false,
...textProps
}: Props) {
return <Text {...textProps}>{formatDateToLocal(dateStr, locale, relative)}</Text>;
}
4 changes: 4 additions & 0 deletions ui/packages/mr-c.app/src/components/time/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import dynamic from 'next/dynamic';
const Time = dynamic(() => import('@/components/time/client-time'), { ssr: false });

export default Time;
51 changes: 51 additions & 0 deletions ui/packages/mr-c.app/src/lib/utils/format-date-to-local.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { formatDateToLocal } from '@/lib/utils/format-date-to-local';

function getRelativeDateStr(interval: number) {
const currentDateTime = new Date().getTime();
return new Date(currentDateTime - interval).toISOString();
}

describe('formatDateToLocal', () => {
describe('default mode', () => {
const certainDateStr = '2023-04-02T15:08:00+09:00';

test('should format time in Korean', () => {
expect(formatDateToLocal(certainDateStr, 'ko-KR')).toBe('2023년 4월 2일');
});

test('should format time in English', () => {
expect(formatDateToLocal(certainDateStr, 'en-US')).toBe('Apr 2, 2023');
});
});

describe('relative mode', () => {
let relativeDateStr: string;

test.each([
{ interval: 1000 * 5, expected: '몇 초 전' },
{ interval: 1000 * 89, expected: '1분 전' },
{ interval: 1000 * 91, expected: '2분 전' },
{ interval: 1000 * 60 * 60 * 24 * 3, expected: '3일 전' },
{ interval: -1 * 1000 * 90, expected: '2분 후' },
])('should format time in Korean', ({ interval, expected }) => {
relativeDateStr = getRelativeDateStr(interval);
expect(formatDateToLocal(relativeDateStr, 'ko-KR', true)).toBe(expected);
});

test.each([
{ interval: 1000 * 5, expected: 'a few seconds ago' },
{ interval: 1000 * 89, expected: 'a minute ago' },
{ interval: 1000 * 91, expected: '2 minutes ago' },
{ interval: 1000 * 60 * 60 * 24 * 3, expected: '3 days ago' },
{ interval: -1 * 1000 * 90, expected: 'in 2 minutes' },
])('should format time in English', ({ interval, expected }) => {
relativeDateStr = getRelativeDateStr(interval);
expect(formatDateToLocal(relativeDateStr, 'en-US', true)).toBe(expected);
});
});

test('should throw an Error when date string is not valid', () => {
const invalidDateStr = 'foo';
expect(() => formatDateToLocal(invalidDateStr, 'en-US')).toThrowError(invalidDateStr);
});
});
35 changes: 35 additions & 0 deletions ui/packages/mr-c.app/src/lib/utils/format-date-to-local.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import 'dayjs/locale/ko';

dayjs.extend(relativeTime);

function isInvalid(dateStr: string) {
return isNaN(Date.parse(dateStr));
}

export type Locale = 'ko-KR' | 'en-US';

// If this function is called on server, it would reflect
// the timezone where the server at rather than the client at.
// This could cause a react hydration error.
export function formatDateToLocal(dateStr: string, locale: Locale, relative = false) {
if (isInvalid(dateStr)) {
throw new Error(`${dateStr} is not a valid date string`);
}

const date = new Date(dateStr);

if (relative) {
return dayjs(date).locale(locale).fromNow();
}

const options: Intl.DateTimeFormatOptions = {
day: 'numeric',
month: 'short',
year: 'numeric',
};
const formatter = new Intl.DateTimeFormat(locale, options);

return formatter.format(date);
}
8 changes: 8 additions & 0 deletions ui/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2291,6 +2291,7 @@ __metadata:
"@typescript-eslint/parser": ^6.7.3
autoprefixer: ^10.4.16
clsx: ^2.1.0
dayjs: ^1.11.10
eslint: ^8.51.0
eslint-config-next: ^13.5.5
jest: ^29.7.0
Expand Down Expand Up @@ -6656,6 +6657,13 @@ __metadata:
languageName: node
linkType: hard

"dayjs@npm:^1.11.10":
version: 1.11.10
resolution: "dayjs@npm:1.11.10"
checksum: a6b5a3813b8884f5cd557e2e6b7fa569f4c5d0c97aca9558e38534af4f2d60daafd3ff8c2000fed3435cfcec9e805bcebd99f90130c6d1c5ef524084ced588c4
languageName: node
linkType: hard

"debug@npm:2.6.9, debug@npm:^2.6.9":
version: 2.6.9
resolution: "debug@npm:2.6.9"
Expand Down

0 comments on commit 9930e0e

Please sign in to comment.