我将Angular应用程序中的日期作为String发送到服务器并转换为java DatePicker
对象以存储在数据库中。
同时从UI发送Date
以在转换时使用客户端的时区。 (谷歌搜索后,我发现这种方法可以根据用户位置获得正确的结果)
编写以下代码进行转换:
timeZoneOffset
预期输出:
默认时区:America / New_York
抵消时区:GMT + 05:30
转换日期:星期四4月5日00:00:00 IST 2018年
服务器中的实际结果:
默认时区:America / New_York
抵消时区:GMT + 05:30
转换日期:4月4日星期三14:30:00 EDT 2018
为什么即使设置用户时区,日期也会减少到一天?我是日期和时间相关概念的新手,我用谷歌搜索了几次没有找到答案,有人可以帮忙解决这个问题。
提前致谢
答案 0 :(得分:1)
它没有减少一天,它减少了11.5小时。这恰好是GMT + 05:30和“America / New_York”之间的时差,即GMT-04:30或GMT-05:30(取决于一年中的时间)。
我认为格林尼治标准时间+05:30在印度的某个地方,因为那是使用30分钟偏移而不是整整一小时的唯一地方。当它在4月5日在印度时,它仍然是4月4日在纽约。问题可能是你没有从客户那里得到时间,所以它将假设午夜。如果您正在进行时区转换,最好包括实际时间。
答案 1 :(得分:1)
Answer by Godfrey是正确的。
LocalDate.parse(
"04/05/2018" ,
DateTimeFormatter.ofPattern( "MM/dd/uuuu" )
)
.atStartOfDay(
ZoneId.of( "Asia/Kolkata" )
)
.toString()
2018-04-05T00:00 + 05:30 [亚/加尔各答]
要在数据库中存储,请使用UTC。
当新的一天在印度开始时,UTC的日期仍然是“昨天”,所以4月4日而不是4月5日。同一时刻,时间轴上的同一点,不同的挂钟时间。
LocalDate.parse(
"04/05/2018" ,
DateTimeFormatter.ofPattern( "MM/dd/uuuu" )
)
.atStartOfDay(
ZoneId.of( "Asia/Kolkata" )
)
.toInstant()
2018-04-04T18:30:00Z
你正在使用可怕的旧日期时间类,这些类已被证明设计糟糕,令人困惑且麻烦。它们现在被 java.time 类取代。
ZoneOffset zoneOffset = ZoneOffset.ofTotalSeconds(-timeZoneOffset * 60);
…
TimeZone timeZone = TimeZone.getTimeZone(zoneOffset);
您正在将现代类(ZoneOffset
)与麻烦的遗留类(TimeZone
)混合在一起。 不要将现代类与遗留类混合。忘记所有旧类,包括Date
,Calendar
和SimpleDateFormat
。 java.time 类旨在完全取代遗留类。
而不是TimeZone
,请使用ZoneId
(和ZoneOffset
)。
LocalDate
将输入字符串解析为LocalDate
。 LocalDate
类表示没有时间且没有时区的仅限日期的值。
String input = "04/05/2018" ;
DateTimeFormatter f = DateTimeFormatter.ofPattern( "MM/dd/uuuu" ) ;
LocalDate ld = LocalDate.parse( input , f ) ;
int timeZoneOffset = -330;
offset-from-UTC 不是时区。偏移量只是从UTC移位的小时数,分钟数和秒数。您选择的变量名称表明可能存在混淆。
ZoneOffset offset = ZoneOffset.of( -3 , 30 ) ;
time zone是特定地区人民使用的偏移的过去,现在和将来变化的历史记录。因此,时区始终优于偏移。
以continent/region
的格式指定proper time zone name,例如America/Montreal
,Africa/Casablanca
或Pacific/Auckland
。切勿使用诸如EST
或IST
之类的3-4字母缩写,因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。
ZoneId z = ZoneId.of( "Asia/Kolkata" ) ; // India time zone. Currently uses offset of +05:30 (five and a half hours ahead of UTC).
你好像是在那个区域的那个日期的第一个时刻。让java.time确定当天的第一时刻。不要以为时间是00:00:00。在某些日期的某些区域,日期可能会在另一个时间开始,例如01:00:00。
ZonedDateTime zdt = ld.atStartOfDay( z ) ; // Determine the first moment of the day on this date in this zone. Not always 00:00:00.
作为为什么你应该使用时区而不仅仅是从UTC偏移的一个例子,看看-330
的示例数据,我可能很容易误解为比UTC晚三个半小时。此偏移目前仅在区域America/St_Johns
中使用,并且仅在一年中的部分时间使用。因此,如果您将-03:30的偏移量应用于该年度错误部分的日期,则您的结果将无效但未被检测到。
但是你的例子没有时区,所以让我们选择偏离UTC而不是区域。
使用int
整数来表示与UTC的偏移量是类型的不良选择。首先,它含糊不清。可能会将-330
解释为在-03:30
偏离计划后三个半小时的笨拙尝试。其次,它使解析比需要更棘手。第三,作为几分钟,它忽略了秒数偏移的可能性。第四,尽管常见的用法和标准用法相反,你使用负数<%>提前提前的UTC(显然)。最后,它忽略了ISO 8601设置的明确标准,用于将偏移量表示为文本:±HH:MM:SS
(和变体)。顺便说一句,填充零在标准中是可选的,但我建议总是包括因为各种库和协议所期望的。
您的意图似乎是整数所需的分钟数。
long seconds =( TimeUnit.MINUTES.toSeconds( - 330 ) * -1 ); // Multiply by negative one to flip the sign to standard ISO 8601 usage, where `+` means “ahead* of UTC and `-` means *behind* UTC.
秒:19800
ZoneOffset offset = ZoneOffset.ofTotalSeconds( ( int ) seconds );
offset.toString():+ 05:30
最后一步:在此偏移量中获取当天的第一个时刻。警告:我们不确定这个偏移在这个日期是否有效,因为我们没有时区。
从返回的ZonedDateTime
转换为OffsetDateTime
。如上所述,确定第一时刻应始终使用时区来完成,从而获得ZonedDateTime
。我们违反了使用偏移量的合理做法,但使用返回的ZonedDateTime
对象会产生误导,因为我们缺少真正的时区,并且只有一个偏移量。因此OffsetDateTime
类使我们的意图清晰,我们的代码更加自我记录。
OffsetDateTime odt = ld.atStartOfDay( offset ).toOffsetDateTime();
同样,这种使用偏移的方法是而不是推荐,因为您应该从用户那里收集时区 name 作为输入而不是偏移量。
通常最好以UTC格式存储时刻。
从Instant
或OffsetDateTime
中提取ZonedDateTime
以获得与UTC相同的时刻。
Instant instant = zdt.toInstant() ;
2018-04-04T18:30:00Z
java.time框架内置于Java 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,例如java.util.Date
,Calendar
和&amp; SimpleDateFormat
现在位于Joda-Time的maintenance mode项目建议迁移到java.time类。
要了解详情,请参阅Oracle Tutorial。并搜索Stack Overflow以获取许多示例和解释。规范是JSR 310。
您可以直接与数据库交换 java.time 对象。使用符合JDBC driver或更高版本的JDBC 4.2。不需要字符串,不需要java.sql.*
类。
从哪里获取java.time类?
ThreeTen-Extra项目使用其他类扩展java.time。该项目是未来可能添加到java.time的试验场。您可以在此处找到一些有用的课程,例如Interval
,YearWeek
,YearQuarter
和more。