我想在 Java 中解析一些日期,但格式没有定义,可能是很多(任何已经很多的 ISO-8601 格式,任何单位的 Unix 时间戳等等) 以下是一些示例:
- 1970-01-01T00:00:00.00Z
- 1234567890
- 1234567890000
- 1234567890000000
- 2021-09-20T17:27:00.000Z+02:00
由于模棱两可的情况,完美的解析可能是不可能的,但是,使用一些逻辑解析大多数常见日期的解决方案可能是可以实现的(例如,时间戳以秒/毫/微/纳米为单位,以给出接近 2000 年的日期,像"08/07/2021"这样的日期可以默认区分月份和日期)。 我没有找到任何简单的方法来在 Java 中做到这一点,而在 python 中,使用 panda 函数to_datetime (https://pandas.pydata.org/docs/reference/api/pandas.to_datetime.html) 的infer_datetime_format是可能的(不是在我的所有样本上工作,但至少是其中一些)。
Java中有一些简单的方法吗?
好吧,首先,我同意 rzwitserloot 的观点,即以自由格式解析日期非常困难且充满歧义。因此,您正在如履薄冰,如果您只是假设用户输入将以您认为的方式正确解析,最终会遇到麻烦。
尽管如此,如果我假设以下任何一项,我们可以使其工作:
-
你根本不在乎它是否会被错误地解析;或者
-
您这样做是为了娱乐或学习目的;或
-
你有一个横幅,上面写着:
如果解析出错,那是您的错。不要怪我们。
无论如何,DateTimeFormatterBuilder
能够构建一个能够解析很多不同模式的DateTimeFormatter
。由于格式化程序支持可选解析,因此可以指示它尝试解析某个值,或者在找不到有效值时跳过该部分。
例如,这个构建器能够解析相当广泛的类似 ISO 的日期,其中包含许多可选部分:
DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder()
.appendPattern("uuuu-M-d")
.optionalStart()
.optionalStart().appendLiteral(' ').optionalEnd()
.optionalStart().appendLiteral('T').optionalEnd()
.appendValue(ChronoField.HOUR_OF_DAY)
.optionalStart()
.appendLiteral(':')
.appendValue(ChronoField.MINUTE_OF_HOUR)
.optionalStart()
.appendLiteral(':')
.appendValue(ChronoField.SECOND_OF_MINUTE)
.optionalStart()
.appendFraction(ChronoField.NANO_OF_SECOND, 1, 9, true)
.optionalEnd()
.optionalEnd()
.optionalEnd()
.appendPattern("[XXXXX][XXXX][XXX][XX][X]")
.optionalEnd();
DateTimeFormatter formatter = builder.toFormatter(Locale.ROOT);
此格式化程序可以成功解析以下所有字符串。
Stream.of(
"2021-09-28",
"2021-07-04T14",
"2021-07-04T14:06",
"2001-09-11 00:00:15",
"1970-01-01T00:00:15.446-08:00",
"2021-07-04T14:06:15.2017323Z",
"2021-09-20T17:27:00.000+02:00"
).forEach(testcase -> System.out.println(formatter.parse(testcase)));
您可以看到,使用optionalStart()
和optionalEnd()
,您可以定义格式的可选部分。
您可能想要解析更多模式。您可以将这些模式添加到上述构建器中。或者,可以使用appendOptional(DateTimeFormatter)
方法包含多个构建器。
由于情况不明确,完美的解析可能是不可能的,但是,使用一些逻辑解析大多数常见日期的解决方案可能是可以实现的
当然,这种广泛的猜测绝对不应该是标准java.*
API的一部分。我认为你也大大低估了歧义。1234567890
?说这可以合理地解析是完全不正确的。
你在这里遇到了很多很多问题:
-
一般来说,Java更喜欢抛出错误而不是猜测。这是语言中固有的(java 几乎没有可选的语法结构;分号不是可选的,
()
方法调用不是可选的,java 故意没有"truthy/false",即if (foo)
只有在foo
是布尔类型的表达式时才有效,不像 python,你可以在那里粘贴任何东西,并且有一个大列表算作 falsy,其余的被认为是真实的。在罗马时,要像罗马人一样:如果这个信条惹恼了你,那么,要么学会爱它,要么勉强接受它,要么用另一种语言编程。这个想法在整个生态系统中都很普遍。值得一提的是,鉴于调试往往比键入可选结构花费更长的时间,java 是客观正确的,或者至少是做出这样的理性决定。 -
要么你不能引入"嘿,这个数字大于 12,因此它不可能是月份"的概念,要么你必须接受某个日期格式解析器是否正确取决于月份中的日期值是高于还是低于 12。我强烈建议你避免像瘟疫一样不符合这一规则的图书馆。到底有什么可能的意义呢?"我的应用程序会正确解析您的日期,但只解析所有日期的 3/5 左右?" 所以,鉴于你不能/不应该考虑到这一点,
1234567890
,那是自 1970 年以来的几秒钟吗?自 1970 年以来的毫秒?那是 5678 年第 34 个月的 12 日,第 90 小时,并假设分钟、秒和毫为零吗?如果一个库猜到了,那么这个库是错的,因为除非你有95%+的把握,否则你不应该猜。 -
当然,显而易见且长期存在的"不要猜测"的例子是101112。那是2012年11月10日(欧式风格)吗?那是 2012 年 10 月 11 日(美式风格),还是 2010 年 11 月 12 日(ISO 风格)?这些都是合理的猜测,因此在这里猜测是错误的。不要,猜猜。除非你真的确定。鉴于这是输入日期的一种常见方式,因此:不惜一切代价猜测客观上是愚蠢的(见上文)。只在非常清楚的时候猜测,否则就出错,这大多是没有用的,因为歧义很容易引入。
-
猜测的概念可能是可以辩护的,但只有更多的信息。例如,如果你给我输入"101112100000",这里猜对是不正确的。但是,如果你还告诉我,一个人输入了这个输入,并且这个人清楚地知道,比如说,德语的地方,那么我可以看到有必要能够把它变成"2012年11月10日,早上10点": 解释为秒或米利斯,因为某些时代被人为因素排除在外, 以及按区域设置划分的日-月-年顺序。
你问:
Java中有一些简单的方法吗?
这整个问题都是不正确的。in Java
部分需要从这个问题中剥离出来,然后答案很简单:不。没有简单的方法可以将字符串解析为日期/时间,而没有比输入字符串更多的信息。如果另一个图书馆说他们可以做到这一点,他们就是在撒谎,或者至少是在文化和来源假设的清单下运作,只要我的腿,你不应该使用那个图书馆。
我不知道任何具有此功能的标准库,但是您始终可以使用DateTimeFormatter类并猜测预定义格式列表上的格式循环,或使用此类提供的格式。
这是您要存档的内容的典型近似值。
在这里你可以看到和旧的实现 https://balusc.omnifaces.org/2007/09/dateutil.html
FTA(https://github.com/tsegall/fta)正是为了解决这个问题而设计的。它目前解析数千种格式,并且不通过预定义的集合来解析,因此通常运行速度非常快。在此示例中,我们显式设置了日期解析模式,但是,它将默认为基于区域设置的智能模式。下面是一个示例:
import com.cobber.fta.dates.DateTimeParser;
import com.cobber.fta.dates.DateTimeParser.DateResolutionMode;
public abstract class Simple {
public static void main(final String[] args) {
final String[] samples = { "1970-01-01T00:00:00.00Z", "2021-09-20T17:27:00.000Z+02:00", "08/07/2021" };
final DateTimeParser dtp = new DateTimeParser().withDateResolutionMode(DateResolutionMode.MonthFirst).withLocale(Locale.ENGLISH);
for (final String sample : samples)
System.err.printf("Format is: '%s'%n", dtp.determineFormatString(sample));
}
}
这将给出以下输出:
Format is: 'yyyy-MM-dd'T'HH:mm:ss.SSX'
Format is: 'yyyy-MM-dd'T'HH:mm:ss.SSSX'
Format is: 'MM/dd/yyyy'