如何在不知道日期格式的情况下将字符串转换为日期?

62

我有个问题。我试图将一些字符串转换为日期,但我不知道日期是以什么格式到达的。

它可能是这样的 yyyy.mm.dd hh:mm:ss 或者 MM.dd.yy hh:mm:ss 以此类推。

我该怎么把这些字符串转换成日期? 我尝试过这个:

DateFormat formatter = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
Date d = (Date)formatter.parse(someDate);

但是当我打印出 someDate 时,它打印出来的格式是:2010-08-05 12:42:48.638 CEST,这意味着 yyyy.mm.dd hh:mm:ss,然而当我运行上面的代码后,日期对象现在变成了 Sat Jan 31 00:42:48 CET 11,这至少很奇怪。

有什么想法可以正确地将字符串格式化为日期?


代码中的日期格式与您所写的不同。 - Alexander Malakhov
前一条评论中打错了,应该是“typo”而不是“type”。 - Alexander Malakhov
顺便问一下,你认为“Sat Jan 31 00:42:48 CET 11”是什么?0000/01/31? - Alexander Malakhov
1
虽然没有被接受,但这里的答案 https://dev59.com/g3A75IYBdhLWcg3wRWsU 提供了一些与 OP 问题相关的选项。 - russellelbert
11个回答

59

你不可能!

如果你有日期2010-08-05,那么它可以是2010年8月5日或2010年5月8日 - 你需要知道日期格式(或至少优先考虑一种格式),以便区分它们。


6
从技术上讲,他是可以的,但他不知道是否正确。 - Paweł Dyda
42
从技术上讲,我可以用整数值精确地表示Pi,但它不会是正确的。 - Joachim Sauer
4
@JoachimSauer:从技术上讲,你永远无法用任意数量的数字精确地表示 Pi ;) - code0100fun
1
这可能很明显,但根据使用情况,仅标记模糊的日期格式并继续可能已经足够了。 - josiah
3
实际上这是一个回答。它的意思是你不能这样做! - Shervin Asgari
显示剩余2条评论

13

我同意Kragen的观点,在一般情况下没有正确的解决方案。但是,如果以下条件成立,您可以使用下面的解决方案:

  1. 您有所有可能格式的集合

  2. 格式之间没有歧义;没有日期表达式可以被两个以上的格式成功解析。

考虑以下解决方案,它迭代了可能格式的列表。此解决方案利用ThreadLocal,以使得在多线程环境中日期解析更加高效(请记住,SimpleDateFormat不是线程安全的):

public class FlexibleDateParser {
    private List<ThreadLocal<SimpleDateFormat>> threadLocals = new  ArrayList<ThreadLocal<SimpleDateFormat>>();

    public FlexibleDateParser(List<String> formats, final TimeZone tz){
        threadLocals.clear();
        for (final String format : formats) {
            ThreadLocal<SimpleDateFormat> dateFormatTL = new ThreadLocal<SimpleDateFormat>() {
                protected SimpleDateFormat initialValue() {
                    SimpleDateFormat sdf = new SimpleDateFormat(format);
                    sdf.setTimeZone(tz); 
                    sdf.setLenient(false);
                    return sdf;
                }
            };
            threadLocals.add(dateFormatTL);
        }       
    }

    public Date parseDate(String dateStr) throws ParseException {
        for (ThreadLocal<SimpleDateFormat> tl : threadLocals) {
            SimpleDateFormat sdf = tl.get();
            try {
                return sdf.parse(dateStr);
            } catch (ParseException e) {
                // Ignore and try next date parser
            }
        }
        // All parsers failed
        return null;
    }       
}

看起来,只有在多个线程使用FlexibleDateParser的单个实例时(例如作为静态或单例存在,或依赖注入在线程创建中),才需要此示例的线程安全性。正确吗? - Nicole
NickC,我相信这是正确的。但是每个请求创建大量的SimpleDateFormat可能会影响性能,因为它是一项昂贵的操作。 - Sebastian

10

如前所述,您至少需要有一个有序的模式候选列表。一旦您有了这个列表,Apache DateUtils提供了一个parseDate(String dateString, String[] patterns)方法,让您可以轻松地尝试一系列日期格式的模式,并通过第一个匹配的模式来解析它:

public static Date parseDate(String str,
                         String[] parsePatterns)
                  throws ParseException

