Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(module:time-picker): input value change not work #5770

Merged
merged 2 commits into from
Sep 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions components/core/time/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@

export * from './candy-date';
export * from './time';
export { NgTimeParser as ɵNgTimeParser, TimeResult as ɵTimeResult } from './time-parser';
94 changes: 94 additions & 0 deletions components/core/time/time-parser.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { registerLocaleData } from '@angular/common';
import zh from '@angular/common/locales/zh';
import { Injector, LOCALE_ID } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { NgTimeParser, TimeResult } from './time-parser';

describe('Parse time with angular format', () => {
let injector: Injector;
let localeId: string;
let parser: NgTimeParser;
let result: TimeResult | null;
let time: Date;

describe('default locale', () => {
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.getTimeResult('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.getTimeResult('12:30:22 PM');
expect(result?.period).toBe(1);

time = parser.toDate('12:30:22 PM');
expect(time.getHours()).toBe(12);
expect(time.getMinutes()).toBe(30);
expect(time.getSeconds()).toBe(22);

time = parser.toDate('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.getTimeResult('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.getTimeResult('12:30:22 p');
expect(result?.period).toBe(1);
});

it('should parse mm(ss) HH', () => {
parser = new NgTimeParser('mm(ss) HH', localeId);
result = parser.getTimeResult('30(22) 12');
expect(result?.period).toBe(null);

time = parser.toDate('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.toDate('10 + 42');
const now = new Date();
expect(time.getHours()).toBe(now.getHours());
expect(time.getMinutes()).toBe(42);
expect(time.getSeconds()).toBe(10);
});
});

describe('zh locale', () => {
registerLocaleData(zh);
beforeEach(() => {
injector = TestBed.configureTestingModule({
providers: [{ provide: LOCALE_ID, useValue: 'zh-CN' }]
});
localeId = injector.get(LOCALE_ID);
});

it('should parse hh:mm:ss a', () => {
parser = new NgTimeParser('hh:mm:ss a', localeId);
result = parser.getTimeResult('04:45:22 上午');
expect(result?.hour).toBe(4);
expect(result?.minute).toBe(45);
expect(result?.second).toBe(22);
expect(result?.period).toBe(0);

time = parser.toDate('04:45:22 下午');
expect(time.getHours()).toBe(16);
});
});
});
143 changes: 143 additions & 0 deletions components/core/time/time-parser.ts
Original file line number Diff line number Diff line change
@@ -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 TimeResult {
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();
}

toDate(str: string): Date {
const result = this.getTimeResult(str);
const time = new Date();

if (isNotNil(result?.hour)) {
time.setHours(result!.hour);
}

if (isNotNil(result?.minute)) {
time.setMinutes(result!.minute);
}

if (isNotNil(result?.second)) {
time.setSeconds(result!.second);
}

if (result?.period === 1 && time.getHours() < 12) {
time.setHours(time.getHours() + 12);
}

return time;
}

getTimeResult(str: string): TimeResult | 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);
}
}
3 changes: 2 additions & 1 deletion components/date-picker/picker.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
} from '@angular/core';
import { slideMotion } from 'ng-zorro-antd/core/animation';

import { ESCAPE } from '@angular/cdk/keycodes';
import { CandyDate, CompatibleValue } from 'ng-zorro-antd/core/time';
import { NgStyleInterface, NzSafeAny } from 'ng-zorro-antd/core/types';
import { DateHelperService } from 'ng-zorro-antd/i18n';
Expand Down Expand Up @@ -372,7 +373,7 @@ export class NzPickerComponent implements OnInit, AfterViewInit, OnChanges, OnDe
}

onOverlayKeydown(event: KeyboardEvent): void {
if (event.key === 'Escape') {
if (event.keyCode === ESCAPE) {
this.datePickerService.setValue(this.datePickerService.initialValue!);
}
}
Expand Down
4 changes: 2 additions & 2 deletions components/i18n/date-helper.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,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, 5)).toBe('14:00');
expect(dateHelper.parseTime('4:00', 'H:mm')?.toTimeString().substr(0, 5)).toBe('04:00');
});
});

Expand Down
12 changes: 5 additions & 7 deletions components/i18n/date-helper.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 { WeekDayIndex, ɵNgTimeParser } from 'ng-zorro-antd/core/time';
import { mergeDateConfig, NzDateConfig, NZ_DATE_CONFIG } from './date-config';
import { NzI18nService } from './nz-i18n.service';

