一、Date类型简介

elasticsearch通过JSON格式来承载数据的,而JSON中是没有Date对应的数据类型的,但是elasticsearch可以通过以下三种方式处理JSON承载的Date数据

  • 符合特定格式化的日期字符串;
  • 基于milliseconds-since-the-epoch的一个长整型数字;
  • 基于seconds-since-the-epoch的一个长整型数字;

索引数据的时候,elasticsearch内部会基于UTC时间,将传入的数据转化为基于milliseconds-since-the-epoch的一个长整型数字;查询数据的时候,elasticsearch内部会将查询转化为range查询;

二、测试数据准备

创建mapping,设置create_date的type为date

PUT my_date_index
{
"mappings": {
"_doc": {
"properties": {
"create_date": {
"type": "date"
}
}
}
}
}

索引以下三个document

PUT my_date_index/_doc/1
{ "create_date": "2015-01-01" } PUT my_date_index/_doc/2
{ "create_date": "2015-01-01T12:10:30Z" } PUT my_date_index/_doc/3
{ "create_date": 1420070400001 }

三、日期查询的诡异之处

我们希望可以通过以下查询命中2015-01-01的记录

POST my_date_index/_search
{
"query": {
"term": {
"create_date": "2015-01-01"
}
}
}

查看执行结果发现命中了三条数据

{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 3,
"max_score" : 1.0,
"hits" : [
{
"_index" : "my_date_index",
"_type" : "_doc",
"_id" : "2",
"_score" : 1.0,
"_source" : {
"create_date" : "2015-01-01T12:10:30Z"
}
},
{
"_index" : "my_date_index",
"_type" : "_doc",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"create_date" : "2015-01-01"
}
},
{
"_index" : "my_date_index",
"_type" : "_doc",
"_id" : "3",
"_score" : 1.0,
"_source" : {
"create_date" : 1420070400001
}
}
]
}
}

通过以下可以看到elasticsearch内部确实将查询重写为一个范围查询create_date:[1420070400000 TO 1420156799999]

POST my_date_index/_search
{
"profile": "true",
"query": {
"term": {
"create_date": "2015-01-01"
}
}
} {
"id" : "[eD2KQtMGSla7jzJQBQVAfQ][my_date_index][0]",
"searches" : [
{
"query" : [
{
"type" : "IndexOrDocValuesQuery",
"description" : "create_date:[1420070400000 TO 1420156799999]",
"time_in_nanos" : 2101,
"breakdown" : {
"score" : 0,
"build_scorer_count" : 0,
"match_count" : 0,
"create_weight" : 2100,
"next_doc" : 0,
"match" : 0,
"create_weight_count" : 1,
"next_doc_count" : 0,
"score_count" : 0,
"build_scorer" : 0,
"advance" : 0,
"advance_count" : 0
}
}
],
"rewrite_time" : 2200,
"collector" : [
{
"name" : "CancellableCollector",
"reason" : "search_cancelled",
"time_in_nanos" : 700,
"children" : [
{
"name" : "SimpleTopScoreDocCollector",
"reason" : "search_top_hits",
"time_in_nanos" : 200
}
]
}
]
}
],
"aggregations" : [ ]
}

接下来我们来分析一下Date数据类型的term查询

我们可以看到termQuery查询直接调用了rangeQuery,并将传入的日期参数作为range的两个范围值;

DateFieldType

@Override
public Query termQuery(Object value, @Nullable QueryShardContext context) {
Query query = rangeQuery(value, value, true, true, ShapeRelation.INTERSECTS, null, null, context);
if (boost() != 1f) {
query = new BoostQuery(query, boost());
}
return query;
}

rangeQuery中会调用parseToMilliseconds计算查询的两个范围值

DateFieldType

