为什么ES里的日期查询总是差8个小时

1、前言

        我们在做 Java 开发过程中其实很少关心时区的问题,因为这个一般都使用系统默认的东八区了,所以在存储时间和查询时间时感觉不到时区的存在,以及可能会发生的问题。但是有 ES(Elasticsearch)开发经验的朋友可能或多或少都遇到或者处理过 ES 中的日期类型的时区问题,当遇到此类问题时可能也会很快解决,因为网上教程很多,如何设置、如何指定日期格式,等等,但是并不见得会深度研究。

        当然,对于一个一直对时区、什么GMT+8、UTC总是一知半解的我来说,总是感觉这类问题像迷一样的存在,于是乎,在网络搜集各种相关知识,今天做一个知识总结与分享,希望能帮到与我有类似疑惑的朋友们。

2、时区、GMT、UTC、 润秒、Asia/Shanghai、UNIX时间戳

        为什么要把时区、GMT、UTC、Asia/Shanghai、UNIX 时间戳这几块内容写在同一段落里呢?其实,在写该篇文章的时候我是列了提纲的,是把这三块分开的,但是后来写的过程中感觉他们不能割裂开,他们是一个整体的知识部分,所以就把这相关的内容整在了一起。

纬线:

是指地球表面某点随地球自转所形成的轨迹。所有的纬线都相互平行,并与经线垂直,纬线指向东西方向。纬线形状为圈。纬线圈的大小不等,赤道为最大的纬线圈,从赤道向两极纬线圈逐渐缩小,到南、北两极缩小为点。


经线:

也称为“子午线”,是地球表面连接南、北两极,并且垂直于赤道的弧线。经线和纬线一样是人类为度量方便而假设出来的辅助线,定义为在地球仪表面连接南北两极并垂直于纬线的半圆。

本初子午线:

        国际上将通过英国伦敦格林尼治天文台原址的那条经线称为0°经线,也叫本初子午线。

时区:

        众所周知,地球是一个球体,并自西往东自转,每自转一周是24小时整,每个地区的人们的习惯都是以当地的午夜作为新的一天的开始(举个反例,例如,我们所有人都不习惯以当地正午以前是13号,过了正午就变成14号。都习惯一整个白天是同一个日期,这就是为什么全球不统一用一个时区来表示时间的原因。),为了克服时间上的混乱,1884年在华盛顿召开的一次国际经度会议(又称国际子午线会议)上,规定将全球划分为24个时区(东、西各12个时区)。规定英国(格林尼治天文台旧址)为中时区(零时区)、东1—12区,西1—12区。规定,格林尼治天文台旧址那条铜线为本初子午线,本初子午线以东为东时区,本初子午线以西为西时区,每个时区横跨经度15度,时间正好是1小时。最后的东、西第12区各跨经度7.5度,以东、西经180度为界。每个时区的中央经线上的时间就是这个时区内统一采用的时间,称为区时,相邻两个时区的时间相差1小时。

时区重点概念:

时区是一个东西跨15°的一个经度范围,每个时区都有一个中央子午线,区时是指该时区的中央经线(即中央子午线)的时间,0时区的中央经线就是本初子午线,0时区横跨西经7.5°+东经7.5°,0时区的时间就是以本初子午线的时间作为标准时间的。

GMT:

        是指英国格林尼治所在地的标准时间,也即世界时间,以地球自转作为参考标准的一种时间计时标准。

UTC:

        即世界协调时,又称世界统一时间,协调世界时是以原子时秒长为基础(以铯原子的震动周期作为参考),在时刻上尽量接近于世界时的一种时间计量系统。

UTC和GMT的关系:

参考:UTC和GMT什么关系? - 知乎

润秒:

        由于世界时间是以地球自转为参考的,现在观测到地球自转是每年都在变慢零点几秒,所以相当于我们的时间和地球自转隔几年后就不一致了,根据世界协调时间,在适当的时候将世界时间加1秒,相当于让我们的表都再等地球1秒钟,这样地球自转的时刻和我们的表都同步了

UNIX时间戳:

        是指从格林尼治时间的1970年1月1日 00:00:00开始所经过的妙数,不考虑润秒。
重点:是指从格林尼治时间的1970-01-01 00:00:00开始计数的秒数,注意,GMT 1970-01-01 00:00:00是北京时间(Asia/Shanghai)的 1970-01-01 08:00:00
举例:GMT 的 1970-01-01 00:00:00 UNIX时间戳是 0,北京时间的 1970-01-01 00:00:00 的 UNIX 时间戳是 -28800000,北京时间的 1970-01-01 08:00:00 的 UNIX 时间戳是 0