通过尝试不同的解析器来解析表示日期的字符串。

按顺序尝试每个解析模式。只有在成功解析整个输入字符串时,才视为解析成功。如果没有匹配任何解析模式,则会抛出ParseException异常。

解析器将对已解析的日期宽容处理。


7

这是一个基于美国日期格式的快速而简单的解决方案。

public Date parseDate(String strDate) throws Exception
{
    if (strDate != null && !strDate.isEmpty())
    {
        SimpleDateFormat[] formats =
                new SimpleDateFormat[] {new SimpleDateFormat("MM-dd-yyyy"), new SimpleDateFormat("yyyyMMdd"),
                        new SimpleDateFormat("MM/dd/yyyy")};

        Date parsedDate = null;

        for (int i = 0; i < formats.length; i++)
        {
            try
            {
                parsedDate = formats[i].parse(strDate);
                return parsedDate;
            }
            catch (ParseException e)
            {
                continue;
            }
        }
    }
    throw new Exception("Unknown date format: '" + strDate + "'");
}

3
你的问题与国际化相关。正如Kragen所回答的,你不能仅仅解析未知格式的日期。尽管你可以扫描所有可能的语言环境并解析某些东西,但你不会知道它是否被正确解析。
简单介绍一下i18n:
问:你能告诉我这个日期指的是哪一天、月份和年份吗?
09/11/10?
答:如果不知道所在地区的语言环境,就无法确定。它可能是任何日期,比如在美国是9月11日,在英国则是11月9日,依此类推。

7
2009年11月10日,日本 :) - oksayt

2
我曾经有一个任务,需要编写一段代码来解析日期字符串,但日期格式事先是未知的。也就是说,我必须解析任何有效的日期格式。我编写了一个项目,并撰写了一篇文章,描述了我的实现思路。以下是文章链接:Java 8 java.time package: parsing any string to date。总的想法是将所有要支持的模式写入外部属性文件中,并从中读取并尝试按照这些格式之一解析您的字符串,直到成功或用完所有格式为止。请注意,顺序也很重要,因为某些字符串可能对多个格式有效(美国/欧洲差异)。优点是,您可以继续向该文件添加/删除格式,而无需更改您的代码。因此,这样的项目也可以根据不同的客户进行定制。"Original Answer"的翻译是"最初的回答"。

1

如果这是一个协议,定义格式 - 可能是ISO格式,这会让除了我们瑞典人以外的所有人都感到恼火...

如果输入来自用户,请让他们设置他们的语言环境。如果可以的话,以完整格式显示解析后的日期,以便用户可以验证它,例如2009年11月10日。


0

我唯一的猜测是,你应该先收集一些统计数据来确定格式。

如果你幸运的话,你会得到像“2010/08/13”这样的日期,可以无歧义地解析。


0
我创建了一个实用工具,尝试解析一些常见的日期格式。 它会尝试检查哪个数字大于12,否则如果两个都小于12,则优先使用用户定义的布尔值preferMonthFirst,基于此,它将在MM/dd/yyyydd/MM/yyyy之间进行选择。

它还接受prefer24HourTime布尔值来解析时间。

我没有使用列表并迭代尝试解析它,并尝试捕获异常,因为异常是昂贵的。所以基于分隔符和长度,我尝试找到日期格式。

您可以在测试用例中找到用法。

https://github.com/rajat-g/DateParser


-1

这里是一个简单的解决方案,对我很有效。 这是一种简单的方法来解析日期,将字符串作为参数传递,并以任何您想要的格式进行解析。

String dateToConvert(String date) {
        String strDate = "";
        try {
            //create SimpleDateFormat object with source string date format
            DateFormat sdfSource = new SimpleDateFormat("yyyy-MM-dd");

            //parse the string into Date object
            Date d = sdfSource.parse(date);
            LocalLog.e(LOG_TAG, d.toString());
            //create SimpleDateFormat object with desired date format
            SimpleDateFormat sdfDestination = new SimpleDateFormat(AppConstants.UNIVERSAL_DATE_FORMAT);

            //parse the date into another format
            strDate = sdfDestination.format(d);

            LocalLog.e(LOG_TAG, strDate);

        } catch (ParseException pe) {
            System.out.println("Parse Exception : " + pe);
        }
        return strDate;
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接