Java将字符串转换为日期,即使提供了浏览器时区,也会减少一天

Java将字符串转换为日期,即使提供了浏览器时区,也会减少一天,java,timezone,date-parsing,date,Java,Timezone,Date Parsing,Date,我正在将Angular应用程序中的日期作为字符串发送到服务器,并将其转换为javadate对象以存储在数据库中 还从UI发送timeZoneOffset,以在转换时使用客户端的时区。(在谷歌搜索之后,我发现这种方法可以根据用户的位置获得正确的结果) 编写了以下代码以进行转换: public static void main(String args[]) throws ParseException { String inputDate = "04/05/2018"; // This dat

我正在将Angular应用程序中的日期作为字符串发送到服务器,并将其转换为java
date
对象以存储在数据库中

还从UI发送
timeZoneOffset
,以在转换时使用客户端的时区。(在谷歌搜索之后,我发现这种方法可以根据用户的位置获得正确的结果)

编写了以下代码以进行转换:

public static void main(String args[]) throws ParseException {
    String inputDate = "04/05/2018"; // This date coming from UI
    int timeZoneOffset = -330; // This offset coming from UI 
                               // (new Date().getTimeZoneOffset())
    getDate(inputDate, timeZoneOffset);
}

public static Date getDate(String inputDate, int timeZoneOffset)
        throws ParseException {
    SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy");
    ZoneOffset zoneOffset = ZoneOffset.ofTotalSeconds(-timeZoneOffset * 60);
    System.out.println("Default time zone: " + TimeZone.getDefault().getID());
    TimeZone timeZone = TimeZone.getTimeZone(zoneOffset);
    System.out.println("Time zone from offset: " + timeZone.getID());
    dateFormat.setTimeZone(timeZone);
    Date date = dateFormat.parse(inputDate);
    System.out.println("Converted date: " + date);
    return date;
}
预期产出

默认时区:美国/纽约
来自偏移的时区:GMT+05:30
转换日期:2018年4月5日星期四00:00:00

服务器中的实际结果

默认时区:美国/纽约
来自偏移的时区:GMT+05:30
转换日期:2018年4月4日星期三美国东部夏令时14:30:00

为什么即使我设置了用户时区,日期也会减少到一天?我是新的日期和时间相关的概念,我谷歌了几次没有找到答案,请有人在这方面提供帮助


提前感谢

它并没有减少一天,而是减少了11.5小时。这恰好是GMT+05:30和“美国/纽约”之间的时差,即GMT-04:30或GMT-05:30(取决于一年中的时间)

GMT+05:30在印度的某个地方,我想,因为那是唯一一个使用30分钟而不是一个小时的偏移量的地方。当印度是4月5日时,纽约仍然是4月4日


问题可能是你没有从客户那里得到时间,所以它将假定为午夜。如果您正在进行时区转换,最好包括实际时间。

它不是减少一天,而是减少11.5小时。这恰好是GMT+05:30和“美国/纽约”之间的时差,即GMT-04:30或GMT-05:30(取决于一年中的时间)

GMT+05:30在印度的某个地方,我想,因为那是唯一一个使用30分钟而不是一个小时的偏移量的地方。当印度是4月5日时,纽约仍然是4月4日

问题可能是你没有从客户那里得到时间,所以它将假定为午夜。如果要进行时区转换,最好包含实际时间。

正确

tl;博士 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 您正在使用糟糕的旧日期时间类,这些类被证明是设计糟糕、令人困惑和麻烦的。它们现在被java.time类取代

完全避免遗留日期时间类 您将现代类(
ZoneOffset
)与麻烦的遗留类(
TimeZone
)混合在一起不要将现代类与传统类混用。忘记所有旧类,包括
Date
Calendar
SimpleDateFormat
。time类被设计为完全取代遗留类

使用
ZoneId
(和
ZoneOffset
)代替
TimeZone

LocalDate
将输入字符串解析为
LocalDate
。该类表示一个仅限日期的值,不包含一天中的时间和时区

String input = "04/05/2018" ;
DateTimeFormatter f = DateTimeFormatter.ofPattern( "MM/dd/uuuu" ) ;
LocalDate ld = LocalDate.parse( input , f ) ;
偏移量与时区 int timeZoneOffset=-330

时间不是时区。偏移量只是从UTC偏移的小时数、分钟数和秒数。您选择的变量名表明在这一点上可能存在混淆

ZoneOffset offset = ZoneOffset.of( -3 , 30 ) ;  
A是特定地区人民使用的过去、现在和未来抵消变化的历史。因此,时区总是比偏移更可取

大陆/地区
的格式指定,例如,或
太平洋/奥克兰
。切勿使用3-4个字母的缩写,如
EST
IST
,因为它们不是真正的时区,也不是标准化的,甚至不是唯一的(!)

一天中的第一刻 你的目标似乎是在那个区域约会的第一刻。让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晚三个半小时。该偏移量目前仅在美国/圣约翰地区使用,并且仅在一年中的部分时间使用。因此,如果对一年中错误部分的日期应用-03:30的偏移量,则结果将无效,但未被检测到

使用偏移(不推荐) 但是您的示例缺少时区,所以让我们使用UTC的偏移量,而不是时区

使用
int
整数表示与UTC的偏移量是一种糟糕的类型选择。首先,它是模棱两可的。那次
-330
可能被解释为是在
-03:30
比预定时间晚三个半小时的情况下进行的笨拙尝试。其次,它使解析比需要的更复杂。第三,作为分钟数,它忽略了以秒为偏移量的可能性。第四,您使用负数表示UTC之前的偏移量(显然),尽管常用用法和标准用法正好相反。最后,它忽略了为将偏移表示为文本而设置的清晰标准:
±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

offset.toString():+05:30

最后的
ZoneId z = ZoneId.of( "Asia/Kolkata" ) ;  // India time zone. Currently uses offset of +05:30 (five and a half hours ahead of UTC). 
ZonedDateTime zdt = ld.atStartOfDay( z ) ;  // Determine the first moment of the day on this date in this zone. Not always 00:00:00.
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.
ZoneOffset offset = ZoneOffset.ofTotalSeconds( ( int ) seconds );
    OffsetDateTime odt = ld.atStartOfDay( offset ).toOffsetDateTime();
Instant instant = zdt.toInstant() ;