В JVM 1.8 отсутствует удобный и простой в использовании класс форматирования класса java.sql.Timestamp с микро и нано секундами. Есть специализированный пакет java.time с достаточно разнообразной функциональностью. Но его использование для преобразования типа java.sql.Timestamp в строку и из строки в тип выглядит как то сложновато. Хотелось иметь простой способ преобразования с функциональностью класса java.text.SimpleDateFormat.
Конечно есть множество советов и вариантов решения этих преобразований в интернет, но все они меня как то не устроили по разным причинам. Повозившись и помучавшись с поиском решения, решил написать свой класс, при этом реализовывать весь разбор строки и форматирование в строку совсем не хотелось. Решил наследовать свой класс от одного из существующих классов, переопределив поведение только интересуемой меня функциональности. Но и здесь меня ждало большое разочарование из-за ограничения зон видимости полей, методов и используемых классов в классах, от которых я пробовал наследоваться. .
Хочешь не хочешь, но надо все же писать полностью свой класс, но писать то все с нуля не хочется. И тут мне в голову пришла идея, раз нельзя наследоваться от класса java.text.SimpleDateFormat, но использовать-то его можно же. Идея реализации простая - форматировать и разбирать милли/микро/нано секунды отдельно. Сказано - сделано.
package ru.funsys.util;
import java.sql.Timestamp;
import java.text.MessageFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Locale;
/**
* Класс форматирования класса Timestamp c милли/микро/нано секундами
*
* @author Валерий Лиховских
*
*/
public class TimestampFormatter {
/**
* строка форматирования даты и времени. Формируется из исходной строки шаблона форматирования
*/
private String format;
/**
* строка форматирования, используемая для парсинга. Формируется из исходной строки шаблона форматирования
*/
private String parse;
/**
* местоположения в строке шаблона форматирования милли/микро/нано секунд. Формируется из исходной строки шаблона форматирования
*/
private int first;
/**
* разрядность после запятой милли/микро/нано секунд, вычисляется по строке шаблона форматирования
*/
private int size;
/**
* Объект локализации
*/
private Locale locale;
/**
* строка шаблона форматирования по умолчанию
*/
public static final String DEFAULT_DATETIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSXXX"; //$NON-NLS-1$
/**
* символ шаблона форматирования милли/микро/нано секунд
*/
public static final char CHAR_S = 'S'; //$NON-NLS-1$
/**
* символ экранирование - одиночный штрих (кавычка)
*/
public static final char CHAR_PRIME = '\''; //$NON-NLS-1$
public TimestampFormatter() {
this(DEFAULT_DATETIME_PATTERN, Locale.getDefault());
}
/**
* @param pattern строка шаблона форматирования по умолчанию
*/
public TimestampFormatter(String pattern) {
this(pattern, Locale.getDefault());
}
/**
* @param pattern строка шаблона форматирования по умолчанию
* @param locale объект локализации
*/
public TimestampFormatter(String pattern, Locale locale) {
first = pattern.indexOf(CHAR_S);
if (first != -1) {
String datetime = pattern.substring(0, first);
int last = pattern.lastIndexOf(CHAR_S) + 1;
if (last > 0 && last < pattern.length()) {
format = datetime + "'{0}'" + pattern.substring(last); // {0} - для милли/микро/нано секунд
parse = datetime + pattern.substring(last);
size = last - first;
} else {
format = datetime + "'{0}'"; // {0} - для милли/микро/нано секунд
parse = datetime;
size = pattern.length() - first;
}
} else {
this.format = pattern;
this.parse = pattern;
size = 0;
}
this.locale = locale;
// Исключить смещение позиции наносекунд из разбираемой строке символа '
int pos = pattern.indexOf(CHAR_PRIME); // текущая позиция символа
while (pos != -1) {
first--;
pos++; // искать следующий символ с позиции
if (pos < pattern.length()) pos = pattern.indexOf(CHAR_PRIME, pos);
else pos = -1;
}
// first теперь имеет значения начала позиции милли/микро/нано секунд в получаемой строке для парсинга
}
/**
* Форматировать Timestamp
*
* @param timestamp значение форматирования
*
* @return строковое представление Timestamp
*/
public String format(Timestamp timestamp) {
String nanos;
if (size == 0) nanos = "";
else nanos = Integer.toString(timestamp.getNanos() + 1000000000).substring(1, 1 + size);
return MessageFormat.format(new SimpleDateFormat(format, locale).format(timestamp), nanos);
}
/**
* Парсить строковое представление Timestamp
*
* @param source строка парсинга
*
* @return результат преобразования в Timestamp
*
* @throws ParseException ошибка форматирования
*/
public Timestamp parse(String source) throws ParseException {
StringBuffer tmpSource = new StringBuffer().append(source.substring(0, first));
if (size > 0) {
tmpSource.append(source.substring(first + size));
}
Timestamp timestamp = new Timestamp(new SimpleDateFormat(parse, locale).parse(tmpSource.toString()).getTime());
if (size > 0) {
int tmpNamo = Integer.parseInt(source.substring(first, first + size));
for (int index = size; index < 9; index++) {
tmpNamo = tmpNamo * 10;
}
timestamp.setNanos(tmpNamo);
}
return timestamp;
}
}
В результате получился компактный класс по своей функциональности сопоставимый с классом java.text.SimpleDateFormat. И самое главное, простой в использовании.
public static void main(String[] args) {
Timestamp t = new Timestamp(System.currentTimeMillis());
t.setNanos(345012000); // устанавливать наносекунды
TimestampFormatter f = new TimestampFormatter();
String s = f.format(t);
System.out.println(s);
try {
// обратное преобразование для проверки
Timestamp n = f.parse(s);
System.out.println(f.format(n));
} catch (Exception e) {
e.printStackTrace();
}
}
Результат
2022-11-18T09:20:35.345012+03:00
2022-11-18T09:20:35.345012+03:00
Дальнейшее использование этого класса, думаю, затруднений не вызовет.