将java.sql.Timestamp转换为另一个时区的java.sql.Timestamp

3

我需要对java.sql.Timestamp进行操作。

函数输入为:格式化后的java.sql.Timestamp日期时间

可能的日期格式有:MM/dd/yyyy hh:mm:ss aa,MM/dd/yyyy hh:mm:ss,MM/dd/yyyy hh:mm aa,MM/dd/yyyy HH:mm,MM/dd/yy hh:mm aa,MM/dd/yy HH:mm,MM/dd/yyyy和其他一些

所需输出:

在另一个时区中的java.sql.Timestamp与输入相同格式化的日期时间

基本上,我需要更改java.sql.Timestamp的时区

我看到其他帖子中提到使用JODA,但由于某些限制,我无法使用它。

我尝试了以下方法: - 将java.sql.Timestamp转换为java.date.Calendar, - 然后更改时区, - 然后将其转换为日期 - 格式化日期以获得相同格式的日期时间

请参见以下代码:

Timestamp ts = "2012-06-20 18:22:42.0";  // I get this type of value from another function
Calendar cal = Calendar.getInstance();
cal.setTime(ts);
cal.add(Calendar.HOUR, -8);
String string = cal.getTime().toString();     // return value is in " DAY MMM dd hh:mm:ss PDT yyyy " format i.e. Wed Jun 20 10:22:42 PDT 2012
SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy hh:mm:ss");  // This could be any format required
Date date;
try {
   date = formatter.parse(string);             // I am getting exception here on parsing 
} catch (ParseException e1) {
   e1.printStackTrace();
}

有人能告诉我这里出了什么问题吗,或者有没有其他方法可以操作java.sql.Timestamp的时区?

谢谢。


我遇到了java.text.ParseException异常:无法解析的日期:"Wed Jun 20 10:22:42 PDT 2012"。 - sankethm7
2
所以,你看到了,格式完全不对,是吧? - Tiago
1
一个 java.sql.Timestamp 没有时区信息,因此无法更改。 - Mark Rotteveel
5个回答

5

你误解并滥用了这些类。

TimestampDate没有时区,只有UTC

在java.sql.Timestamp上操作时区

java.sql.Timestamp始终是UTC中的某一刻。没有其他时区参与,只有UTC。同样适用于java.util.Date——始终在UTC中,没有其他时区参与。

因此,如上所引用的问题是没有意义的。

TimestampDate没有“格式”

< p > TimestampDate 都没有“格式”。它们使用自己内部定义的方式来跟踪日期时间。它们不是字符串,因此没有格式。您可以生成一个字符串以表示它们的值,但这样的字符串与生成对象是不同和独立的。

java.time

你正在使用过时的日期时间类,这些类已经在多年前被 java.time 类所取代。 TimestampDate 都被 Instant 取代。 Instant 类表示时间线上的一个瞬间,以 UTC 为基准,精度为 纳秒(最多九位小数)。
你的输入是:
String input = "2012-06-20 18:22:42.0" ;

该输入几乎符合标准的ISO 8601格式。要完全符合要求,请用T替换中间的空格。

String input = "2012-06-20 18:22:42.0".replace( " " , "T" ) ;

将其解析为LocalDateTime,因为它缺少UTC偏移量或时区指示器。
LocalDateTime ldt = LocalDateTime.parse( input ) ;

一段类似于你输入的字符串的LocalDateTime并不代表一个瞬间,也不是时间线上的一个点。如果没有时区或偏移量的上下文,它没有实际意义。它只表示在大约26-27小时范围内的潜在时刻。如果您知道预期的时区,请应用它以获取ZonedDateTime对象。
ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = ldt.atZone( z ) ;

关于您提到的其他格式,您的问题并不清晰。请在 Stack Overflow 上搜索 DateTimeFormatter 类以查看许多使用 java.time 类生成/解析字符串的示例和讨论。但首先,需要明确一个重要概念:字符串不是日期时间对象,日期时间对象也不是字符串。

数据库

如果您使用 java.sql.Timestamp 与数据库交换数据,则不再需要该类。从 JDBC 4.2 及更高版本开始,您可以直接使用 java.time 对象与数据库交换数据。
myPreparedStatement.setObject( … , instant ) ;

…和…

Instant instant = myResultSet.getObject( … , Instant.class ) ;

关于 java.time

java.time 框架内置于 Java 8 及更高版本中。这些类取代了老旧的 遗留 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

Joda-Time项目现在处于维护模式,建议迁移到java.time类。

了解更多信息,请参阅Oracle教程。并在Stack Overflow上搜索许多示例和说明。规范是JSR 310

您可以直接与数据库交换java.time对象。使用符合JDBC 4.2或更高版本的JDBC驱动程序。无需字符串,也不需要java.sql.*类。

如何获取java.time类?

