diff --git a/mycore-base/pom.xml b/mycore-base/pom.xml
index acae9a6f09..84ebc9ae26 100644
--- a/mycore-base/pom.xml
+++ b/mycore-base/pom.xml
@@ -401,6 +401,11 @@
xalanxalan
+
+ de.thetaphi
+ forbiddenapis
+ provided
+ org.apache.logging.log4jlog4j-slf4j-impl
diff --git a/mycore-base/src/main/java/org/mycore/common/date/MCRDateFormatter.java b/mycore-base/src/main/java/org/mycore/common/date/MCRDateFormatter.java
new file mode 100644
index 0000000000..bf876e016b
--- /dev/null
+++ b/mycore-base/src/main/java/org/mycore/common/date/MCRDateFormatter.java
@@ -0,0 +1,33 @@
+/*
+ * This file is part of *** M y C o R e ***
+ * See https://www.mycore.de/ for details.
+ *
+ * MyCoRe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MyCoRe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MyCoRe. If not, see .
+ */
+
+package org.mycore.common.date;
+
+import java.time.Instant;
+import java.util.Date;
+
+/**
+ * A {@link MCRDateFormatter} implements a date formatting strategy.
+ */
+public sealed interface MCRDateFormatter permits MCRDateFormatterBase, MCRInstantFormatterBase {
+
+ String format(Date date);
+
+ String format(Instant instant);
+
+}
diff --git a/mycore-base/src/main/java/org/mycore/common/date/MCRDateFormatterBase.java b/mycore-base/src/main/java/org/mycore/common/date/MCRDateFormatterBase.java
new file mode 100644
index 0000000000..a3e73a76c8
--- /dev/null
+++ b/mycore-base/src/main/java/org/mycore/common/date/MCRDateFormatterBase.java
@@ -0,0 +1,38 @@
+/*
+ * This file is part of *** M y C o R e ***
+ * See https://www.mycore.de/ for details.
+ *
+ * MyCoRe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MyCoRe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MyCoRe. If not, see .
+ */
+
+package org.mycore.common.date;
+
+import java.time.Instant;
+import java.util.Date;
+
+/**
+ * {@link MCRDateFormatterBase} is a base implementation of {@link MCRDateFormatter} that
+ * formats {@link Date} objects.
+ */
+public non-sealed abstract class MCRDateFormatterBase implements MCRDateFormatter {
+
+ @Override
+ public abstract String format(Date date);
+
+ @Override
+ public final String format(Instant instant) {
+ return format(Date.from(instant));
+ }
+
+}
diff --git a/mycore-base/src/main/java/org/mycore/common/date/MCRDateFormatterUtils.java b/mycore-base/src/main/java/org/mycore/common/date/MCRDateFormatterUtils.java
new file mode 100644
index 0000000000..5cfcfa0b3b
--- /dev/null
+++ b/mycore-base/src/main/java/org/mycore/common/date/MCRDateFormatterUtils.java
@@ -0,0 +1,49 @@
+/*
+ * This file is part of *** M y C o R e ***
+ * See https://www.mycore.de/ for details.
+ *
+ * MyCoRe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MyCoRe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MyCoRe. If not, see .
+ */
+
+package org.mycore.common.date;
+
+import java.time.ZoneId;
+import java.util.Locale;
+
+/**
+ * Utility class providing functions commonly used by implementations of {@link MCRDateFormatter}.
+ */
+public final class MCRDateFormatterUtils {
+
+ private MCRDateFormatterUtils() {
+ }
+
+ public static Locale getLocale(String locale) {
+ return switch (locale) {
+ case null -> Locale.getDefault();
+ case "ROOT" -> Locale.ROOT;
+ case "DEFAULT" -> Locale.getDefault();
+ case String languageTag -> Locale.forLanguageTag(languageTag);
+ };
+ }
+
+ public static ZoneId getTimeZone(String timeZone) {
+ return switch (timeZone) {
+ case null -> ZoneId.systemDefault();
+ case "DEFAULT" -> ZoneId.systemDefault();
+ case String zoneId -> ZoneId.of(zoneId);
+ };
+ }
+
+}
diff --git a/mycore-base/src/main/java/org/mycore/common/date/MCRDateStyler.java b/mycore-base/src/main/java/org/mycore/common/date/MCRDateStyler.java
new file mode 100644
index 0000000000..04ac43710a
--- /dev/null
+++ b/mycore-base/src/main/java/org/mycore/common/date/MCRDateStyler.java
@@ -0,0 +1,114 @@
+/*
+ * This file is part of *** M y C o R e ***
+ * See https://www.mycore.de/ for details.
+ *
+ * MyCoRe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MyCoRe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MyCoRe. If not, see .
+ */
+
+package org.mycore.common.date;
+
+import static org.mycore.common.date.MCRDateFormatterUtils.getLocale;
+import static org.mycore.common.date.MCRDateFormatterUtils.getTimeZone;
+
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.time.format.FormatStyle;
+import java.time.temporal.TemporalAccessor;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.function.Supplier;
+
+import org.mycore.common.config.annotation.MCRConfigurationProxy;
+import org.mycore.common.config.annotation.MCRProperty;
+
+import de.thetaphi.forbiddenapis.SuppressForbidden;
+
+/**
+ * A {@link MCRDateStyler} is a {@link MCRDateFormatter} that uses
+ * {@link DateTimeFormatter#format(TemporalAccessor)}, instantiated with
+ * {@link DateTimeFormatter#ofLocalizedDateTime(FormatStyle)}, to format a date.
+ *
+ * The following configuration options are available:
+ *
+ *
The property suffix {@link MCRDateStyler#DATE_STYLE_KEY} can be used to
+ * specify the date style.
+ *
The property suffix {@link MCRDateStyler#LOCALE_KEY} can be used to
+ * specify the locale (optional, defaults to {@link Locale#getDefault()};
+ * special values DEFAULT and ROOT can be used to set
+ * {@link Locale#getDefault()} and {@link Locale#ROOT} respectively).
+ *
The property suffix {@link MCRDateStyler#TIME_ZONE_KEY}can be used to
+ * specify the timezone (optional, defaults to {@link ZoneId#systemDefault()};
+ * special value DEFAULT can be used to set {@link ZoneId#systemDefault()}).
+ *
+ */
+@MCRConfigurationProxy(proxyClass = MCRDateStyler.Factory.class)
+public final class MCRDateStyler extends MCRInstantFormatterBase {
+
+ public static final String DATE_STYLE_KEY = "DateStyle";
+
+ public static final String LOCALE_KEY = "Locale";
+
+ public static final String TIME_ZONE_KEY = "TimeZone";
+
+ private final DateTimeFormatter formatter;
+
+ public MCRDateStyler(FormatStyle dateStyle) {
+ this(dateStyle, Locale.getDefault(), ZoneId.systemDefault());
+ }
+
+ public MCRDateStyler(FormatStyle dateStyle, Locale locale) {
+ this(dateStyle, locale, ZoneId.systemDefault());
+ }
+
+ @SuppressForbidden
+ public MCRDateStyler(FormatStyle dateStyle, Locale locale, ZoneId zoneId) {
+ Objects.requireNonNull(dateStyle, "Date style must not be null");
+ Objects.requireNonNull(locale, "Locale must not be null");
+ Objects.requireNonNull(zoneId, "Zone ID must not be null");
+ this.formatter = DateTimeFormatter.ofLocalizedDate(dateStyle).localizedBy(locale).withZone(zoneId);
+ }
+
+ @Override
+ public String format(Instant instant) {
+
+ return formatter.format(instant);
+ }
+
+ public static final class Factory implements Supplier {
+
+ @MCRProperty(name = DATE_STYLE_KEY)
+ public String dateStyle;
+
+ @MCRProperty(name = LOCALE_KEY, required = false)
+ public String locale;
+
+ @MCRProperty(name = TIME_ZONE_KEY, required = false)
+ public String timeZone;
+
+ @Override
+ public MCRDateStyler get() {
+ return new MCRDateStyler(FormatStyle.valueOf(dateStyle), getLocale(locale), getTimeZone(timeZone));
+ }
+
+ }
+
+}
diff --git a/mycore-base/src/main/java/org/mycore/common/date/MCRDateTimeFormatter.java b/mycore-base/src/main/java/org/mycore/common/date/MCRDateTimeFormatter.java
new file mode 100644
index 0000000000..062b1f6ad2
--- /dev/null
+++ b/mycore-base/src/main/java/org/mycore/common/date/MCRDateTimeFormatter.java
@@ -0,0 +1,110 @@
+/*
+ * This file is part of *** M y C o R e ***
+ * See https://www.mycore.de/ for details.
+ *
+ * MyCoRe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MyCoRe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MyCoRe. If not, see .
+ */
+
+package org.mycore.common.date;
+
+import static org.mycore.common.date.MCRDateFormatterUtils.getLocale;
+import static org.mycore.common.date.MCRDateFormatterUtils.getTimeZone;
+
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.TemporalAccessor;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.function.Supplier;
+
+import org.mycore.common.config.annotation.MCRConfigurationProxy;
+import org.mycore.common.config.annotation.MCRProperty;
+
+/**
+ * A {@link MCRDateTimeFormatter} is a {@link MCRDateFormatter} that uses
+ * {@link DateTimeFormatter#format(TemporalAccessor)}, instantiated with
+ * * {@link DateTimeFormatter#ofPattern(String)}, to format a date.
+ *
+ * The following configuration options are available:
+ *
+ *
The property suffix {@link MCRDateTimeFormatter#FORMAT_KEY} can be used to
+ * specify the format.
+ *
The property suffix {@link MCRDateTimeFormatter#LOCALE_KEY} can be used to
+ * specify the locale (optional, defaults to {@link Locale#getDefault()};
+ * special values DEFAULT and ROOT can be used to set
+ * {@link Locale#getDefault()} and {@link Locale#ROOT} respectively).
+ *
The property suffix {@link MCRDateTimeFormatter#TIME_ZONE_KEY}can be used to
+ * specify the timezone (optional, defaults to {@link ZoneId#systemDefault()};
+ * special value DEFAULT can be used to set {@link ZoneId#systemDefault()}).
+ *
+ */
+@MCRConfigurationProxy(proxyClass = MCRDateTimeFormatter.Factory.class)
+public final class MCRDateTimeFormatter extends MCRInstantFormatterBase {
+
+ public static final String FORMAT_KEY = "Format";
+
+ public static final String LOCALE_KEY = "Locale";
+
+ public static final String TIME_ZONE_KEY = "TimeZone";
+
+ private final DateTimeFormatter formatter;
+
+ public MCRDateTimeFormatter(String format) {
+ this(format, Locale.getDefault(), ZoneId.systemDefault());
+ }
+
+ public MCRDateTimeFormatter(String format, Locale locale) {
+ this(format, locale, ZoneId.systemDefault());
+ }
+
+ public MCRDateTimeFormatter(String format, Locale locale, ZoneId zoneId) {
+ Objects.requireNonNull(format, "Format style must not be null");
+ Objects.requireNonNull(locale, "Locale must not be null");
+ Objects.requireNonNull(zoneId, "Zone ID must not be null");
+ this.formatter = DateTimeFormatter.ofPattern(format, locale).withZone(zoneId);
+ }
+
+ @Override
+ public String format(Instant instant) {
+
+ return formatter.format(instant);
+ }
+
+ public static final class Factory implements Supplier {
+
+ @MCRProperty(name = FORMAT_KEY)
+ public String format;
+
+ @MCRProperty(name = LOCALE_KEY, required = false)
+ public String locale;
+
+ @MCRProperty(name = TIME_ZONE_KEY, required = false)
+ public String timeZone;
+
+ @Override
+ public MCRDateTimeFormatter get() {
+ return new MCRDateTimeFormatter(format, getLocale(locale), getTimeZone(timeZone));
+ }
+
+ }
+
+}
diff --git a/mycore-base/src/main/java/org/mycore/common/date/MCRDateTimeStyler.java b/mycore-base/src/main/java/org/mycore/common/date/MCRDateTimeStyler.java
new file mode 100644
index 0000000000..87b2a74ea4
--- /dev/null
+++ b/mycore-base/src/main/java/org/mycore/common/date/MCRDateTimeStyler.java
@@ -0,0 +1,123 @@
+/*
+ * This file is part of *** M y C o R e ***
+ * See https://www.mycore.de/ for details.
+ *
+ * MyCoRe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MyCoRe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MyCoRe. If not, see .
+ */
+
+package org.mycore.common.date;
+
+import static org.mycore.common.date.MCRDateFormatterUtils.getLocale;
+import static org.mycore.common.date.MCRDateFormatterUtils.getTimeZone;
+
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.time.format.FormatStyle;
+import java.time.temporal.TemporalAccessor;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.function.Supplier;
+
+import org.mycore.common.config.annotation.MCRConfigurationProxy;
+import org.mycore.common.config.annotation.MCRProperty;
+
+import de.thetaphi.forbiddenapis.SuppressForbidden;
+
+/**
+ * A {@link MCRDateTimeStyler} is a {@link MCRDateFormatter} that uses
+ * {@link DateTimeFormatter#format(TemporalAccessor)}, instantiated with
+ * {@link DateTimeFormatter#ofLocalizedDateTime(FormatStyle, FormatStyle)}, to format a date.
+ *
+ * The following configuration options are available:
+ *
+ *
The property suffix {@link MCRDateTimeStyler#DATE_STYLE_KEY} can be used to
+ * specify the date style.
+ *
The property suffix {@link MCRDateTimeStyler#TIME_STYLE_KEY} can be used to
+ * specify the time style.
+ *
The property suffix {@link MCRDateTimeStyler#LOCALE_KEY} can be used to
+ * specify the locale (optional, defaults to {@link Locale#getDefault()};
+ * special values DEFAULT and ROOT can be used to set
+ * {@link Locale#getDefault()} and {@link Locale#ROOT} respectively).
+ *
The property suffix {@link MCRDateTimeStyler#TIME_ZONE_KEY}can be used to
+ * specify the timezone (optional, defaults to {@link ZoneId#systemDefault()};
+ * special value DEFAULT can be used to set {@link ZoneId#systemDefault()}).
+ *
+ */
+@MCRConfigurationProxy(proxyClass = MCRDateTimeStyler.Factory.class)
+public final class MCRDateTimeStyler extends MCRInstantFormatterBase {
+
+ public static final String DATE_STYLE_KEY = "DateStyle";
+
+ public static final String TIME_STYLE_KEY = "TimeStyle";
+
+ public static final String LOCALE_KEY = "Locale";
+
+ public static final String TIME_ZONE_KEY = "TimeZone";
+
+ private final DateTimeFormatter formatter;
+
+ public MCRDateTimeStyler(FormatStyle dateStyle, FormatStyle timeStyle) {
+ this(dateStyle, timeStyle, Locale.getDefault(), ZoneId.systemDefault());
+ }
+
+ public MCRDateTimeStyler(FormatStyle dateStyle, FormatStyle timeStyle, Locale locale) {
+ this(dateStyle, timeStyle, locale, ZoneId.systemDefault());
+ }
+
+ @SuppressForbidden
+ public MCRDateTimeStyler(FormatStyle dateStyle, FormatStyle timeStyle, Locale locale, ZoneId zoneId) {
+ Objects.requireNonNull(dateStyle, "Date style must not be null");
+ Objects.requireNonNull(dateStyle, "Time style must not be null");
+ Objects.requireNonNull(locale, "Locale must not be null");
+ Objects.requireNonNull(zoneId, "Zone ID must not be null");
+ formatter = DateTimeFormatter.ofLocalizedDateTime(dateStyle, timeStyle).localizedBy(locale).withZone(zoneId);
+ }
+
+ @Override
+ public String format(Instant instant) {
+
+ return formatter.format(instant);
+ }
+
+ public static final class Factory implements Supplier {
+
+ @MCRProperty(name = DATE_STYLE_KEY)
+ public String dateStyle;
+
+ @MCRProperty(name = TIME_STYLE_KEY)
+ public String timeStyle;
+
+ @MCRProperty(name = LOCALE_KEY, required = false)
+ public String locale;
+
+ @MCRProperty(name = TIME_ZONE_KEY, required = false)
+ public String timeZone;
+
+ @Override
+ public MCRDateTimeStyler get() {
+ return new MCRDateTimeStyler(FormatStyle.valueOf(dateStyle), FormatStyle.valueOf(timeStyle),
+ getLocale(locale), getTimeZone(timeZone));
+ }
+
+ }
+
+}
diff --git a/mycore-base/src/main/java/org/mycore/common/date/MCRFLDateScrambler.java b/mycore-base/src/main/java/org/mycore/common/date/MCRFLDateScrambler.java
new file mode 100644
index 0000000000..014efb5cf1
--- /dev/null
+++ b/mycore-base/src/main/java/org/mycore/common/date/MCRFLDateScrambler.java
@@ -0,0 +1,60 @@
+/*
+ * This file is part of *** M y C o R e ***
+ * See https://www.mycore.de/ for details.
+ *
+ * MyCoRe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MyCoRe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MyCoRe. If not, see .
+ */
+
+package org.mycore.common.date;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * A {@link MCRFLDateScrambler} is a {@link MCRDateFormatter} that uses a scrambling algorithm introduced by Frank Lützenkirchen to format a date.
+ *
+ */
+public final class MCRFLDateScrambler extends MCRDateFormatterBase {
+
+ @Override
+ public String format(Date date) {
+
+ Calendar calendar = new GregorianCalendar(TimeZone.getTimeZone("GMT+01:00"), Locale.ENGLISH);
+ calendar.setTime(date);
+
+ int yyy = 2268 - calendar.get(Calendar.YEAR);
+ int ddd = 500 - calendar.get(Calendar.DAY_OF_YEAR);
+ int hh = calendar.get(Calendar.HOUR_OF_DAY);
+ int mm = calendar.get(Calendar.MINUTE);
+ int ss = calendar.get(Calendar.SECOND);
+ int sss = 99_999 - (hh * 3600 + mm * 60 + ss);
+ String ddddd = String.valueOf(yyy * 366 + ddd);
+
+ return String.valueOf(ddddd.charAt(4)) + ddddd.charAt(2)
+ + ddddd.charAt(1) + ddddd.charAt(3) + ddddd.charAt(0) + sss;
+
+ }
+
+}
diff --git a/mycore-base/src/main/java/org/mycore/common/date/MCRISO8601DateFormatter.java b/mycore-base/src/main/java/org/mycore/common/date/MCRISO8601DateFormatter.java
new file mode 100644
index 0000000000..8acc940d28
--- /dev/null
+++ b/mycore-base/src/main/java/org/mycore/common/date/MCRISO8601DateFormatter.java
@@ -0,0 +1,109 @@
+/*
+ * This file is part of *** M y C o R e ***
+ * See https://www.mycore.de/ for details.
+ *
+ * MyCoRe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MyCoRe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MyCoRe. If not, see .
+ */
+
+package org.mycore.common.date;
+
+import static org.mycore.common.date.MCRDateFormatterUtils.getLocale;
+import static org.mycore.common.date.MCRDateFormatterUtils.getTimeZone;
+
+import java.time.ZoneId;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.function.Supplier;
+
+import org.mycore.common.config.annotation.MCRConfigurationProxy;
+import org.mycore.common.config.annotation.MCRProperty;
+import org.mycore.datamodel.common.MCRISO8601Date;
+
+/**
+ * A {@link MCRISO8601DateFormatter} is a {@link MCRDateFormatter} that uses
+ * {@link MCRISO8601Date#format(String, Locale)} to format a date.
+ *
+ * The following configuration options are available:
+ *
+ *
The property suffix {@link MCRSimpleDateFormatter#FORMAT_KEY} can be used to
+ * specify the format.
+ *
The property suffix {@link MCRSimpleDateFormatter#LOCALE_KEY} can be used to
+ * specify the locale (optional, defaults to {@link Locale#getDefault()};
+ * special values DEFAULT and ROOT can be used to set
+ * {@link Locale#getDefault()} and {@link Locale#ROOT} respectively).
+ *
The property suffix {@link MCRSimpleDateFormatter#TIME_ZONE_KEY}can be used to
+ * specify the timezone (optional, defaults to {@link ZoneId#systemDefault()};
+ * special value DEFAULT can be used to set {@link ZoneId#systemDefault()}).
+ *
+ */
+@MCRConfigurationProxy(proxyClass = MCRISO8601DateFormatter.Factory.class)
+public final class MCRISO8601DateFormatter extends MCRDateFormatterBase {
+
+ private final String format;
+
+ private final Locale locale;
+
+ private final String timeZone;
+
+ public MCRISO8601DateFormatter(String format) {
+ this(format, Locale.ROOT, ZoneId.systemDefault());
+ }
+
+ public MCRISO8601DateFormatter(String format, Locale locale) {
+ this(format, locale, ZoneId.systemDefault());
+ }
+
+ public MCRISO8601DateFormatter(String format, Locale locale, ZoneId zoneId) {
+ this.format = Objects.requireNonNull(format, "Format must not be null");
+ this.locale = Objects.requireNonNull(locale, "Locale must not be null");
+ this.timeZone = Objects.requireNonNull(zoneId, "Zone ID must not be null").getId();
+ }
+
+ @Override
+ public String format(Date date) {
+
+ MCRISO8601Date isoDate = new MCRISO8601Date();
+ isoDate.setDate(date);
+
+ return isoDate.format(format, locale, timeZone);
+
+ }
+
+ public static final class Factory implements Supplier {
+
+ @MCRProperty(name = "Format")
+ public String format;
+
+ @MCRProperty(name = "Locale", required = false)
+ public String locale;
+
+ @MCRProperty(name = "TimeZone", required = false)
+ public String timeZone;
+
+ @Override
+ public MCRISO8601DateFormatter get() {
+ return new MCRISO8601DateFormatter(format, getLocale(locale), getTimeZone(timeZone));
+ }
+
+ }
+
+}
diff --git a/mycore-base/src/main/java/org/mycore/common/date/MCRInstantFormatterBase.java b/mycore-base/src/main/java/org/mycore/common/date/MCRInstantFormatterBase.java
new file mode 100644
index 0000000000..34943d4af1
--- /dev/null
+++ b/mycore-base/src/main/java/org/mycore/common/date/MCRInstantFormatterBase.java
@@ -0,0 +1,38 @@
+/*
+ * This file is part of *** M y C o R e ***
+ * See https://www.mycore.de/ for details.
+ *
+ * MyCoRe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MyCoRe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MyCoRe. If not, see .
+ */
+
+package org.mycore.common.date;
+
+import java.time.Instant;
+import java.util.Date;
+
+/**
+ * {@link MCRInstantFormatterBase} is a base implementation of {@link MCRDateFormatter} that
+ * formats {@link Instant} objects.
+ */
+public non-sealed abstract class MCRInstantFormatterBase implements MCRDateFormatter {
+
+ @Override
+ public final String format(Date date) {
+ return format(date.toInstant());
+ }
+
+ @Override
+ public abstract String format(Instant instant);
+
+}
diff --git a/mycore-base/src/main/java/org/mycore/common/date/MCRSimpleDateFormatter.java b/mycore-base/src/main/java/org/mycore/common/date/MCRSimpleDateFormatter.java
new file mode 100644
index 0000000000..b82861ade2
--- /dev/null
+++ b/mycore-base/src/main/java/org/mycore/common/date/MCRSimpleDateFormatter.java
@@ -0,0 +1,116 @@
+/*
+ * This file is part of *** M y C o R e ***
+ * See https://www.mycore.de/ for details.
+ *
+ * MyCoRe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MyCoRe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MyCoRe. If not, see .
+ */
+
+package org.mycore.common.date;
+
+import static org.mycore.common.date.MCRDateFormatterUtils.getLocale;
+import static org.mycore.common.date.MCRDateFormatterUtils.getTimeZone;
+
+import java.text.SimpleDateFormat;
+import java.time.ZoneId;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.TimeZone;
+import java.util.function.Supplier;
+
+import org.mycore.common.config.annotation.MCRConfigurationProxy;
+import org.mycore.common.config.annotation.MCRProperty;
+
+/**
+ * A {@link MCRSimpleDateFormatter} is a {@link MCRDateFormatter} that uses
+ * {@link SimpleDateFormat#format(Date)} to format a date.
+ *
+ * The following configuration options are available:
+ *
+ *
The property suffix {@link MCRSimpleDateFormatter#FORMAT_KEY} can be used to
+ * specify the format.
+ *
The property suffix {@link MCRSimpleDateFormatter#LOCALE_KEY} can be used to
+ * specify the locale (optional, defaults to {@link Locale#getDefault()};
+ * special values DEFAULT and ROOT can be used to set
+ * {@link Locale#getDefault()} and {@link Locale#ROOT} respectively).
+ *
The property suffix {@link MCRSimpleDateFormatter#TIME_ZONE_KEY}can be used to
+ * specify the timezone (optional, defaults to {@link ZoneId#systemDefault()};
+ * special value DEFAULT can be used to set {@link ZoneId#systemDefault()}).
+ *
+ */
+@MCRConfigurationProxy(proxyClass = MCRSimpleDateFormatter.Factory.class)
+public final class MCRSimpleDateFormatter extends MCRDateFormatterBase {
+
+ public static final String FORMAT_KEY = "Format";
+
+ public static final String LOCALE_KEY = "Locale";
+
+ public static final String TIME_ZONE_KEY = "TimeZone";
+
+ private final ThreadLocal formatHolder;
+
+ public MCRSimpleDateFormatter(String format) {
+ this(format, Locale.getDefault(), ZoneId.systemDefault());
+ }
+
+ public MCRSimpleDateFormatter(String format, Locale locale) {
+ this(format, locale, ZoneId.systemDefault());
+ }
+
+ public MCRSimpleDateFormatter(String format, Locale locale, ZoneId zoneId) {
+ Objects.requireNonNull(format, "Format must not be null");
+ Objects.requireNonNull(locale, "Locale must not be null");
+ Objects.requireNonNull(zoneId, "Zone ID must not be null");
+ formatHolder = ThreadLocal.withInitial(getFormatSupplier(format, locale, TimeZone.getTimeZone(zoneId)));
+ }
+
+ private static Supplier getFormatSupplier(String format, Locale locale, TimeZone timeZone) {
+ return () -> {
+ SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format, locale);
+ simpleDateFormat.setTimeZone(timeZone);
+ return simpleDateFormat;
+ };
+ }
+
+ @Override
+ public String format(Date date) {
+ return formatHolder.get().format(date);
+ }
+
+ public static final class Factory implements Supplier {
+
+ @MCRProperty(name = FORMAT_KEY)
+ public String format;
+
+ @MCRProperty(name = LOCALE_KEY, required = false)
+ public String locale;
+
+ @MCRProperty(name = TIME_ZONE_KEY, required = false)
+ public String timeZone;
+
+ @Override
+ public MCRSimpleDateFormatter get() {
+ return new MCRSimpleDateFormatter(format, getLocale(locale), getTimeZone(timeZone));
+ }
+
+ }
+
+}
diff --git a/mycore-base/src/main/java/org/mycore/datamodel/classifications2/mapping/MCRDefaultXMappingClassificationGenerator.java b/mycore-base/src/main/java/org/mycore/datamodel/classifications2/mapping/MCRDefaultXMappingClassificationGenerator.java
index e48e9f95e4..daf5c02dae 100644
--- a/mycore-base/src/main/java/org/mycore/datamodel/classifications2/mapping/MCRDefaultXMappingClassificationGenerator.java
+++ b/mycore-base/src/main/java/org/mycore/datamodel/classifications2/mapping/MCRDefaultXMappingClassificationGenerator.java
@@ -59,9 +59,9 @@
*
The property suffix {@link MCRXMappingClassificationGeneratorBase#EVALUATOR_KEY} can be used to
* specify the evaluator used to obtain category IDs from.
*
For the evaluator, the property suffix {@link MCRSentinel#DEFAULT_KEY} can be used to
- * exclude the evaluator from the configuration and use a default {@link MCRSimpleXMappingEvaluator} instead.
+ * exclude the evaluator from the configuration and use a default {@link MCRSimpleXMappingEvaluator} instead.
*
The property suffix {@link MCRXMappingClassificationGeneratorBase#ON_MISSING_MAPPED_CATEGORY_KEY} can be used to
- * specify the behaviour, when a mapped category ID is missing.
+ * specify the behavior, when a mapped category ID is missing.
*
* Example:
*
diff --git a/mycore-base/src/main/java/org/mycore/datamodel/classifications2/mapping/MCRXPathClassificationMappingCondition.java b/mycore-base/src/main/java/org/mycore/datamodel/classifications2/mapping/MCRXPathClassificationMappingCondition.java
index 3133a3127f..7942610f4d 100644
--- a/mycore-base/src/main/java/org/mycore/datamodel/classifications2/mapping/MCRXPathClassificationMappingCondition.java
+++ b/mycore-base/src/main/java/org/mycore/datamodel/classifications2/mapping/MCRXPathClassificationMappingCondition.java
@@ -31,13 +31,13 @@
/**
* A {@link MCRXPathClassificationMappingCondition} is a {@link Condition} that
- * determines the condition of a MyCoRe object based on a configurable X-Path evaluating the
+ * determines the condition of a MyCoRe object based on a configurable XPath evaluating the
* XML representation of the MyCoRe object.
*
* The following configuration options are available:
*
*
The property suffix {@link MCRXPathClassificationMappingCondition#X_PATH_KEY} can be used to
- * specify the X-Path to be used.
+ * specify the XPath to be used.
*
* Example:
*
@@ -53,7 +53,7 @@ public final class MCRXPathClassificationMappingCondition implements Condition {
private final String xPath;
public MCRXPathClassificationMappingCondition(String xPath) {
- this.xPath = Objects.requireNonNull(xPath, "X-Path must not be null");
+ this.xPath = Objects.requireNonNull(xPath, "XPath must not be null");
}
@Override
diff --git a/mycore-base/src/test/java/org/mycore/common/date/MCRDateStylerTest.java b/mycore-base/src/test/java/org/mycore/common/date/MCRDateStylerTest.java
new file mode 100644
index 0000000000..815c582fc3
--- /dev/null
+++ b/mycore-base/src/test/java/org/mycore/common/date/MCRDateStylerTest.java
@@ -0,0 +1,98 @@
+/*
+ * This file is part of *** M y C o R e ***
+ * See https://www.mycore.de/ for details.
+ *
+ * MyCoRe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MyCoRe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MyCoRe. If not, see .
+ */
+
+package org.mycore.common.date;
+
+import static java.time.format.FormatStyle.LONG;
+import static java.time.format.FormatStyle.SHORT;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.FormatStyle;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+public class MCRDateStylerTest {
+
+ public static final ZoneId ZONE_ID = ZoneId.systemDefault();
+
+ private static Stream provideDates() {
+ List argumentsList = new ArrayList<>();
+ argumentsList.add(Arguments.of(toDate(2000, 1, 1, 0, 0), SHORT, "01.01.00"));
+ argumentsList.add(Arguments.of(toDate(2000, 1, 1, 0, 0), LONG, "1. Januar 2000"));
+ argumentsList.add(Arguments.of(toDate(2000, 12, 1, 10, 12), SHORT, "01.12.00"));
+ argumentsList.add(Arguments.of(toDate(2000, 12, 1, 10, 12), LONG, "1. Dezember 2000"));
+ argumentsList.add(Arguments.of(toDate(2012, 11, 10, 9, 8), SHORT, "10.11.12"));
+ argumentsList.add(Arguments.of(toDate(2012, 11, 10, 9, 8), LONG, "10. November 2012"));
+ argumentsList.add(Arguments.of(toDate(2025, 11, 5, 13, 0), SHORT, "05.11.25"));
+ argumentsList.add(Arguments.of(toDate(2025, 11, 5, 13, 0), LONG, "5. November 2025"));
+ return argumentsList.stream();
+ }
+
+ private static Date toDate(int year, int month, int day, int hour, int minute) {
+ return Date.from(toInstant(year, month, day, hour, minute));
+ }
+
+ @ParameterizedTest
+ @MethodSource("provideDates")
+ public void formatDate(Date date, FormatStyle dateFormat, String expected) {
+
+ String formattedDate = new MCRDateStyler(dateFormat, Locale.GERMAN).format(date);
+
+ assertEquals(expected, formattedDate);
+
+ }
+
+ private static Stream provideInstants() {
+ List argumentsList = new ArrayList<>();
+ argumentsList.add(Arguments.of(toInstant(2000, 1, 1, 0, 0), SHORT, "01.01.00"));
+ argumentsList.add(Arguments.of(toInstant(2000, 1, 1, 0, 0), LONG, "1. Januar 2000"));
+ argumentsList.add(Arguments.of(toInstant(2000, 12, 1, 10, 12), SHORT, "01.12.00"));
+ argumentsList.add(Arguments.of(toInstant(2000, 12, 1, 10, 12), LONG, "1. Dezember 2000"));
+ argumentsList.add(Arguments.of(toInstant(2012, 11, 10, 9, 8), SHORT, "10.11.12"));
+ argumentsList.add(Arguments.of(toInstant(2012, 11, 10, 9, 8), LONG, "10. November 2012"));
+ argumentsList.add(Arguments.of(toInstant(2025, 11, 5, 13, 0), SHORT, "05.11.25"));
+ argumentsList.add(Arguments.of(toInstant(2025, 11, 5, 13, 0), LONG, "5. November 2025"));
+ return argumentsList.stream();
+ }
+
+ private static Instant toInstant(int year, int month, int day, int hour, int minute) {
+ LocalDateTime localDateTime = LocalDateTime.of(year, month, day, hour, minute);
+ return localDateTime.atZone(ZONE_ID).toInstant();
+ }
+
+ @ParameterizedTest
+ @MethodSource("provideInstants")
+ public void formatInstant(Instant instant, FormatStyle dateFormat, String expected) {
+
+ String formattedDate = new MCRDateStyler(dateFormat, Locale.GERMAN).format(instant);
+
+ assertEquals(expected, formattedDate);
+
+ }
+
+}
diff --git a/mycore-base/src/test/java/org/mycore/common/date/MCRDateTimeFormatterTest.java b/mycore-base/src/test/java/org/mycore/common/date/MCRDateTimeFormatterTest.java
new file mode 100644
index 0000000000..bcb6f6e0a8
--- /dev/null
+++ b/mycore-base/src/test/java/org/mycore/common/date/MCRDateTimeFormatterTest.java
@@ -0,0 +1,98 @@
+/*
+ * This file is part of *** M y C o R e ***
+ * See https://www.mycore.de/ for details.
+ *
+ * MyCoRe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MyCoRe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MyCoRe. If not, see .
+ */
+
+package org.mycore.common.date;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+public class MCRDateTimeFormatterTest {
+
+ public static final String DATE_FORMAT_1 = "yyyyMMddHHmmss";
+
+ public static final String DATE_FORMAT_2 = "yyyy-MM-dd'T'HH:mm";
+
+ public static final ZoneId ZONE_ID = ZoneId.systemDefault();
+
+ private static Stream provideDates() {
+ List argumentsList = new ArrayList<>();
+ argumentsList.add(Arguments.of(toDate(2000, 1, 1, 0, 0), DATE_FORMAT_1, "20000101000000"));
+ argumentsList.add(Arguments.of(toDate(2000, 1, 1, 0, 0), DATE_FORMAT_2, "2000-01-01T00:00"));
+ argumentsList.add(Arguments.of(toDate(2000, 12, 1, 10, 12), DATE_FORMAT_1, "20001201101200"));
+ argumentsList.add(Arguments.of(toDate(2000, 12, 1, 10, 12), DATE_FORMAT_2, "2000-12-01T10:12"));
+ argumentsList.add(Arguments.of(toDate(2012, 11, 10, 9, 8), DATE_FORMAT_1, "20121110090800"));
+ argumentsList.add(Arguments.of(toDate(2012, 11, 10, 9, 8), DATE_FORMAT_2, "2012-11-10T09:08"));
+ argumentsList.add(Arguments.of(toDate(2025, 11, 5, 13, 0), DATE_FORMAT_1, "20251105130000"));
+ argumentsList.add(Arguments.of(toDate(2025, 11, 5, 13, 0), DATE_FORMAT_2, "2025-11-05T13:00"));
+ return argumentsList.stream();
+ }
+
+ private static Date toDate(int year, int month, int day, int hour, int minute) {
+ return Date.from(toInstant(year, month, day, hour, minute));
+ }
+
+ @ParameterizedTest
+ @MethodSource("provideDates")
+ public void formatDate(Date date, String format, String expected) {
+
+ String formattedDate = new MCRDateTimeFormatter(format).format(date);
+
+ assertEquals(expected, formattedDate);
+
+ }
+
+ private static Stream provideInstants() {
+ List argumentsList = new ArrayList<>();
+ argumentsList.add(Arguments.of(toInstant(2000, 1, 1, 0, 0), DATE_FORMAT_1, "20000101000000"));
+ argumentsList.add(Arguments.of(toInstant(2000, 1, 1, 0, 0), DATE_FORMAT_2, "2000-01-01T00:00"));
+ argumentsList.add(Arguments.of(toInstant(2000, 12, 1, 10, 12), DATE_FORMAT_1, "20001201101200"));
+ argumentsList.add(Arguments.of(toInstant(2000, 12, 1, 10, 12), DATE_FORMAT_2, "2000-12-01T10:12"));
+ argumentsList.add(Arguments.of(toInstant(2012, 11, 10, 9, 8), DATE_FORMAT_1, "20121110090800"));
+ argumentsList.add(Arguments.of(toInstant(2012, 11, 10, 9, 8), DATE_FORMAT_2, "2012-11-10T09:08"));
+ argumentsList.add(Arguments.of(toInstant(2025, 11, 5, 13, 0), DATE_FORMAT_1, "20251105130000"));
+ argumentsList.add(Arguments.of(toInstant(2025, 11, 5, 13, 0), DATE_FORMAT_2, "2025-11-05T13:00"));
+ return argumentsList.stream();
+ }
+
+ private static Instant toInstant(int year, int month, int day, int hour, int minute) {
+ LocalDateTime localDateTime = LocalDateTime.of(year, month, day, hour, minute);
+ return localDateTime.atZone(ZONE_ID).toInstant();
+ }
+
+ @ParameterizedTest
+ @MethodSource("provideInstants")
+ public void formatInstant(Instant instant, String format, String expected) {
+
+ String formattedDate = new MCRDateTimeFormatter(format).format(instant);
+
+ assertEquals(expected, formattedDate);
+
+ }
+
+}
diff --git a/mycore-base/src/test/java/org/mycore/common/date/MCRDateTimeStylerTest.java b/mycore-base/src/test/java/org/mycore/common/date/MCRDateTimeStylerTest.java
new file mode 100644
index 0000000000..31d937be04
--- /dev/null
+++ b/mycore-base/src/test/java/org/mycore/common/date/MCRDateTimeStylerTest.java
@@ -0,0 +1,99 @@
+/*
+ * This file is part of *** M y C o R e ***
+ * See https://www.mycore.de/ for details.
+ *
+ * MyCoRe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MyCoRe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MyCoRe. If not, see .
+ */
+
+package org.mycore.common.date;
+
+import static java.time.format.FormatStyle.LONG;
+import static java.time.format.FormatStyle.MEDIUM;
+import static java.time.format.FormatStyle.SHORT;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.FormatStyle;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+public class MCRDateTimeStylerTest {
+
+ public static final ZoneId ZONE_ID = ZoneId.systemDefault();
+
+ private static Stream provideDates() {
+ List argumentsList = new ArrayList<>();
+ argumentsList.add(Arguments.of(toDate(2000, 1, 1, 0, 0), SHORT, SHORT, "01.01.00, 00:00"));
+ argumentsList.add(Arguments.of(toDate(2000, 1, 1, 0, 0), LONG, MEDIUM, "1. Januar 2000, 00:00:00"));
+ argumentsList.add(Arguments.of(toDate(2000, 12, 1, 10, 12), SHORT, SHORT, "01.12.00, 10:12"));
+ argumentsList.add(Arguments.of(toDate(2000, 12, 1, 10, 12), LONG, MEDIUM, "1. Dezember 2000, 10:12:00"));
+ argumentsList.add(Arguments.of(toDate(2012, 11, 10, 9, 8), SHORT, SHORT, "10.11.12, 09:08"));
+ argumentsList.add(Arguments.of(toDate(2012, 11, 10, 9, 8), LONG, MEDIUM, "10. November 2012, 09:08:00"));
+ argumentsList.add(Arguments.of(toDate(2025, 11, 5, 13, 0), SHORT, SHORT, "05.11.25, 13:00"));
+ argumentsList.add(Arguments.of(toDate(2025, 11, 5, 13, 0), LONG, MEDIUM, "5. November 2025, 13:00:00"));
+ return argumentsList.stream();
+ }
+
+ private static Date toDate(int year, int month, int day, int hour, int minute) {
+ return Date.from(toInstant(year, month, day, hour, minute));
+ }
+
+ @ParameterizedTest
+ @MethodSource("provideDates")
+ public void formatDate(Date date, FormatStyle dateFormat, FormatStyle timeFormat, String expected) {
+
+ String formattedDate = new MCRDateTimeStyler(dateFormat, timeFormat, Locale.GERMAN).format(date);
+
+ assertEquals(expected, formattedDate);
+
+ }
+
+ private static Stream provideInstants() {
+ List argumentsList = new ArrayList<>();
+ argumentsList.add(Arguments.of(toInstant(2000, 1, 1, 0, 0), SHORT, SHORT, "01.01.00, 00:00"));
+ argumentsList.add(Arguments.of(toInstant(2000, 1, 1, 0, 0), LONG, MEDIUM, "1. Januar 2000, 00:00:00"));
+ argumentsList.add(Arguments.of(toInstant(2000, 12, 1, 10, 12), SHORT, SHORT, "01.12.00, 10:12"));
+ argumentsList.add(Arguments.of(toInstant(2000, 12, 1, 10, 12), LONG, MEDIUM, "1. Dezember 2000, 10:12:00"));
+ argumentsList.add(Arguments.of(toInstant(2012, 11, 10, 9, 8), SHORT, SHORT, "10.11.12, 09:08"));
+ argumentsList.add(Arguments.of(toInstant(2012, 11, 10, 9, 8), LONG, MEDIUM, "10. November 2012, 09:08:00"));
+ argumentsList.add(Arguments.of(toInstant(2025, 11, 5, 13, 0), SHORT, SHORT, "05.11.25, 13:00"));
+ argumentsList.add(Arguments.of(toInstant(2025, 11, 5, 13, 0), LONG, MEDIUM, "5. November 2025, 13:00:00"));
+ return argumentsList.stream();
+ }
+
+ private static Instant toInstant(int year, int month, int day, int hour, int minute) {
+ LocalDateTime localDateTime = LocalDateTime.of(year, month, day, hour, minute);
+ return localDateTime.atZone(ZONE_ID).toInstant();
+ }
+
+ @ParameterizedTest
+ @MethodSource("provideInstants")
+ public void formatInstant(Instant instant, FormatStyle dateFormat, FormatStyle timeFormat, String expected) {
+
+ String formattedDate = new MCRDateTimeStyler(dateFormat, timeFormat, Locale.GERMAN).format(instant);
+
+ assertEquals(expected, formattedDate);
+
+ }
+
+}
diff --git a/mycore-base/src/test/java/org/mycore/common/date/MCRFLDateScamblerTest.java b/mycore-base/src/test/java/org/mycore/common/date/MCRFLDateScamblerTest.java
new file mode 100644
index 0000000000..ac42856f27
--- /dev/null
+++ b/mycore-base/src/test/java/org/mycore/common/date/MCRFLDateScamblerTest.java
@@ -0,0 +1,159 @@
+/*
+ * This file is part of *** M y C o R e ***
+ * See https://www.mycore.de/ for details.
+ *
+ * MyCoRe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MyCoRe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MyCoRe. If not, see .
+ */
+
+package org.mycore.common.date;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.TimeZone;
+import java.util.stream.Stream;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+public class MCRFLDateScamblerTest {
+
+ private static final Logger LOGGER = LogManager.getLogger();
+
+ public static final ZoneId ZONE_ID = TimeZone.getTimeZone("GMT+01:00").toZoneId();
+
+ private static Stream provideDates() {
+ List argumentsList = new ArrayList<>();
+ argumentsList.add(Arguments.of(toDate(2000, 1, 1, 0, 0), "7588999999"));
+ argumentsList.add(Arguments.of(toDate(2000, 12, 1, 10, 12), "2285963279"));
+ argumentsList.add(Arguments.of(toDate(2012, 11, 10, 9, 8), "1838967119"));
+ argumentsList.add(Arguments.of(toDate(2025, 11, 5, 13, 0), "9192853199"));
+ return argumentsList.stream();
+ }
+
+ private static Date toDate(int year, int month, int day, int hour, int minute) {
+ return Date.from(toInstant(year, month, day, hour, minute));
+ }
+
+ @ParameterizedTest
+ @MethodSource("provideDates")
+ public void formatDate(Date date, String expectedScramble) {
+
+ String scambled = new MCRFLDateScrambler().format(date);
+ String unscrambled = unscramble(scambled);
+
+ LOGGER.info("Date: {} => {} => {}", date, scambled, unscrambled);
+
+ assertEquals(expectedScramble, scambled);
+
+ }
+
+ private static Stream provideInstants() {
+ List argumentsList = new ArrayList<>();
+ argumentsList.add(Arguments.of(toInstant(2000, 1, 1, 0, 0), "7588999999"));
+ argumentsList.add(Arguments.of(toInstant(2000, 12, 1, 10, 12), "2285963279"));
+ argumentsList.add(Arguments.of(toInstant(2012, 11, 10, 9, 8), "1838967119"));
+ argumentsList.add(Arguments.of(toInstant(2025, 11, 5, 13, 0), "9192853199"));
+ return argumentsList.stream();
+ }
+
+ private static Instant toInstant(int year, int month, int day, int hour, int minute) {
+ LocalDateTime localDateTime = LocalDateTime.of(year, month, day, hour, minute);
+ return localDateTime.atZone(ZONE_ID).toInstant();
+ }
+
+ @ParameterizedTest
+ @MethodSource("provideInstants")
+ public void formatInstant(Instant instant, String expectedScramble) {
+
+ String scambled = new MCRFLDateScrambler().format(instant);
+ String unscrambled = unscramble(scambled);
+
+ LOGGER.info("Instant: {} => {} => {}", instant, scambled, unscrambled);
+
+ assertEquals(expectedScramble, scambled);
+
+ }
+
+ @SuppressWarnings("unused")
+ public static String unscramble(String scramble) {
+
+ if (!scramble.matches("[0-9]{10}")) {
+ return "Invalid string length";
+ }
+
+ String prefix = scramble.substring(0, 5);
+ String sssString = scramble.substring(5);
+
+ char[] dddddChars = new char[5];
+ dddddChars[4] = prefix.charAt(0);
+ dddddChars[2] = prefix.charAt(1);
+ dddddChars[1] = prefix.charAt(2);
+ dddddChars[3] = prefix.charAt(3);
+ dddddChars[0] = prefix.charAt(4);
+ String dddddString = new String(dddddChars);
+
+ String decodedDate = "YYYY-MM-DD";
+ String decodedTime = "HH:mm:ss";
+
+ try {
+
+ int ddddd = Integer.parseInt(dddddString);
+
+ int yyy = (ddddd - 134) / 366;
+ int ddd = ddddd - (yyy * 366);
+
+ int year = 2268 - yyy;
+ int dayOfYear = 500 - ddd;
+
+ LocalDate date = LocalDate.ofYearDay(year, dayOfYear);
+ decodedDate = date.format(DateTimeFormatter.ISO_LOCAL_DATE);
+
+ } catch (Exception e) {
+ decodedDate = "INVALID_DATE";
+ }
+
+ try {
+
+ int sss = Integer.parseInt(sssString);
+ int totalSeconds = 99999 - sss;
+
+ int hh = totalSeconds / 3600;
+ int mm = (totalSeconds % 3600) / 60;
+ int ss = totalSeconds % 60;
+
+ if (hh >= 0 && hh < 24 && mm >= 0 && mm < 60 && ss >= 0 && ss < 60) {
+ decodedTime = String.format("%02d:%02d:%02d", hh, mm, ss);
+ } else {
+ decodedTime = "INVALID_TIME";
+ }
+
+ } catch (NumberFormatException e) {
+ decodedTime = "INVALID_TIME";
+ }
+
+ return decodedDate + " " + decodedTime;
+ }
+
+}
diff --git a/mycore-base/src/test/java/org/mycore/common/date/MCRISO8601DateFormatterTest.java b/mycore-base/src/test/java/org/mycore/common/date/MCRISO8601DateFormatterTest.java
new file mode 100644
index 0000000000..fd0d611d51
--- /dev/null
+++ b/mycore-base/src/test/java/org/mycore/common/date/MCRISO8601DateFormatterTest.java
@@ -0,0 +1,98 @@
+/*
+ * This file is part of *** M y C o R e ***
+ * See https://www.mycore.de/ for details.
+ *
+ * MyCoRe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MyCoRe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MyCoRe. If not, see .
+ */
+
+package org.mycore.common.date;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+public class MCRISO8601DateFormatterTest {
+
+ public static final String DATE_FORMAT_1 = "yyyyMMddHHmmss";
+
+ public static final String DATE_FORMAT_2 = "yyyy-MM-dd'T'HH:mm";
+
+ public static final ZoneId ZONE_ID = ZoneId.systemDefault();
+
+ private static Stream provideDates() {
+ List argumentsList = new ArrayList<>();
+ argumentsList.add(Arguments.of(toDate(2000, 1, 1, 0, 0), DATE_FORMAT_1, "20000101000000"));
+ argumentsList.add(Arguments.of(toDate(2000, 1, 1, 0, 0), DATE_FORMAT_2, "2000-01-01T00:00"));
+ argumentsList.add(Arguments.of(toDate(2000, 12, 1, 10, 12), DATE_FORMAT_1, "20001201101200"));
+ argumentsList.add(Arguments.of(toDate(2000, 12, 1, 10, 12), DATE_FORMAT_2, "2000-12-01T10:12"));
+ argumentsList.add(Arguments.of(toDate(2012, 11, 10, 9, 8), DATE_FORMAT_1, "20121110090800"));
+ argumentsList.add(Arguments.of(toDate(2012, 11, 10, 9, 8), DATE_FORMAT_2, "2012-11-10T09:08"));
+ argumentsList.add(Arguments.of(toDate(2025, 11, 5, 13, 0), DATE_FORMAT_1, "20251105130000"));
+ argumentsList.add(Arguments.of(toDate(2025, 11, 5, 13, 0), DATE_FORMAT_2, "2025-11-05T13:00"));
+ return argumentsList.stream();
+ }
+
+ private static Date toDate(int year, int month, int day, int hour, int minute) {
+ return Date.from(toInstant(year, month, day, hour, minute));
+ }
+
+ @ParameterizedTest
+ @MethodSource("provideDates")
+ public void formatDate(Date date, String format, String expected) {
+
+ String formattedDate = new MCRISO8601DateFormatter(format).format(date);
+
+ assertEquals(expected, formattedDate);
+
+ }
+
+ private static Stream provideInstants() {
+ List argumentsList = new ArrayList<>();
+ argumentsList.add(Arguments.of(toInstant(2000, 1, 1, 0, 0), DATE_FORMAT_1, "20000101000000"));
+ argumentsList.add(Arguments.of(toInstant(2000, 1, 1, 0, 0), DATE_FORMAT_2, "2000-01-01T00:00"));
+ argumentsList.add(Arguments.of(toInstant(2000, 12, 1, 10, 12), DATE_FORMAT_1, "20001201101200"));
+ argumentsList.add(Arguments.of(toInstant(2000, 12, 1, 10, 12), DATE_FORMAT_2, "2000-12-01T10:12"));
+ argumentsList.add(Arguments.of(toInstant(2012, 11, 10, 9, 8), DATE_FORMAT_1, "20121110090800"));
+ argumentsList.add(Arguments.of(toInstant(2012, 11, 10, 9, 8), DATE_FORMAT_2, "2012-11-10T09:08"));
+ argumentsList.add(Arguments.of(toInstant(2025, 11, 5, 13, 0), DATE_FORMAT_1, "20251105130000"));
+ argumentsList.add(Arguments.of(toInstant(2025, 11, 5, 13, 0), DATE_FORMAT_2, "2025-11-05T13:00"));
+ return argumentsList.stream();
+ }
+
+ private static Instant toInstant(int year, int month, int day, int hour, int minute) {
+ LocalDateTime localDateTime = LocalDateTime.of(year, month, day, hour, minute);
+ return localDateTime.atZone(ZONE_ID).toInstant();
+ }
+
+ @ParameterizedTest
+ @MethodSource("provideInstants")
+ public void formatInstant(Instant instant, String format, String expected) {
+
+ String formattedDate = new MCRISO8601DateFormatter(format).format(instant);
+
+ assertEquals(expected, formattedDate);
+
+ }
+
+}
diff --git a/mycore-base/src/test/java/org/mycore/common/date/MCRMockDateFormatter.java b/mycore-base/src/test/java/org/mycore/common/date/MCRMockDateFormatter.java
new file mode 100644
index 0000000000..e410e8d725
--- /dev/null
+++ b/mycore-base/src/test/java/org/mycore/common/date/MCRMockDateFormatter.java
@@ -0,0 +1,20 @@
+package org.mycore.common.date;
+
+import java.time.Instant;
+
+public class MCRMockDateFormatter extends MCRInstantFormatterBase {
+
+ private final ThreadLocal lastFormattedDateHolder = new ThreadLocal<>();
+
+ @Override
+ public String format(Instant instant) {
+ String formatted = Long.toString(instant.getEpochSecond());
+ lastFormattedDateHolder.set(formatted);
+ return formatted;
+ }
+
+ public String lastFormattedDate() {
+ return lastFormattedDateHolder.get();
+ }
+
+}
diff --git a/mycore-base/src/test/java/org/mycore/common/date/MCRSimpleDateFormatterTest.java b/mycore-base/src/test/java/org/mycore/common/date/MCRSimpleDateFormatterTest.java
new file mode 100644
index 0000000000..8f51753bcb
--- /dev/null
+++ b/mycore-base/src/test/java/org/mycore/common/date/MCRSimpleDateFormatterTest.java
@@ -0,0 +1,98 @@
+/*
+ * This file is part of *** M y C o R e ***
+ * See https://www.mycore.de/ for details.
+ *
+ * MyCoRe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MyCoRe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MyCoRe. If not, see .
+ */
+
+package org.mycore.common.date;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+public class MCRSimpleDateFormatterTest {
+
+ public static final String DATE_FORMAT_1 = "yyyyMMddHHmmss";
+
+ public static final String DATE_FORMAT_2 = "yyyy-MM-dd'T'HH:mm";
+
+ public static final ZoneId ZONE_ID = ZoneId.systemDefault();
+
+ private static Stream provideDates() {
+ List argumentsList = new ArrayList<>();
+ argumentsList.add(Arguments.of(toDate(2000, 1, 1, 0, 0), DATE_FORMAT_1, "20000101000000"));
+ argumentsList.add(Arguments.of(toDate(2000, 1, 1, 0, 0), DATE_FORMAT_2, "2000-01-01T00:00"));
+ argumentsList.add(Arguments.of(toDate(2000, 12, 1, 10, 12), DATE_FORMAT_1, "20001201101200"));
+ argumentsList.add(Arguments.of(toDate(2000, 12, 1, 10, 12), DATE_FORMAT_2, "2000-12-01T10:12"));
+ argumentsList.add(Arguments.of(toDate(2012, 11, 10, 9, 8), DATE_FORMAT_1, "20121110090800"));
+ argumentsList.add(Arguments.of(toDate(2012, 11, 10, 9, 8), DATE_FORMAT_2, "2012-11-10T09:08"));
+ argumentsList.add(Arguments.of(toDate(2025, 11, 5, 13, 0), DATE_FORMAT_1, "20251105130000"));
+ argumentsList.add(Arguments.of(toDate(2025, 11, 5, 13, 0), DATE_FORMAT_2, "2025-11-05T13:00"));
+ return argumentsList.stream();
+ }
+
+ private static Date toDate(int year, int month, int day, int hour, int minute) {
+ return Date.from(toInstant(year, month, day, hour, minute));
+ }
+
+ @ParameterizedTest
+ @MethodSource("provideDates")
+ public void formatDate(Date date, String format, String expected) {
+
+ String formattedDate = new MCRSimpleDateFormatter(format).format(date);
+
+ assertEquals(expected, formattedDate);
+
+ }
+
+ private static Stream provideInstants() {
+ List argumentsList = new ArrayList<>();
+ argumentsList.add(Arguments.of(toInstant(2000, 1, 1, 0, 0), DATE_FORMAT_1, "20000101000000"));
+ argumentsList.add(Arguments.of(toInstant(2000, 1, 1, 0, 0), DATE_FORMAT_2, "2000-01-01T00:00"));
+ argumentsList.add(Arguments.of(toInstant(2000, 12, 1, 10, 12), DATE_FORMAT_1, "20001201101200"));
+ argumentsList.add(Arguments.of(toInstant(2000, 12, 1, 10, 12), DATE_FORMAT_2, "2000-12-01T10:12"));
+ argumentsList.add(Arguments.of(toInstant(2012, 11, 10, 9, 8), DATE_FORMAT_1, "20121110090800"));
+ argumentsList.add(Arguments.of(toInstant(2012, 11, 10, 9, 8), DATE_FORMAT_2, "2012-11-10T09:08"));
+ argumentsList.add(Arguments.of(toInstant(2025, 11, 5, 13, 0), DATE_FORMAT_1, "20251105130000"));
+ argumentsList.add(Arguments.of(toInstant(2025, 11, 5, 13, 0), DATE_FORMAT_2, "2025-11-05T13:00"));
+ return argumentsList.stream();
+ }
+
+ private static Instant toInstant(int year, int month, int day, int hour, int minute) {
+ LocalDateTime localDateTime = LocalDateTime.of(year, month, day, hour, minute);
+ return localDateTime.atZone(ZONE_ID).toInstant();
+ }
+
+ @ParameterizedTest
+ @MethodSource("provideInstants")
+ public void formatInstant(Instant instant, String format, String expected) {
+
+ String formattedDate = new MCRSimpleDateFormatter(format).format(instant);
+
+ assertEquals(expected, formattedDate);
+
+ }
+
+}
diff --git a/mycore-mods/src/main/java/org/mycore/mods/classification/mapping/MCRMODSXMappingClassificationGenerator.java b/mycore-mods/src/main/java/org/mycore/mods/classification/mapping/MCRMODSXMappingClassificationGenerator.java
index b07fb28850..cc9870f5c3 100644
--- a/mycore-mods/src/main/java/org/mycore/mods/classification/mapping/MCRMODSXMappingClassificationGenerator.java
+++ b/mycore-mods/src/main/java/org/mycore/mods/classification/mapping/MCRMODSXMappingClassificationGenerator.java
@@ -57,9 +57,9 @@
*
The property suffix {@link MCRXMappingClassificationGeneratorBase#EVALUATOR_KEY} can be used to
* specify the evaluator used to obtain category IDs from.
*
For the evaluator, the property suffix {@link MCRSentinel#DEFAULT_KEY} can be used to
- * exclude the evaluator from the configuration and use a default {@link MCRSimpleXMappingEvaluator} instead.
+ * exclude the evaluator from the configuration and use a default {@link MCRSimpleXMappingEvaluator} instead.
*
The property suffix {@link MCRXMappingClassificationGeneratorBase#ON_MISSING_MAPPED_CATEGORY_KEY} can be used to
- * specify the behaviour, when a mapped classification value is missing.
+ * specify the behavior, when a mapped classification value is missing.
*
* Example:
*
- * Set an optional Count precision, if not set or set to -1 the pure number is used (1,2,.., 999).
- * Count always relativ to type and date.
- *
- * MCR.PI.Generator.myGenerator.CountPrecision=3 # will produce 001, 002, ... , 999
+ * {@link MCRGenericPIGenerator} is a {@link MCRPIGenerator} for arbitrary identifiers
+ * that generates identifiers using a general pattern and other information dependent on the
+ * replacement markers contained in that pattern.
+ *
+ *
+ * The replacement marker {@link MCRGenericPIGenerator#PLACE_HOLDER_CURRENT_DATE}
+ * will be replaced with the current date, formatted by the given date formatter.
+ *
+ *
+ * The replacement marker {@link MCRGenericPIGenerator#PLACE_HOLDER_OBJECT_DATE}
+ * will be replaced with the objects creation date, formatted by the given date formatter.
+ *
+ *
+ * The replacement marker {@link MCRGenericPIGenerator#PLACE_HOLDER_OBJECT_PROJECT}
+ * will be replaced with the object IDs {@link MCRObjectID#getProjectId()}, mapped by the given project mapping.
+ *
+ *
+ * The replacement marker {@link MCRGenericPIGenerator#PLACE_HOLDER_OBJECT_TYPE}
+ * will be replaced with the object IDs {@link MCRObjectID#getTypeId()}, mapped by the given type mapping.
+ *
+ *
+ * The replacement marker {@link MCRGenericPIGenerator#PLACE_HOLDER_OBJECT_NUMBER}
+ * will be replaced with the object IDs {@link MCRObjectID#getNumberAsString()}.
+ *
+ *
+ * The replacement marker of form {@link MCRGenericPIGenerator#XPATH_PATTERN}
+ * will be replaced with the result of evaluating the objects XML representation with
+ * a given xPath, using the n-th given xPath, when the pattern represents the number n.
+ *
+ *
+ * The replacement marker {@link MCRGenericPIGenerator#PLACE_HOLDER_COUNT}
+ * will be replaced with a unique count number with the given count precision.
+ *
+ *
*
- * Set the Type of the generated pi.
+ * Example patterns:
+ *
- * MCR.PI.Generator.myGenerator.XPath.1=/mycoreobject/metadata/def.shelf/shelf/
- * MCR.PI.Generator.myGenerator.XPath.2=/mycoreobject/metadata/def.path2/path2/
- *
- * @author Sebastian Hofmann
+ * The following configuration options are available:
+ *
+ *
The property suffix {@link MCRGenericPIGenerator#GENERAL_PATTERN_KEY} can be used to
+ * specify the pattern.
+ *
The property suffix {@link MCRGenericPIGenerator#DATE_FORMATTER_KEY} can be used to
+ * specify the date formatter to be used (optional, defaults to {@link MCRSimpleDateFormatter} with format
+ * {@link MCRGenericPIGenerator#DEFAULT_DATE_FORMAT} and locale {@link MCRGenericPIGenerator#DEFAULT_DATE_LOCALE}).
+ *
The property suffix {@link MCRGenericPIGenerator#OBJECT_PROJECT_MAPPING_KEY} can be used to
+ * specify the project ID mappings to be used.
+ *
The property suffix {@link MCRGenericPIGenerator#OBJECT_TYPE_MAPPING_KEY} can be used to
+ * specify the type ID mapping to be used.
+ *
The property suffix {@link MCRGenericPIGenerator#COUNT_PRECISION_KEY} can be used to
+ * specify number of digits to be used for the count (optional, defaults to -1,
+ * which uses the natural number of digits).
+ *
The property suffix {@link MCRGenericPIGenerator#TYPE_KEY} can be used to
+ * specify identifier type.
+ *
The property suffix {@link MCRGenericPIGenerator#X_PATH_KEY} can be used to
+ * specify the list of xPaths.
+ *