В 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
Дальнейшее использование этого класса, думаю, затруднений не вызовет.