Expand All @@ -34,7 +34,7 @@ export abstract class DateHelperService {

abstract getISOWeek(date: Date): number;
abstract getFirstDayOfWeek(): WeekDayIndex;
abstract format(date: Date, formatStr: string): string;
abstract format(date: Date | null, formatStr: string): string;
abstract parseDate(text: string, formatStr?: string): Date;
abstract parseTime(text: string, formatStr?: string): Date | undefined;
}
Expand Down Expand Up @@ -108,10 +108,8 @@ export class DateHelperByDatePipe extends DateHelperService {
return new Date(text);
}

parseTime(text: string): Date | undefined {
if (!text) {
return;
}
return new Date(Date.parse(`1970-01-01 ${text}`));
parseTime(text: string, formatStr: string): Date {
const parser = new ɵNgTimeParser(formatStr, this.i18n.getLocaleId());
return parser.toDate(text);
}
}
8 changes: 6 additions & 2 deletions components/time-picker/demo/use12-hours.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { Component } from '@angular/core';
@Component({
selector: 'nz-demo-time-picker-use12-hours',
template: `
<nz-time-picker [(ngModel)]="time" [nzUse12Hours]="true"></nz-time-picker>
<nz-time-picker [(ngModel)]="time" [nzUse12Hours]="true" nzFormat="h:mm a"></nz-time-picker>
<nz-time-picker [(ngModel)]="time" [nzUse12Hours]="true" (ngModelChange)="log($event)"></nz-time-picker>
<nz-time-picker [(ngModel)]="time" [nzUse12Hours]="true" (ngModelChange)="log($event)" nzFormat="h:mm a"></nz-time-picker>
`,
styles: [
`
Expand All @@ -16,4 +16,8 @@ import { Component } from '@angular/core';
})
export class NzDemoTimePickerUse12HoursComponent {
time: Date | null = null;

log(value: Date): void {
console.log(value);
}
}
1 change: 0 additions & 1 deletion components/time-picker/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,3 @@
export * from './time-picker.component';
export * from './time-picker.module';
export * from './time-picker-panel.component';
export * from './time-value-accessor.directive';
23 changes: 10 additions & 13 deletions components/time-picker/time-picker-panel.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@ import { InputBoolean, isNotNil } from 'ng-zorro-antd/core/util';
import { DateHelperService } from 'ng-zorro-antd/i18n';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

import { TimeHolder } from './time-holder';
import { NzTimeValueAccessorDirective } from './time-value-accessor.directive';

function makeRange(length: number, step: number = 1, start: number = 0): number[] {
return new Array(Math.ceil(length / step)).fill(0).map((_, i) => (i + start) * step);
Expand Down Expand Up @@ -109,6 +107,11 @@ export type NzTimePickerUnit = 'hour' | 'minute' | 'second' | '12-hour';
{{ 'Calendar.lang.now' | nzI18n }}
</a>
</li>
<li class="ant-picker-ok">
<button nz-button type="button" nzSize="small" nzType="primary" (click)="onClickOk()">
{{ 'Calendar.lang.ok' | nzI18n }}
</button>
</li>
</ul>
</div>
`,
Expand Down Expand Up @@ -148,8 +151,6 @@ export class NzTimePickerPanelComponent implements ControlValueAccessor, OnInit,
secondRange!: ReadonlyArray<{ index: number; disabled: boolean }>;
use12HoursRange!: ReadonlyArray<{ index: number; value: string }>;

@ViewChild(NzTimeValueAccessorDirective, { static: false })
nzTimeValueAccessorDirective?: NzTimeValueAccessorDirective;
@ViewChild('hourListElement', { static: false })
hourListElement?: DebugElement;
@ViewChild('minuteListElement', { static: false }) minuteListElement?: DebugElement;
Expand Down Expand Up @@ -277,14 +278,6 @@ export class NzTimePickerPanelComponent implements ControlValueAccessor, OnInit,
return this._nzSecondStep;
}

selectInputRange(): void {
setTimeout(() => {
if (this.nzTimeValueAccessorDirective) {
this.nzTimeValueAccessorDirective.setRange();
}
});
}

buildHours(): void {
let hourRanges = 24;
let disabledHours = this.nzDisabledHours?.();
Expand Down Expand Up @@ -501,6 +494,10 @@ export class NzTimePickerPanelComponent implements ControlValueAccessor, OnInit,
this.closePanel.emit();
}

onClickOk(): void {
this.closePanel.emit();
}

isSelectedHour(hour: { index: number; disabled: boolean }): boolean {
return hour.index === this.time.viewHours;
}
Expand All @@ -523,9 +520,9 @@ export class NzTimePickerPanelComponent implements ControlValueAccessor, OnInit,
this.time.changes.pipe(takeUntil(this.unsubscribe$)).subscribe(() => {
this.changed();
this.touched();
this.scrollToTime(120);
});
this.buildTimes();
this.selectInputRange();
setTimeout(() => {
this.scrollToTime();
this.firstScrolled = true;
Expand Down
Loading