ElasticSearch里面关于日期的存储方式,解决差8个小时

本文讲解ElasticSearch中时间字段的处理方式,包括时区差异、时间格式化、日期聚合查询等关键操作,并提供Java代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在ElasticSearch里面最常用的就是时间字段了,经常会在群里看到一些小伙伴提出有关时间的问题,为什么es查询的时间跟我实际看到的时间差8个小时呢。如果我们了解了ElasticSearch底层的时间存储方式就会比较容易的理解这个问题。 

下面散仙先普及下时区的知识,想必大家也不陌生学过地理的同学都知道全球有24个时区每个时区的跨度是经度15度, 


相较于两地时间表,可以显示世界各时区时间和地名的世界时区表(World Time),就显得精密与复杂多了,通常世界时区表的表盘上会标示着全球24个时区的城市名称,但究竟这24个时区是如何产生的?过去世界各地原本各自订定当地时间,但随着交通和电讯的发达,各地交流日益频繁,不同的地方时间,造成许多困扰,于是在西元1884年的国际会议上制定了全球性的标准时,明定以英国伦敦格林威治这个地方为零度经线的起点(亦称为本初子午线),并以地球由西向东每24小时自转一周360°,订定每隔经度15°,时差1小时。而每15°的经线则称为该时区的中央经线,将全球划分为24个时区,其中包含23个整时区及180°经线左右两侧的2个半时区 
就全球的时间来看,东经的时间比西经要早,也就是如果格林威治时间是中午12时,则中央经线15°E的时区为下午1时,中央经线30°E时区的时间为下午2时;反之,中央经线15°W的时区时间为上午11时,中央经线30°W时区的时间为上午10时。以台湾为例,台湾位于东经121°,换算后与格林威治就有8小时的时差。如果两人同时从格林威治的0°各往东、西方前进,当他们在经线180°时,就会相差24小时,所以经线180°被定为国际换日线,由西向东通过此线时日期要减去一日,反之,若由东向西则要增加一日。 


几个时间名词: 
  1. GMT:格林威治标准时间 
  2. UTC:世界协调时间 
  3. DST:夏日节约时间 
  4. CST:中国标准时间 
其中GMT时间可以近似认为和UTC时间是相等的,但从精度上来说UTC时间更精确。其误差值必须保持在0.9秒以内 

CST= GMT + 8 =UTC + 8 

从上面可以看出来中国的时间是等于UTC时间+8小时,es默认存储时间的格式是UTC时间,如果我们查询es然后获取时间日期默认的数据,会发现跟当前的时间差8个小时,这其实是正常的,因为es默认存储是用的UTC时间,所以我们需要做的就是读取long型时间戳,然后重新格式化成下面的时间戳,即可获得正确的时间 :

yyyy-MM-dd HH:mm:ss 
像差8个时区的事情,最容易见到的就是,我们使用logstash收集的日志,发送到es里面,然后通过head查询就能发现不一致,但是如果我们用kibana查询,就不会发现时区问题,为什么? 因为kibana已经处理时区问题了,所以在kibana的页面显示的时间是正确的。 

此外在使用Java Client聚合查询日期的时候,需要注意时区问题,因为默认的es是按照UTC标准时区算的,所以不设置的聚合统计结果是不正确的。 

在es的DateHistogramBuilder里面有几个比较重要的参数:

field:指定按那个字段聚合  
interval:聚合的时间单位(年,季度,月,周,天,小时,分钟,秒)  
format:日期格式  
time_zone:时区指定  
offset:时间偏移量  

注意,默认不设置时区参数,es是安装UTC的时间进行查询的,所以分组的结果可能与预期不一样,所以我们要指定时区为Asia/Shanghai代表北京的时区,这样才能获取正确的聚合结果 

curl方式如下: 

GET my_index/_search?size=0  
{  
  "aggs": {  
    "by_day": {  
      "date_histogram": {  
        "field":     "ctime",  
        "interval":  "day",  
        "time_zone": "Asia/Shanghai"  
      }  
    }  
  }  
}  

Java代码如下: 

SearchRequestBuilder search = client.prepareSearch("search2017-02*").setTypes("log");  
DateHistogramBuilder dateagg = AggregationBuilders.dateHistogram("dateagg");  
dateagg.field("ctime");//聚合时间字段  
dateagg.interval(DateHistogramInterval.DAY);//按天聚合第一天的0点到第二天的0点  
dateagg.timeZone("Asia/Shanghai");//指定时区  
dateagg.offset("+8h");//默认都是从0点开始计算一天的,通过这个offset,我们可以把第一天的6点到第二天的6点当做一天来聚合  
search.addAggregation(dateagg);  

