java - 由于 Java SimpleDateFormat 问题,在 Oracle DB 中保存时间数据(带区域)无法正常工作

标签 java oracle hibernate simpledateformat

我有一个定义为TIMESTAMP WITH TIME ZONE的字段。

要保存的值以:“09-23-2019 10:03:11 pm”(位于美国/夏威夷地区)开头。

这就是我试图保存到数据库的内容(所有日期信息加上区域)

数据库以UTC格式存储时间信息。

到目前为止,日期已存储在数据库中,如下所示:

DAYS
---------------------------------------------------------------------------
23-SEP-19 10.03.11.000000 PM -05:00
23-SEP-19 10.03.11.000000 PM -05:00

在处理过程中,它运行以下代码:

    dateStr: the date (as seen above)
    ZoneLoc: 'US/Hawaii'

    public Calendar convDateStrWithZoneTOCalendar(String dateStr,
            String ZoneLoc) throws Exception {

        // convert the string sent in from user (which uses AM/PM) to one that uses military time (24HR)
        // it
        String formattedDate = null;
        DateFormat readFormat = new SimpleDateFormat(this.getPattern());
        DateFormat writeFormat = new SimpleDateFormat("MM-dd-yyyy'T'HH:mm:ss'Z'");
        writeFormat.setTimeZone(TimeZone.getTimeZone(ZoneLoc));
        Date date = null;
        date = readFormat.parse(dateStr);
        formattedDate = writeFormat.format(date);

        // see if you can parse the date needed WITH the TimeZone
        Date d;
        SimpleDateFormat sdf = new SimpleDateFormat("MM-dd-yyyy'T'HH:mm:ss'Z'");
        sdf.setTimeZone(TimeZone.getTimeZone(ZoneLoc));
        d = sdf.parse(formattedDate);
        Calendar cal = Calendar.getInstance();
        cal.setTime(d);

        system.out.println(" ZONELOC VALUE " + ZoneLoc);
        system.out.println(" RETURNED VALUE " + cal );

        return cal;
    }

返回的日历信息是:

ZONELOC VALUE IS US/Hawaii

