从昨晚开始,到今天中午之前,一直在纠结时间存储问题,昨晚是纠结时间取出来的问题。

其实我的想法很简单,我就想java.util.Date  存储到 Elasticsearch  ,然后从 Elasticsearch  中再取出来的时候,它是个Date ,不需要我任何转换。

但是发现好像不行。

我开始在创建 Mapping  的时候,就是为:



 


1. //...省略部分代码
2. .startObject("create_date").field("type","date").field("format","yyyy-MM-dd HH:mm:ss").endObject()
3. //...省略部分代码

指定了TypeDate ,并且formatyyyy-MM-dd HH:mm:ss ,然后new Date(); 插入后报错:



 


1. message [MapperParsingException[failed to parse [create_date]]; nested: IllegalArgumentException[Invalid format: "2016-07-04T03:03:12.616Z" is malformed at "T03:03:12.616Z"];]

根据错误提示,我先把时间格式化,然后插入:



 



  1. result.put("create_date", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(create_date));

然后插入OK。后来我看了源码,才恍然大悟。新版本(我不知道从什么版本开始,我以前最开始用的是0.9)值是根据value 的类型来判断。我贴一下。

org.elasticsearch.common.xcontent.XContentBuilder  中 1248 行。



 


1. private void writeValue(Object value) throws IOException {
2. if (value == null) {
3. generator.writeNull();
4. return;
5. }
6. Class<?> type = value.getClass();
7. if (type == String.class) {
8. generator.writeString((String) value);
9. } else if (type == Integer.class) {
10. generator.writeNumber(((Integer) value).intValue());
11. } else if (type == Long.class) {
12. generator.writeNumber(((Long) value).longValue());
13. } else if (type == Float.class) {
14. generator.writeNumber(((Float) value).floatValue());
15. } else if (type == Double.class) {
16. generator.writeNumber(((Double) value).doubleValue());
17. } else if (type == Byte.class) {
18. generator.writeNumber(((Byte)value).byteValue());
19. } else if (type == Short.class) {
20. generator.writeNumber(((Short) value).shortValue());
21. } else if (type == Boolean.class) {
22. generator.writeBoolean(((Boolean) value).booleanValue());
23. } else if (type == GeoPoint.class) {
24. generator.writeStartObject();
25. generator.writeNumberField("lat", ((GeoPoint) value).lat());
26. generator.writeNumberField("lon", ((GeoPoint) value).lon());
27. generator.writeEndObject();
28. } else if (value instanceof Map) {
29. writeMap((Map) value);
30. } else if (value instanceof Path) {
31. //Path implements Iterable<Path> and causes endless recursion and a StackOverFlow if treated as an Iterable here
32. generator.writeString(value.toString());
33. } else if (value instanceof Iterable) {
34. generator.writeStartArray();
35. for (Object v : (Iterable<?>) value) {
36. writeValue(v);
37. }
38. generator.writeEndArray();
39. } else if (value instanceof Object[]) {
40. generator.writeStartArray();
41. for (Object v : (Object[]) value) {
42. writeValue(v);
43. }
44. generator.writeEndArray();
45. } else if (type == byte[].class) {
46. generator.writeBinary((byte[]) value);
47. /* 注意这里:如果是Date类型,就是以字符串输出。
48. 如果你跟进去看。代码在下个片段。
49. */
50. } else if (value instanceof Date) {
51. generator.writeString(XContentBuilder.defaultDatePrinter.print(((Date) value).getTime()));
52. } else if (value instanceof Calendar) {
53. generator.writeString(XContentBuilder.defaultDatePrinter.print((((Calendar) value)).getTimeInMillis()));
54. } else if (value instanceof ReadableInstant) {
55. generator.writeString(XContentBuilder.defaultDatePrinter.print((((ReadableInstant) value)).getMillis()));
56. } else if (value instanceof BytesReference) {
57. BytesReference bytes = (BytesReference) value;
58. if (!bytes.hasArray()) {
59. bytes = bytes.toBytesArray();
60. }
61. generator.writeBinary(bytes.array(), bytes.arrayOffset(), bytes.length());
62. } else if (value instanceof BytesRef) {
63. BytesRef bytes = (BytesRef) value;
64. generator.writeBinary(bytes.bytes, bytes.offset, bytes.length);
65. } else if (value instanceof Text) {
66. Text text = (Text) value;
67. if (text.hasBytes() && text.bytes().hasArray()) {
68. generator.writeUTF8String(text.bytes().array(), text.bytes().arrayOffset(), text.bytes().length());
69. } else if (text.hasString()) {
70. generator.writeString(text.string());
71. } else {
72. BytesArray bytesArray = text.bytes().toBytesArray();
73. generator.writeUTF8String(bytesArray.array(), bytesArray.arrayOffset(), bytesArray.length());
74. }
75. } else if (value instanceof ToXContent) {
76. ((ToXContent) value).toXContent(this, ToXContent.EMPTY_PARAMS);
77. } else if (value instanceof double[]) {
78. generator.writeStartArray();
79. for (double v : (double[]) value) {
80. generator.writeNumber(v);
81. }
82. generator.writeEndArray();
83. } else if (value instanceof long[]) {
84. generator.writeStartArray();
85. for (long v : (long[]) value) {
86. generator.writeNumber(v);
87. }
88. generator.writeEndArray();
89. } else if (value instanceof int[]) {
90. generator.writeStartArray();
91. for (int v : (int[]) value) {
92. generator.writeNumber(v);
93. }
94. generator.writeEndArray();
95. } else if (value instanceof float[]) {
96. generator.writeStartArray();
97. for (float v : (float[]) value) {
98. generator.writeNumber(v);
99. }
100. generator.writeEndArray();
101. } else if (value instanceof short[]) {
102. generator.writeStartArray();
103. for (short v : (short[]) value) {
104. generator.writeNumber(v);
105. }
106. generator.writeEndArray();
107. } else {
108. // if this is a "value" object, like enum, DistanceUnit, ..., just toString it
109. // yea, it can be misleading when toString a Java class, but really, jackson should be used in that case
110. generator.writeString(value.toString());
111. //throw new ElasticsearchIllegalArgumentException("type not supported for generic value conversion: " + type);
112. }
113. }