@Override
public Query rangeQuery(Object lowerTerm, Object upperTerm, boolean includeLower, boolean includeUpper, ShapeRelation relation,
@Nullable DateTimeZone timeZone, @Nullable DateMathParser forcedDateParser, QueryShardContext context) {
failIfNotIndexed();
if (relation == ShapeRelation.DISJOINT) {
throw new IllegalArgumentException("Field [" + name() + "] of type [" + typeName() +
"] does not support DISJOINT ranges");
}
DateMathParser parser = forcedDateParser == null
? dateMathParser
: forcedDateParser;
long l, u;
if (lowerTerm == null) {
l = Long.MIN_VALUE;
} else {
l = parseToMilliseconds(lowerTerm, !includeLower, timeZone, parser, context);
if (includeLower == false) {
++l;
}
}
if (upperTerm == null) {
u = Long.MAX_VALUE;
} else {
u = parseToMilliseconds(upperTerm, includeUpper, timeZone, parser, context);
if (includeUpper == false) {
--u;
}
}
Query query = LongPoint.newRangeQuery(name(), l, u);
if (hasDocValues()) {
Query dvQuery = SortedNumericDocValuesField.newSlowRangeQuery(name(), l, u);
query = new IndexOrDocValuesQuery(query, dvQuery);
}
return query;
}

通过以下代码可以看到,左边界的值会覆盖new MutableDateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeZone.UTC)对应的位置的数字,右边界的值会覆盖ew MutableDateTime(1970, 1, 1, 23, 59, 59, 999, DateTimeZone.UTC)对应位置的数字;所以我们查询中输入2015-01-01,相当于查询这一天之内的所有记录;

JodaDateMathParser

private long parseDateTime(String value, DateTimeZone timeZone, boolean roundUpIfNoTime) {
DateTimeFormatter parser = dateTimeFormatter.parser;
if (timeZone != null) {
parser = parser.withZone(timeZone);
}
try {
MutableDateTime date;
// We use 01/01/1970 as a base date so that things keep working with date
// fields that are filled with times without dates
if (roundUpIfNoTime) {
date = new MutableDateTime(1970, 1, 1, 23, 59, 59, 999, DateTimeZone.UTC);
} else {
date = new MutableDateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeZone.UTC);
}
final int end = parser.parseInto(date, value, 0);
if (end < 0) {
int position = ~end;
throw new IllegalArgumentException("Parse failure at index [" + position + "] of [" + value + "]");
} else if (end != value.length()) {
throw new IllegalArgumentException("Unrecognized chars at the end of [" + value + "]: [" + value.substring(end) + "]");
}
return date.getMillis();
} catch (IllegalArgumentException e) {
throw new ElasticsearchParseException("failed to parse date field [{}] with format [{}]", e, value,
dateTimeFormatter.pattern());
}
}

一般我们使用的日期都是精确到秒,那么只要我们将输入数据精确到秒基本上就可以命中记录;如果还是命中多个记录,那么就需要将数据的精度提高到毫秒,并且查询输入的时候也需要带上毫秒;

POST my_date_index/_search
{
"query": {
"term": {
"create_date": "2015-01-01T12:10:30Z"
}
}
} {
"took" : 10,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 1,
"max_score" : 1.0,
"hits" : [
{
"_index" : "my_date_index",
"_type" : "_doc",
"_id" : "2",
"_score" : 1.0,
"_source" : {
"create_date" : "2015-01-01T12:10:30Z"
}
}
]
}
}

四、自定义时间字符串的解析格式

elasticsearch中date默认的日期格式是表征epoch_millis的长整型数字或者符合strict_date_optional_time格式的字符串;

public static final DateFormatter DEFAULT_DATE_TIME_FORMATTER = DateFormatter.forPattern("strict_date_optional_time||epoch_millis");

strict_date_optional_time

strict限制时间字符串中的年月日部分必须是4、2、2个数字,不足部分在前边补0,例如20230123;

date_optional_time则要求字符串可以不包含时间部分,但是必须包含日期部分;

strict_date_optional_time支持的完整的时间格式如下

 date-opt-time     = date-element ['T' [time-element] [offset]]
date-element = std-date-element | ord-date-element | week-date-element
std-date-element = yyyy ['-' MM ['-' dd]]
ord-date-element = yyyy ['-' DDD]
week-date-element = xxxx '-W' ww ['-' e]
time-element = HH [minute-element] | [fraction]
minute-element = ':' mm [second-element] | [fraction]
second-element = ':' ss [fraction]
fraction = ('.' | ',') digit+

