如何正确处理服务器端和客户端的时间节省

How to correctly deal with time saving on server and client side

我读了很多关于时间的书(增量时间、观察时间、偏移量、时区...) 现在我正在用 Spring 开发一个后端架构,到了必须将它们组合在一起的地步。客户端发送和接收带有时间戳和值的 JSON,我可以决定语法应该如何。我不确定星座是否按照我计划的方式正确,所以如果你在必要的地方纠正我,我会很高兴。

首先:我是如何理解基本概念的。

口头禅:始终使用 utc 时间。

格式:iso8601 YYYY-MM-DDThh:mm:ss.sTZD(例如 1997-07-16T19:20:30.45+01:00) 要么 1997-07-16T20:20:30.45Z

时区:例如"Europe/Berlin" 这是时区数据库的 ID,其中的特定规则 区域已定义(DST 偏移量 ..)。

偏移量:告诉您相对于 utc 时间的正偏移量或负偏移量。

我的计划: 在我的例子中,数据可能是在不同的时区内采样的。也很可能在采样过程中更改了时区。

所以我想在我的mongodb中存储如下数据:

 "TimeStamp": {
                            "StartTime":  "1997-07-16T20:20:30.45Z",
                            "EndTime":  "1997-07-16T20:20:30.45Z",
                            "StartTimeZone":"Europe/Berlin",
                            "EndTimeZone":"Europe/Berlin",
                            "StartTimeOffset": +7,
                            "EndTimeOffset" : +7
                        } 

客户端可以从返回数据中选择一个时间间隔。间隔也必须定义为没有偏移量的 UTC iso 格式。据我所知,对于 UTC 格式,我可以在日期上执行 $lte 和 $gte 操作以过滤时间间隔。 因此,客户端会收到一个包含多个 JSOnObject 的 JSOnArray。每个对象都有一个值和一个时间戳对象。通过使用 TimeZone,可以查看数据采样时的偏移量,因此我不必添加偏移量信息,因此可以计算用户的本地时间。但是如果偏移量发生变化怎么办。我想存储偏移量也是有意义的,这样就可以重建 "old" 时区规则。 您认为这是一个好的解决方案,还是我 forgot/misunderstood/can 可以更有效地完成一些事情

在Java中,如果你想谈论一个特定的时间点,使用Instant。如果 2 个事件发生在不同的时区,但在同一实际时刻,它们的 Instant 值将相同。

如果你想知道那个地区的各种时钟当时说的是什么,你需要知道你在哪个时区。例如:

Instant now = Instant.now();  // refers to a point in time, independent of location
LocalDateTime nowHere = now.atZone(ZoneId.systemDefault()).toLocalDate();  // refers to "what the clocks say" at your machine's current timezone
LocalDateTime nowSomewhereElse = now.atZone(ZoneId.of("Timezone string")).toLocalDate();  // same as above, but for somewhere else.

以下 API 是 Java8 的一部分,与 JPA 和其他常用 libraries/APIs 完全兼容。如果您只关心事件发生的日期,则存在等效 类。

也可以使用 instant.toEpochMilli()instant.getEpochSecond()instant.getNano().

Instant 转换为时间戳

你的问题比较混乱。

如果您有 UTC 时间,您知道 offset-from-UTC 为零,因此您不关心进一步的时区信息。

Instant instant = Instant.now() ;      // Capture the current moment as seen in UTC (an offset-from-UTC of zero hours-minutes-seconds).
String output = instant.toString() ;   // Generate text representing this moment in UTC in standard ISO 8601 format. The `Z` on end means UTC (an offset of zero), and is pronounced "Zulu". 

如果有人想通过在德国使用的 wall-clock 时间看到那一刻,他们可以应用 ZoneId 产生 ZonedDateTime object.

ZoneId zBerlin = ZoneId.of( "Europe/Berlin" ) ;
ZonedDateTime zdtBerlin = instant.atZone( zBerlin ) ;

如果有人想通过日本使用的 wall-clock 时间看到那一刻,同上。

ZoneId zTokyo = ZoneId.of( "Asia/Tokyo" ) ; 
ZonedDateTime zdtTokyo = instant.atZone( zTokyo ) ;

如果有人想通过魁北克使用的 wall-clock 时间看到那一刻,同上。

ZoneId zMontréal = ZoneId.of( "America/Montreal" ) ; 
ZonedDateTime zdtMontréal = instant.atZone( zMontréal ) ;

