从不同时区的日期开始计算时间



我有一个MySQL数据库,它存储一个日期时间值,比如说2020-10-11 12:00:00。(yyyy-mm-dd hh:mm:ss格式)

此日期的类型(在mysql中)为DATETIME

当我在我的控制器中检索这个数据时,它具有java7类型";日期";。但我怀疑,由于我所在的地区,它增加了一个时区CEST。在这里,我已经发现令人困惑的是,当显示这个不应该附加时区的日期时,它实际上有。。。并且调试器说它是";2020-10-11 12:00:00 CEST";。

我的问题是日期没有用CEST时区存储。例如,它是与美国/纽约的一个一起存放的。编辑:我这行的意思是,日期是用纽约的时区从纽约存储的。所以,那里确实是凌晨12:00:00,但在马德里是下午18:00:00。我需要18:00:00的时间。

所以在纽约,当时有人做了一个插页。这意味着欧洲的时代不同了。我需要计算一下哪个时间在欧洲,而在美国是上午12点。但当我检索到它时,我的计算机一直将该日期设置为CEST,所以我所有的解析尝试都失败了。。。这是我的想法:

Date testingDate // This date is initialized fetching the "2020-10-11 12:00:00" from mySql
Calendar calendar = new GregorianCalendar()
calendar.setTime(testingDate)
calendar.setTimeZone(TimeZone.getTimeZone("America/New_York")
SimpleDateFormat localDateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
TimeZone localTimeZone = TimeZone.getTimeZone("Europe/Madrid")
localDateFormatter.setTimeZone(localTimeZone)
String localStringDate = localDateFormatter.format(calendar.getTime())
Date newDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(localStringDate)

我的想法是:我创建一个全新的日历,把我在美国的时间放在上面,我还说嘿,这个日历应该有美国时区。因此,当我使用来自欧洲的格式化程序获取时间时,它应该添加相应的小时数。这在我的脑海中很有意义,但它在代码D中不起作用:我真的不想自己计算时间差并添加或减去小时数,因为在我看来,这看起来非常硬编码。

有人能给我一些关于我解释错了什么的想法吗?或者我应该如何更好地解决这个问题?

重要提示:我使用的是java7和grails2.3.6。

我的问题是日期没有与CEST时区一起存储。例如,它是与美国/纽约的一个一起存放的。

根据我对MySQL的了解,这是不可能的。

Calendar calendar = new GregorianCalendar()

不,不要。API日历是一场灾难。使用java.time,这是java中唯一一个真正工作并且没有完全损坏/设计非常糟糕的API。如果做不到(java7非常过时且不安全,您必须升级!),还有jsr310的后台端口。添加依赖项并使用它。

让我先解释一下如何理解时间,否则,这个问题的任何答案都无法正确理解:

时间到了

有三个完全不同的概念,它们通常都被简化为"时间"的意思,但你不应该简化它们-这三个不同的想法并没有真正的关联,如果你把它们混淆了,问题总是会出现。你不能在这三个概念之间转换,除非你故意这么做!

  • "solarflares时间":它们将时间时刻描述为一个普遍的全球概念,即某事已经发生或将要发生"该太阳耀斑是在X〃处观察到的;是一个"日光浴"时间。最好的存储方式是millis自epoch以来
  • "预约时间":这些描述了一个特定的时刻,它过去或将在某个本地化的地方,但以一种全球可理解的方式表述"我们下周二5点有一个zoom会议;就是其中之一。它实际上并不是恒定的,因为地区可以决定采用新的时区,或者例如为了夏令时而移动"切换日期"。例如,如果你在"2021年11月5日17:00"与牙医预约,并且你想知道距离预约开始还有多少小时,那么这个值不应该因为你飞往另一个时区并从那里看到这个数字而改变。但是,如果您在中进行预约的国家/地区决定取消夏令时,则应更改。这就是这个和"solarflares"的区别。这一点仍然可能因政治决定而改变
  • "唤醒报警时间":这些描述了一个更多变的概念:在某种程度上,人类指的是时间,而不是指任何特定的瞬间,甚至是试图指的;我喜欢8点起床;,因此,如果你跨越时区旅行,那么下一次闹钟响起之前的时间是不断变化的

现在,回答您的问题:

我有一个MySQL数据库,它存储了一个日期时间值,比如说2020-10-11 12:00:00。(yyyy-mm-dd hh:mm:ss格式)

没那么快。该列的确切类型是什么?你的CREATE TABLE声明中有什么?这里要弄清楚的关键是磁盘上实际存储的是什么?是日光浴、约会还是叫醒?有DATEDATETIMETIMESTAMP,多年来,mysql已经显著改变了这些东西的存储方式。

我相信,假设你使用的是现代的存储方式(因此,新的mysql,没有明确模仿旧行为的设置),例如DATETIME在引擎盖下存储符号、年、天、小时、分钟和秒,这意味着它是唤醒警报风格:其中没有时区信息,因此,实际时刻根本没有设置,取决于谁在询问。

与TIMEZONE相比,TIMEZONE存储为UTC历元秒,因此它是solarflares时间,并且根本不包括任何时区。你必须分开存放。据我所知,这3种时间表示(约会时间)中最有用的是,而不是mysql中的东西。这很烦人;mysql往往是这样,所以也许是理所当然的。

在java中,所有3个概念都存在:

  • 太阳辐射时间为java.time.Instantjava.util.Datejava.sql.TimestampSystem.currentTimeMillis()也是太阳辐射时间。"日期"是solarflares时间戳是疯狂的,但API被替换是有原因的。

  • 预约时间为java.time.ZonedDateTime

  • 唤醒报警时间为CCD_ 13。

当我在控制器中检索此数据时,它具有java7类型";日期";。

右。所以,索拉费时间。

关键是:

如果存储在MySQL中的时间类型与java端的时间类型不匹配,就会发生痛苦

这听起来确实像是磁盘上有唤醒警报时间,它最终在java端作为solarflares时间。这意味着某人涉及时区转换。可能发生在mysql内部,也可能发生在mysql和jdbc驱动程序之间的飞行中(mysql将其转换为"在线"),或者jdbc驱动将其与java.sql.Timestamp.匹配

最好的解决方案是根本不转换,唯一真正的方法是更改mysql表定义以匹配java,因此,使CREATE TABLE (foo TIMESTAMP),因为TIMESTAMP也是solarflares时间,或者,在JDBC级别使用,而不是:

someResultSet.getTimestamp(col);

因为它返回solarflares时间,但是:

someResultSet.getObject(col, LocalDateTime.class);

问题是:您的JDBC驱动程序可能不支持此功能。如果没有,那么它就是一个糟糕的JDBC驱动程序,但这种情况有时会发生。

这仍然是最好的方案——方案A。所以,除非别无选择,否则不要选择糟糕的方案B。

计划B:

承认转换发生了,这是非常令人讨厌和容易出错的。因此,请确保您仔细而明确地管理它:确保设置了正确的SET调用,以便mysql对我们所在时区的感知相匹配。如果您确实需要预约时间,可以考虑将时区存储为表中的一列。等等。

多亏了@rzwitserloot,我找到了解决方案。

首先,我将从数据库中获取数据。我将通过将驱动程序/mysql转换为LocalDateTime来消除它添加的任何时区。然后,我将使用在数据库中存储数据时使用的时区创建一个新的ZonedDateTime。

一旦我有了ZonedDateTime,就可以使用我当前的时区进行转换了。我将获得一个具有适当时间的新ZonedDateTime对象。

然后我再加几行,把它转换回我的主";日期";类别:

我已经按照建议使用了ThreeTen后台端口。

Date dateMySQL //Initialized with the date from mysql
Calendar calendar = new GregorianCalendar()
calendar.setTime(dateMySQL)
org.threeten.bp.LocalDateTime localDateTime = org.threeten.bp.LocalDateTime.of(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH)+1,
calendar.get(Calendar.DAY_OF_MONTH), calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE),
calendar.get(Calendar.SECOND))
String timezone //Initialized with the timezone from mysql (Ex: "America/New_York") 
ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, ZoneId.of(timezone))
ZonedDateTime utcDate = zonedDateTime.withZoneSameInstant(ZoneId.of("Europe/Madrid"))
calendar.setTimeInMillis(utcDate.toInstant().toEpochMilli())
Date desiredDate = calendar.time

dateMySQL:";2020-10-11 10:00:00"//CEST由于我的司机

时区:";America/New_York";

设计日期:";2020-10-11 19:00:00"//CEST耶!

最新更新