由于 Java SimpleDateFormat 问题,在 Oracle DB 中保存时间数据(带区域)不起作用
Saving time data (with Zone) in Oracle DB not working because of Java SimpleDateFormat issue
我有一个定义为 TIMESTAMP WITH TIME ZONE
的字段。
要保存的值开始于:"09-23-2019 10:03:11 pm"
在 US/Hawaii
的区域。
这就是我要保存到数据库的内容(所有日期信息加上区域)
数据库以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]
返回值中似乎没有设置 US/Hawaii。
我该怎么做才能确保设置成功?
在那之后,我可以将它放在数据库中,看看设置是否会 "stick" 而不是恢复到 America/Chicago
更新
@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
。怎样才能让 US/Hawaii
坚持下去而不是回到 America/Chicago
?
根据 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
您可能会发现使用通用时区组件更容易。
根据这个输出:
java.util.GregorianCalendar[time=1569294191000,...
上面的时间值(表示自 unix 纪元 (1970-01-01T00:00Z
) 以来的 1569294191000 毫秒)相当于 Chicago 中的 09-23-2019 10:03 PM
。那是因为readFormat
使用的是系统的默认时区(可能是America/Chicago
,只需检查TimeZone.getDefault()
的值)。
要解析输入 09-23-2019 10:03:11 pm
并将其视为夏威夷当地时间,您只需将相应的时区设置为 SimpleDateFormat
实例(在本例中为 readFormat
,因为它需要知道输入日期在哪个时区——因为你没有设置任何时区,它使用系统的默认值)。您也不需要其他格式化程序(writeFormat
和sdf
),只需使用一个格式化程序即可获取相应的日期:
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()
returns 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新Date/TimeAPI
旧的 类(Date
、Calendar
和 SimpleDateFormat
)有 lots of problems and design issues,它们正在被新的 APIs.
主要问题之一是使用不同的时区是多么困难和令人困惑。
如果您正在使用 Java 8,请考虑使用 new java.time API. It's easier, less bugged and less error-prone than the old APIs.
如果您使用 Java <= 7,您可以使用 ThreeTen Backport, a great backport for Java 8's new date/time classes. And for Android, there's the ThreeTenABP (more on how to use it ).
下面的代码适用于两者。
唯一的区别是包名称(在 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());
在Java8中,你还可以这样做:
Calendar calendar = GregorianCalendar.from(hawaiiDate);
如果您需要与旧的 Calendar
和 Date
API 的互操作性,您可以在内部使用新的 API 进行计算并转换 from/to API 需要的时候。
我有一个定义为 TIMESTAMP WITH TIME ZONE
的字段。
要保存的值开始于:"09-23-2019 10:03:11 pm"
在 US/Hawaii
的区域。
这就是我要保存到数据库的内容(所有日期信息加上区域)
数据库以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]
返回值中似乎没有设置 US/Hawaii。
我该怎么做才能确保设置成功?
在那之后,我可以将它放在数据库中,看看设置是否会 "stick" 而不是恢复到 America/Chicago
更新 @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
。怎样才能让 US/Hawaii
坚持下去而不是回到 America/Chicago
?
根据 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
您可能会发现使用通用时区组件更容易。
根据这个输出:
java.util.GregorianCalendar[time=1569294191000,...
上面的时间值(表示自 unix 纪元 (1970-01-01T00:00Z
) 以来的 1569294191000 毫秒)相当于 Chicago 中的 09-23-2019 10:03 PM
。那是因为readFormat
使用的是系统的默认时区(可能是America/Chicago
,只需检查TimeZone.getDefault()
的值)。
要解析输入 09-23-2019 10:03:11 pm
并将其视为夏威夷当地时间,您只需将相应的时区设置为 SimpleDateFormat
实例(在本例中为 readFormat
,因为它需要知道输入日期在哪个时区——因为你没有设置任何时区,它使用系统的默认值)。您也不需要其他格式化程序(writeFormat
和sdf
),只需使用一个格式化程序即可获取相应的日期:
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()
returns 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新Date/TimeAPI
旧的 类(Date
、Calendar
和 SimpleDateFormat
)有 lots of problems and design issues,它们正在被新的 APIs.
主要问题之一是使用不同的时区是多么困难和令人困惑。
如果您正在使用 Java 8,请考虑使用 new java.time API. It's easier, less bugged and less error-prone than the old APIs.
如果您使用 Java <= 7,您可以使用 ThreeTen Backport, a great backport for Java 8's new date/time classes. And for Android, there's the ThreeTenABP (more on how to use it
下面的代码适用于两者。
唯一的区别是包名称(在 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());
在Java8中,你还可以这样做:
Calendar calendar = GregorianCalendar.from(hawaiiDate);
如果您需要与旧的 Calendar
和 Date
API 的互操作性,您可以在内部使用新的 API 进行计算并转换 from/to API 需要的时候。