我们使用2015/01/01搜索的时候,elasticsearch无法解析就会报错

POST my_date_index/_search
{
"profile": "true",
"query": {
"term": {
"create_date": "2015/01/01"
}
}
} {
"error": {
"root_cause": [
{
"type": "parse_exception",
"reason": "failed to parse date field [2015/01/01] with format [strict_date_optional_time||epoch_millis]"
}
],
"type": "search_phase_execution_exception",
"reason": "all shards failed",
"phase": "query",
"grouped": true,
"failed_shards": [
{
"shard": 0,
"index": "my_date_index",
"node": "eD2KQtMGSla7jzJQBQVAfQ",
"reason": {
"type": "query_shard_exception",
"reason": "failed to create query: {\n \"term\" : {\n \"create_date\" : {\n \"value\" : \"2015/01/01\",\n \"boost\" : 1.0\n }\n }\n}",
"index_uuid": "9MTRkZcMTnK8GgK9vKwUuA",
"index": "my_date_index",
"caused_by": {
"type": "parse_exception",
"reason": "failed to parse date field [2015/01/01] with format [strict_date_optional_time||epoch_millis]",
"caused_by": {
"type": "illegal_argument_exception",
"reason": "Unrecognized chars at the end of [2015/01/01]: [/01/01]"
}
}
}
}
],
"caused_by": {
"type": "parse_exception",
"reason": "failed to parse date field [2015/01/01] with format [strict_date_optional_time||epoch_millis]",
"caused_by": {
"type": "illegal_argument_exception",
"reason": "Unrecognized chars at the end of [2015/01/01]: [/01/01]"
}
}
},
"status": 400
}

我们可以在mapping或者在搜索的时候指定format

POST my_date_index/_search
{
"query": {
"range" : {
"create_date" : {
"gte": "2015/01/01",
"lte": "2015/01/01",
"format": "yyyy/MM/dd"
}
}
}
} {
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 3,
"max_score" : 1.0,
"hits" : [
{
"_index" : "my_date_index",
"_type" : "_doc",
"_id" : "2",
"_score" : 1.0,
"_source" : {
"create_date" : "2015-01-01T12:10:30Z"
}
},
{
"_index" : "my_date_index",
"_type" : "_doc",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"create_date" : "2015-01-01"
}
},
{
"_index" : "my_date_index",
"_type" : "_doc",
"_id" : "3",
"_score" : 1.0,
"_source" : {
"create_date" : 1420070400001
}
}
]
}
}

