由于Java SimpleDateFormat问题,Oracle DB中的时间数据(使用Zone)无法正常工作

时间:2017-08-02 16:11:52

标签: java oracle hibernate simpledateformat

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

要保存的值以"09-23-2019 10:03:11 pm"区域中的US/Hawaii开头。

这就是我要保存到DB(所有日期信息加区域)

数据库以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价值是美国/夏威夷

     

返回值是   java.util.GregorianCalendar中[时间= 1577678591000,areFieldsSet =真,areAllFieldsSet =真,宽大=真,区= sun.util.calendar.ZoneInfo [ID = “美国/芝加哥”,偏移= -21600000,dstSavings = 3600000,useDaylight =真,过渡= 235,lastRule = java.util.SimpleTimeZone中[ID =美国/芝加哥,偏移= -21600000,dstSavings = 3600000,useDaylight =真,startYear = 0,STARTMODE = 3,startMonth = 2,朝九特派= 8, startDayOfWeek = 1,开始时间= 7200000,startTimeMode = 0,endMode = 3,endMonth = 10,endday指定= 1,一个endDayOfWeek = 1,结束时间= 7200000,endTimeMode = 0]],Firstdayofweek可= 1,minimalDaysInFirstWeek = 1,ERA = 1, YEAR = 2019,月= 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,微差= 0,ZONE_OFFSET = -21600000,DST_OFFSET = 0]

看起来美国/夏威夷没有被设置为返回值。

我该怎么做才能确保设置好?

之后,我可以将它放在数据库中,看看设置是否“坚持”而不是恢复为America/Chicago

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

  

2017-08-02 13:38:49 TRACE ohtype.descriptor.sql.BasicBinder - 绑定参数[26] as [TIMESTAMP] - [java.util.GregorianCalendar [time = 1569294191000,areFieldsSet = true,areAllFieldsSet =真,宽松=真,区= sun.util.calendar.ZoneInfo [ID = “美国/芝加哥”,偏移= -21600000,dstSavings = 3600000,useDaylight =真,过渡= 235,lastRule = java.util.SimpleTimeZone中[ID =美国/芝加哥,偏移= -21600000,dstSavings = 3600000,useDaylight =真,startYear = 0,STARTMODE = 3,startMonth = 2,朝九特派= 8,startDayOfWeek = 1,开始时间= 7200000,startTimeMode = 0,endMode = 3, endMonth = 10,endday指定= 1,一个endDayOfWeek = 1,结束时间= 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,微差= 0,ZONE_OFFSET = -21600000,DST_OFFSET = 3600000]]

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

23-SEP-19 10.03.11.000000 PM -05:00

即使指定America/Chicago,区域仍为US/Hawaii。如何让US/Hawaii坚持而不是恢复到America/Chicago

2 个答案:

答案 0 :(得分:2)

根据这个输出:

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 PM。 实际上,日期本身只包含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中[时间= 1569312191000,areFieldsSet =真,areAllFieldsSet =真,宽大=真,区= sun.util.calendar.ZoneInfo [ID = <强> “US /夏威夷”下,偏移量= -36000000,dstSavings = 0,useDaylight =假,过渡= 7,lastRule =空],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 下,微差= 0,ZONE_OFFSET = - 36000000,DST_OFFSET = 0]

Java新日期/时间API

旧类(DateCalendarSimpleDateFormat)有lots of problemsdesign issues,它们将被新API取代。< / p>

其中一个主要问题是使用不同的时区是多么困难和困惑。

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

如果您使用的是 Java&lt; = 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);

如果您需要与旧的CalendarDate API进行互操作,则可以在内部使用新API进行计算,并在需要时从/转换为API。

答案 1 :(得分:1)

根据SimpleDateFormat,我认为你的格式化字符串是错误的。您还可以在返回的值中看到月份和日期是错误的。 MONTH=11,DAY_OF_MONTH=29

这是你现在拥有的: 23-SEP-19 10.03.11.000000 PM -05:00

我认为格式化字符串应为:'dd-MMM-yy hh.mm.ss.SSSSSS a Z'

看起来时区问题可能是因为里面有一个冒号。 SimpleDateFormat的文档表明它需要采用这种格式而不是RFC 822时区:-0500您可能会发现更容易使用常规时区组件。