Skip to content

Commit

Permalink
feat(legacy): InputTime & InputDateTime support AM / PM forma…
Browse files Browse the repository at this point in the history
…ts (#9595)
  • Loading branch information
nsbarsukov authored Oct 24, 2024
1 parent 05456e5 commit 0f67a78
Show file tree
Hide file tree
Showing 37 changed files with 430 additions and 132 deletions.
74 changes: 63 additions & 11 deletions projects/cdk/date-time/test/time.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,35 @@ describe('TuiTime', () => {
expect(time.seconds).toBe(0);
expect(time.ms).toBe(888);
});

describe('mode with AM / PM', () => {
(
[
['12:00 AM', {hours: 0, minutes: 0}],
['12:34 AM', {hours: 0, minutes: 34}],
['12:59 AM', {hours: 0, minutes: 59}],
['01:00 AM', {hours: 1, minutes: 0}],
['11:00 AM', {hours: 11, minutes: 0}],
['11:59 AM', {hours: 11, minutes: 59}],
['12:00 PM', {hours: 12, minutes: 0}],
['12:01 PM', {hours: 12, minutes: 1}],
['12:59 PM', {hours: 12, minutes: 59}],
['01:00 PM', {hours: 13, minutes: 0}],
['11:00 PM', {hours: 23, minutes: 0}],
['11:59 PM', {hours: 23, minutes: 59}],
['04:59', {hours: 4, minutes: 59}],
] as const
).forEach(([timeString, {hours, minutes}]) => {
it(`from ${timeString}`, () => {
const time = TuiTime.fromString(timeString);

expect(time.hours).toBe(hours);
expect(time.minutes).toBe(minutes);
expect(time.seconds).toBe(0);
expect(time.ms).toBe(0);
});
});
});
});

describe('current', () => {
Expand Down Expand Up @@ -334,22 +363,45 @@ describe('TuiTime', () => {
});
});

