要进行JDBC查询,我需要将日期传递给它。日期保存在PostgreSQL数据库的Date字段类型中,表示特定日期,不需要任何时间。
由于我只需要日期,所以我决定使用一个特定的对象,它只代表没有时间的日期,即来自Joda时间包的LocalDate。我认为这很重要,因为如果我使用datetime对象,它将携带多余的时间数据,并且可能会在夏令时结束时导致错误,此时时钟将向后推迟一小时(尽管这种情况非常罕见,但并非不可能)。
但当我开始尝试用preparedStatement.setDate方法的可接受的参数来处理LocalDate对象时,我找不到合适的方法。
setDate接受java.sql.Date作为参数。构造java.sql.Date对象的唯一选择是以毫秒为单位传递时间。
但这违背了使用Joda时间包中的LocalDate的所有目的,因为在这个转换中,我们返回到毫秒,而当这些转换发生时,时钟可能会推迟一个小时,并将日期更改为前一个日期。
所以,现在我的代码中有一行:
1
| preparedStatement. setDate(1, new java. sql. Date(localDate. toDate(). getTime())); |
但这是将LocalDate转换为setDate格式的最佳方法吗?
我对夏令时和相应的时钟移位的担心是否合理?
是否有更好的方法将日期(并且只有没有时间的日期)传递给JDBC PreparedStatement?
- 数据库端的语义是什么?如果是即时的,而不是约会的话,你还是会被炒的。
- @从表中可以看出,它只是日期而不是时间(因为它需要4个字节,而不是8个字节作为日期时间类型)。
- 使用您的技术应该是安全的,因为LocalDate#toDate应该考虑所有时区问题。
- @但是,如果我们创建一个LocalDate的实例,然后在它上面调用toDate(),然后将时钟倒回一小时,当天变为前一天,然后调用getTime()(仍然是前一天),然后构造java.sql.Date(仍然是前一天)。所以我们以前一天结束。我知道我可以在没有日光节约时间的情况下使用UTC。但是,当我根本不需要时间(只需要日期)的时候,我仍然没有看到使用任何与时间相关的值的目的。
- 这不是可能发生的。您所拥有的毫秒时间与上下文无关:它唯一地与在该时间点有效的时区相关。唯一真正的问题是,当两个瞬间映射到同一个时间标签时(当时钟向后转动时,相同的时间标签重复出现的事件)。在这些情况下,实际上已经对确切的语义进行了一些澄清,但无论如何,您不应该受到它们的影响。
- 至于你希望用Java获得明智的日期/时间语义学,你就不走运了。Java 8应该给我们一个新的API(又一次!),主要基于jodatime。但我不知道这是否意味着JDBC将在同一版本中使用它。
- @马尔科托波尼克,你能解释一下为什么我不在乎时钟倒转吗?因为现在我是这样理解的(我可能错了):我们创建了一个对象localDate.toDate(),在某一天内持有00:00。但今天在江户十一〔八〕时,时钟倒计时了。此时此刻,我们的对象仍将代表同一时间实例,但在我的本地时间系统中,它将是-1小时,即前一天的23:00。所以时间实例的日期取决于我们当前的DST状态。
- 你真的测试过这个吗?因为如果您正确地从即时转换回日期/时间,则不会发生这种情况。通过考虑TZ在该瞬间有效,而不是转换时有效的内容,将即时转换为日期/时间标签。如果您在一年中重复转换完全相同的毫秒值,您将始终得到完全相同的答案,即使同时您所在的时区规则发生了变化。
- 看看乔恩·斯基特的这个传奇式回答。我认为这应该与你对案件的理解有关。
- @马尔科托普尼克啊。这一刻我错了!因此,如果我转换localDate.toDate(),它将节省对象中的时区,这样它将始终代表原始LocalDate对象的日期。所有随后的转换和转换都将使用该时区,因此日期不会更改。对吗?
- 不需要保存TZ。Date的一个实例只是一个毫秒值的包装器,只能在指定了所需的目标Tz的情况下转换为日期/时间标签。您可以看到,Date中所有与TZ相关的功能现在都被弃用了。另一方面,Calendar对地区敏感。
- 所以这就是危险所在:JDBC驱动程序必须将毫秒转换为日期标签(如果这真的是您的数据库保存的内容),然后它必须将从数据库中检索到的标签转换为毫秒瞬间。原则上,这仍然是上下文无关的。
- 也许这是一个让你困惑的点:转换是根据一个区域设置完成的,而不是一个特定的时区。时区是从要转换的即时的区域设置派生的。
- @所以,时区是从datetime实例的当前有效区域设置中获取的。所以这个对象java.sql.Date(localDate.toDate().getTime())是不变的。
- 是的,没错。因此,如果在较低的级别(JDBC/数据库)没有任何问题,那么这应该可以正常工作。
- @谢谢你!现在这个话题对我来说或多或少是清楚的!
- 好的,我现在应该重写它作为一个答案,以便将来的访问者能够更容易地访问它。
- @马尔科托普尼克,那太好了!
使用您的技术应该是安全的,因为LocalDate#toDate将考虑所有时区问题。所得到的毫秒瞬间与上下文无关:它唯一地与您用于转换的区域设置中在该时间点有效的时区相关。换言之,如果您在一年中重复转换完全相同的毫秒值,您将始终得到完全相同的答案,即使同时您所在位置的时区规则发生了变化,因为JDK引用了一个记录全世界所有时区变化的完整历史的数据库。
在对这些问题进行推理时,重要的是要记住,当前时区对转换没有影响,转换是由您的区域设置参数化的,并且只在被转换的即时上下文中解析时区。
我真心同情你对这一切感到的不安:它把一个简单而紧张的操作变成了一个复杂的计算迷宫,它只会招来麻烦。希望Java 8和它的新事物会有一个积极的转变(是的,再一次!)日期/时间API,基于jodatime。
- 希望JDBC也能利用这一点,开始接受LocatDate对象作为setDate的参数!
- @实际上,您可以直接与JDBC4.2及更高版本交换java.time对象。使用PreparedStatement::setObject和ResultSet::getObject而不是setDate和getDate。有关详细信息,请参阅我对同一问题的回答。myPreparedStatement.setObject( … , localDate )和myResultSet.getObject( … , LocalDate.class )。
我今天也遇到了同样的问题。我正在使用JDK 8。经过几个小时的搜索,我终于在JavaSE 8文档中找到了答案。这就是解决方案:
1
| statement. setDate(5, java. sql. Date. valueOf(personToUpdate. getBirthday())); |
语句是PreparedStatement实例。"persontoUpdate.getBirthday()"是本地日期的类型。
- 我不相信这对当地时间的乔达有用
- 你把Java 8的本地日期与Joda LocalDate混淆了。问题是关于joda localdate。
译文;
1 2 3 4
| myPreparedStatement.setObject( // Pass java.time objects directly to database with JDBC 4.2 or later.
… ,
LocalDate.now() // Get current date today. Better to pass optional `ZoneId` time zone object explicitly than rely implicitly on JVM’s current default.
) |
java.time
8、Java和以后,新的框架是建立和java.time"。这successor碘-时间是由定义JSR 310和扩展,通过threeten额外项目。 <P /好的。
我们看到hopefully好eventually JDBC驱动器更新这directly处理新java.time类型。但是,直到然后我们需要继续在这java.sql。*类型。fortunately,新方法已被添加,这conveniently convert之间的类型。 <P /好的。
对一只给我个好时代,"一天的时间和井区的Java类型是LocalDate(这是相当相似的碘的时间)。 <P /好的。
作为对你的关注,这一LocalDate区相关时,它matters当你是一只translating约会这一日期时,那一刻他在timeline。给我一只是淡淡的vague与皇马的想法,直到你translate意义吧,它是一种跨弯矩的timeline(这是他在午夜和一些时间区)。例如,"今天"得到充分的测定时间区。我们使用的ZoneId和java.time类。 <P /好的。
1
| LocalDate today = LocalDate.now( ZoneId.of("America/Montreal" ) ); |
如果你omitted,JVM的违约时间流区是用于测定的数据。与其他字,下面的两行是等价的。 <P /好的。
1 2
| LocalDate today = LocalDate.now();
LocalDate today = LocalDate.now( ZoneId.systemDefault() ); |
我consider第一版本,now(),这是一个可怜的API的设计选择。这个隐式应用的JVM的违约时间流区causes井端的混乱,错误,和痛苦的垃圾?他developers。为一件事,在JVM的违约varies流由机,由主机操作系统和邮件系统的管理员也settings,主编。worse,JVM的违约可以改变在任何时间、任何代码,在运行时,通过螺纹与任何应用程序,在任何JVM。这是最好的做法是,你总是specify desired /预期的时间区。 <P /好的。
现在,我们有一java.time对象为"今天",如何把它变成"数据库吗? <P /好的。
4.2与JDBC或以后,directly交易所java.time对象和你的数据库。 <P /好的。
1
| myPreparedStatement.setObject( … , today ) ; |
这种检索: <P /好的。
1
| LocalDate ld = myResultSet.getObject( … , LocalDate.class ) ; |
如果你不能这样做. 4.2以后升级或java.sql.Date:使用对象。和Java类,8岁,gained新方法,toLocalDate和valueOf。"是我们的"java.time桥实体型的java.sql型。 <P /好的。
1
| java. sql. Date sqlToday = java. sql. Date. valueOf( today ); |
从那里的usual PreparedStatement装卸。 <P /好的。
1
| myPreparedStatement.setDate( 1 , sqlToday ); |
我只给你时间与
或许你有concerns,这样你只需要把这件业务。 <P /好的。
例如,如果您需要知道,如果出于法律原因,合同是在一天结束前签署的,那么只有当日期表示一个特定时刻(如蒙特利尔午夜的钟声)时,日期才是错误的数据类型。新的一天在巴黎比在蒙特利尔来得早,所以巴黎的"今天"在蒙特利尔是"昨天"。如果您的合同期限在法律上定义为一天的月底,那么您必须申请时区。要应用时区,您必须具有日期时间而不是仅日期。你可以从LocalDate跳到ZonedDateTime,但我认为这太复杂了。数据库应该从一开始就使用日期时间类型。好的。
在Postgres中,日期时间类型表示TIMESTAMP WITH TIME ZONE类型。这个名字用词不当,因为时区实际上没有存储。把它看作是"时区的时间戳"。Postgres使用来自UTC的任何偏移量或与传入数据相关的时区信息来调整到UTC,然后丢弃该偏移量/区域信息。另一种类型,TIMESTAMP WITHOUT TIME ZONE,完全忽略偏移量/区域信息,对于大多数业务应用程序来说,这是错误的行为。好的。
我怀疑许多开发人员或DBA可能会使NA?凭直觉认为日期只有明显意义的错误。但事实上,如果您有特定的或严格的面向时间的需求,例如有关"签订合同"、"收到发票"或"聘用公司高管"等事件的合法性,则应使用日期时间值,而不是仅使用日期。好的。
换句话说,关于问题作者的评论:好的。
And I expected there should be a way to work with dates without resorting to time instances.
Ok.
不,我认为这是自找麻烦。如果时间很重要,请使用日期时间而不是仅日期。好的。关于JavaTimes
JavaTimeFr框架是在Java 8和之后构建的。这些类取代了麻烦的旧遗留日期时间类,如java.util.Date、Calendar和SimpleDateFormat。好的。
现在处于维护模式的joda time项目建议迁移到java.time类。好的。
要了解更多信息,请参阅Oracle教程。以及搜索堆栈溢出以获得许多示例和解释。规格为JSR 310。好的。
在哪里获取java.time类?好的。
- Java Java看见8,9,和以后
- 内置。
- 方的标准的Java API和一个bundled执行。
- 9 adds Java和一些小的功能是固定的。
- 看到Java和Java SE 7 6
- 多功能性的java.time冰后两个Java 6 ported &;7 threeten - backport。
- Android
- 以后的版本的Android系束的java.time类的实现等。
- 在之前的Android项目,threetenabp adapts threeten - backport(上述以上)。看到如何使用threetenabp…
《threeten额外项目extends java.time与额外的类。这个项目是一个证明的地面为java.time加两个可能的未来。你可以在这里找到一些有用的类如Interval,YearWeek,YearQuarter,和更多。
好。 好的。
由于org.joda.time.todatemiddy()和org.joda.time.todatemiddy(datetimezone zone)已被弃用,这是一个非常适合我的解决方案。
我的典型班级,要坚持:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| ...
import org.joda.time.LocalDate;
...
public class MyObject implements Serializable {
...
private LocalDate startDate ;
...
private EndDate startDate ;
// Getters and Setters
...
...
} |
我是我坚持开始日期的另一个班级,我有:
1
| myObject.setStartDate(new LocalDate(myObject.getStartDate().toDateTimeAtStartOfDay(DateTimeZone.getDefault()))); |
- toDateTimeAtStartOfDay是打字错误吗?您是指joda time DateTime类上的withTimeAtStartOfDay方法吗?
- 不,这不是打字错误。在使用它之前,我将startDate设置为,比如说,"2015-01-01",在其中,它被持久保存到数据库中为"2014-12-31"。这现在可以工作了,因为到localdate的转换是按原样完成的。
- 你能链接到医生吗?我找不到那个方法。
尝试使用LocalDate#toDateMidnight(),将时间设置为0,然后使用DateMidnight#toDate()。
1
| Date date = localDate. toDateMidnight(). toDate(); |
或者,如果您使用的是jodatime 1.5或更高版本,请使用LocalDate#toDateTimeAtStartOfDay(),然后使用DateTime#toDate()。
希望帮助
- 使用toDateMidnight可以解决什么问题?
- Jodatime日期午夜
- 这仍然将日期强制转换为当前的特定实例。在这种转换之后,当前时刻可能会经过DST周期的结束,时钟会倒转一小时。之后,我们保存的那一刻的实例将代表前一天的23:00。这就是问题所在。我希望有一种方法可以不用借助时间实例来处理日期。
- 另外,它还提供了什么toDate方法?
- 在最新版本的Joda Time中,与午夜相关的类和方法已被弃用。而是调用方法withTimeAtStartOfDay。一天中的第一个时刻不一定总是00:00:00.0。