Как анализировать даты в нескольких форматах с помощью SimpleDateFormat
Я пытаюсь разобрать некоторые даты, выходящие из документа. Похоже, что пользователи ввели эти даты в аналогичном, но не точном формате.
вот форматы:
9/09
9/2009
09/2009
9/1/2009
9-1-2009
Каков наилучший способ попытаться разобрать все это? Они кажутся наиболее распространенными, но я думаю, что меня висит в том, что если у меня есть шаблон "M/yyyy", который всегда будет улавливаться до "MM/yyyy", мне нужно настроить блоки try/catch вложенных в наименее ограничительный, наиболее ограничительный способ? похоже, что это наверняка потребует много дублирования кода, чтобы получить это право.
Ответы
Ответ 1
Вам нужно будет использовать разные объекты SimpleDateFormat
для каждого шаблона. Тем не менее, вам не нужно так много разных, благодаря этому:
Число: Для форматирования количество букв шаблона - это минимальное количество цифр, а более короткие числа дополняются нулями до этой суммы. При синтаксическом анализе количество букв шаблона игнорируется, если только не требуется разделить два смежных поля.
Итак, вам понадобятся эти форматы:
-
"M/y"
(охватывает 9/09
, 9/2009
и 09/2009
) -
"M/d/y"
(что охватывает 9/1/2009
) -
"Mdy"
(охватывает 9-1-2009
)
Итак, мой совет - написать метод, который работает примерно так (не проверено):
// ...
List<String> formatStrings = Arrays.asList("M/y", "M/d/y", "M-d-y");
// ...
Date tryParse(String dateString)
{
for (String formatString : formatStrings)
{
try
{
return new SimpleDateFormat(formatString).parse(dateString);
}
catch (ParseException e) {}
}
return null;
}
Ответ 2
Как просто определить несколько шаблонов? Они могут поступать из файла конфигурации, содержащего известные шаблоны, жестко закодированные, как:
List<SimpleDateFormat> knownPatterns = new ArrayList<SimpleDateFormat>();
knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"));
knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm.ss'Z'"));
knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"));
knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd' 'HH:mm:ss"));
knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"));
for (SimpleDateFormat pattern : knownPatterns) {
try {
// Take a try
return new Date(pattern.parse(candidate).getTime());
} catch (ParseException pe) {
// Loop on
}
}
System.err.println("No known Date format found: " + candidate);
return null;
Ответ 3
Мэтт подход выше, но, пожалуйста, имейте в виду, что вы столкнетесь с проблемами, если используете его для различения дат формата y/M/d
и d/M/y
. Например, форматировщик, инициализированный с помощью y/M/d
, примет дату, подобную 01/01/2009
, и вернет вам дату, которая явно не то, что вы хотели. Я исправил проблему следующим образом, но у меня есть ограниченное время, и я не доволен решением по двум основным причинам:
- Он нарушает один из Quidelines Джоша Блоха, в частности "не используйте исключения для обработки потока программы".
- Я вижу, что метод
getDateFormat()
становится немного кошмаром, если вам нужно, чтобы обрабатывать множество других форматов даты.
Если бы мне пришлось сделать что-то, что могло бы обрабатывать множество и множество разных форматов дат и должно было быть высокоэффективным, то я думаю, что я бы использовал подход создания перечисления, который привязывал каждое другое регулярное выражение даты к его формату. Затем используйте MyEnum.values()
для прокрутки перечисления и теста с помощью if(myEnum.getPattern().matches(date))
, а не для ловли dateformatexception.
В любом случае, если говорить, следующее может обрабатывать даты форматов 'y/M/d' 'y-M-d' 'y M d' 'd/M/y' 'd-M-y' 'd M y'
и всех других вариантов тех, которые включают в себя также форматы времени:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class DateUtil {
private static final String[] timeFormats = {"HH:mm:ss","HH:mm"};
private static final String[] dateSeparators = {"/","-"," "};
private static final String DMY_FORMAT = "dd{sep}MM{sep}yyyy";
private static final String YMD_FORMAT = "yyyy{sep}MM{sep}dd";
private static final String ymd_template = "\\d{4}{sep}\\d{2}{sep}\\d{2}.*";
private static final String dmy_template = "\\d{2}{sep}\\d{2}{sep}\\d{4}.*";
public static Date stringToDate(String input){
Date date = null;
String dateFormat = getDateFormat(input);
if(dateFormat == null){
throw new IllegalArgumentException("Date is not in an accepted format " + input);
}
for(String sep : dateSeparators){
String actualDateFormat = patternForSeparator(dateFormat, sep);
//try first with the time
for(String time : timeFormats){
date = tryParse(input,actualDateFormat + " " + time);
if(date != null){
return date;
}
}
//didn't work, try without the time formats
date = tryParse(input,actualDateFormat);
if(date != null){
return date;
}
}
return date;
}
private static String getDateFormat(String date){
for(String sep : dateSeparators){
String ymdPattern = patternForSeparator(ymd_template, sep);
String dmyPattern = patternForSeparator(dmy_template, sep);
if(date.matches(ymdPattern)){
return YMD_FORMAT;
}
if(date.matches(dmyPattern)){
return DMY_FORMAT;
}
}
return null;
}
private static String patternForSeparator(String template, String sep){
return template.replace("{sep}", sep);
}
private static Date tryParse(String input, String pattern){
try{
return new SimpleDateFormat(pattern).parse(input);
}
catch (ParseException e) {}
return null;
}
}
Ответ 4
В Apache commons lang, в классе DateUtils у нас есть метод parseDate. Мы можем использовать это для синтаксического анализа даты.
Также в другой библиотеке Joda-time также есть метод для анализа даты.
Ответ 5
Это решение проверяет все возможные форматы перед выбросом исключения. Это решение более удобно, если вы пытаетесь протестировать несколько форматов даты.
Date extractTimestampInput(String strDate){
final List<String> dateFormats = Arrays.asList("yyyy-MM-dd HH:mm:ss.SSS", "yyyy-MM-dd");
for(String format: dateFormats){
SimpleDateFormat sdf = new SimpleDateFormat(format);
try{
return sdf.parse(strDate);
} catch (ParseException e) {
//intentionally empty
}
}
throw new IllegalArgumentException("Invalid input for date. Given '"+strDate+"', expecting format yyyy-MM-dd HH:mm:ss.SSS or yyyy-MM-dd.");
}
Ответ 6
Вот полный пример (с основным методом), который может быть добавлен как класс утилиты в ваш проект. Все форматы, указанные в SimpleDateFormate API, поддерживаются ниже.
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.apache.commons.lang.time.DateUtils;
public class DateUtility {
public static Date parseDate(String inputDate) {
Date outputDate = null;
String[] possibleDateFormats =
{
"yyyy.MM.dd G 'at' HH:mm:ss z",
"EEE, MMM d, ''yy",
"h:mm a",
"hh 'o''clock' a, zzzz",
"K:mm a, z",
"yyyyy.MMMMM.dd GGG hh:mm aaa",
"EEE, d MMM yyyy HH:mm:ss Z",
"yyMMddHHmmssZ",
"yyyy-MM-dd'T'HH:mm:ss.SSSZ",
"yyyy-MM-dd'T'HH:mm:ss.SSSXXX",
"YYYY-'W'ww-u",
"EEE, dd MMM yyyy HH:mm:ss z",
"EEE, dd MMM yyyy HH:mm zzzz",
"yyyy-MM-dd'T'HH:mm:ssZ",
"yyyy-MM-dd'T'HH:mm:ss.SSSzzzz",
"yyyy-MM-dd'T'HH:mm:sszzzz",
"yyyy-MM-dd'T'HH:mm:ss z",
"yyyy-MM-dd'T'HH:mm:ssz",
"yyyy-MM-dd'T'HH:mm:ss",
"yyyy-MM-dd'T'HHmmss.SSSz",
"yyyy-MM-dd",
"yyyyMMdd",
"dd/MM/yy",
"dd/MM/yyyy"
};
try {
outputDate = DateUtils.parseDate(inputDate, possibleDateFormats);
System.out.println("inputDate ==> " + inputDate + ", outputDate ==> " + outputDate);
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return outputDate;
}
public static String formatDate(Date date, String requiredDateFormat) {
SimpleDateFormat df = new SimpleDateFormat(requiredDateFormat);
String outputDateFormatted = df.format(date);
return outputDateFormatted;
}
public static void main(String[] args) {
DateUtility.parseDate("20181118");
DateUtility.parseDate("2018-11-18");
DateUtility.parseDate("18/11/18");
DateUtility.parseDate("18/11/2018");
DateUtility.parseDate("2018.11.18 AD at 12:08:56 PDT");
System.out.println("");
DateUtility.parseDate("Wed, Nov 18, '18");
DateUtility.parseDate("12:08 PM");
DateUtility.parseDate("12 o'clock PM, Pacific Daylight Time");
DateUtility.parseDate("0:08 PM, PDT");
DateUtility.parseDate("02018.Nov.18 AD 12:08 PM");
System.out.println("");
DateUtility.parseDate("Wed, 18 Nov 2018 12:08:56 -0700");
DateUtility.parseDate("181118120856-0700");
DateUtility.parseDate("2018-11-18T12:08:56.235-0700");
DateUtility.parseDate("2018-11-18T12:08:56.235-07:00");
DateUtility.parseDate("2018-W27-3");
}
}
Ответ 7
Если вы работаете в Java 1.8, вы можете использовать DateTimeFormatterBuilder
public static boolean isTimeStampValid(String inputString)
{
DateTimeFormatterBuilder dateTimeFormatterBuilder = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ofPattern("" + "[yyyy-MM-dd'T'HH:mm:ss.SSSZ]" + "[yyyy-MM-dd]"));
DateTimeFormatter dateTimeFormatter = dateTimeFormatterBuilder.toFormatter();
try {
dateTimeFormatter.parse(inputString);
return true;
} catch (DateTimeParseException e) {
return false;
}
}
Смотрите сообщение: Java 8 Дата, эквивалентная DateTimeFormatterBuilder от Joda с несколькими форматами парсера?
Ответ 8
Для современного ответа я игнорирую требование использования SimpleDateFormat
. Хотя использование этого класса для синтаксического анализа было хорошей идеей в 2010 году, когда этот вопрос был задан, он теперь устарел. Замена DateTimeFormatter
вышла в 2014 году. Идея в следующем почти такая же, как в принятом ответе.
private static DateTimeFormatter[] parseFormatters = Stream.of("M/yy", "M/y", "M/d/y", "M-d-y")
.map(DateTimeFormatter::ofPattern)
.toArray(DateTimeFormatter[]::new);
public static YearMonth parseYearMonth(String input) {
for (DateTimeFormatter formatter : parseFormatters) {
try {
return YearMonth.parse(input, formatter);
} catch (DateTimeParseException dtpe) {
// ignore, try next format
}
}
throw new IllegalArgumentException("Could not parse " + input);
}
Это анализирует каждую из входных строк из вопроса в год-месяц 2009-09
. Важно сначала попробовать двузначный год, так как "M/y"
может также анализировать 9/09
, но вместо 0009-09
.
Ограничение вышеуказанного кода означает, что он игнорирует день месяца из строк, которые имеют один, например 9/1/2009
. Возможно, это нормально, если большинство форматов имеют только месяц и год. Чтобы получить его, вы должны попробовать LocalDate.parse()
, а не YearMonth.parse()
для форматов, которые включают d
в строке шаблона. Конечно, это можно сделать.
Ответ 9
Реализовано то же самое в scala, пожалуйста, помогите urself с преобразованием на Java, основная логика и используемые функции остаются неизменными.
import java.text.SimpleDateFormat
import org.apache.commons.lang.time.DateUtils
object MultiDataFormat {
def main(args: Array[String]) {
val dates =Array("2015-10-31","26/12/2015","19-10-2016")
val possibleDateFormats:Array[String] = Array("yyyy-MM-dd","dd/MM/yyyy","dd-MM-yyyy")
val sdf = new SimpleDateFormat("yyyy-MM-dd") //change it as per the requirement
for (date<-dates) {
val outputDate = DateUtils.parseDateStrictly(date, possibleDateFormats)
System.out.println("inputDate ==> " + date + ", outputDate ==> " +outputDate + " " + sdf.format(outputDate) )
}
}
}
Ответ 10
Используя DateTimeFormatter, это может быть достигнуто следующим образом:
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.Date;
import java.util.TimeZone;
public class DateTimeFormatTest {
public static void main(String[] args) {
String pattern = "[yyyy-MM-dd[['T'][ ]HH:mm:ss[.SSSSSSSz][.SSS[XXX][X]]]]";
String timeSample = "2018-05-04T13:49:01.7047141Z";
SimpleDateFormat simpleDateFormatter = new SimpleDateFormat("dd/MM/yy HH:mm:ss");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
TemporalAccessor accessor = formatter.parse(timeSample);
ZonedDateTime zTime = LocalDateTime.from(accessor).atZone(ZoneOffset.UTC);
Date date=new Date(zTime.toEpochSecond()*1000);
simpleDateFormatter.setTimeZone(TimeZone.getTimeZone(ZoneOffset.UTC));
System.out.println(simpleDateFormatter.format(date));
}
}
Обратите внимание на String pattern
, это комбинация нескольких шаблонов. В открытых [
и закрытых ]
квадратных скобках вы можете указать любые виды шаблонов.
Ответ 11
У меня было несколько форматов даты в JSON, и извлекал CSV с универсальным форматом. Я посмотрел несколько мест, попробовал разные способы, но в конце я смог преобразовать с помощью следующего простого кода.
private String getDate(String anyDateFormattedString) {
@SuppressWarnings("deprecation")
Date date = new Date(anyDateFormattedString);
SimpleDateFormat dateFormat = new SimpleDateFormat(yourDesiredDateFormat);
String convertedDate = dateFormat.format(date);
return convertedDate;
}