所有这些(instantzdtBerlinzdtTokyozdtMontréal)都代表了同一时刻,时间轴上的同一点。只有 wall-clock 时间不同。想象一下与每个地区的参与者进行的电话会议。他们都经历了同一时刻,但当他们抬头看着挂在各自墙上的时钟时,他们各自看到了不同的读数。

TimeZone: eg "Europe/Berlin" This is the id for timezone database where the specific rules for the zones are defined ( DST offset .. ).

Offset: Tells you the positive or negative offset to the utc time.

弄清楚zone和offset的含义:

  • Offset-from-UTC 只是 hours-minutes-seconds 的数字。而已。例如,-07:00.
  • 时区。时区是特定地区的人们使用的偏移量的过去、现在和未来变化的历史。例如,在一个政治家疯狂到采用 Daylight Saving Time (DST), their offset-from-UTC changes twice per year, jumping a head an hour, and then falling back an hour. DST does not bend time in an Einstein/Relativity sense 的地方,DST 只是玩 wall-clock 时间的愚蠢游戏。

data was sampled within different timezones

你不在乎。如果您在 UTC 中捕获了片刻,这就是您所需要的。

It's also porrible that a timezone is changed during the sampling process.

再一次……你不在乎。如果您在 UTC 中捕获了片刻,这就是您所需要的。

So I want to store the data as follows in my mongodb:

 "TimeStamp": {
    "StartTime":  "1997-07-16T20:20:30.45Z",
    "EndTime":  "1997-07-16T20:20:30.45Z",
    "StartTimeZone":"Europe/Berlin",
    "EndTimeZone":"Europe/Berlin",
    "StartTimeOffset": +7,
    "EndTimeOffset" : +7
} 

不,太多了。最后四行是多余的,可能会造成混淆。您只需要:

 "TimeStamp": {
    "StartTime":  "1997-07-16T20:20:30.45Z",
    "EndTime":  "1997-07-16T20:20:30.45Z"
} 

顺便说一下,术语 timestamp 通常表示特定时刻,而不是时间跨度。我建议一个更好的名字,例如 timespan.

 "TimeSpan": {
    "StartTime":  "1997-07-16T20:20:30.45Z",
    "EndTime":  "1997-07-16T20:20:30.45Z"
} 

顺便说一句,在您的示例中,开始时间等于结束时间。可能是你发错了。

With using the TimeZone one can have a look how the offset was at the time when the data was sampled so I don't have to add offset information

您不需要额外的偏移量信息。前两行末尾的 Z 告诉您需要知道的一切。 Z,发音为“Zulu”,表示 UTC,偏移量为零。

String input = "1997-07-16T20:20:30.45Z" ;    // The `Z` = UTC, an offset of zero.
Instant instant = Instant.parse( input ) ;
ZoneId z = ZoneId.of( "Pacific/Auckland" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;     // Same moment, different wall-clock time.

so one can calculate the local time of the user

显然,这里的“用户”一词用词不当。你指的是收集数据的人,而不是接收数据的人。所以您想记住收集数据的时区。

因此捆绑收集数据样本或仪器读数的时区名称。

 "TimeSpan": {
    "StartTime":  "1997-07-16T20:20:30.45Z",
    "EndTime":  "1997-07-16T20:20:30.45Z"
    "SampleTimeZone": "Europe/Berlin"
} 

当您想本地化某个时刻时,您只需要一个时刻 (Instant) 和一个时区 (ZoneId)。从那里你可以 生成 使用该区域的 wall-clock 时间的字符串。

String startInput = …  // Pull "StartTime" element from JSON.
String zoneName = …  // Pull "SampleTimeZone" from JSON.
Instant instant = Instant.parse( startInput ) ;  // "1997-07-16T20:20:30.45Z"
ZoneName zone = ZoneId.of( zoneName ) ;  //  "Europe/Berlin" 
ZonedDateTime zdt = instant.atZone( zone ) ;
String output = zdt.toString() ;    // Or use a `DateTimeFormatter`. 

Do you think this is a good solution

没有。您对 UTC、时区和 offset-from-UTC 不知何故无关感到困惑。但它们都是相连的。 UTC 是唯一真实时间。有些地方使用偏移量来调整其所在地区的 wall-clock 时间。时区是该地区这些变化的历史记录。