Histogram hs= search.get().getAggregations().get("dateagg");  
List<Histogram.Bucket> buckets =   (List<Histogram.Bucket>) hs.getBuckets();//获取结果  
for(Histogram.Bucket bk:buckets){  
//下面的转化,也是因为默认是UTC的时间,所以我们要获取时间戳,自己转化  
    System.out.println(new DateTime(Long.parseLong(bk.getKeyAsString()+"")).toString("yyyy-MM-dd HH:mm:ss") +"  "+bk.getDocCount());  
}  
client.close();  
上面的这个例子,基本涵盖了日期聚合核心功能,其中时区和偏移量时两个非常有用的而且需要特别注意的参数,不设置时区直接统计结果肯定是不准确的,offset偏移量这个参数,在某些时刻也是有用的,它可以自己定义一天的开始,比如设置从第一天的3点到第二天的3点为一天,默认都是从0点开始0点结束算做一天的,最后一点需要注意的是在输出打印时间的时候也要考虑转化因为默认也是UTC的时间,所以我们直接取出时间戳,自己格式化时间即可。 

官网文档: 

https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-datehistogram-aggregation.html  
#include <reg52.h> #include “EEPROM.h” #define Year 0 #define Month 2 #define Day 4 #define Hour 6 #define Min 8 #define Sec 10 void Store_time(unsigned char RecvBuff); void Init_Interrupt(void); void Init_Serial(void); void Init_Clock(void); void Update_Time(void); void Send_time(void); void Set_time(void); static unsigned char Settime_Buffer[12][10]; static unsigned char RecvBuff[5]={0,0,0,0,0}; static unsigned int count = 0; static unsigned int RecvCount = 0; static unsigned int Flag = 0; static unsigned int Index_Row=0; sbit key0=P3^0; sbit key1=P3^1; sbit key2=P3^2; typedef struct { unsigned char Cur_Time[12][10]; } Time; static Time Time_buffer = {“2025”,“-”,“07”,“-”,“02”," “,“13”,”:“,“12”,”:",“25”,“Wed”}; Time *Ptr_time = &Time_buffer; void main() { Init_Interrupt(); Init_Clock(); Init_Serial(); Read_FixTime(0x2000); while(1) { } } void Init_Interrupt(void) { EA = 1; ES = 1; } void Init_Clock(void) { TMOD = 0X21; ET1 = 1; TR1 = 1; TH1 = 0xf3; TL1 = TH1; ET0 = 1; TR0 =1; TH0 = 0x3c; TL0 = 0xb0; } void Init_Serial(void) { SCON = 0x50; PCON = 0x80; } void Timer0_ISR() interrupt 1 { TH0 = 0x3c; TL0 = 0xb0; count++; if((count >= 20) && (!Flag)) { Update_Time(); Send_time(); Save_FixedTime(Ptr_time->Cur_Time); count = 0; } } void Set_timeISR() interrupt 4 { char c; int RecvCount = 0; if(RI == 1) { RI = 0; c = SBUF; if(c == ‘') { RecvBuff[RecvCount] = '’; } else if( c != ‘*’) { RecvBuff[RecvCount++] = c; RecvCount = 0; Flag=1; } } } void Update_Time(void) { Ptr_time->Cur_Time[Sec][1] = Ptr_time->Cur_Time[Sec][1]+1; if( Ptr_time->Cur_Time[Sec][1] >= ‘9’) { Ptr_time->Cur_Time[Sec][1] = ‘0’; Ptr_time->Cur_Time[Sec][0] = Ptr_time->Cur_Time[Sec][0] + 1; if(Ptr_time->Cur_Time[Sec][0]>=‘6’) { Ptr_time->Cur_Time[Sec][0] = ‘0’; Ptr_time->Cur_Time[Sec][1] = ‘0’; Ptr_time->Cur_Time[Min][1] = Ptr_time->Cur_Time[Min][1]+1; if( Ptr_time->Cur_Time[Min][1] >= ‘9’) { Ptr_time->Cur_Time[Min][1] = ‘0’; Ptr_time->Cur_Time[Min][0] = Ptr_time->Cur_Time[Min][0]+1; if( Ptr_time->Cur_Time[Min][0] >= ‘6’) { Ptr_time->Cur_Time[Min][0] = ‘0’; Ptr_time->Cur_Time[Hour][1] = Ptr_time->Cur_Time[6][1]+1; if(Ptr_time->Cur_Time[Hour][1] >= ‘2’) { if(Ptr_time->Cur_Time[Hour][1] >= ‘4’) { Ptr_time->Cur_Time[Hour][1] = ‘0’; Ptr_time->Cur_Time[Hour][0] = ‘0’; Ptr_time->Cur_Time[Day][1] = Ptr_time->Cur_Time[Day][1]+1; if(Ptr_time->Cur_Time[Day][1] >= ‘9’) { Ptr_time->Cur_Time[Day][1] = ‘1’; Ptr_time->Cur_Time[Day][0] = Ptr_time->Cur_Time[Day][0] +1; if(Ptr_time->Cur_Time[Day][0] >= ‘3’) { if(Ptr_time->Cur_Time[Day][1] > ‘1’) { Ptr_time->Cur_Time[Day][0] = ‘1’; Ptr_time->Cur_Time[Month][1] = Ptr_time->Cur_Time[Month][1]+1; } } } } } else if(Ptr_time->Cur_Time[Hour][1] >= ‘9’) { Ptr_time->Cur_Time[Hour][1] = ‘0’; Ptr_time->Cur_Time[Hour][0] = Ptr_time->Cur_Time[Hour][0]+1; } } } } } } void Send_time(void) { unsigned int i,j; if(count >= 20) { for (i = 0;i < 12 ;i++) { for(j=0;j < 10;j++) { SBUF = Ptr_time->Cur_Time[i][j] ; while(!TI) { } TI = 0; } } } } void Store_time(unsigned char RecvBuff) { unsigned int i; Flag = 0; for(i = 0;(i < 4) && (RecvBuff[i] != ‘*’ );i++) { Ptr_time->Cur_Time[Index_Row][5] = RecvBuff[i]; } Index_Row++; if(Index_Row>11) { Index_Row = 0; } } #include<reg52.h> #include<EEPROM.h> void IAP_Dsiable(void) { ISP_CMD = 0x00; ISP_TRIG = 0x00; ISP_CONTR = 0x00; } static void IAP_Trigger(void) { ISP_TRIG = 0x46; ISP_TRIG = 0xB9; } unsigned char IAP_data_R(unsigned int addr) { unsigned char dat; ISP_CONTR = 0x81; ISP_CMD = 0X01; ISP_ADDRL = addr; ISP_ADDRH = addr>>8; IAP_Trigger(); dat = ISP_DATA; IAP_Dsiable(); return dat; } void IAP_Erase(unsigned int addr) { ISP_CONTR = 0x81; ISP_CMD = 0X03; ISP_ADDRL = addr; ISP_ADDRH = addr>>8; IAP_Trigger(); IAP_Dsiable(); } void IAP_data_w(unsigned char dat,unsigned int addr) { ISP_CONTR = 0x81; ISP_CMD = 0X02; ISP_ADDRL = addr; ISP_ADDRH = addr>>8; IAP_Trigger(); ISP_DATA = dat; IAP_Dsiable(); } void Save_FixedTime(unsigned char strings[12][10]) { unsigned int i,j,addr=0x2000; IAP_Erase(0x2000); for(i=0;i < 12 ;i++) { for(j=0;j < 5 && strings[i][j]!=0;j++) { IAP_data_w(strings[i][j],0x2000); } } } void Read_FixTime(unsigned char string[][10]) { unsigned int i,j,addr=0x2000; unsigned char dat0; for(i=0;i < 12;i++) { for(j=0;(j <10 )&& (string[i][j] != ‘*’);j++) { dat0 = IAP_data_R(addr); string[i][j] = dat0; addr++; if(dat0 == 0) break; } if(j==10) string[i][9] = 0; } } #ifndef EEPROM_H #define EEPROM_H sfr ISP_DATA=0xe2; sfr ISP_ADDRH=0xe3; sfr ISP_ADDRL=0xe4; sfr ISP_CMD=0xe5; sfr ISP_TRIG=0xe6; sfr ISP_CONTR=0xe7; void IAP_data_w(unsigned char dat,unsigned int addr); void IAP_Erase(unsigned int addr); void Save_FixedTime(unsigned char strings[12][10]); void Read_FixTime(unsigned char string); #endif 分析上述代码问题以及解决方法
最新发布
07-05
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值