Elastic Search 和 Y10k(超过 4 位数的年份)
Posted
技术标签:
【中文标题】Elastic Search 和 Y10k(超过 4 位数的年份)【英文标题】:Elastic Search and Y10k (years with more than 4 digits) 【发布时间】:2020-06-23 18:23:37 【问题描述】:我发现这个问题与 Elastic Search 查询有关,但由于 ES date format documentation 链接到 API documentation for the java.time.format.DateTimeFormatter 类,因此问题并不是真正的 ES 特定的。
简短摘要:我们遇到了超过 9999 年的日期问题,更准确地说,是超过 4 位数的年份。
存储在 ES 中的文档有一个日期字段,它在索引描述符中定义为格式“日期”,对应于使用 DateTimeFormatter 的模式语言的“yyyy-MM-dd”。我们正在获取用户输入,使用 org.apache.commons.validator.DateValidator.isValid 验证输入,也使用模式“yyyy-MM-dd”,如果有效,我们使用用户输入创建一个 ES 查询。如果用户输入类似 20202-12-03 的内容,则会失败并出现异常。搜索词可能不是故意的,但预期的行为是找不到任何东西,也不是软件咳出异常。
问题是 org.apache.commons.validator.DateValidator 在内部使用较旧的 SimpleDateFormat 类来验证输入是否符合模式,并且 SimpleDateFormat 解释的“yyyy”的含义类似于:至少使用4 位数,但如果需要允许更多位数。因此,使用模式“yyyy-MM-dd”创建 SimpleDateFormat 将解析像“20202-07-14”这样的输入,并类似地格式化年份超过 9999 的 Date 对象。
新的 DateTimeFormatter 类更加严格,意味着“yyyy”正好四位数。它将无法解析像“20202-07-14”这样的输入字符串,也无法格式化超过 9999 年的 Temporal 对象。值得注意的是,DateTimeFormatter 本身能够处理可变长度字段。例如,常量 DateTimeFormatter.ISO_LOCAL_DATE 不等同于“yyyy-MM-dd”,但符合 ISO8601,允许超过四位数字的年份,但至少使用四位数字。此常量是使用 DateTimeFormatterBuilder 以编程方式创建的,而不是使用模式字符串。
ES 不能配置为使用 DateTimeFormatter 中定义的常量,如 ISO_LOCAL_DATE,而只能使用模式字符串。 ES 也知道预定义模式的列表,有时文档中也会提到 ISO 标准,但它们似乎是错误的,并忽略了有效的 ISO 日期字符串可以包含五位数的年份。
我可以为 ES 配置多个允许的日期模式列表,例如“yyyy-MM-dd||yyyyy-MM-dd”。这将允许四位数和五位数的年份,但不允许六位数的年份。我可以通过添加另一个允许的模式来支持六位数的年份:“yyyy-MM-dd||yyyyy-MM-dd||yyyyyy-MM-dd”,但是它会在七位数的年份中失败,依此类推。
我是否在监督某些事情,或者真的不可能将 ES(或使用模式字符串的 DateTimeFormatter 实例)配置为具有 ISO 标准所使用的至少四位数(但可能更多)的年份字段?
【问题讨论】:
我不确定我是否理解,也许我不需要。输入 5 位数的年份是错误的。我认为这样报告没有错。即使你坚持接受 5 位数的年份,也没有人会输入 6 位数,所以无论是否报告为错误,谁在乎? 5 位数年份不是错误。你为什么这么认为? 搜索词可能不是故意的……这就是我所说的错误。您是否明确要求能够在 9999 年之后进行搜索,如果是,为什么? @OleV.V.搜索姓氏“oqgfqhf”也可能不是故意的,但仍然不是错误。预期的行为是找不到记录。这同样适用于日期字段。搜索一个有效但未知的值应该不会返回任何结果,但不会是错误。我们没有明确要求能够搜索 oqgfqhf 作为姓氏,也没有超出预期范围的日期,也没有明确提到 2020-06-28 作为我们应该支持的搜索词。 您的代码。你的决定。既然你不能使用我的这个想法,请把它扔在你的肩膀上,我没有问题。 【参考方案1】:编辑
ISO 8601
由于您的要求是符合 ISO 8601,我们先来看看 ISO 8601 是怎么说的(引自底部链接):
为了表示 0000 之前或 9999 之后的年份,标准还 允许扩大年度代表,但只能通过事先 发送者和接收者之间的协议。扩大的一年 表示 [±YYYYY] 必须有一个商定的额外年份数 超出最小四位数的数字,并且必须以 + 为前缀 或 - 用符号代替更常见的 AD/BC(或 CE/BCE)符号; …
所以20202-12-03
在 ISO 8601 中不是有效日期。如果您明确告知用户您接受最多 6 位数的年份,则 +20202-12-03
和 -20202-12-03
是有效的,并且仅与 @ 987654327@ 或-
签名。
接受超过 4 位数字
格式模式uuuu-MM-dd
根据 ISO 8601 格式化和解析日期,也包括四位数以上的年份。例如:
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("uuuu-MM-dd");
LocalDate date = LocalDate.parse("+20202-12-03", dateFormatter);
System.out.println("Parsed: " + date);
System.out.println("Formatted back: " + date.format(dateFormatter));
输出:
Parsed: +20202-12-03 Formatted back: +20202-12-03
对于前缀减号而不是加号,它的工作原理非常相似。
接受超过 4 位不带符号的数字
yyyy-MM-dd||yyyyy-MM-dd||yyyyyy-MM-dd||yyyyyyy-MM-dd||yyyyyyyy-MM-dd||yyyyyyyyy-MM-dd
正如我所说,这不符合 ISO 8601。我也同意你的观点,即它不好。显然它会在 10 位或更多位上失败,但无论如何都会因为不同的原因而失败:java.time 在 -999 999 999 到 +999 999 999 之间处理年份。所以尝试yyyyyyyyyy-MM-dd
(10 位年份)会会给你带来严重的麻烦,除非在用户输入带有前导零的年份的极端情况下。
对不起,这是最好的。 DateTimeFormatter
格式模式不支持您要求的所有内容。没有(单一)模式可以为您提供 0000 到 9999 范围内的四位数年份以及之后的年份更多位数。
DateTimeFormatter
的文档说明了格式化和解析年份:
年份:字母数决定了使用填充的最小字段宽度。如果字母数是两个,那么 使用减少的两位数形式。对于打印,这将输出 最右边两位数。对于解析,这将使用基础进行解析 2000 的值,导致 2000 到 2099 范围内的年份 包括的。如果字母数少于四个(但不是两个), 那么这个符号只在负年份输出
SignStyle.NORMAL
。否则,如果焊盘宽度为 超出,根据SignStyle.EXCEEDS_PAD
。
因此,无论您选择哪种模式字母,您都将无法解析没有符号的数字较多的年份,和数字较少的年份将被格式化为带有前导零的这么多数字.
原答案
您可能可以摆脱u-MM-dd
的模式。示范:
String formatPattern = "u-MM-dd";
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(formatPattern);
LocalDate normalDate = LocalDate.parse("2020-07-14", dateFormatter);
String formattedAgain = normalDate.format(dateFormatter);
System.out.format("LocalDate: %s. String: %s.%n", normalDate, formattedAgain);
LocalDate largeDate = LocalDate.parse("20202-07-14", dateFormatter);
String largeFormattedAgain = largeDate.format(dateFormatter);
System.out.format("LocalDate: %s. String: %s.%n", largeDate, largeFormattedAgain);
输出:
LocalDate: 2020-07-14. String: 2020-07-14. LocalDate: +20202-07-14. String: 20202-07-14.
反直觉但非常实用的一个格式字母并不意味着 1 位数字,而是 尽可能多的数字。因此,上述情况的另一面是 1000 年之前的年份将被格式化为少于 4 位数字。正如您所说,这不符合 ISO 8601。
关于模式字母y
和u
年份的区别,请参见底部的链接。
您也可以考虑使用一个M
和/或一个d
来接受2020-007-014
,但同样,对于小于10 的数字,这将导致格式化为1 位数字,例如2020-7-14
,这可能不是t 你想要什么,又不同意 ISO。
链接
Years section ***文章:ISO 8601 Documentation ofDateTimeFormatter
uuuu
versus yyyy
in DateTimeFormatter
formatting pattern codes in Java?
【讨论】:
我们希望遵循 ISO 标准,因此不能接受单个模式字母(无论是 u 还是 y)。【参考方案2】:也许这会起作用:
[uuuu][uuuuu][...]-MM-dd
方括号之间的格式说明符是可选部分。括号内的格式说明符可以重复以允许接受多个选项。
此模式将允许四位或五位数字的年份,但拒绝所有其他情况。
Here is this pattern in action。请注意,此模式对于将字符串解析为LocalDate
很有用。但是,要将LocalDate
实例格式化 成字符串,模式应该是uuuu-MM-dd
。那是因为两个可选的年份部分会导致年份数字被打印两次。
重复所有可能的年份数字计数,是您可以得到的最接近的方法,以使其按您期望的方式工作。
DateTimeFormatter
当前实现的问题在于,当您指定4 个或更多 u
或y
s 时,解析器会尝试准确地消费 年位数。但是,如果 小于 4,那么解析器将尝试消耗尽可能多的数据。我不知道这种行为是否是故意的。
因此,可以使用格式化程序构建器来实现预期的行为,但不能使用模式字符串。正如 JodaStephen once pointed out,“模式是可能的格式化程序的子集”。
也许#
、 和
这些字符保留供将来使用,在这方面会有用。
【讨论】:
我已经提到了在问题中配置多个模式的可能性(ES 语法不同)以及为什么这不是解决方案。您在此处建议的配置将允许 4 或 5 位数的年份,而不是所需的“4 或更多”。 @jarnbjo 好吧,从 Elastic Search 的角度来看,这是一个单一的模式字符串。我已经更新了答案。【参考方案3】:更新
您可以使用DateTimeFormatterBuilder#appendValueReduced 将一年中的位数限制在4-9
位数的范围内。
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
public class Main
public static void main(String[] args)
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.appendValueReduced(ChronoField.YEAR, 4, 9, 1000)
.appendPattern("-MM-dd")
.toFormatter();
String[] dateStrArr = "2017-10-20", "20171-10-20", "201712-10-20", "2017123-10-20" ;
for (String dateStr : dateStrArr)
System.out.println(LocalDate.parse(dateStr, formatter));
输出:
2017-10-20
+20171-10-20
+201712-10-20
+2017123-10-20
原答案
您可以使用模式[uuuu][u]-MM-dd
,其中[uuuu]
符合4 位数年份,[u]
可以满足年份允许的任意位数的要求。
演示:
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
public class Main
public static void main(String[] args)
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("[uuuu][u]-MM-dd");
String[] dateStrArr = "2017-10-20", "20171-10-20", "201712-10-20", "2017123-10-20" ;
for (String dateStr : dateStrArr)
System.out.println(LocalDate.parse(dateStr, formatter));
输出:
2017-10-20
+20171-10-20
+201712-10-20
+2017123-10-20
【讨论】:
虽然它适用于解析,但您的格式化程序格式与 20172017-10-20、+2017120171-10-20、+201712201712-10-20 和 +20171232017123-10-20 的日期相同,相反符合要求。 [uuuu][u] 没有多大意义,因为模式 uuuu 已经被模式 u 覆盖了。出于解析目的,“[uuuu][u]-MM-dd”和“u-MM-dd”是等价的,正如我已经在对 Ole 答案的评论中所写的那样,简单地使用 u 是不可接受的,因为它允许使用数年少于 4 位数,这是不想要的。 @jarnbjo - 我发布了一个更新,它将限制4-9
范围内的数字。如果仍然不符合要求,请告诉我。
我已经在问题中写道,我可以通过使用 DateTimeFormatterBuilder 创建 DateFormatBuilder 来做我想做的事情,但是不能使用 DateTimeFormatterBuilder 来配置 Elastic Search。这也没有回答我的问题。以上是关于Elastic Search 和 Y10k(超过 4 位数的年份)的主要内容,如果未能解决你的问题,请参考以下文章