elasticsearch之日期类型有点怪的更多相关文章

  1. C# 把日期字符串转换为日期类型 (MM大写为月、小写为分钟)

    string dtStr; DateTime dtTime; 尝试把时间字符串转为DateTime格式 if (DateTime.TryParse(dtStr, out dtTime)) { //st ...

  2. java mysql 日期类型

    mysql(版本:5.1.50)的时间日期类型如下: datetime 8bytes xxxx-xx-xx xx:xx:xx 1000-01-01 00:00:00到9999-12-31 23:59: ...

  3. Java 基础【09】 日期类型

    java api中日期类型的继承关系 java.lang.Object --java.util.Date --java.sql.Date --java.sql.Time --java.sql.Time ...

  4. SpringMVC对日期类型的转换

    在做web开发的时候,页面传入的都是String类型,SpringMVC可以对一些基本的类型进行转换,但是对于日期类的转换可能就需要我们配置. 1.如果查询类使我们自己写,那么在属性前面加上@Date ...

  5. spring mvc 注解访问控制器以及接收form数据的方式,包括直接接收日期类型及对象的方法

    Spring 中配置扫描器 <!-- springmvc的扫描器--> <context:component-scan base-package="com.beifeng. ...

  6. excel转换日期格式,将yyyymmdd类型日期转换成yyyy-mm-dd等日期类型方法

    源数据日期格式:例如: 20160420 20160422 目标日期格式类型: 2016-4-20 2016-4-22 或 2016/04/20 2016/04/22 方法: 一.选中相应数据的单元格 ...

  7. C# 字符串string类型转换成DateTime类型 或者 string转换成DateTime?(字符串转换成可空日期类型)

    在c#中,string类型转换成DateTime类型是经常用到的,作为基本的知识,这里在此做个小结.一般来说可以使用多种方法进行转换,最常用的就是使用Convert.ToDateTime(string ...

  8. mysql 日期类型比较

    MySQL 日期类型:日期格式.所占存储空间.日期范围 比较. 日期类型        存储空间       日期格式                 日期范围 ------------ ------ ...

  9. MySQL字符串转日期类型

    MySQL字符串转日期类型 select str_to_date('2014-08-20 00:00:00', '%Y-%m-%d %H:%i:%s'); >2014-08-20 00:00:0 ...

  10. springMVC注解@initbinder日期类型的属性自动转换

    在实际操作中经常会碰到表单中的日期 字符串和Javabean中的日期类型的属性自动转换, 而springMVC默认不支持这个格式的转换,所以必须要手动配置, 自定义数据类型的绑定才能实现这个功能. 一 ...

随机推荐

  1. PHP 正在“杀死”Python

    最近,我突然发现自己好像又在逆潮流而动.可能我的想法与很多朋友不同,我认为 PHP 这个编程语言界的"混蛋"比以往任何时候都更受欢迎. 或许你会质疑--PHP 不是已经完蛋了吗?市 ...

  2. 基于python的数学建模---场线与数值解(微分方程)

    import numpy as np from scipy import integrate import matplotlib.pyplot as plt import sympy def plot ...

  3. 基于python的数学建模---scipy库

    instance1: 求解下列线性规划问题 s.t.   代码: from scipy import optimizeimport numpy as npc = np.array([2,3,-5])A ...

  4. Cacheable VS Non-Cacheable

    1 基本概念 在嵌入式软件开发中,经常会碰到说某块内存是cache的,还是non-cache的,它们究竟是什么意思?分别用在什么场景?non-cache和cache的内存区域怎么配置?这篇博文将会围绕 ...

  5. ArcObjects SDK开发 004 如何学习好ArcObjects SDK开发

    1.基于Arcobjects SDK可以做什么 基于Arcobjects SDK开发,大部分情况下就是做桌面GIS应用程序.AO写的代码是不能直接在Web服务上运行的,但如果你前端是JS,需要后端处理 ...

  6. 【文档资料】Linux、Vi/Vim常用命令、文件夹和文件介绍

    一.Linux 1.系统信息[左1] 查看磁盘空间使用情况:df+参数 查看当前指定文件或目录的大小:du 查看不同硬件信息:cat/proc/xxx 查看系统和空闲内存:free +参数 SSH退出 ...

  7. 【JVM调优】Day02:CMS的三色标记算法、分区的G1回收器、短时停顿的ZGC回收器

    一.CMS及其三色标记算法 1.核心 标记整个图谱的过程分为多步 多个线程相互工作,才能标记完 标记的算法,JVM虚拟机.go语言使用的都是三色标记算法 2.含义 从那个地方开始,用三种颜色替代 一开 ...

  8. 协程- gevent模块

    协程 1.什么是协助:在单线程下实现并发效果 2.协程的原理: 通过代码监听IO操作一旦遇到 IO 操作就立刻切换下一个程序 让cpu一直在工作 这样就可以一直占用CPU的效率 提高程序执行效率 切换 ...

  9. Linux—软件管理

    Linux 软件管理 1.软件管理简介 Redhat和Centos中软件管理是依靠软件包管理器(RPM)来实现的. RPM(Redhat Package Manager)软件包管理器提供了在linux ...

  10. Jmeter 之模块控制器

    模块控制器作用: 模块控制器相当于python中的import 操作,即可以导入本线程组或者其他线程组下的控制器测试片段直接执行. 说明:被导入的测试片段可以是启用.禁用,导入后都将被执行. 字段解释 ...