3、国际日期变更线

        全球各地都以自己所看到的太阳的位置作为确定一天的标准,把与自己所在地方相应的地球另一面的一条经线作为“日期变更线”也叫国际日界线,这样就有许多条“日期变更线”,使用起来很不方便。为了解决这个问题,应该规定一条全世界共同的、可供对照的“日期变更线”。随着标准时计时的区时系统的确立,东西十二区重叠,计时相同但日期不同,为避免混乱,公认180°经线作为日期变更线,因为是以“格林尼治时间”为标准的日期变更线。这条“日期变更线”就叫“国际日期变更线”。

        为了避免日期上的混乱,1884年国际经度会议规定了一条国际日期变更线。这条变更线位于太平洋中的180°经线上,作为地球上“今天”和“昨天”的分界线,因此称为“国际日期变更线”。这条穿过太平洋的“国际日期变更线”,为了方便地方生活,避开了一些岛屿和地区,这是为了使它们不致分成两个日期,因此,它不是一条直线而是有几个曲折的曲线。

        为避免在一个国家中同时存在着两种日期,日界线并不是一条直线,而是折线。它北起北极,通过白令海峡、太平洋,直到南极。这样,日界线就不再穿过任何国家。这条线上的子夜,即地方时间零点,为日期的分界时间。按照规定,凡越过这条变更线时,日期都要发生变化:从东向西越过这条界线时,日期要加一天,从西向东越过这条界线时,日期要减去一天。 又叫“人为日界线”。

        常识:中国在国际日期变更线的西边,而美国在国际日期变更线的东边,所以美国时间是肯定比中国时间要晚的。最先进入新的一天的国家是:新西兰(UTC+12)、斐济(UTC+12)、基里巴斯(圣诞岛:UTC+14)、汤加(UTC+13)等国,最晚一天进入新一天的国家是纽埃(UTC-11)、美属萨摩亚群岛(UTC-11)。

参考:世界时间


4、关于北京时间

        中国科学院国家授时中心(以下简称“国家授时中心”),前身是中国科学院陕西天文台,成立于1966年,是我国唯一的专门、全面从事时间频率基础研究和应用研究的科研机构,承担着我国国家标准时间(北京时间)的产生、保持和发播任务,建设和运行着的长短波授时系统是我国的第一批国家重大科技基础设施,建成了国内唯一的天地一体星地综合卫星导航授时试验平台,为我国国家时间频率体系、卫星导航系统的建设和发展做出了重要贡献。
        国家授时中心总部位于陕西省西安市临潼区,在西安航天产业基地、渭南蒲城设有分部,另有授时发播台(如蒲城长、短波授时台,商丘低频时码发射台)、授时监测站、测定轨站分布在全国
来自国家授时中心官网:http://www.ntsc.cas.cn/dwgk/zxjj/

        上世纪 60 年代初,需要在内陆腹地建一个专门的授时台站,当时选派了大量人员进行选址,最后选定陕西渭南蒲城。这其中主要有两个原因:陕西位于中原,略偏西,地处我国心脏地带,发播出来的信号可以覆盖我国主要城市。陕西自然屏障保护较好,有秦岭遮挡,授时台建在这里相对安全。

        西安授时中心发布的时间并不是西安地区的时间,而是东经120°的当地时间(也即北京时间),东经120°经过的城市也不少,比较有名的是江苏常州的,在常州恐龙园有专门的一个打卡地-东经120°观光塔,就是准确的东经120°,也就是北京时间的基准线。另外还有杭州富阳区、义务、绍兴、泰兴、武进、霞浦等等很多地区。

        所以虽然授时中心在西安,但并不是发布的西安时间,而是东经120°的时间,如常州。

5、中国为什么只采用东经120°的东八区时间

        只是为了方便全国行政管理,时间上统一一致,避免麻烦。但是世界上很多其他国家并不只用一个时区去计时的,如俄罗斯横跨11个时区,不过后来将11个法定时区减少为9个法定时区。美国本土也有多个法定时区。

6、为何全球不统一时间日期,而要分时区

        人们倾向于把日与日的分界线放在一个特殊的时刻。什么样的时刻算特殊呢,比如正午。为什么正午特殊呢?因为这时候太阳高度最高,那帮天文学家觉得这个位置测起来方便。但是为什么不用正午做分界线呢?你想想呀,大白天正干着活呢,突然日期就变了,给生活添麻烦了吧。所以就选了另一个特殊的时刻,太阳在最高高度位置之前180°的地方,也就是午夜的时候,作为日与日的分界线。

参考:知乎
作者:钱曦
链接:https://www.zhihu.com/question/22698104/answer/22313034


7、灵魂拷问,为什么es中我传入日期是 gte 2021-12-20 00:00:00 能查出 2021-12-19 16:00:00 至 2021-12-20 00:00:00 间的数据

        这是因为在将日期数据存入es时都是先将其转换为JSON再存入,JSON类型的日期再存入ES时ES会将日期转换为一个UTC时区的长整型的毫秒时间戳(除非在JSON日期里指定了其他时区),这是在存入ES时做的一些转换。

        如:我们存入北京时间的 1970-01-01 08:00:00 -> 首先在没有特别指定时区时ES会认为这是GMT\UTC时间的1970-01-01 08:00:00 即存储下来的时间戳为 28800000;-> 进行根据日期的范围查询:我们要查出北京时间 1970-01-01 08:00:00 以后的数据时会用 gte 1970-01-01 08:00:00,在比较前将北京时间1970-01-01 08:00:00转化为时间戳是0,也即 gte 0,也即将该0 时间戳当做是GMT\UTC的0进行比较查询,在ES里存储的时间戳0其实是北京时间的1970-01-01 00:00:00,所以会多查出8个小时数据的问题。

