src/app/core/basic-datatypes/date-range/date-range.datatype.ts
Datatype for a period of time: the start and the end date in one field.
Both dates are stored as date-only values (see DateOnlyDatatype) in one object,
and either of them may be missing:
@DatabaseField({dataType: "date-range"}) period: DateRangeValue; // {"start": "2026-03-01", "end": "2026-05-31"} in the database
DefaultDatatype<
DateRangeValue,
DateRangeDatabaseValue
>
DefaultDatatype<
DateRangeValue,
DateRangeDatabaseValue
>
Properties |
|
Methods |
| Async anonymize | ||||||
anonymize(value: DateRangeValue)
|
||||||
|
Inherited from
DefaultDatatype
|
||||||
|
Parameters :
Returns :
Promise<DateRangeValue>
|
| getExportColumns | ||||||
getExportColumns(schemaField: EntitySchemaField)
|
||||||
|
Inherited from
DefaultDatatype
|
||||||
|
Export the start and the end date in separate columns.
Parameters :
Returns :
ExportColumnMapping[]
|
| sortValue | ||||||
sortValue(value: DateRangeValue)
|
||||||
|
Inherited from
DefaultDatatype
|
||||||
|
Sort by the start date (or by the end date, if there is no start date).
Parameters :
Returns :
number | undefined
|
| transformToDatabaseFormat | ||||||
transformToDatabaseFormat(value: DateRangeValue)
|
||||||
|
Inherited from
DefaultDatatype
|
||||||
|
Parameters :
Returns :
DateRangeDatabaseValue | undefined
|
| transformToObjectFormat | ||||||||||||
transformToObjectFormat(value: DateRangeDatabaseValue, schemaField?: EntitySchemaField, parent?: any)
|
||||||||||||
|
Inherited from
DefaultDatatype
|
||||||||||||
|
Parameters :
Returns :
DateRangeValue | undefined
|
| Static detectAllFieldsInEntity | ||||||||||||
detectAllFieldsInEntity(entityOrType: Entity | EntityConstructor, dataTypes: string | string[])
|
||||||||||||
|
Inherited from
DefaultDatatype
|
||||||||||||
|
Detect all fields of the given datatype(s) in an entity's schema.
Parameters :
Returns :
literal type[]
Array of matching fields with their id and schema definition. |
| Static detectFieldInEntity | ||||||||||||
detectFieldInEntity(entityOrType: Entity | EntityConstructor, dataTypes: string | string[])
|
||||||||||||
|
Inherited from
DefaultDatatype
|
||||||||||||
|
Detect the first field of the given datatype(s) in an entity's schema. Scans the schema for a field whose Subclasses typically override this without the extra
Parameters :
Returns :
string | undefined
The field name of the first matching field, or |
| getReferenceSelector | ||||||||||||
getReferenceSelector(fieldId: string, schemaField: EntitySchemaField, referencedId: string)
|
||||||||||||
|
Inherited from
DefaultDatatype
|
||||||||||||
|
Mango conditions matching documents whose The conditions are matched against the database format, so only the datatype that
writes that format can build them - which is why this lives here rather than in the
calling service. Return every shape the value may have been stored in rather than
branching on The caller treats these as candidates only and still post-filters in memory, so matching too much is harmless - matching too little silently loses references. Example :
Parameters :
Returns :
[] | undefined
the conditions to OR together, or |
| Async importMapFunction | ||||||||||||||||||||
importMapFunction(val: any, schemaField: EntitySchemaField, additional?: any, importProcessingContext?: any)
|
||||||||||||||||||||
|
Inherited from
DefaultDatatype
|
||||||||||||||||||||
|
The function used to map values from the import data to values in the entities to be created. to share information across processing of multiple columns and rows.
Parameters :
Returns :
Promise<EntityType>
|
| Async importMatchField | ||||||||||||||||
importMatchField(schemaField: EntitySchemaField, columns: ColumnImportInput[], importProcessingContext?: ImportProcessingContext)
|
||||||||||||||||
|
Inherited from
DefaultDatatype
|
||||||||||||||||
|
Map all columns mapped to one target field to the value(s) stored there. ImportService calls this once per target field per row with every column mapped to that field. The default handles trimming and array splitting and delegates each individual value to importMapFunction. Datatypes that need to consider several columns together (e.g. matching an entity by more than one property) override this. (isArray=true), or undefined if nothing was mapped
Parameters :
Returns :
Promise<unknown>
the value to store: a single value (isArray=false) or array (isArray=true), or undefined if nothing was mapped |
| normalizeSchemaField | ||||||||
normalizeSchemaField(schemaField: EntitySchemaField)
|
||||||||
|
Inherited from
DefaultDatatype
|
||||||||
|
Return the (potentially adjusted) schema field for this datatype. Called when schema fields are set up (e.g. from config), allowing the datatype to normalize or fill in required settings. Override this in a subclass to enforce constraints
(e.g. always setting
Parameters :
Returns :
EntitySchemaField
The schema field to use (default: unchanged) |
| Static Readonly dataType |
Type : string
|
Default value : "date-range"
|
|
Inherited from
DefaultDatatype
|
| editComponent |
Type : string
|
Default value : "EditDateRange"
|
|
Inherited from
DefaultDatatype
|
| Readonly internalValidators |
Type : []
|
Default value : [endNotBeforeStart]
|
|
Inherited from
DefaultDatatype
|
| Static Readonly label |
Type : string
|
Default value : $localize`:datatype-label:date range (start and end date)`
|
|
Inherited from
DefaultDatatype
|
| viewComponent |
Type : string
|
Default value : "DisplayDateRange"
|
|
Inherited from
DefaultDatatype
|
| Readonly importAllowsMultiMapping |
Type : boolean
|
Default value : false
|
|
Inherited from
DefaultDatatype
|
|
Whether this datatype allows multiple values to be mapped to the same entity field during import. |
| Optional importConfigComponent |
Type : string
|
|
Inherited from
DefaultDatatype
|
|
A component to be rendered inline to configure the import transformation (e.g. defining a format or value mapping). The component receives inputs:
|
| Optional importConfigDialog |
Type : string
|
|
Inherited from
DefaultDatatype
|
|
A dialog component in which the import transformation is configured (see MappingDialogData for the data it receives). Datatypes that require such a dialog get it opened automatically as soon as the user maps a column to one of their fields, so that the suggested settings are reviewed and confirmed rather than silently skipped. |
import { Injectable } from "@angular/core";
import { AbstractControl, ValidationErrors } from "@angular/forms";
import {
DefaultDatatype,
ExportColumnMapping,
} from "../../entity/default-datatype/default.datatype";
import { EntitySchemaField } from "../../entity/schema/entity-schema-field";
import { DateOnlyDatatype } from "../date-only/date-only.datatype";
/** A period of time with a start and an end date, either of which can be missing (open-ended). */
export interface DateRangeValue {
start?: Date;
end?: Date;
}
/** How a {@link DateRangeValue} is stored in the database: dates as "YYYY-MM-DD" (without time). */
export interface DateRangeDatabaseValue {
start?: string;
end?: string;
}
/** Validator for the value of the field: the end must not be before the start. */
export function endNotBeforeStart(
control: AbstractControl<DateRangeValue | null>,
): ValidationErrors | null {
const { start, end } = control.value ?? {};
if (start && end && end < start) {
return {
dateRange: {
errorMessage: $localize`The end date must not be before the start date.`,
},
};
}
return null;
}
/**
* Datatype for a period of time: the start and the end date in one field.
*
* Both dates are stored as date-only values (see {@link DateOnlyDatatype}) in one object,
* and either of them may be missing:
* `@DatabaseField({dataType: "date-range"}) period: DateRangeValue; // {"start": "2026-03-01", "end": "2026-05-31"} in the database`
*/
@Injectable()
export class DateRangeDatatype extends DefaultDatatype<
DateRangeValue,
DateRangeDatabaseValue
> {
static override readonly dataType = "date-range";
static override readonly label: string = $localize`:datatype-label:date range (start and end date)`;
override viewComponent = "DisplayDateRange";
override editComponent = "EditDateRange";
override readonly internalValidators = [endNotBeforeStart];
/** converts the single dates, so that they are stored like all other date-only fields */
private readonly dateOnly = new DateOnlyDatatype();
override transformToDatabaseFormat(
value: DateRangeValue,
): DateRangeDatabaseValue | undefined {
const start = this.dateOnly.transformToDatabaseFormat(value?.start);
const end = this.dateOnly.transformToDatabaseFormat(value?.end);
if (!start && !end) {
return undefined;
}
return { ...(start && { start }), ...(end && { end }) };
}
override transformToObjectFormat(
value: DateRangeDatabaseValue,
schemaField?: EntitySchemaField,
parent?: any,
): DateRangeValue | undefined {
if (!value || typeof value !== "object") {
return undefined;
}
const start = this.dateOnly.transformToObjectFormat(
value.start,
schemaField,
parent,
);
const end = this.dateOnly.transformToObjectFormat(
value.end,
schemaField,
parent,
);
if (!start && !end) {
return undefined;
}
return { ...(start && { start }), ...(end && { end }) };
}
/** Sort by the start date (or by the end date, if there is no start date). */
override sortValue(value: DateRangeValue): number | undefined {
return (value?.start ?? value?.end)?.getTime();
}
/** Export the start and the end date in separate columns. */
override getExportColumns(
schemaField: EntitySchemaField,
): ExportColumnMapping<DateRangeValue>[] {
if (!schemaField.label) {
return [];
}
return [
{
keySuffix: "_start",
label: $localize`:Export column header|e.g. Project period (start):${schemaField.label} (start)`,
resolveValue: (value) => value?.start,
},
{
keySuffix: "_end",
label: $localize`:Export column header|e.g. Project period (end):${schemaField.label} (end)`,
resolveValue: (value) => value?.end,
},
];
}
override async anonymize(value: DateRangeValue): Promise<DateRangeValue> {
const [start, end] = await Promise.all([
value?.start && this.dateOnly.anonymize(value.start),
value?.end && this.dateOnly.anonymize(value.end),
]);
return { ...(start && { start }), ...(end && { end }) };
}
}