我们看下这部分:XContentBuilder.defaultDatePrinter.print(((Date) value).getTime()) 进去后。看到如下:



 


1. /**
2. * Prints a millisecond instant to a String.
3. * <p>
4. * This method will use the override zone and the override chronology if
5. * they are set. Otherwise it will use the ISO chronology and default zone.
6. *
7. * @param
8. * @return
9. */
10. public String print(long instant) {
11. StringBuilder buf = new StringBuilder(requirePrinter().estimatePrintedLength());
12. try {
13. printTo((Appendable) buf, instant);
14. } catch (IOException ex) {
15. // StringBuilder does not throw IOException
16. }
17. return buf.toString();
18. }

 

看到这里就明白了吧。他最终的输出方式都是以字符串输出,只是默认的格式是:1970-01-01T00:00:00Z ,也就是默认的 UTC 格式。我的时间转换结果成:2016-07-04T03:03:12.616Z 这里并且有时区的概念,东八区,这里输出的时间少了8 个小时。这个得注意。

总结了下。最终输出都是String 类型。感觉不友好。我本想的是,我不管存入是怎么样,我取出来得是Date 对象就可以了。



官网时间(Date)格式说明

关于时间类型说明:https:///guide/en/elasticsearch/reference/current/date.html

关于时间类型格式化:https:///guide/en/elasticsearch/reference/current/mapping-date-format.html#strict-date-time

JSON doesn’t have a date datatype, so dates in Elasticsearch can either be:
• strings containing formatted dates, e.g. "2015-01-01" or "2015/01/01 12:10:30".
• a long number representing milliseconds-since-the-epoch.
• an integer representing seconds-since-the-epoch.
Internally, dates are converted to UTC (if the time-zone is specified) and stored as a long number representing milliseconds-since-the-epoch.
Date formats can be customised, but if no format is specified then it uses the default:


 


1. "strict_date_optional_time||epoch_millis"
This means that it will accept dates with optional timestamps, which conform to the formats supported by strict_date_optional_time or milliseconds-since-the-epoch.


解决方法及问题:



1.时间输出格式,如果是默认UTC格式,时间不是我们常用的格式,而且时区问题,少了8个小时。

    解决方案:

  • 直接用毫秒值,缺点为不直观。
  • 直接设置format为你想要的格式,比如“yyyy-MM-dd HH:mm:ss” 然后存储的时候,指定格式,并且 Mapping  也是指定相同的format


2.存储Date,和取出来也是Dete?

    解决方案:

好了上面观点纯属个人观点。可能存在错误和参杂个人色彩。请勿作为直接参考。错误的地方,请在下面留言。