Skip to content

feat(datetime): localize am/pm labels in time picker #25389

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

Merged
merged 35 commits into from
Jun 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
285b223
feat(datetime): localize am/pm labels in time picker
sean-perkins Jun 1, 2022
f695339
test(datetime): calculating popover min width
sean-perkins Jun 1, 2022
3759965
test(datetime): parsing am/pm labels
sean-perkins Jun 1, 2022
3b19ec1
test(datetime): localization
sean-perkins Jun 1, 2022
62e86eb
chore(): add updated snapshots
Ionitron Jun 1, 2022
861c9c2
chore(): prettier formatting
sean-perkins Jun 1, 2022
708bc88
fix(): use auto sizing for popover width
sean-perkins Jun 3, 2022
9c554be
refactor(): getLocalizedDayPeriod
sean-perkins Jun 3, 2022
5646510
chore(): remove reference screenshots
sean-perkins Jun 3, 2022
9c476a3
chore(): add updated snapshots
Ionitron Jun 3, 2022
d16c73e
feat(): render day period column based on locale
sean-perkins Jun 6, 2022
543f28f
fix(): popover sizing using fit-content
sean-perkins Jun 6, 2022
7117214
chore(): remove reference screenshots
sean-perkins Jun 6, 2022
7d43b20
chore(): add updated snapshots
Ionitron Jun 6, 2022
f06b59c
chore(): remove bad captures
sean-perkins Jun 6, 2022
0d49cf9
chore(): add updated snapshots
Ionitron Jun 6, 2022
347e0c5
chore(): remove invalid capture
sean-perkins Jun 6, 2022
db9b375
chore(): add updated snapshots
Ionitron Jun 6, 2022
3f12a3b
test(): wait for datetime-ready class
sean-perkins Jun 7, 2022
c84b712
Merge remote-tracking branch 'origin/FW-437' into FW-437
sean-perkins Jun 7, 2022
a0cbfa5
chore(): remove screenshot
sean-perkins Jun 7, 2022
ce24350
test(): wait for attached vs. visible
sean-perkins Jun 7, 2022
502cf12
chore(): lint + prettier
sean-perkins Jun 7, 2022
f37041c
test(): scroll datetime into view
sean-perkins Jun 7, 2022
3110443
chore(): add updated snapshots
Ionitron Jun 7, 2022
6b62b1b
chore(): clean-up
sean-perkins Jun 7, 2022
2ad9224
Merge remote-tracking branch 'origin/feature-6.2' into FW-437
sean-perkins Jun 7, 2022
1d1806d
Merge remote-tracking branch 'origin/feature-6.2' into FW-437
sean-perkins Jun 7, 2022
b66ad93
fix(): type signature conflict
sean-perkins Jun 7, 2022
873ef70
fix(): do not render day period if using 24 hour
sean-perkins Jun 10, 2022
27b549a
refactor(): removeDateTzOffset util
sean-perkins Jun 10, 2022
d43a046
refactor(): use Intl.DatetimeFormat to format 24 hour format
sean-perkins Jun 10, 2022
d19443a
Merge remote-tracking branch 'origin/feature-6.2' into FW-437
sean-perkins Jun 10, 2022
f5237cd
refactor(): day period column order with css
sean-perkins Jun 14, 2022
a4a6036
chore(): return early if no items before accessing variables
sean-perkins Jun 14, 2022
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
175 changes: 106 additions & 69 deletions core/src/components/datetime/datetime.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,15 @@ import {
getPickerMonths,
getToday,
} from './utils/data';
import { addTimePadding, getFormattedHour, getFormattedTime, getMonthAndDay, getMonthAndYear } from './utils/format';
import { is24Hour, isMonthFirstLocale } from './utils/helpers';
import {
addTimePadding,
getFormattedHour,
getLocalizedTime,
getLocalizedDayPeriod,
getMonthAndDay,
getMonthAndYear,
} from './utils/format';
import { is24Hour, isLocaleDayPeriodRTL, isMonthFirstLocale } from './utils/helpers';
import {
calculateHourFromAMPM,
convertDataToISO,
Expand All @@ -37,7 +44,7 @@ import {
getPreviousYear,
getStartOfWeek,
} from './utils/manipulation';
import { clampDate, convertToArrayOfNumbers, getPartsFromCalendarDay, parseDate } from './utils/parse';
import { clampDate, convertToArrayOfNumbers, getPartsFromCalendarDay, parseAmPm, parseDate } from './utils/parse';
import {
getCalendarDayState,
isDayDisabled,
Expand Down Expand Up @@ -1085,6 +1092,7 @@ export class Datetime implements ComponentInterface {
this.highlightActiveParts = !!value;
const valueToProcess = parseDate(value || getToday());
const { month, day, year, hour, minute, tzOffset } = clampDate(valueToProcess, this.minParts, this.maxParts);
const ampm = parseAmPm(hour!);

this.setWorkingParts({
month,
Expand All @@ -1093,7 +1101,7 @@ export class Datetime implements ComponentInterface {
hour,
minute,
tzOffset,
ampm: hour! >= 12 ? 'pm' : 'am',
ampm,
});

this.activeParts = {
Expand All @@ -1103,7 +1111,7 @@ export class Datetime implements ComponentInterface {
hour,
minute,
tzOffset,
ampm: hour! >= 12 ? 'pm' : 'am',
ampm,
};
};

Expand Down Expand Up @@ -1526,71 +1534,99 @@ export class Datetime implements ComponentInterface {
ampmItems: PickerColumnItem[],
use24Hour: boolean
) {
return (
<ion-picker-internal>
{this.renderHourPickerColumn(hoursItems)}
{this.renderMinutePickerColumn(minutesItems)}
{!use24Hour && this.renderDayPeriodPickerColumn(ampmItems)}
</ion-picker-internal>
);
}

private renderHourPickerColumn(hoursItems: PickerColumnItem[]) {
if (hoursItems.length === 0) return [];

const { color, activePartsClone, workingParts } = this;

return (
<ion-picker-internal>
<ion-picker-column-internal
color={color}
value={activePartsClone.hour}
items={hoursItems}
numericInput
onIonChange={(ev: CustomEvent) => {
this.setWorkingParts({
...workingParts,
hour: ev.detail.value,
});
this.setActiveParts({
...activePartsClone,
hour: ev.detail.value,
});
<ion-picker-column-internal
color={color}
value={activePartsClone.hour}
items={hoursItems}
numericInput
onIonChange={(ev: CustomEvent) => {
this.setWorkingParts({
...workingParts,
hour: ev.detail.value,
});
this.setActiveParts({
...activePartsClone,
hour: ev.detail.value,
});

ev.stopPropagation();
}}
></ion-picker-column-internal>
<ion-picker-column-internal
color={color}
value={activePartsClone.minute}
items={minutesItems}
numericInput
onIonChange={(ev: CustomEvent) => {
this.setWorkingParts({
...workingParts,
minute: ev.detail.value,
});
this.setActiveParts({
...activePartsClone,
minute: ev.detail.value,
});
ev.stopPropagation();
}}
></ion-picker-column-internal>
);
}

ev.stopPropagation();
}}
></ion-picker-column-internal>
{!use24Hour && (
<ion-picker-column-internal
color={color}
value={activePartsClone.ampm}
items={ampmItems}
onIonChange={(ev: CustomEvent) => {
const hour = calculateHourFromAMPM(workingParts, ev.detail.value);

this.setWorkingParts({
...workingParts,
ampm: ev.detail.value,
hour,
});

this.setActiveParts({
...activePartsClone,
ampm: ev.detail.value,
hour,
});

ev.stopPropagation();
}}
></ion-picker-column-internal>
)}
</ion-picker-internal>
private renderMinutePickerColumn(minutesItems: PickerColumnItem[]) {
if (minutesItems.length === 0) return [];

const { color, activePartsClone, workingParts } = this;

return (
<ion-picker-column-internal
color={color}
value={activePartsClone.minute}
items={minutesItems}
numericInput
onIonChange={(ev: CustomEvent) => {
this.setWorkingParts({
...workingParts,
minute: ev.detail.value,
});
this.setActiveParts({
...activePartsClone,
minute: ev.detail.value,
});

ev.stopPropagation();
}}
></ion-picker-column-internal>
);
}

private renderDayPeriodPickerColumn(dayPeriodItems: PickerColumnItem[]) {
if (dayPeriodItems.length === 0) return [];

const { color, activePartsClone, workingParts, locale } = this;
const isDayPeriodRTL = isLocaleDayPeriodRTL(locale);

return (
<ion-picker-column-internal
style={isDayPeriodRTL ? { order: '-1' } : {}}
color={color}
value={activePartsClone.ampm}
items={dayPeriodItems}
onIonChange={(ev: CustomEvent) => {
const hour = calculateHourFromAMPM(workingParts, ev.detail.value);

this.setWorkingParts({
...workingParts,
ampm: ev.detail.value,
hour,
});

this.setActiveParts({
...activePartsClone,
ampm: ev.detail.value,
hour,
});

ev.stopPropagation();
}}
></ion-picker-column-internal>
);
}

Expand Down Expand Up @@ -1629,7 +1665,7 @@ export class Datetime implements ComponentInterface {
}
}}
>
{getFormattedTime(this.activePartsClone, use24Hour)}
{getLocalizedTime(this.locale, this.activePartsClone, use24Hour)}
</button>,
<ion-popover
alignment="center"
Expand All @@ -1651,6 +1687,7 @@ export class Datetime implements ComponentInterface {
}}
style={{
'--offset-y': '-10px',
'--min-width': 'fit-content',
}}
// Allow native browser keyboard events to support up/down/home/end key
// navigation within the time picker.
Expand All @@ -1670,7 +1707,7 @@ export class Datetime implements ComponentInterface {
* should just be the default segment.
*/
private renderTime() {
const { workingParts, presentation } = this;
const { workingParts, presentation, locale } = this;
const timeOnlyPresentation = presentation === 'time';
const use24Hour = is24Hour(this.locale, this.hourCycle);
const { hours, minutes, am, pm } = generateTime(
Expand Down Expand Up @@ -1699,14 +1736,14 @@ export class Datetime implements ComponentInterface {
const ampmItems = [];
if (am) {
ampmItems.push({
text: 'AM',
text: getLocalizedDayPeriod(locale, 'am'),
value: 'am',
});
}

if (pm) {
ampmItems.push({
text: 'PM',
text: getLocalizedDayPeriod(locale, 'pm'),
value: 'pm',
});
}
Expand Down
11 changes: 11 additions & 0 deletions core/src/components/datetime/test/format.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
getFormattedHour,
addTimePadding,
getMonthAndYear,
getLocalizedDayPeriod,
} from '../utils/format';

describe('generateDayAriaLabel()', () => {
Expand Down Expand Up @@ -88,3 +89,13 @@ describe('getMonthAndYear()', () => {
expect(getMonthAndYear('es-ES', { month: 4, day: 1, year: 2006 })).toEqual('abril de 2006');
});
});

describe('getLocalizedDayPeriod', () => {
it('should return AM when the date is in the morning', () => {
expect(getLocalizedDayPeriod('en-US', 'am'));
});

it('should return PM when the date is in the afternoon', () => {
expect(getLocalizedDayPeriod('en-US', 'pm'));
});
});
Loading