Date field typeedit

JSON doesn’t have a date data type, so dates in Elasticsearch can either be:

  • strings containing formatted dates, e.g. "2015-01-01" or "2015/01/01 12:10:30".
  • a number representing milliseconds-since-the-epoch.
  • a number representing seconds-since-the-epoch (configuration).

Internally, dates are converted to UTC (if the time-zone is specified) and stored as a long number representing milliseconds-since-the-epoch.

Queries on dates are internally converted to range queries on this long representation, and the result of aggregations and stored fields is converted back to a string depending on the date format that is associated with the field.

参考:Date data type | Elasticsearch Guide [7.8] | Elastic

参考:format | Elasticsearch Guide [7.8] | Elastic

参考:Elasticsearch 滞后8个小时等时区问题,一网打尽! - 熊哥club


8、es时区问题解决

        如果要将日期类型存入es时不用默认的UTC,可以在JSON序列化时指定日期类型的时区为东八区,如下。

如果是阿里巴巴的 FastJson 可在 @JSONField 里指定带时区的格式

    @JSONField(format = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
    private Date updateTime;

如果Jackson可在 @JsonFormat 里指定带时区的格式

    @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
    @ApiModelProperty(value = "修改时间")

参考:DateTimeFormatter (Java Platform SE 8 )

9、Kibana 时区设置修改

可以在 Kibana 的 Advanced Settings 里的 Timezone for date formatting 选项里设置,默认是使用当前浏览器的时区。

10、总结

        时区问题学习总结到此为止,如果有错误或可完善的地方欢迎批评指正,我会在后续有时间了持续更新修正该片文章。如果觉得该文章帮到了您,也请不吝点赞评论,谢谢~

参考资料:

资料大部分来源百度百科、知乎、国家授时中心官网等

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
您好,如果您想要查询 Elasticsearch 中近24小时数据,可以使用 Elasticsearch 的时间范围查询,然后再通过聚合计算出24个点的数据。 以下是一个示例代码,假设我们有一个名为 `logs` 的索引,其中包含 `timestamp` 和 `value` 两个字段: ```java import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.aggregations.AggregationBuilders; import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval; import org.elasticsearch.search.aggregations.metrics.avg.Avg; import org.elasticsearch.search.sort.SortBuilders; import org.elasticsearch.search.sort.SortOrder; import java.io.IOException; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.List; public class ElasticsearchQuery { public static void main(String[] args) throws IOException { // 创建 Elasticsearch 客户端 RestHighLevelClient client = createClient(); // 查询范围为近24小时 Instant now = Instant.now(); Instant twentyFourHoursAgo = now.minus(24, ChronoUnit.HOURS); // 构建查询 SearchRequest searchRequest = new SearchRequest("logs"); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(QueryBuilders.rangeQuery("timestamp") .gte(twentyFourHoursAgo.toEpochMilli()) .lte(now.toEpochMilli())); searchSourceBuilder.sort(SortBuilders.fieldSort("timestamp").order(SortOrder.ASC)); searchRequest.source(searchSourceBuilder); // 构建聚合,将结果分成24个点,每个点的时间间隔为1小时 searchSourceBuilder.aggregation(AggregationBuilders .dateHistogram("hourly_avg") .field("timestamp") .dateHistogramInterval(DateHistogramInterval.HOUR) .minDocCount(1) .subAggregation(AggregationBuilders.avg("avg_value").field("value"))); // 执行查询 SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT); // 解析结果 List<Double> hourlyAverages = new ArrayList<>(); for (org.elasticsearch.search.aggregations.bucket.histogram.Histogram.Bucket bucket : searchResponse.getAggregations().get("hourly_avg").getBuckets()) { Avg avg = bucket.getAggregations().get("avg_value"); double value = avg.getValue(); hourlyAverages.add(value); } System.out.println(hourlyAverages); // 关闭客户端 client.close(); } private static RestHighLevelClient createClient() { // 创建 Elasticsearch 客户端 return new RestHighLevelClient( RestClient.builder( new HttpHost("localhost", 9200, "http") ) ); } } ``` 在这个示例中,我们首先计算出当前时间和24小时前的时间,然后使用范围查询来筛选出这个时间范围内的数据。然后,我们使用 Elasticsearch 的时间直方图(`date_histogram`)聚合来将结果分成24个点,每个点的时间间隔为1小时。在每个点上,我们使用平均值聚合来计算该点的平均值。最后,我们将每个点的平均值存储在一个列表中,并输出结果。 希望这可以帮助您。如果您有任何其他问题,请随时问我。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值