كيفية تحليل التواريخ بتنسيقات متعددة باستخدام SimpleDateFormat

StackOverflow https://stackoverflow.com/questions/4024544

سؤال

أحاول تحليل بعض التواريخ التي تخرج من وثيقة. يبدو أن المستخدمين قد أدخلوا هذه التواريخ بتنسيق مماثل ولكن ليس دقيقًا.

هنا التنسيقات:

9/09
9/2009
09/2009
9/1/2009
9-1-2009 

ما هي أفضل طريقة لمحاولة تحليل كل هذه؟ يبدو أن هذه هي الأكثر شيوعًا ، لكني أعتقد أن ما يعلقني هو أنه إذا كان لدي نمط من "m/yyyy" ، فلن يصطاد دائمًا قبل "mm/yyyy" هل يجب علي إعداد كتل المحاولة متداخلة بأقل تقييدية لأكثر الطرق التقييدية؟ يبدو أنه من المؤكد أنه سيستغرق الكثير من تكرار التعليمات البرمجية للحصول على هذا بشكل صحيح.

هل كانت مفيدة؟

المحلول

You'll need to use a different SimpleDateFormat object for each different pattern. That said, you don't need that many different ones, thanks to this:

Number: For formatting, the number of pattern letters is the minimum number of digits, and shorter numbers are zero-padded to this amount. For parsing, the number of pattern letters is ignored unless it's needed to separate two adjacent fields.

So, you'll need these formats:

  • "M/y" (that covers 9/09, 9/2009, and 09/2009)
  • "M/d/y" (that covers 9/1/2009)
  • "M-d-y" (that covers 9-1-2009)

So, my advice would be to write a method that works something like this (untested):

// ...
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;
}

نصائح أخرى

ماذا عن مجرد تحديد أنماط متعددة؟ قد تأتي من ملف التكوين الذي يحتوي على أنماط معروفة ، وترميزه بشدة يقرأ مثل:

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;

نهج مات أعلاه جيد ، ولكن يرجى العلم أنك ستواجه مشاكل إذا كنت تستخدمها للتمييز بين تواريخ التنسيق y/M/d و d/M/y. على سبيل المثال ، تم تهيئة التنسيق مع y/M/d سوف يقبل موعد مثل 01/01/2009 وتعيدك إلى موعد ليس من الواضح أنه تريد. لقد أصلحت المشكلة على النحو التالي ، لكن لدي وقت محدود ولست سعيدًا بالحل لسببين رئيسيين:

  1. إنه ينتهك أحد Quidelines Josh Bloch ، وتحديداً "لا تستخدم استثناءات للتعامل مع تدفق البرنامج".
  2. أستطيع أن أرى getDateFormat() تصبح الطريقة قليلاً من كابوس إذا كنت بحاجة إليها للتعامل مع الكثير من تنسيقات التاريخ الأخرى.

إذا اضطررت إلى صنع شيء يمكن أن يتعامل مع الكثير والكثير من تنسيقات التاريخ المختلفة وتحتاج إلى أن أكون أداءً عالياً ، فأعتقد أنني سأستخدم نهج إنشاء التعداد الذي يربط كل تاريخ مختلف بتنسيقه. ثم استخدام MyEnum.values() للحلق من خلال التعداد واختبار مع if(myEnum.getPattern().matches(date)) بدلا من اصطياد dateFormatexception.

ANWAY ، هذا ما يلي ، يمكن أن يتعامل مع تواريخ التنسيقات '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;
    }


}

في Apache Commons Lang ، DateUtils الفصل لدينا طريقة تسمى parsedate. يمكننا استخدام هذا لتحليل التاريخ.

أيضا مكتبة أخرى Joda-time لديها أيضا طريقة ل تحليل التاريخ.

يتحقق هذا الحل من جميع التنسيقات الممكنة قبل إلقاء استثناء. هذا الحل أكثر ملاءمة إذا كنت تحاول اختبار تنسيقات تاريخ متعددة.

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.");

}

فيما يلي المثال الكامل (بالطريقة الرئيسية) التي يمكن إضافتها كفئة فئة في مشروعك. كل التنسيق المذكور في التبسيط 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");
    }

}

إذا كنت تعمل في 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 تاريخ مكافئ لـ Joda's DateTimeFormatterBuilder مع تنسيقات محلل متعددة؟

للإجابة الحديثة ، أتجاهل متطلبات الاستخدام 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 في سلسلة الأنماط. بالتأكيد يمكن القيام به.

تم تنفيذ نفسه في Scala ، يرجى المساعدة في التحويل إلى 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) )
  }
}

}

باستخدام 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, ، هذا هو مزيج من أنماط متعددة. في مفتوح [ وأغلق ] أقواس مربعة يمكنك ذكر أي نوع من الأنماط.

كنت أعاني من تنسيقات تاريخ متعددة في 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;
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top