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
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,28 @@
<mat-form-field>
<mat-label i18n>Date format</mat-label>
<input [formControl]="format" matInput />
<mat-hint i18n>
e.g. YYYY-MM-DD
<a
href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format"
target="_blank"
rel="noopener"
>(help)</a
>

<mat-hint>
<div i18n>
e.g. YYYY-MM-DD
<a
href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format"
target="_blank"
rel="noopener"
>(help)</a
>
</div>

@if (hasLowercaseMM) {
<div i18n>
Warning: "mm" is used for minutes, for months use "MM" instead.
</div>
}

@if (format.errors) {
<div class="warning" i18n>This format cannot parse all dates.</div>
}
</mat-hint>
@if (format.errors) {
<mat-error i18n> Format cannot parse all dates </mat-error>
}
</mat-form-field>

<app-help-button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@ mat-list-item {
margin: 5px 0;
border-radius: 5px;
}

.warning {
color: red;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { ComponentFixture, TestBed } from "@angular/core/testing";
import {
ComponentFixture,
fakeAsync,
TestBed,
tick,
} from "@angular/core/testing";

import { DateImportConfigComponent } from "./date-import-config.component";
import { MappingDialogData } from "app/core/import/import-column-mapping/mapping-dialog-data";
Expand Down Expand Up @@ -37,41 +42,44 @@ describe("DateImportConfigComponent", () => {
expect(component).toBeTruthy();
});

it("should parse dates with entered format", () => {
component.format.setValue("d/m/yyyy");
it("should parse dates with entered format", fakeAsync(() => {
component.format.setValue("D/M/YYYY");
tick();

//Tests may fail with moment.js > 2.29v
expect(component.values.map(({ parsed }) => parsed)).toEqual([
moment("2023-02-01").toDate(),
moment("2023-04-14").toDate(),
moment("2023-04-05").toDate(),
]);
});
}));

it("should sort dates that could not be parsed to top", () => {
component.format.setValue("dd/mm/yyyy");
it("should sort dates that could not be parsed to top", fakeAsync(() => {
component.format.setValue("DD/MM/YYYY");
tick();

expect(component.values[0].value).toBe("5/4/2023");
expect(component.values[0].parsed).toBeUndefined();
expect(component.values[1].parsed).toBeDate("2023-02-01");
expect(component.values[2].parsed).toBeDate("2023-04-14");
});
}));

it("should ask for confirmation on save if some dates could not be parsed", () => {
it("should ask for confirmation on save if some dates could not be parsed", fakeAsync(() => {
const confirmationSpy = spyOn(
TestBed.inject(ConfirmationDialogService),
"getConfirmation",
);
component.format.setValue("dd/mm/yyyy");
component.format.setValue("DD/MM/YYYY");
tick();

component.save();

expect(confirmationSpy).toHaveBeenCalled();
});
}));

it("should set the format as additional on save", async () => {
expect(data.col.additional).toBeUndefined();
component.format.setValue("d/m/yyyy");
component.format.setValue("D/M/YYYY");
const closeSpy = spyOn(TestBed.inject(MatDialogRef), "close");

await component.save();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { MatListModule } from "@angular/material/list";
import { MatButtonModule } from "@angular/material/button";
import { HelpButtonComponent } from "../../../common-components/help-button/help-button.component";
import { DynamicComponent } from "../../../config/dynamic-components/dynamic-component.decorator";
import { DateDatatype } from "../date.datatype";

/**
* Configuration dialog for parsing date value of data imported from a file.
Expand Down Expand Up @@ -43,6 +44,12 @@ export class DateImportConfigComponent {
valid = false;
values: { value: string; parsed?: Date }[] = [];

/**
* The date formatting interprets lowercase "mm" as minutes instead of months,
* this may lead to misunderstandings, so we check for it and show a warning if detected.
*/
hasLowercaseMM: boolean;

constructor() {
this.values = this.data.values
.filter((val) => !!val)
Expand All @@ -51,18 +58,24 @@ export class DateImportConfigComponent {
this.format.setValue(this.data.col.additional);
}

checkDateValues() {
async checkDateValues() {
this.format.setErrors(undefined);
this.values.forEach((val) => {
this.hasLowercaseMM = /mm/.test(this.format.value || "");
const dateType = new DateDatatype();
for (const val of this.values) {
// TODO: check and improve the date parsing. Tests fail with moment.js > 2.29
const date = moment(val.value, this.format.value?.toUpperCase(), true);
if (date.isValid()) {
val.parsed = date.toDate();
const date = await dateType.importMapFunction(
val.value,
undefined, // the schema is not needed here, we can skip loading it
this.format.value,
);
if (date instanceof Date && !isNaN(date.getTime())) {
val.parsed = date;
} else {
delete val.parsed;
this.format.setErrors({ parsingError: true });
}
});
}
// Sort unparsed dates to front
this.values.sort((v1, v2) =>
v1.parsed && !v2.parsed ? 1 : !v1.parsed && v2.parsed ? -1 : 0,
Expand All @@ -78,7 +91,7 @@ export class DateImportConfigComponent {
));

if (confirmed) {
this.data.col.additional = this.format.value?.toUpperCase();
this.data.col.additional = this.format.value;
this.dialog.close();
}
}
Expand Down
25 changes: 25 additions & 0 deletions src/app/core/basic-datatypes/date/date.datatype.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,29 @@ describe("Schema data type: date", () => {
expect(result).toBeUndefined();
expect(Logging.debug).toHaveBeenCalled();
});

it("should parse dates with importMapFunction", async () => {
const datatype = new DateDatatype();

const validDate = await datatype.importMapFunction(
"2020-12-31",
null,
"YYYY-MM-DD",
);
expect(validDate).toEqual(new Date(2020, 11, 31));

const invalidDate = await datatype.importMapFunction(
"broken date",
null,
"YYYY-MM-DD",
);
expect(invalidDate).toBeUndefined();

const dateTime = await datatype.importMapFunction(
"01.01.2025 15:06",
null,
"DD.MM.YYYY HH:mm",
);
expect(dateTime).toEqual(new Date(2025, 0, 1, 15, 6));
});
});
2 changes: 1 addition & 1 deletion src/app/core/basic-datatypes/date/date.datatype.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export class DateDatatype<DBFormat = string> extends DefaultDatatype<

override async importMapFunction(
val: any,
schemaField: EntitySchemaField,
schemaField?: EntitySchemaField | undefined,
additional?: any,
) {
const date = moment(val, additional, true);
Expand Down
Loading