diff --git a/components/core/time/public-api.ts b/components/core/time/public-api.ts index 69e5f0db587..b2c828f91e2 100644 --- a/components/core/time/public-api.ts +++ b/components/core/time/public-api.ts @@ -5,3 +5,4 @@ export * from './candy-date'; export * from './time'; +export * from './time-parser'; diff --git a/components/core/time/time-parser.spec.ts b/components/core/time/time-parser.spec.ts new file mode 100644 index 00000000000..ff0d0abde4c --- /dev/null +++ b/components/core/time/time-parser.spec.ts @@ -0,0 +1,68 @@ +import { Injector, LOCALE_ID } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; +import { NgTimeParser, TimeParserResult } from './time-parser'; + +describe('Parse time with angular format', () => { + let injector: Injector; + let localeId: string; + let parser: NgTimeParser; + let result: TimeParserResult | null; + let time: Date; + + beforeEach(() => { + injector = TestBed.configureTestingModule({}); + localeId = injector.get(LOCALE_ID); + }); + + it('should parse hh:mm:ss a', () => { + parser = new NgTimeParser('hh:mm:ss a', localeId); + result = parser.getTimeParserResult('12:30:22 AM'); + expect(result?.hour).toBe(12); + expect(result?.minute).toBe(30); + expect(result?.second).toBe(22); + expect(result?.period).toBe(0); + + result = parser.getTimeParserResult('12:30:22 PM'); + expect(result?.period).toBe(1); + + time = parser.getTime('12:30:22 PM'); + expect(time.getHours()).toBe(12); + expect(time.getMinutes()).toBe(30); + expect(time.getSeconds()).toBe(22); + + time = parser.getTime('05:30:22 PM'); + expect(time.getHours()).toBe(17); + }); + + it('should parse hh:mm:ss aaaaa', () => { + parser = new NgTimeParser('hh:mm:ss aaaaa', localeId); + result = parser.getTimeParserResult('12:30:22 a'); + expect(result?.hour).toBe(12); + expect(result?.minute).toBe(30); + expect(result?.second).toBe(22); + expect(result?.period).toBe(0); + + result = parser.getTimeParserResult('12:30:22 p'); + expect(result?.period).toBe(1); + }); + + it('should parse mm(ss) HH', () => { + parser = new NgTimeParser('mm(ss) HH', localeId); + result = parser.getTimeParserResult('30(22) 12'); + expect(result?.period).toBe(null); + + time = parser.getTime('30(22) 12'); + expect(time.getHours()).toBe(12); + expect(time.getMinutes()).toBe(30); + expect(time.getSeconds()).toBe(22); + }); + + it('should parse ss + mm', () => { + parser = new NgTimeParser('ss + mm', localeId); + time = parser.getTime('10 + 42'); + const now = new Date(); + expect(time.getHours()).toBe(now.getHours()); + expect(time.getMinutes()).toBe(42); + expect(time.getSeconds()).toBe(10); + }); +}); diff --git a/components/core/time/time-parser.ts b/components/core/time/time-parser.ts new file mode 100644 index 00000000000..3c71d1021f1 --- /dev/null +++ b/components/core/time/time-parser.ts @@ -0,0 +1,143 @@ +/** + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE + */ + +// from https://github.com/hsuanxyz/ng-time-parser +import { FormStyle, getLocaleDayPeriods, TranslationWidth } from '@angular/common'; +import { isNotNil } from 'ng-zorro-antd/core/util'; + +export interface TimeParserResult { + hour: number | null; + minute: number | null; + second: number | null; + period: number | null; +} + +export class NgTimeParser { + regex: RegExp = null!; + matchMap: { [key: string]: null | number } = { + hour: null, + minute: null, + second: null, + periodNarrow: null, + periodWide: null, + periodAbbreviated: null + }; + + constructor(private format: string, private localeId: string) { + this.genRegexp(); + } + + getTime(str: string): Date { + const result = this.getTimeParserResult(str); + const time = new Date(); + + if (result?.hour) { + time.setHours(result.hour); + } + + if (result?.minute) { + time.setMinutes(result.minute); + } + + if (result?.second) { + time.setSeconds(result.second); + } + + if (result?.period === 1 && time.getHours() < 12) { + time.setHours(time.getHours() + 12); + } + + return time; + } + + getTimeParserResult(str: string): TimeParserResult | null { + const match = this.regex.exec(str); + let period = null; + if (match) { + if (isNotNil(this.matchMap.periodNarrow)) { + period = getLocaleDayPeriods(this.localeId, FormStyle.Format, TranslationWidth.Narrow).indexOf( + match[this.matchMap.periodNarrow + 1] + ); + } + if (isNotNil(this.matchMap.periodWide)) { + period = getLocaleDayPeriods(this.localeId, FormStyle.Format, TranslationWidth.Wide).indexOf(match[this.matchMap.periodWide + 1]); + } + if (isNotNil(this.matchMap.periodAbbreviated)) { + period = getLocaleDayPeriods(this.localeId, FormStyle.Format, TranslationWidth.Abbreviated).indexOf( + match[this.matchMap.periodAbbreviated + 1] + ); + } + return { + hour: isNotNil(this.matchMap.hour) ? Number.parseInt(match[this.matchMap.hour + 1], 10) : null, + minute: isNotNil(this.matchMap.minute) ? Number.parseInt(match[this.matchMap.minute + 1], 10) : null, + second: isNotNil(this.matchMap.second) ? Number.parseInt(match[this.matchMap.second + 1], 10) : null, + period + }; + } else { + return null; + } + } + + genRegexp(): void { + let regexStr = this.format.replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$&'); + const hourRegex = /h{1,2}/i; + const minuteRegex = /m{1,2}/; + const secondRegex = /s{1,2}/; + const periodNarrow = /aaaaa/; + const periodWide = /aaaa/; + const periodAbbreviated = /a{1,3}/; + + const hourMatch = hourRegex.exec(this.format); + const minuteMatch = minuteRegex.exec(this.format); + const secondMatch = secondRegex.exec(this.format); + const periodNarrowMatch = periodNarrow.exec(this.format); + let periodWideMatch: null | RegExpExecArray = null; + let periodAbbreviatedMatch: null | RegExpExecArray = null; + if (!periodNarrowMatch) { + periodWideMatch = periodWide.exec(this.format); + } + if (!periodWideMatch && !periodNarrowMatch) { + periodAbbreviatedMatch = periodAbbreviated.exec(this.format); + } + + const matchs = [hourMatch, minuteMatch, secondMatch, periodNarrowMatch, periodWideMatch, periodAbbreviatedMatch] + .filter(m => !!m) + .sort((a, b) => a!.index - b!.index); + + matchs.forEach((match, index) => { + switch (match) { + case hourMatch: + this.matchMap.hour = index; + regexStr = regexStr.replace(hourRegex, '(\\d{1,2})'); + break; + case minuteMatch: + this.matchMap.minute = index; + regexStr = regexStr.replace(minuteRegex, '(\\d{1,2})'); + break; + case secondMatch: + this.matchMap.second = index; + regexStr = regexStr.replace(secondRegex, '(\\d{1,2})'); + break; + case periodNarrowMatch: + this.matchMap.periodNarrow = index; + const periodsNarrow = getLocaleDayPeriods(this.localeId, FormStyle.Format, TranslationWidth.Narrow).join('|'); + regexStr = regexStr.replace(periodNarrow, `(${periodsNarrow})`); + break; + case periodWideMatch: + this.matchMap.periodWide = index; + const periodsWide = getLocaleDayPeriods(this.localeId, FormStyle.Format, TranslationWidth.Wide).join('|'); + regexStr = regexStr.replace(periodWide, `(${periodsWide})`); + break; + case periodAbbreviatedMatch: + this.matchMap.periodAbbreviated = index; + const periodsAbbreviated = getLocaleDayPeriods(this.localeId, FormStyle.Format, TranslationWidth.Abbreviated).join('|'); + regexStr = regexStr.replace(periodAbbreviated, `(${periodsAbbreviated})`); + break; + } + }); + + this.regex = new RegExp(regexStr); + } +} diff --git a/components/i18n/date-helper.service.spec.ts b/components/i18n/date-helper.service.spec.ts index e366ed26f8e..ad268fdabdc 100644 --- a/components/i18n/date-helper.service.spec.ts +++ b/components/i18n/date-helper.service.spec.ts @@ -38,8 +38,8 @@ describe('DateHelperService', () => { }); it('should do parseTime correctly', () => { - expect(dateHelper.parseTime('14:00')?.toTimeString().substr(0, 8)).toBe('14:00:00'); - expect(dateHelper.parseTime('4:00')?.toTimeString().substr(0, 8)).toBe('04:00:00'); + expect(dateHelper.parseTime('14:00', 'HH:mm')?.toTimeString().substr(0, 8)).toBe('14:00:00'); + expect(dateHelper.parseTime('4:00', 'H:mm')?.toTimeString().substr(0, 8)).toBe('04:00:00'); }); }); diff --git a/components/i18n/date-helper.service.ts b/components/i18n/date-helper.service.ts index 9262f86f534..eb106bfa7fc 100644 --- a/components/i18n/date-helper.service.ts +++ b/components/i18n/date-helper.service.ts @@ -9,7 +9,7 @@ import fnsFormat from 'date-fns/format'; import fnsGetISOWeek from 'date-fns/getISOWeek'; import fnsParse from 'date-fns/parse'; -import { WeekDayIndex } from 'ng-zorro-antd/core/time'; +import { NgTimeParser, WeekDayIndex } from 'ng-zorro-antd/core/time'; import { convertTokens } from './convert-tokens'; import { mergeDateConfig, NzDateConfig, NZ_DATE_CONFIG, NZ_DATE_FNS_COMPATIBLE } from './date-config'; import { NzI18nService } from './nz-i18n.service'; @@ -117,9 +117,8 @@ export class DateHelperByDatePipe extends DateHelperService { return new Date(text); } - parseTime(text: string): Date { - // const formatReg = new RegExp(formatStr.replace(/h|hh|m|mm|s|ss/, '\w+'), 'gi'); - const now = new Date(); - return new Date(`${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()} ${text}`); + parseTime(text: string, formatStr: string): Date { + const parser = new NgTimeParser(formatStr, this.i18n.getLocaleId()); + return parser.getTime(text); } } diff --git a/components/time-picker/demo/use12-hours.ts b/components/time-picker/demo/use12-hours.ts index 76243c29c54..3c9573124ed 100644 --- a/components/time-picker/demo/use12-hours.ts +++ b/components/time-picker/demo/use12-hours.ts @@ -3,8 +3,8 @@ import { Component } from '@angular/core'; @Component({ selector: 'nz-demo-time-picker-use12-hours', template: ` - - + + `, styles: [ ` @@ -16,4 +16,8 @@ import { Component } from '@angular/core'; }) export class NzDemoTimePickerUse12HoursComponent { time: Date | null = null; + + log(value: Date): void { + console.log(value); + } }