ThreeTen-Extra 项目扩展了 java.time 的附加类。该项目是 java.time 可能未来添加的试验场。您可能会在这里找到一些有用的类,如 Interval, YearWeek, YearQuarter更多


感谢您的澄清和详细说明。顺便提一下,这个问题非常古老:2012年。当时Java SE 7是最新发布的版本。 - sankethm7
以上帮助我找到了解决方案:java.sql.Timestamp是UTC的时间点。 对于SQL Server(2008及更高版本),可以使用以下语句返回当前时区偏移量(以分钟为单位) datepart(tzoffset,SYSDATETIMEOFFSET()) 然后,我将java.sql.Timestamp转换为Joda DateTime对象,并发出了一个.plusMinutes的偏移值,该值在sql语句中返回。由于夏令时的影响,我在每个查询中都返回时区偏移量。 - toddcscar
Postgres和Mysql都不支持Instant。有哪些驱动程序可以支持呢? - andreoss
@andreoss 在任何JDBC驱动程序中支持“Instant”都非常简单:OffsetDateTime#toInstantInstant#atOffset(ZoneOffset.UTC)。这就是为什么JDBC 4.2团队要求支持OffsetDateTime但不支持Instant毫无意义的原因。 - Basil Bourque

1

将时间戳视为一个固定的时间点,与你在地球上看钟表的位置无关。

如果您想显示某个时区中某个人在那一瞬间日历/时钟上的内容,可以将日历设置为该时区,然后将您的SimpleDateFormat与该日历相关联。

例如:

public void testFormat() throws Exception {
    Calendar pacific = Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles"));
    Calendar atlantic = Calendar.getInstance(TimeZone.getTimeZone("America/New_York"));
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    Timestamp ts = new Timestamp(System.currentTimeMillis());
    sdf.setCalendar(pacific);
    System.out.println(sdf.format(ts));
    sdf.setCalendar(atlantic);
    System.out.println(sdf.format(ts));
}

我的输出是:

2012-06-25 20:27:12.506
2012-06-25 23:27:12.506

嘿@Phatfingers,我尝试了你的解决方案,似乎我走在了正确的轨道上,但问题是我已经有了格式化的时间戳以UTC显示时间。我需要将其转换为不同的时区。在您的代码示例中,在sdf.setCalendar(pacific)行上,它没有将时间转换为PST,因为我的开发时区是'pasific'。我需要将检索到的“ts”设置为UTC,然后将其转换为不同的时区;但我无法实现它。是否有任何方法可以说明'ts'中的值是UTC。然后将其转换为所需的时区? - sankethm7
关键是将您的时间戳视为仅代表UTC,并将日期和时间的显示视为包含时区的演示功能。忽略时间戳具有使用系统时区的toString()方法的事实。在幕后,它始终以UTC跟踪时间,并没有真正的时区概念。您不需要将时间戳转换为一个时区或另一个时区,而是使用一个时区或另一个时区格式化时间戳的输出。 - phatfingers
如果您的系统时区是PST,并且Timestamp的本地toString()输出似乎是有效的UTC时间,则您可能无意中调整了Timestamp的值,使其偏离正确时间8小时。您不希望出现这种情况。 - phatfingers

1

问题已解决,我将代码放在这里供参考。

Timestamp ts = "2012-06-20 18:22:42.0"; // input date in Timestamp format
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
Calendar cal = Calendar.getInstance();
cal.setTime(ts)
cal.add(Calendar.HOUR,-7); // Time different between UTC and PDT is -7 hours
String convertedCal = dateFormat.format(cal.getTime());  // This String is converted datetime
 /* Now convert String formatted DateTime to Timestamp*/
SimpleDateFormat formatFrom = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
try {    
    Date date = formatFrom.parse(convertedCal);
    Timestamp finalTS = new Timestamp(date.getTime()); // Final value in Timestamp: 2012-06-20 11:22:42.0
} catch (Exception e) {
    e.printStackTrace();            
}

请解释一下你所做的事情。现在唯一的方法是比较问题代码和答案。同时,请接受你认为“解决”你问题的答案。你也可以接受自己的答案。 - Piro
对于未来的读者,这个解决方案没有考虑夏令时或闰年。一个更完整的解决方案将使用时区和日历对象,这些对象在其他答案中有所描述。 - Azeroth2b

0

parse 方法不是从 java.text.DateFormat 继承的单个参数吗? - fudo
@Sebastian,我尝试使用parse(string, parsePosition)。但它返回null。 - sankethm7

0

你能不能简单的这样做:

  1. 获取原始的毫秒时间
  2. 将时区差异转换成毫秒
  3. 从原始时间中加或减去差异。
  4. 使用新的毫秒时间创建一个新的时间戳

我已经有了时间戳格式的格式化时间,实际上它是UTC时间。我需要将其转换为不同的时区,并以相同的格式和时间戳类型返回。因此,我认为计算毫秒数并不是在不同时区转换时间的好方法。我将其保留为最后的选择。 - sankethm7

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接