如何在p:calendar中使用java.time.ZonedDateTime / LocalDateTime [英] How to use java.time.ZonedDateTime / LocalDateTime in p:calendar
问题描述
我在Java EE应用程序中使用Joda Time进行日期时间操作,其中由相关客户端提交的日期时间的字符串表示在使用以下转换例程进行转换之前,先将其提交给数据库,即 getAsObject()
方法在JSF转换器中。
I had been using Joda Time for date-time manipulation in a Java EE application in which a string representation of date-time submitted by the associated client had been converted using the following conversion routine before submitting it to a database i.e. in the getAsObject()
method in a JSF converter.
org.joda.time.format.DateTimeFormatter formatter = org.joda.time.format.DateTimeFormat.forPattern("dd-MMM-yyyy hh:mm:ss a Z").withZone(DateTimeZone.UTC);
DateTime dateTime = formatter.parseDateTime("05-Jan-2016 03:04:44 PM +0530");
System.out.println(formatter.print(dateTime));
本地时区提前5小时30分钟 UTC
/ GMT
。因此,转换为 UTC
应从使用Joda时间正确发生的日期 - 时间扣除5小时30分钟。它会按预期显示以下输出。
The local time zone given is 5 hours and 30 minutes ahead of UTC
/ GMT
. Therefore, the conversion to UTC
should deduct 5 hours and 30 minutes from the date-time given which happens correctly using Joda Time. It displays the following output as expected.
05-Jan-2016 09:34:44 AM +0000
►时区偏移 +0530
代替 +05:30
,因为它依赖于以此格式提交区域偏移的< p:calendar>
。似乎不可能更改< p:calendar>
的此行为(否则不需要此问题本身)。
► The time zone offset +0530
in place of +05:30
has been taken because it is dependent upon <p:calendar>
which submits a zone offset in this format. It does not seem possible to change this behaviour of <p:calendar>
(This question itself would not have been needed otherwise).
但是,如果尝试在Java 8中使用Java Time API,同样的事情会被破坏。
The same thing is however broken, if attempted using the Java Time API in Java 8.
java.time.format.DateTimeFormatter formatter = java.time.format.DateTimeFormatter.ofPattern("dd-MMM-yyyy hh:mm:ss a Z").withZone(ZoneOffset.UTC);
ZonedDateTime dateTime = ZonedDateTime.parse("05-Jan-2016 03:04:44 PM +0530", formatter);
System.out.println(formatter.format(dateTime));
意外显示以下不正确的输出。
It unexpectedly displays the following incorrect output.
05-Jan-2016 03:04:44 PM +0000
显然,转换的日期 - 时间不是根据要转换的 UTC
。
Obviously, the date-time converted is not according to UTC
in which it is supposed to convert.
它需要进行以下更改才能正常工作。
It requires the following changes to be adopted for it to work correctly.
java.time.format.DateTimeFormatter formatter = java.time.format.DateTimeFormatter.ofPattern("dd-MMM-yyyy hh:mm:ss a z").withZone(ZoneOffset.UTC);
ZonedDateTime dateTime = ZonedDateTime.parse("05-Jan-2016 03:04:44 PM +05:30", formatter);
System.out.println(formatter.format(dateTime));
其中依次显示以下内容。
Which in turn displays the following.
05-Jan-2016 09:34:44 AM Z
Z
已替换为 z
和 +0530
已替换为 +05:30
。
Z
has been replaced with z
and +0530
has been replaced with +05:30
.
为什么这两个API有不同
Why these two APIs have a different behaviour in this regard has been ignored wholeheartedly in this question.
对于< p:calendar> $可考虑使用什么中间方式方法c $ c>和Java Time在Java 8中一致地工作,但
< p:calendar>
内部使用 SimpleDateFormat
java.util.Date
?
What middle way approach can be considered for <p:calendar>
and Java Time in Java 8 to work consistently and coherently though <p:calendar>
internally uses SimpleDateFormat
along with java.util.Date
?
失败测试场景。
转换器:
@FacesConverter("dateTimeConverter")
public class DateTimeConverter implements Converter {
@Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (value == null || value.isEmpty()) {
return null;
}
try {
return ZonedDateTime.parse(value, DateTimeFormatter.ofPattern("dd-MMM-yyyy hh:mm:ss a Z").withZone(ZoneOffset.UTC));
} catch (IllegalArgumentException | DateTimeException e) {
throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, null, "Message"), e);
}
}
@Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (value == null) {
return "";
}
if (!(value instanceof ZonedDateTime)) {
throw new ConverterException("Message");
}
return DateTimeFormatter.ofPattern("dd-MMM-yyyy hh:mm:ss a z").withZone(ZoneId.of("Asia/Kolkata")).format(((ZonedDateTime) value));
// According to a time zone of a specific user.
}
}
XHTML有< p :calendar>
。
<p:calendar id="dateTime"
timeZone="Asia/Kolkata"
pattern="dd-MMM-yyyy hh:mm:ss a Z"
value="#{bean.dateTime}"
showOn="button"
required="true"
showButtonPanel="true"
navigator="true">
<f:converter converterId="dateTimeConverter"/>
</p:calendar>
<p:message for="dateTime"/>
<p:commandButton value="Submit" update="display" actionListener="#{bean.action}"/><br/><br/>
<h:outputText id="display" value="#{bean.dateTime}">
<f:converter converterId="dateTimeConverter"/>
</h:outputText>
时区完全透明地取决于用户的当前时区。
只有一个属性的bean。
The bean having nothing other than a single property.
@ManagedBean
@ViewScoped
public class Bean implements Serializable {
private ZonedDateTime dateTime; // Getter and setter.
private static final long serialVersionUID = 1L;
public Bean() {}
public void action() {
// Do something.
}
}
这将以意想不到的方式工作,如
This will work in an unexpected way as demonstrated in the second last example / middle in the first three code snippets.
具体来说,如果输入 05-Jan-2016 12:00:00 AM + 0530
,它将重新显示 05-Jan-2016 05:30:00 AM IST
,因为原始转换 05- Jan-2016 12:00:00 AM +0530
到 UTC
。
Specifically, if you enter 05-Jan-2016 12:00:00 AM +0530
, it will redisplay 05-Jan-2016 05:30:00 AM IST
because the original conversion of 05-Jan-2016 12:00:00 AM +0530
to UTC
in the converter fails.
从偏移量 +05:30
到 UTC
的本地时区转换,然后从 UTC
回到该时区必须明显地重新显示通过日历组件输入的相同日期时间,这是转换器的基本功能。
Conversion from a local time zone whose offset is +05:30
to UTC
and then conversion from UTC
back to that time zone must obviously redisplay the same date-time as inputted through the calendar component which is the rudimentary functionality of the converter given.
更新:
JPA转换器转换为 java.sql.Timestamp
和 java.time.ZonedDateTime
。
The JPA converter converting to and from java.sql.Timestamp
and java.time.ZonedDateTime
.
import java.sql.Timestamp;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
@Converter(autoApply = true)
public final class JodaDateTimeConverter implements AttributeConverter<ZonedDateTime, Timestamp> {
@Override
public Timestamp convertToDatabaseColumn(ZonedDateTime dateTime) {
return dateTime == null ? null : Timestamp.from(dateTime.toInstant());
}
@Override
public ZonedDateTime convertToEntityAttribute(Timestamp timestamp) {
return timestamp == null ? null : ZonedDateTime.ofInstant(timestamp.toInstant(), ZoneOffset.UTC);
}
}
推荐答案
具体问题是您从Joda的无日期时间实例 DateTime
到Java8的分区日期时间实例 ZonedDateTime
,而不是Java8的zoneless日期时间实例 LocalDateTime
。
Your concrete problem is that you migrated from Joda's zoneless date time instance DateTime
to Java8's zoned date time instance ZonedDateTime
instead of Java8's zoneless date time instance LocalDateTime
.
使用 ZonedDateTime
(或 OffsetDateTime
)而不是 LocalDateTime
需要
-
不要在日期时间转换期间强制设定时区(偏移)。相反,在解析期间将使用输入字符串的时区(如果有),并且在格式化期间必须使用存储在
ZonedDateTime
实例中的时区。
Do not force a time zone (offset) during date time conversion. Instead, the time zone of the input string, if any, will be used during parsing, and the time zone stored in
ZonedDateTime
instance must be used during formatting.
DateTimeFormatter#withZone()
只会给出 ZonedDateTime
在分析期间作为回退(仅在输入字符串或格式模式中缺少时区时使用),并且在格式化期间将作为覆盖(时区存储在 ZonedDateTime
完全忽略)。这是你的可观察问题的根本原因。只要在创建格式化程序时省略 withZone()
就可以修复它。
The DateTimeFormatter#withZone()
will only give confusing results with ZonedDateTime
as it will act as fallback during parsing (it's only used when time zone is absent in input string or format pattern), and it will act as override during formatting (the time zone stored in ZonedDateTime
is entirely ignored). This is the root cause of your observable problem. Just omitting withZone()
while creating the formatter should fix it.
请注意, ,并且没有 timeOnly =true
,那么您不需要指定< p:calendar timeZone>
。即使你这样做,你宁愿使用 TimeZone.getTimeZone(zonedDateTime.getZone())
,而不是硬编码。
Do note that when you have specified a converter, and don't have timeOnly="true"
, then you don't need to specify <p:calendar timeZone>
. Even when you do, you'd rather like to use TimeZone.getTimeZone(zonedDateTime.getZone())
instead of hardcoding it.
您需要在所有图层(包括数据库)上携带时区(偏移)。但是,如果您的数据库具有没有时区的日期时间列类型,那么在持续时间期间时区信息会丢失,并且在从数据库返回时会遇到麻烦。
You need to carry the time zone (offset) along over all layers, including the database. If your database, however, has a "date time without time zone" column type, then the time zone information gets lost during persist and you will run into trouble when serving back from database.
不清楚您使用的是哪个DB,但请记住,一些DB不支持从 TIMESTAMP WITH TIME ZONE 列类型=https://docs.oracle.com/cd/B19306_01/server.102/b14225/ch4datetime.htm> Oracle 和 PostgreSQL DB。例如, MySQL不支持它 。
It's unclear which DB you're using, but keep in mind that some DBs doesn't support a TIMESTAMP WITH TIME ZONE
column type as known from Oracle and PostgreSQL DBs. For example, MySQL does not support it. You'd need a second column.
如果这些更改不可接受,那么您需要回到 LocalDateTime
,并依赖于所有层(包括数据库)中的固定/预定义时区。通常使用UTC。
If those changes are not acceptable, then you need to step back to LocalDateTime
and rely on fixed/predefined time zone throughout all layers, including the database. Usually UTC is used for this.
在使用 ZonedDateTime
时使用适当的 TIMESTAMP WITH TIME ZONE
DB列类型,使用以下JSF转换器在UI中的 String
和 ZonedDateTime
模型。该转换器将从父组件中查找模式
和 locale
属性。如果父组件本身不支持模式
或 locale
属性,只需将它们添加为< f:attribute name =...value =...>
。如果 locale
属性不存在,则将使用(默认)< f:view locale>
。 没有 timeZone
属性,原因如上文#1中所述。
When using ZonedDateTime
with an appropriate TIMESTAMP WITH TIME ZONE
DB column type, use the below JSF converter to convert between String
in the UI and ZonedDateTime
in the model. This converter will lookup the pattern
and locale
attributes from the parent component. If the parent component doesn't natively support a pattern
or locale
attribute, simply add them as <f:attribute name="..." value="...">
. If the locale
attribute is absent, the (default) <f:view locale>
will be used instead. There is no timeZone
attribute for the reason as explained in #1 here above.
@FacesConverter(forClass=ZonedDateTime.class)
public class ZonedDateTimeConverter implements Converter {
@Override
public String getAsString(FacesContext context, UIComponent component, Object modelValue) {
if (modelValue == null) {
return "";
}
if (modelValue instanceof ZonedDateTime) {
return getFormatter(context, component).format((ZonedDateTime) modelValue);
} else {
throw new ConverterException(new FacesMessage(modelValue + " is not a valid ZonedDateTime"));
}
}
@Override
public Object getAsObject(FacesContext context, UIComponent component, String submittedValue) {
if (submittedValue == null || submittedValue.isEmpty()) {
return null;
}
try {
return ZonedDateTime.parse(submittedValue, getFormatter(context, component));
} catch (DateTimeParseException e) {
throw new ConverterException(new FacesMessage(submittedValue + " is not a valid zoned date time"), e);
}
}
private DateTimeFormatter getFormatter(FacesContext context, UIComponent component) {
return DateTimeFormatter.ofPattern(getPattern(component), getLocale(context, component));
}
private String getPattern(UIComponent component) {
String pattern = (String) component.getAttributes().get("pattern");
if (pattern == null) {
throw new IllegalArgumentException("pattern attribute is required");
}
return pattern;
}
private Locale getLocale(FacesContext context, UIComponent component) {
Object locale = component.getAttributes().get("locale");
return (locale instanceof Locale) ? (Locale) locale
: (locale instanceof String) ? new Locale((String) locale)
: context.getViewRoot().getLocale();
}
}
并使用下面的JPA转换器在JDBC中的 ZonedDateTime
之间进行转换(JDBC中的 java.util.Calendar
TIMESTAMP WITH TIME ZONE
键入的列):
And use the below JPA converter to convert between ZonedDateTime
in the model and java.util.Calendar
in JDBC (the decent JDBC driver will require/use it for TIMESTAMP WITH TIME ZONE
typed column):
@Converter(autoApply=true)
public class ZonedDateTimeAttributeConverter implements AttributeConverter<ZonedDateTime, Calendar> {
@Override
public Calendar convertToDatabaseColumn(ZonedDateTime entityAttribute) {
if (entityAttribute == null) {
return null;
}
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(entityAttribute.toInstant().toEpochMilli());
calendar.setTimeZone(TimeZone.getTimeZone(entityAttribute.getZone()));
return calendar;
}
@Override
public ZonedDateTime convertToEntityAttribute(Calendar databaseColumn) {
if (databaseColumn == null) {
return null;
}
return ZonedDateTime.ofInstant(databaseColumn.toInstant(), databaseColumn.getTimeZone().toZoneId());
}
}
在JSF和JPA中处理 LocalDateTime
当使用基于UTC的 LocalDateTime数据库列类型,使用以下JSF转换器在
和之间转换
String LocalDateTime
。此转换器将查找模式
, timeZone
和 locale
属性从父组件。如果父组件本身不支持模式
, timeZone
和/或 locale
属性,只需将它们添加为< f:attribute name =...value =...>
。 timeZone
属性必须表示输入字符串的回退时区(当模式
不包含时区时)和输出字符串的时区。
Dealing with LocalDateTime
in JSF and JPA
When using UTC based LocalDateTime
with an appropriate UTC based TIMESTAMP
(without time zone!) DB column type, use the below JSF converter to convert between String
in the UI and LocalDateTime
in the model. This converter will lookup the pattern
, timeZone
and locale
attributes from the parent component. If the parent component doesn't natively support a pattern
, timeZone
and/or locale
attribute, simply add them as <f:attribute name="..." value="...">
. The timeZone
attribute must represent the fallback time zone of the input string (when the pattern
doesn't contain a time zone), and the time zone of the output string.
@FacesConverter(forClass=LocalDateTime.class)
public class LocalDateTimeConverter implements Converter {
@Override
public String getAsString(FacesContext context, UIComponent component, Object modelValue) {
if (modelValue == null) {
return "";
}
if (modelValue instanceof LocalDateTime) {
return getFormatter(context, component).format(ZonedDateTime.of((LocalDateTime) modelValue, ZoneOffset.UTC));
} else {
throw new ConverterException(new FacesMessage(modelValue + " is not a valid LocalDateTime"));
}
}
@Override
public Object getAsObject(FacesContext context, UIComponent component, String submittedValue) {
if (submittedValue == null || submittedValue.isEmpty()) {
return null;
}
try {
return ZonedDateTime.parse(submittedValue, getFormatter(context, component)).withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime();
} catch (DateTimeParseException e) {
throw new ConverterException(new FacesMessage(submittedValue + " is not a valid local date time"), e);
}
}
private DateTimeFormatter getFormatter(FacesContext context, UIComponent component) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(getPattern(component), getLocale(context, component));
ZoneId zone = getZoneId(component);
return (zone != null) ? formatter.withZone(zone) : formatter;
}
private String getPattern(UIComponent component) {
String pattern = (String) component.getAttributes().get("pattern");
if (pattern == null) {
throw new IllegalArgumentException("pattern attribute is required");
}
return pattern;
}
private Locale getLocale(FacesContext context, UIComponent component) {
Object locale = component.getAttributes().get("locale");
return (locale instanceof Locale) ? (Locale) locale
: (locale instanceof String) ? new Locale((String) locale)
: context.getViewRoot().getLocale();
}
private ZoneId getZoneId(UIComponent component) {
Object timeZone = component.getAttributes().get("timeZone");
return (timeZone instanceof TimeZone) ? ((TimeZone) timeZone).toZoneId()
: (timeZone instanceof String) ? ZoneId.of((String) timeZone)
: null;
}
}
并使用下面的JPA转换器在模型中的 LocalDateTime
和JDBC中的 java.sql.Timestamp
之间进行转换(令人满意的JDBC驱动程序将需要/使用它 TIMESTAMP
键入的列):
And use the below JPA converter to convert between LocalDateTime
in the model and java.sql.Timestamp
in JDBC (the decent JDBC driver will require/use it for TIMESTAMP
typed column):
@Converter(autoApply=true)
public class LocalDateTimeAttributeConverter implements AttributeConverter<LocalDateTime, Timestamp> {
@Override
public Timestamp convertToDatabaseColumn(LocalDateTime entityAttribute) {
if (entityAttribute == null) {
return null;
}
return Timestamp.valueOf(entityAttribute);
}
@Override
public LocalDateTime convertToEntityAttribute(Timestamp databaseColumn) {
if (databaseColumn == null) {
return null;
}
return databaseColumn.toLocalDateTime();
}
}
$ b b
将 LocalDateTimeConverter
应用于您的特定案例< p:calendar>
您需要更改以下内容:
Applying LocalDateTimeConverter
to your specific case with <p:calendar>
You need to change the below:
-
由于
p:calendar>
不会通过forClass
查找转换器,您需要重新注册;或者如下改变注释:
As the
<p:calendar>
doesn't lookup converters byforClass
, you'd either need to re-register it with<converter><converter-id>localDateTimeConverter
infaces-config.xml
, or to alter the annotation as below
@FacesConverter("localDateTimeConverter")
由于没有 timeOnly =true
的< p:calendar>
code> timeZone ,并在弹出窗口中提供一个编辑它的选项,您需要删除 timeZone
属性, (模式
中缺少时区时,才需要此属性)。
As the <p:calendar>
without timeOnly="true"
ignores the timeZone
, and offers in the popup an option to edit it, you need to remove the timeZone
attribute to avoid that the converter gets confused (this attribute is only required when the time zone is absent in the pattern
).
您需要在输出期间指定所需的显示 timeZone
属性(使用 ZonedDateTimeConverter
时不需要此属性,因为它已存储 ZonedDateTime
)。
You need to specify the desired display timeZone
attribute during output (this attribute is not required when using ZonedDateTimeConverter
as it's already stored in ZonedDateTime
).
这是完整的工作片段:
Here's the full working snippet:
<p:calendar id="dateTime"
pattern="dd-MMM-yyyy hh:mm:ss a Z"
value="#{bean.dateTime}"
showOn="button"
required="true"
showButtonPanel="true"
navigator="true">
<f:converter converterId="localDateTimeConverter" />
</p:calendar>
<p:message for="dateTime" autoUpdate="true" />
<p:commandButton value="Submit" update="display" action="#{bean.action}" /><br/><br/>
<h:outputText id="display" value="#{bean.dateTime}">
<f:converter converterId="localDateTimeConverter" />
<f:attribute name="pattern" value="dd-MMM-yyyy hh:mm:ss a Z" />
<f:attribute name="timeZone" value="Asia/Kolkata" />
</h:outputText>
如果您打算创建自己的< my:convertLocalDateTime& / code>的属性,你需要将它们作为类bean的属性添加到转换器类中,并将它们注册到
*。taglib.xml
如此答案中所示:为具有属性的Converter创建自定义标记
<h:outputText id="display" value="#{bean.dateTime}">
<my:convertLocalDateTime pattern="dd-MMM-yyyy hh:mm:ss a Z"
timeZone="Asia/Kolkata" />
</h:outputText>
这篇关于如何在p:calendar中使用java.time.ZonedDateTime / LocalDateTime的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!