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
<think>好的,我现在需要分析用户提供的代码中的问题并给出解决方法。首先,我会通读整个代码,看看有没有明显的语法错误或者逻辑问题。 首先看主函数部分。主函数里调用了几个初始化函数,然后进入无限循环。看起来结构没问题,但需要注意各模块的初始化是否正确。 接下来看各个头文件和宏定义。代码开头包含的是“EEPROM.h”,但标准C头文件应该用尖括号,用户自定义的用双引号。这里可能应该是双引号,但原代码中使用了中文引号“”,这会导致编译错误,需要改为英文引号。 然后看结构体定义和指针。结构体Time的初始化部分,字符串用的是中文引号“”,这在C语言中是不允许的,应该用英文双引号""。另外,Cur_Time数组的每个元素是10个字符的数组,但初始化时有些字段如"-"可能长度不够,比如“-”后面应该有两个字符吗?比如日期中的月份是两位,所以可能没问题。不过初始化时每个字符串的格式是否正确需要检查,比如“2025”是四位,后面用空格分隔,可能没问题,但要注意是否每个元素都正确对应。 接下来是全局变量的声明,RecvBuff数组大小为5,可能在接收数据时会溢出,需要确认接收的数据长度是否合适。例如,在Set_timeISR函数中,RecvBuff[RecvCount++] = c; 当RecvCount超过4时会导致数组越界,应该限制RecvCount不超过4,或者增加数组大小。 再看定时器0的中断服务程序Timer0_ISR。其中有一个Flag判断,当Flag为0时才执行Update_Time和Send_time。但Flag是在串口中断中设置的,可能存在并发访问的问题,需要考虑原子操作或使用volatile关键字。 串口中断服务程序Set_timeISR中有一些问题。首先,函数名应该是正确的中断入口,比如在8051中,串口中断号是4,所以函数声明正确。但内部处理接收数据时,当收到的字符不是'*'时,将字符存入RecvBuff,并且重置RecvCount为0,这可能导致无法正确接收多字符的数据包。例如,当连续发送多个字符时,每次都会重置RecvCount,导致只能保存第一个字符。应该将RecvCount递增,而不是重置为0。另外,RecvCount的局部变量可能覆盖了全局变量,导致计数不正确,应该移除局部变量声明。 Update_Time函数中处理时间递增的逻辑可能存在错误。例如,Sec的个位加1,如果超过'9'则进位到十位,但Sec的十位是否应该到6?比如秒的范围是0-59,所以十位最多到5。但代码中判断是否大于等于'9',这会导致当Sec为59时,加1后变成60,此时十位变成6,个位0,这可能不正确。应该在Sec十位大于等于6时重置。类似的问题可能存在于分钟、小时、天的处理中。 另外,在小时处理部分,代码中有判断if(Ptr_time->Cur_Time[Hour][1] >= '2'),接着里面又判断是否大于等于'4',这可能有问题。比如,当Hour的十位为2,个位为3(即23点),加1后变成24,此时应重置为00点。所以需要更严格的逻辑来处理小时进位。 Send_time函数中的发送逻辑,外层循环条件是count >=20,但count是在定时器中断中被重置的,可能每次定时器触发都会执行一次,但count变量是否在其他地方被正确维护?需要确认。 在EEPROM相关的函数中,Save_FixedTime函数在写入数据时,地址addr固定为0x2000,而不是递增的地址,这会导致每次写入都覆盖同一个地址。应该每次写入后addr递增。 Read_FixTime函数中参数应该是unsigned char string[][10],但原代码中声明为unsigned char string,这会导致类型不匹配,应该修改为正确的二维数组指针。 另外,代码中存在很多中文标点符号,如引号、逗号、分号等,这会导致编译错误,必须全部改为英文符号。 还有,结构体初始化时的字符串用的是中文引号,应该改为英文双引号。例如,"2025"应该用英文引号,而不是“2025”。 此外,在Set_timeISR函数中,当收到空格字符时,代码处理为RecvBuff[RecvCount] = '',但这里的单引号可能应该是双引号,或者具体字符处理是否正确? 还有,RecvBuff在Store_time函数中被使用,但RecvBuff的大小是5,而Store_time函数中有一个for循环,可能试图处理更多字符,导致溢出。 总结一下,主要问题包括: 1. 中文符号错误,如引号、分号、逗号等。 2. 结构体初始化的字符串使用中文引号。 3. 接收缓冲区RecvBuff的大小不足,可能导致溢出。 4. 串口中断处理中的RecvCount处理逻辑错误,导致无法正确接收连续数据。 5. 时间更新逻辑中的进位判断错误,如秒、分钟、小时、天的进位条件不正确。 6. EEPROM写入函数中的地址未递增,导致数据覆盖。 7. Read_FixTime函数的参数类型不匹配。 8. Flag变量的并发访问问题,可能需要volatile关键字。 9. 全局变量RecvCount在中断中被局部变量覆盖。 10. 结构体Cur_Time的存储方式是否正确,每个元素是字符串还是单独的字符? 解决方法: 1. 替换所有中文符号为英文符号。 2. 修正结构体初始化的字符串引号。 3. 扩大RecvBuff数组大小,或在接收时进行长度检查。 4. 修改串口中断中的RecvCount处理逻辑,正确递增而非重置。 5. 修正时间进位逻辑,确保符合实际时间规则。 6. 在EEPROM写入时递增地址。 7. 修改Read_FixTime函数的参数为正确的二维数组指针。 8. 将Flag声明为volatile。 9. 移除Set_timeISR中的局部RecvCount变量。 10. 检查Cur_Time的存储结构是否适合时间表示。</think>上述代码存在多个语法错误、逻辑错误及设计缺陷,具体分析如下: ### 主要问题分析 1. **中文符号错误** - 所有引号(`“”`)、分号(`;`)、逗号(`,`)均使用中文符号,导致编译失败。 - 示例:`#include “EEPROM.h”` 应改为 `#include "EEPROM.h"`。 2. **结构体初始化错误** - `Time_buffer` 初始化使用中文引号 `“2025”`,应改为英文双引号 `"2025"`。 - 部分时间字段格式错误,如 `" "`(空格)应为 `" "`(英文空格)。 3. **接收缓冲区溢出风险** - `RecvBuff[5]` 仅能存储5字节数据,但在 `Set_timeISR` 中未限制接收长度,可能导致越界。 - `Store_time` 函数中未验证 `RecvBuff` 数据长度。 4. **串口中断处理逻辑错误** - `Set_timeISR` 中 `RecvCount = 0;` 导致每次接收字符后重置计数器,无法接收连续数据。 - 局部变量 `int RecvCount = 0;` 覆盖全局变量,导致计数错误。 5. **时间更新逻辑错误** - 秒、分钟、小时、日的进位条件错误: - 秒的十位应 ≤ '5'(0-59),但代码允许到 '6'。 - 小时逻辑混乱,`if(Ptr_time->Cur_Time[Hour][1] >= '2')` 后的判断存在冗余和错误。 6. **EEPROM写入地址未递增** - `Save_FixedTime` 函数始终写入地址 `0x2000`,导致数据覆盖,应递增 `addr`。 7. **函数参数类型不匹配** - `Read_FixTime` 声明为 `void Read_FixTime(unsigned char string)`,但应为 `void Read_FixTime(unsigned char string[][10])`。 8. **并发访问问题** - `Flag` 变量在中断和主函数中同时访问,未使用 `volatile` 关键字,可能导致优化问题。 ### 解决方案 1. **替换所有中文符号为英文符号**: ```c #include "EEPROM.h" // 替换为英文引号 ``` 2. **修正结构体初始化**: ```c static Time Time_buffer = {"2025","-","07","-","02"," ","13",":","12",":","25","Wed"}; ``` 3. **扩大接收缓冲区并限制长度**: ```c static unsigned char RecvBuff[12]; // 扩大至12字节 ``` 在 `Set_timeISR` 中添加长度检查: ```c if(RecvCount < sizeof(RecvBuff)-1) RecvBuff[RecvCount++] = c; ``` 4. **修复串口中断逻辑**: - 移除局部变量 `int RecvCount = 0;`。 - 修正接收逻辑: ```c if(c != '*') { if(RecvCount < sizeof(RecvBuff)-1) RecvBuff[RecvCount++] = c; } ``` 5. **修正时间进位逻辑**: ```c // 秒进位示例 if(Ptr_time->Cur_Time[Sec][1] > '9') { Ptr_time->Cur_Time[Sec][1] = '0'; Ptr_time->Cur_Time[Sec][0]++; if(Ptr_time->Cur_Time[Sec][0] > '5') { // 继续进位到分钟... } } ``` 6. **EEPROM写入地址递增**: ```c for(i=0;i < 12 ;i++) { for(j=0;j < 5 && strings[i][j]!=0;j++) { IAP_data_w(strings[i][j], addr++); } } ``` 7. **修正函数参数类型**: ```c void Read_FixTime(unsigned char string[][10]); // 声明为二维数组 ``` 8. **添加 `volatile` 关键字**: ```c static volatile unsigned int Flag = 0; // 避免编译器优化 ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值