Skip to content
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
29 changes: 29 additions & 0 deletions core/src/components/datetime/datetime.scss
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,35 @@

opacity: 1;
}
/**
* Changing the physical order of the
* picker columns in the DOM is added
* work, so we just use `order` instead.
*
* The picker automatically configures
* the text alignment, so when switching
* the order we need to manually switch
* the text alignment too.
*/
:host .datetime-year .order-month-first .month-column {
order: 1;
}

:host .datetime-year .order-month-first .year-column {
order: 2;
}

:host .datetime-year .order-year-first .month-column {
order: 2;

text-align: end;
}

:host .datetime-year .order-year-first .year-column {
order: 1;

text-align: start;
}

// Calendar
// -----------------------------------
Expand Down
16 changes: 12 additions & 4 deletions core/src/components/datetime/datetime.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ import {
getMonthAndYear
} from './utils/format';
import {
is24Hour
is24Hour,
isMonthFirstLocale
} from './utils/helpers';
import {
calculateHourFromAMPM,
Expand Down Expand Up @@ -1097,25 +1098,31 @@ export class Datetime implements ComponentInterface {
}

private renderYearView() {
const { presentation, workingParts } = this;
const { presentation, workingParts, locale } = this;
const calendarYears = getCalendarYears(this.todayParts, this.minParts, this.maxParts, this.parsedYearValues);
const showMonth = presentation !== 'year';
const showYear = presentation !== 'month';

const months = getPickerMonths(this.locale, workingParts, this.minParts, this.maxParts, this.parsedMonthValues);
const months = getPickerMonths(locale, workingParts, this.minParts, this.maxParts, this.parsedMonthValues);
const years = calendarYears.map(year => {
return {
text: `${year}`,
value: year
}
})
const showMonthFirst = isMonthFirstLocale(locale);
const columnOrder = showMonthFirst ? 'month-first' : 'year-first';
return (
<div class="datetime-year">
<div class="datetime-year-body">
<div class={{
'datetime-year-body': true,
[`order-${columnOrder}`]: true
}}>
<ion-picker-internal>
{
showMonth &&
<ion-picker-column-internal
class="month-column"
color={this.color}
items={months}
value={workingParts.month}
Expand Down Expand Up @@ -1150,6 +1157,7 @@ export class Datetime implements ComponentInterface {
{
showYear &&
<ion-picker-column-internal
class="year-column"
color={this.color}
items={years}
value={workingParts.year}
Expand Down
18 changes: 17 additions & 1 deletion core/src/components/datetime/test/helpers.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {
isLeapYear,
getNumDaysInMonth,
is24Hour
is24Hour,
isMonthFirstLocale
} from '../utils/helpers';

describe('daysInMonth()', () => {
Expand Down Expand Up @@ -51,3 +52,18 @@ describe('is24Hour()', () => {
expect(is24Hour('en-GB-u-hc-h12')).toBe(false);
})
})

describe('isMonthFirstLocale()', () => {
it('should return true if the locale shows months first', () => {
expect(isMonthFirstLocale('en-US')).toBe(true);
expect(isMonthFirstLocale('en-GB')).toBe(true);
expect(isMonthFirstLocale('es-ES')).toBe(true);
expect(isMonthFirstLocale('ro-RO')).toBe(true);
});

it('should return false if the locale shows years first', () => {
expect(isMonthFirstLocale('zh-CN')).toBe(false);
expect(isMonthFirstLocale('ja-JP')).toBe(false);
expect(isMonthFirstLocale('ko-KR')).toBe(false);
});
})
50 changes: 50 additions & 0 deletions core/src/components/datetime/test/locale/e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,53 @@ test('locale', async () => {
expect(screenshotCompare).toMatchScreenshot();
}
});

test('it should render month and year with an en-US locale', async () => {
const page = await newE2EPage({
url: '/src/components/datetime/test/locale?ionic:_testing=true'
});

const screenshotCompares = [];
const datetime = await page.find('ion-datetime');

datetime.setProperty('locale', 'en-US');
await page.waitForChanges();

const button = await page.find('ion-datetime >>> .calendar-month-year ion-item');
await button.click();
await page.waitForChanges();

const yearBody = await page.find('ion-datetime >>> .datetime-year-body');
expect(yearBody).toHaveClass('order-month-first');

screenshotCompares.push(await page.compareScreenshot());

for (const screenshotCompare of screenshotCompares) {
expect(screenshotCompare).toMatchScreenshot();
}
});

test('it should render year and month with a ja-JP locale', async () => {
const page = await newE2EPage({
url: '/src/components/datetime/test/locale?ionic:_testing=true'
});

const screenshotCompares = [];
const datetime = await page.find('ion-datetime');

datetime.setProperty('locale', 'ja-JP');
await page.waitForChanges();

const button = await page.find('ion-datetime >>> .calendar-month-year ion-item');
await button.click();
await page.waitForChanges();

const yearBody = await page.find('ion-datetime >>> .datetime-year-body');
expect(yearBody).toHaveClass('order-year-first');

screenshotCompares.push(await page.compareScreenshot());

for (const screenshotCompare of screenshotCompares) {
expect(screenshotCompare).toMatchScreenshot();
}
});
25 changes: 25 additions & 0 deletions core/src/components/datetime/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,28 @@ export const is24Hour = (locale: string, hourCycle?: 'h23' | 'h12') => {
export const getNumDaysInMonth = (month: number, year: number) => {
return (month === 4 || month === 6 || month === 9 || month === 11) ? 30 : (month === 2) ? isLeapYear(year) ? 29 : 28 : 31;
}

/**
* Certain locales display month then year while
* others display year then month.
* We can use Intl.DateTimeFormat to determine
* the ordering for each locale.
*/
export const isMonthFirstLocale = (locale: string) => {

/**
* By setting month and year we guarantee that only
* month, year, and literal (slashes '/', for example)
* values are included in the formatToParts results.
*
* The ordering of the parts will be determined by
* the locale. So if the month is the first value,
* then we know month should be shown first. If the
* year is the first value, then we know year should be shown first.
*
* This ordering can be controlled by customizing the locale property.
*/
const parts = new Intl.DateTimeFormat(locale, { month: 'numeric', year: 'numeric' }).formatToParts(new Date());

return parts[0].type === 'month';
}