Skip to content

Commit

Permalink
feat(input-date-picker): normalize year to current century (#7622)
Browse files Browse the repository at this point in the history
**Related Issue:** #7588

## Summary

Normalizes year to current century. 

Ex: If the user types `01/01/20` the `value` will be corrected to
`01/01/2020`
  • Loading branch information
anveshmekala authored Aug 31, 2023
1 parent 237a2c5 commit 0ca35e9
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -849,4 +849,74 @@ describe("calcite-input-date-picker", () => {

expect(await getActiveMonth(page)).toBe("October");
});

describe("normalize year", () => {
it("should normalize year to current century when user types the value", async () => {
const page = await newE2EPage();
await page.setContent(html`<calcite-input-date-picker normalize-year></calcite-input-date-picker>`);

const element = await page.find("calcite-input-date-picker");
const changeEvent = await page.spyOnEvent("calciteInputDatePickerChange");

await element.click();
await page.waitForChanges();
await page.keyboard.type("3/7/20");
await page.keyboard.press("Enter");
await page.waitForChanges();

expect(await element.getProperty("value")).toBe("2020-03-07");
expect(await element.getProperty("valueAsDate")).toBeDefined();
expect(changeEvent).toHaveReceivedEventTimes(1);
});

it("should normalize year to current century when value is parsed as attribute", async () => {
const page = await newE2EPage();
await page.setContent(
html`<calcite-input-date-picker normalize-year value="20-01-01"></calcite-input-date-picker>`
);

const element = await page.find("calcite-input-date-picker");
const changeEvent = await page.spyOnEvent("calciteInputDatePickerChange");

expect(await element.getProperty("value")).toBe("2020-01-01");
expect(await element.getProperty("valueAsDate")).toBeDefined();
expect(changeEvent).toHaveReceivedEventTimes(0);
});

it("should normalize year to current century when user types the value in range", async () => {
const page = await newE2EPage();
await page.setContent("<calcite-input-date-picker normalize-year range></calcite-input-date-picker>");
const element = await page.find("calcite-input-date-picker");
const changeEvent = await page.spyOnEvent("calciteInputDatePickerChange");

await element.click();
await page.waitForChanges();
await page.keyboard.type("1/1/20");
await page.keyboard.press("Enter");
await page.waitForChanges();

expect(await element.getProperty("value")).toEqual(["2020-01-01", ""]);
expect(changeEvent).toHaveReceivedEventTimes(1);

await page.keyboard.type("2/2/20");
await page.keyboard.press("Enter");
await page.waitForChanges();

expect(await element.getProperty("value")).toEqual(["2020-01-01", "2020-02-02"]);
expect(changeEvent).toHaveReceivedEventTimes(2);
});

it("should normalize year to current century when value is changed programmatically in range", async () => {
const page = await newE2EPage();
await page.setContent("<calcite-input-date-picker normalize-year range></calcite-input-date-picker>");
const element = await page.find("calcite-input-date-picker");
const changeEvent = await page.spyOnEvent("calciteInputDatePickerChange");

element.setProperty("value", ["00-03-07", "00-03-08"]);
await page.waitForChanges();

expect(await element.getProperty("value")).toEqual(["2000-03-07", "2000-03-08"]);
expect(changeEvent).toHaveReceivedEventTimes(0);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,16 @@ export const darkModeRTL_TestOnly = (): string => html`
</div>
`;
darkModeRTL_TestOnly.parameters = { modes: modesDarkDefault };

export const normalizeYearWithGermanLocale_TestOnly = (): string => html`
<div style="width: 400px">
<calcite-input-date-picker normalize-year range lang="de"></calcite-input-date-picker>
</div>
<script>
(async () => {
await customElements.whenDefined("calcite-input-date-picker");
const inputDatePicker = await document.querySelector("calcite-input-date-picker").componentOnReady();
inputDatePicker.value = ["00-03-07", "00-03-08"];
})();
</script>
`;
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
dateFromISO,
dateFromLocalizedString,
dateFromRange,
datePartsFromISO,
datePartsFromLocalizedString,
dateToISO,
inRange,
Expand Down Expand Up @@ -77,6 +78,7 @@ import {
} from "../../utils/focusTrapComponent";
import { FocusTrap } from "focus-trap";
import { guid } from "../../utils/guid";
import { normalizeToCurrentCentury, isTwoDigitYear } from "./utils";

@Component({
tag: "calcite-input-date-picker",
Expand Down Expand Up @@ -155,8 +157,16 @@ export class InputDatePicker
let newValueAsDate: Date | Date[];

if (Array.isArray(newValue)) {
if (isTwoDigitYear(newValue[0]) || isTwoDigitYear(newValue[1])) {
this.value = newValue.map((val) => this.getNormalizedDate(val));
return;
}
newValueAsDate = getValueAsDateRange(newValue);
} else if (newValue) {
if (isTwoDigitYear(newValue)) {
this.value = this.getNormalizedDate(newValue);
return;
}
newValueAsDate = dateFromISO(newValue);
} else {
newValueAsDate = undefined;
Expand Down Expand Up @@ -273,6 +283,11 @@ export class InputDatePicker
*/
@Prop({ reflect: true }) name: string;

/**
* Normalizes year to current century.
*/
@Prop({ reflect: true }) normalizeYear = false;

/**
* Specifies the Unicode numeral system used by the component for localization. This property cannot be dynamically changed.
*
Expand Down Expand Up @@ -435,9 +450,17 @@ export class InputDatePicker
const { open } = this;
open && this.openHandler(open);
if (Array.isArray(this.value)) {
if (isTwoDigitYear(this.value[0]) || isTwoDigitYear(this.value[1])) {
this.value = this.value.map((val) => this.getNormalizedDate(val));
return;
}
this.valueAsDate = getValueAsDateRange(this.value);
} else if (this.value) {
try {
if (isTwoDigitYear(this.value)) {
this.value = this.getNormalizedDate(this.value);
return;
}
this.valueAsDate = dateFromISO(this.value);
} catch (error) {
this.warnAboutInvalidValue(this.value);
Expand Down Expand Up @@ -1027,9 +1050,16 @@ export class InputDatePicker
const valueIsArray = Array.isArray(valueAsDate);

const newStartDate = valueIsArray ? valueAsDate[0] : null;
const newStartDateISO = valueIsArray ? dateToISO(newStartDate) : "";
let newStartDateISO = valueIsArray ? dateToISO(newStartDate) : "";
if (newStartDateISO && isTwoDigitYear(newStartDateISO)) {
newStartDateISO = this.getNormalizedDate(newStartDateISO);
}

const newEndDate = valueIsArray ? valueAsDate[1] : null;
const newEndDateISO = valueIsArray ? dateToISO(newEndDate) : "";
let newEndDateISO = valueIsArray ? dateToISO(newEndDate) : "";
if (newEndDateISO && isTwoDigitYear(newEndDateISO)) {
newEndDateISO = this.getNormalizedDate(newEndDateISO);
}

const newValue = newStartDateISO || newEndDateISO ? [newStartDateISO, newEndDateISO] : "";

Expand Down Expand Up @@ -1061,7 +1091,11 @@ export class InputDatePicker
}

const oldValue = this.value;
const newValue = dateToISO(value as Date);
let newValue = dateToISO(value as Date);

if (isTwoDigitYear(newValue)) {
newValue = this.getNormalizedDate(newValue);
}

if (newValue === oldValue) {
return;
Expand Down Expand Up @@ -1110,4 +1144,18 @@ export class InputDatePicker
)
.join("")
: "";

private getNormalizedDate(value: string): string {
if (!value) {
return "";
}

if (!this.normalizeYear) {
return value;
}

const { day, month, year } = datePartsFromISO(value);
const normalizedYear = normalizeToCurrentCentury(Number(year));
return `${normalizedYear}-${month}-${day}`;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { isTwoDigitYear, normalizeToCurrentCentury } from "./utils";

describe("utils", () => {
describe("isTwoDigitYear", () => {
it("returns whether a given ISO string date has two digit year", () => {
expect(isTwoDigitYear("2023-08-01")).toBe(false);
expect(isTwoDigitYear("00-08-01")).toBe(true);
expect(isTwoDigitYear("")).toBe(false);
});
});

describe("normalizedYear", () => {
it("return normalized date for a given ISO string date with two digit year", () => {
expect(normalizeToCurrentCentury(20)).toEqual(2020);
expect(normalizeToCurrentCentury(0)).toBe(2000);
expect(normalizeToCurrentCentury(1)).toBe(2001);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { datePartsFromISO } from "../../utils/date";

/**
* Specifies if an ISO string date (YYYY-MM-DD) has two digit year.
*
* @param {string} value
* @returns {boolean}
*/
export function isTwoDigitYear(value: string): boolean {
if (!value) {
return false;
}
const { year } = datePartsFromISO(value);
return Number(year) < 100;
}

/**
* Returns a normalized year to current century from a given two digit year number.
*
* @param {number} twoDigitYear
* @returns {string}
*/
export function normalizeToCurrentCentury(twoDigitYear: number): number {
const currentYear = new Date().getFullYear();
const normalizedYear = Math.floor(currentYear / 100) * 100 + twoDigitYear;
return normalizedYear;
}
8 changes: 8 additions & 0 deletions packages/calcite-components/src/utils/date.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { DateLocaleData } from "../components/date-picker/utils";
import {
dateFromISO,
dateFromRange,
datePartsFromISO,
dateToISO,
formatCalendarYear,
getOrder,
Expand Down Expand Up @@ -250,3 +251,10 @@ describe("parseCalendarYear", () => {
expect(parseCalendarYear(2566, { "default-calendar": "buddhist" } as DateLocaleData)).toBe(2023);
});
});

describe("datePartsFromISO", () => {
it("returns date, year, month from parsed ISO string date", () => {
expect(datePartsFromISO("2023-08-01")).toEqual({ day: "01", month: "08", year: "2023" });
expect(datePartsFromISO("00-08-01")).toEqual({ day: "01", month: "08", year: "00" });
});
});
11 changes: 11 additions & 0 deletions packages/calcite-components/src/utils/date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,17 @@ export function dateToISO(date?: Date): string {
return "";
}

/**
* Retrieve day, month, and year strings from a ISO string (YYYY-mm-dd)
*
* @param string
* @param isoDate
*/
export function datePartsFromISO(isoDate: string): { day: string; month: string; year: string } {
const dateParts = isoDate.split("-");
return { day: dateParts[2], month: dateParts[1], year: dateParts[0] };
}

/**
* Check if two dates are the same day, month, year
*
Expand Down

0 comments on commit 0ca35e9

Please sign in to comment.