Looking at the source code of DateTimeFormatter
, we see that ISO_INSTANT
has its own legacy code for parsing and formatting. If we want to preserve that, in the sense that everything should work just like there, except for our separators, then the only clean way would be to overwrite the original methods. Alas, that cannot be done, because all relevant classes are either final or package-private.
In this situation, although it looks undesirable at first, the best solution is really to tamper directly with the string. Since our deviations from ISO format are simple and well-defined, and since ISO format itself has constant string length in most places, that string editing is not so complicated.
I have written two methods for parsing and formatting which will take any string as a date separator ('-' in original ISO) or time separator (':' in original ISO), including the empty string. We interpret null-separators as empty strings as well. Enjoy!
import java.time.temporal.TemporalAccessor;
import java.util.ArrayList;
import java.util.List;
import static java.time.format.DateTimeFormatter.ISO_INSTANT;
public class IsoInstantExtendedDateTimeFormatter {
public static String format(TemporalAccessor temporal, String dateSep, String timeSep) {
String original = ISO_INSTANT.format(temporal);
String[] dateTime = original.split("T");
if (dateTime.length < 2) {
return original;
}
return replaceFirstTwoChars(dateTime[0], "-", dateSep) + "T" + replaceFirstTwoChars(dateTime[1], ":", timeSep);
}
public static TemporalAccessor parse(CharSequence text, String dateSep, String timeSep) {
String[] dateTime = text.toString().split("T");
if (dateTime.length < 2) {
return ISO_INSTANT.parse(text);
}
String input = replaceFirstTwoChars(dateTime[0], dateSep, "-", 4, 2)
+ "T" + replaceFirstTwoChars(dateTime[1], timeSep, ":", 2, 2);
return ISO_INSTANT.parse(input);
}
private static String replaceFirstTwoChars(String text, String s1, String s2) {
return replaceFirstTwoChars(text, s1, s2, -1, -1);
}
private static String replaceFirstTwoChars(String text, String s1, String s2, int lenFirstPart, int lenSecondPart) {
if (text == null) {
return null;
}
if (s1 == null) {
s1 = "";
}
if (s2 == null) {
s2 = "";
}
int endSecondPart;
if (s1.isEmpty()) {
if (s2.isEmpty()) {
return text;
}
endSecondPart = lenFirstPart + lenSecondPart;
} else {
lenFirstPart = text.indexOf(s1);
endSecondPart = text.indexOf(s1, lenFirstPart + s1.length());
}
if (lenFirstPart < 0 || endSecondPart < 0) {
return text;
}
return text.substring(0, lenFirstPart)
+ s2
+ text.substring(lenFirstPart + s1.length(), endSecondPart)
+ s2
+ text.substring(endSecondPart + s1.length());
}
// --- All code below is just for demo purposes ---
private static final String[] dateStrings = new String[]{
"20220331T112233Z", "20220404T223344.555Z",
"20220331T11.22.33Z", "20220404T22.33.44.555Z",
"2022/03/31T11.22.33Z", "2022/04/04T22.33.44.555Z"
};
private static final String[] dateSeps = new String[]{ "", "", "", "", "/", "/" };
private static final String[] timeSeps = new String[]{ "", "", ".", ".", ".", "." };
public static void main(String[] args) {
List<TemporalAccessor> dates = new ArrayList<>();
for (int i = 0; i<dateStrings.length; i++) {
dates.add(parse(dateStrings[i], dateSeps[i], timeSeps[i]));
}
for (TemporalAccessor date : dates) {
System.out.println(format(date, "", "")
+ " | " + format(date, "", ".")
+ " | " + format(date, "/", ".")
+ " | " + ISO_INSTANT.format(date));
}
}
}
input.replace(".", ":");
... It's probably as good as a complex date time formatter. – Phrygia