it('stringify', () => {
const time = new TuiTime(6, 36, 1, 1);
describe('toString(mode) method', () => {
it('without mode parameter', () => {
const time = new TuiTime(6, 36, 1, 1);

expect(time.toString()).toBe('06:36:01.001');
});
expect(time.toString()).toBe('06:36:01.001');
});

it('stringify and fill zeros for seconds', () => {
const time = new TuiTime(6, 36, 0, 0);
it('stringify and fill zeros for seconds', () => {
const time = new TuiTime(6, 36, 0, 0);

expect(time.toString('HH:MM:SS')).toBe('06:36:00');
});
expect(time.toString('HH:MM:SS')).toBe('06:36:00');
});

it('stringify and fill zeros for seconds and ms', () => {
const time = new TuiTime(6, 36, 0, 0);
it('stringify and fill zeros for seconds and ms', () => {
const time = new TuiTime(6, 36, 0, 0);

expect(time.toString('HH:MM:SS.MSS')).toBe('06:36:00.000');
expect(time.toString('HH:MM:SS.MSS')).toBe('06:36:00.000');
});

describe('HH:MM AA', () => {
(
[
[new TuiTime(0, 0), '12:00 AM'],
[new TuiTime(0, 30), '12:30 AM'],
[new TuiTime(0, 59), '12:59 AM'],
[new TuiTime(1, 1), '01:01 AM'],
[new TuiTime(11, 11), '11:11 AM'],
[new TuiTime(11, 59), '11:59 AM'],
[new TuiTime(12, 0), '12:00 PM'],
[new TuiTime(13, 0), '01:00 PM'],
[new TuiTime(16, 0), '04:00 PM'],
[new TuiTime(23, 59), '11:59 PM'],
] as const
).forEach(([time, timeString]) => {
it(`{hours: ${time.hours}, minutes: ${time.minutes}} => ${timeString}`, () => {
expect(time.toString('HH:MM AA')).toBe(timeString);
});
});
});
});

describe('valueOf returns', () => {
Expand Down
48 changes: 43 additions & 5 deletions projects/cdk/date-time/time.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/// <reference types="@taiga-ui/tsconfig/ng-dev-mode" />

import {CHAR_NO_BREAK_SPACE} from '@taiga-ui/cdk/constants';
import {tuiInRange} from '@taiga-ui/cdk/utils/math';

import {
Expand Down Expand Up @@ -113,7 +114,7 @@ export class TuiTime implements TuiTimeLike {
* Parses string into TuiTime object
*/
public static fromString(time: string): TuiTime {
const hours = Number(time.slice(0, 2));
const hours = this.parseHours(time);
const minutes = Number(time.slice(3, 5)) || 0;
const seconds = Number(time.slice(6, 8)) || 0;
const ms = Number(time.slice(9, 12)) || 0;
Expand All @@ -134,6 +135,29 @@ export class TuiTime implements TuiTimeLike {
);
}

private static parseMeridiemPeriod(time: string): 'AM' | 'PM' | null {
return (
(/[AP]M/.exec(time.toUpperCase().replaceAll(/\W/g, ''))?.[0] as
| 'AM'
| 'PM') || null
);
}

private static parseHours(time: string): number {
const hours = Number(time.slice(0, 2));
const meridiem = this.parseMeridiemPeriod(time);

if (!meridiem) {
return hours;
}

if (hours === 12) {
return meridiem === 'AM' ? 0 : 12;
}

return meridiem === 'PM' ? hours + 12 : hours;
}

/**
* Shifts time by hours and minutes
*/
Expand Down Expand Up @@ -165,14 +189,18 @@ export class TuiTime implements TuiTimeLike {
* Converts TuiTime to string
*/
public toString(mode?: TuiTimeMode): string {
const needAddMs = mode === 'HH:MM:SS.MSS' || (!mode && this.ms > 0);
const needAddMs = mode?.startsWith('HH:MM:SS.MSS') || (!mode && this.ms > 0);
const needAddSeconds =
needAddMs || mode === 'HH:MM:SS' || (!mode && this.seconds > 0);
const hhMm = `${this.formatTime(this.hours)}:${this.formatTime(this.minutes)}`;
needAddMs || mode?.startsWith('HH:MM:SS') || (!mode && this.seconds > 0);
const {hours = this.hours, meridiem = ''} = mode?.includes('AA')
? this.toTwelveHour(this.hours)
: {};
const hhMm = `${this.formatTime(hours)}:${this.formatTime(this.minutes)}`;
const ss = needAddSeconds ? `:${this.formatTime(this.seconds)}` : '';
const mss = needAddMs ? `.${this.formatTime(this.ms, 3)}` : '';
const aa = meridiem && `${CHAR_NO_BREAK_SPACE}${meridiem}`;

return `${hhMm}${ss}${mss}`;
return `${hhMm}${ss}${mss}${aa}`;
}

public valueOf(): number {
Expand Down Expand Up @@ -203,4 +231,14 @@ export class TuiTime implements TuiTimeLike {
private formatTime(time: number, digits = 2): string {
return String(time).padStart(digits, '0');
}

private toTwelveHour(hours: number): {hours: number; meridiem: string} {
const meridiem = hours >= 12 ? 'PM' : 'AM';

if (hours === 0 || hours === 12) {
return {meridiem, hours: 12};
}

return {meridiem, hours: hours % 12};
}
}
8 changes: 7 additions & 1 deletion projects/cdk/date-time/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@
* * YMD - yyyy.mm.dd
*/
export type TuiDateMode = 'DMY' | 'MDY' | 'YMD';
export type TuiTimeMode = 'HH:MM:SS.MSS' | 'HH:MM:SS' | 'HH:MM';
export type TuiTimeMode =
| 'HH:MM AA'
| 'HH:MM:SS AA'
| 'HH:MM:SS.MSS AA'
| 'HH:MM:SS.MSS'
| 'HH:MM:SS'
| 'HH:MM';

/**
* Optionally has year and/or month and/or day
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,13 +174,19 @@ test.describe('InputDateTime', () => {
);

await timeModeSelect.textfield.click();
await timeModeSelect.selectOptions([1]);
await timeModeSelect.selectOptions([2]);

await expect(timeModeSelect.textfield).toHaveValue('HH:MM:SS');

await inputDateTime.textfield.focus();

await expect(inputDateTime.host).toHaveScreenshot('03-timeMode=HH:MM.SS.png');

await timeModeSelect.textfield.click();
await timeModeSelect.selectOptions([2]);
await timeModeSelect.selectOptions([4]);

await expect(timeModeSelect.textfield).toHaveValue('HH:MM:SS.MSS');

await inputDateTime.textfield.focus();

await expect(inputDateTime.host).toHaveScreenshot(
Expand All @@ -196,6 +202,27 @@ test.describe('InputDateTime', () => {

await expect(inputDateTime.textfield).toHaveValue('07.06.2024, 23:59:00.000');
});

test.describe('AM / PM', () => {
test.beforeEach(async ({page}) => {
await tuiGoto(page, `${DemoRoute.InputDateTime}/API?timeMode=HH:MM%20AA`);
await inputDateTime.textfield.pressSequentially('2092020');

await expect(inputDateTime.textfield).toHaveValue('20.09.2020');
});

test('330a => 03:30 AM', async () => {
await inputDateTime.textfield.pressSequentially('330a');

await expect(inputDateTime.textfield).toHaveValue('20.09.2020, 03:30 AM');
});

test('330p => 03:30 PM', async () => {
await inputDateTime.textfield.pressSequentially('330p');

await expect(inputDateTime.textfield).toHaveValue('20.09.2020, 03:30 PM');
});
});
});

test.describe('invalid date', () => {
Expand Down
Loading

0 comments on commit 0f67a78

Please sign in to comment.