RETURNED VALUE IS java.util.GregorianCalendar[time=1577678591000,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/Chicago",offset=-21600000,dstSavings=3600000,useDaylight=true,transitions=235,lastRule=java.util.SimpleTimeZone[id=America/Chicago,offset=-21600000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2019,MONTH=11,WEEK_OF_YEAR=1,WEEK_OF_MONTH=5,DAY_OF_MONTH=29,DAY_OF_YEAR=363,DAY_OF_WEEK=1,DAY_OF_WEEK_IN_MONTH=5,AM_PM=1,HOUR=10,HOUR_OF_DAY=22,MINUTE=3,SECOND=11,MILLISECOND=0,ZONE_OFFSET=-21600000,DST_OFFSET=0]

返回值中似乎未设置美国/夏威夷。

我可以做什么来确保这得到设置?

之后,我可以将其放入数据库中,看看设置是否会“粘住”并且不会恢复到美国/芝加哥

更新 @Patrick H - 感谢您的投入。我按照您指定的模式进行了更改,并且能够保存数据。现在看起来像这样:

2017-08-02 13:38:49 TRACE o.h.type.descriptor.sql.BasicBinder - binding parameter [26] as [TIMESTAMP] - [java.util.GregorianCalendar[time=1569294191000,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/Chicago",offset=-21600000,dstSavings=3600000,useDaylight=true,transitions=235,lastRule=java.util.SimpleTimeZone[id=America/Chicago,offset=-21600000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2019,MONTH=8,WEEK_OF_YEAR=39,WEEK_OF_MONTH=4,DAY_OF_MONTH=23,DAY_OF_YEAR=266,DAY_OF_WEEK=2,DAY_OF_WEEK_IN_MONTH=4,AM_PM=1,HOUR=10,HOUR_OF_DAY=22,MINUTE=3,SECOND=11,MILLISECOND=0,ZONE_OFFSET=-21600000,DST_OFFSET=3600000]]

数据库中的数据如下所示:

23-SEP-19 10.03.11.000000 PM -05:00

即使指定了 US/Hawaii,区域仍然是 America/Chicago。如何才能让美国/夏威夷坚持下去而不回到美国/芝加哥

最佳答案

根据此输出:

java.util.GregorianCalendar[time=1569294191000,...

上面的时间值(这意味着自unix纪元(1970-01-01T00:00Z)以来的1569294191000毫秒)相当于09-23-2019 10:03 PM芝加哥。那是因为readFormat正在使用系统的默认时区(可能是 America/Chicago ,只需检查 TimeZone.getDefault() 的值)。

解析输入09-23-2019 10:03:11 pm并将其视为夏威夷本地时间,只需将相应时区设置为 SimpleDateFormat实例(在本例中为 readFormat ,因为它需要知道输入日期在哪个时区 - 因为您没有设置任何时区,所以它使用系统的默认值)。您也不需要其他格式化程序( writeFormatsdf ),只需使用一个格式化程序即可获取相应的日期:

SimpleDateFormat parser = new SimpleDateFormat("MM-dd-yyyy hh:mm:ss a");
// the input is in Hawaii timezone
parser.setTimeZone(TimeZone.getTimeZone("US/Hawaii"));
Date date = parser.parse("09-23-2019 10:03:11 pm");

date上述时间相当于夏威夷晚上 10:03。 实际上,日期本身仅包含 unix 纪元的毫秒数(date.getTime() 返回 1569312191000)和 has no format nor any timezone information

然后您可以将其设置为 Calendar实例(不要忘记设置日历的时区):

Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("US/Hawaii"));
cal.setTime(date);

自从我使用带有时区类型的 Oracle 时间戳以来已经有一段时间了,但我认为这将是 enough to save the correct values 。日历的值为:

java.util.GregorianCalendar[time=1569312191000,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="US/Hawaii",offset=-36000000,dstSavings=0,useDaylight=false,transitions=7,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2019,MONTH=8,WEEK_OF_YEAR=39,WEEK_OF_MONTH=4,DAY_OF_MONTH=23,DAY_OF_YEAR=266,DAY_OF_WEEK=2,DAY_OF_WEEK_IN_MONTH=4,AM_PM=1,HOUR=10,HOUR_OF_DAY=22,MINUTE=3,SECOND=11,MILLISECOND=0,ZONE_OFFSET=-36000000,DST_OFFSET=0]


Java 新的日期/时间 API

旧类( DateCalendarSimpleDateFormat )有 lots of problemsdesign issues ,并且它们正在被新的 API 取代。

主要问题之一是使用不同时区是多么困难和困惑。

如果您使用的是 Java 8,请考虑使用 new java.time API 。更容易,less bugged and less error-prone than the old APIs .

如果您使用的是 Java <= 7,则可以使用 ThreeTen Backport ,Java 8 新日期/时间类的一个很好的向后移植。对于 Android,有 ThreeTenABP (更多关于如何使用它 here )。

下面的代码适用于两者。 唯一的区别是包名称(在 Java 8 中是 java.time ,在 ThreeTen Backport(或 Android 的 ThreeTenABP)中是 org.threeten.bp ),但类和方法名称是相同的。

解析输入09-23-2019 10:03:11 pm您可以使用 DateTimeFormatter并将其解析为 LocalDateTime - 输入没有时区信息,因此我们只考虑日期和时间,然后我们可以将其转换为时区。

// parse the input
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
    // parse AM/PM and am/pm
    .parseCaseInsensitive()
    // input pattern
    .appendPattern("MM-dd-yyyy hh:mm:ss a")
    // use English locale for am/pm symbols
    .toFormatter(Locale.ENGLISH);
LocalDateTime dt = LocalDateTime.parse("09-23-2019 10:03:11 pm", fmt);
// convert to Hawaii timezone
ZonedDateTime hawaiiDate = dt.atZone(ZoneId.of("US/Hawaii"));

最新的 JDBC 驱动程序支持新的 API(但我猜仅适用于 Java 8),但如果您仍然需要使用 Calendar ,您可以轻松转换 ZonedDateTime给它:

Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("US/Hawaii"));
calendar.setTimeInMillis(hawaiiDate.toInstant().toEpochMilli());

在 Java 8 中,您还可以执行以下操作:

Calendar calendar = GregorianCalendar.from(hawaiiDate);

如果您需要与旧版 Calendar 进行互操作和Date API,您可以在内部使用新的 API 进行计算,并在需要时与 API 进行转换。

关于java - 由于 Java SimpleDateFormat 问题,在 Oracle DB 中保存时间数据(带区域)无法正常工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45465945/

相关文章:

java - 等效于 Java 8 中的 Scala 的 foldLeft

mysql/oracle 存储数学公式

sql - 在 Oracle 中创建 bool 属性?

java - Hibernate在使用二级缓存时如何与数据库进行同步

java - 尽管采取了建议的步骤,API 调用仍会引发 sslhandshakeException

java - 为什么我的函数没有给出正确的输出?

c# - 具有 Oracle 托管驱动程序将 NUMBER(10) 映射到 long 的 Entity Framework 不起作用

java - Hibernate 多个表的连接和限制

java - org.springframework.transaction.CannotCreateTransactionException : Could not open Hibernate Session for transaction

不同目录中的 Java 类路径