我的InstantRenderer 班级
您可以轻松创建自己的渲染器实现。
这是我编写的渲染器,用于处理 Grid 小部件,显示包含 Instant 对象的对象。 Instant 是一个时刻,时间线上的一个特定点,如 UTC 所示(零时分秒的偏移量)。 Instant 类是 java.time 框架中使用的基本构建块类。
这里的想法是我们获取Instant 对象,应用指定的ZoneId 来获得ZonedDateTime 对象。该ZonedDateTime 对象使用指定的DateTimeFormatter 对象在String 对象中生成文本。该文本将ZonedDateTime对象automatically localized的内容表示为指定Locale对象的人类语言和文化规范。
ZoneId 和Locale 附加到调用程序员传递的DateTimeFormatter。
我这里的代码是基于 Vaadin Ltd 公司为他们的LocalDateTimeRenderer 类'source-code found on their GitHub site 发布的代码。
我修剪了那个类的 API。他们的 API 允许传递格式化模式字符串而不是 DateTimeFormatter 对象。我不认为渲染器有责任从这样的字符串生成格式化程序对象,因此也处理任何由此产生的错误条件。他们的 API 允许传递 Locale 对象。 Locale 对象可以附加到调用程序员传递的DateTimeFormatter 对象。我看不出这个渲染器类应该如何不必要地参与将传递的语言环境分配给传递的格式化程序。调用程序可以在将格式化程序传递给我们的渲染器之前完成该分配。
这是定义 InstantRenderer 以渲染 Instant 对象以在 Vaadin 14 中的 Grid 中显示的典型用法。
invoicesGrid
.addColumn(
new InstantRenderer <>( Invoice :: getWhenCreated ,
DateTimeFormatter
.ofLocalizedDateTime( FormatStyle.SHORT , FormatStyle.MEDIUM )
.withLocale( Locale.CANADA_FRENCH )
.withZone( ZoneId.of( "America/Montreal" ) )
)
)
.setHeader( "Created" )
;
以Continent/Region 的格式指定proper time zone name,例如America/Montreal、Africa/Casablanca 或Pacific/Auckland。切勿使用 2-4 个字母的缩写,例如 EST 或 IST,因为它们不是真正的时区,没有标准化,甚至不是唯一的 (!)。
请注意,java.time 类使用immutable objects。 withZone 和 withLocale 方法产生一个新的 DateTimeFormatter 而不是改变原来的。因此,您可能希望保留一个全局单例DateTimeFormatter,并偏好短日期和较长时间。
DateTimeFormatter f = DateTimeFormatter
.ofLocalizedDateTime(
FormatStyle.SHORT , // Length of date portion.
FormatStyle.MEDIUM // Length of time-of-day portion.
)
;
然后在您的代码的其他地方,应用每个用户自己的首选区域和语言环境。您会得到另一个专用的DateTimeFormatter 对象,而由于java.time 中使用的不可变对象模式,原始对象不受影响。
invoicesGrid
.addColumn(
new InstantRenderer <>( Invoice :: getWhenCreated ,
f
.withLocale( user.getPreferredLocale() )
.withZone( user.getPreferredZone() )
)
)
.setHeader( "Created" )
;
顺便说一句,构造函数还有第三个可选参数:String 用于在被渲染的Instant 对象为空的情况下使用。默认是不向用户显示任何文本,一个空的"" 字符串。如果您愿意,您可以传递一些其他字符串,例如null 或void。
这是我班级的源代码。请注意,我在顶部附近的 Javadoc 中进行了一些讨论。
我使用与 Vaadin Ltd 相同的 Apache License 2,因此您可以自己使用和更改此代码。欢迎您的反馈。
package work.basil.example.ui;
/*
* Copyright 2000-2020 Vaadin Ltd.
* Copyright 2020 Basil Bourque.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Locale;
import java.util.Objects;
import com.vaadin.flow.data.renderer.BasicRenderer;
import com.vaadin.flow.function.ValueProvider;
/*
* This class is based on source-code directly copied from
* `LocalDateTimeRenderer.java` of Vaadin 14.1.x
* as written and published by Vaadin Ltd. from their GitHub page.
*
* https://github.com/vaadin/flow/blob/master/flow-data/src/main/java/com/vaadin/flow/data/renderer/LocalDateTimeRenderer.java
*
* I re-purposed that class to handle `Instant` objects rather than `LocalDateTime`
* objects. An `Instant` represents a moment, whereas `LocalDateTime` cannot because
* of it lacking any concept of time zone or offset-from-UTC. In contrast, `Instant`
* represents a moment in UTC (an offset-from-UTC of zero hours-minutes-seconds).
*
* By default, a `Instant` object renders in Vaadin by way of its `toString` method
* generating text in standard ISO 8601 format YYYY-MM-DDTHH:MM:SS.SSSSSSSSSZ.
*
* If you want other than ISO 8601 format in UTC, use this class. In this class, we
* apply a time zone (`ZoneId`) to the `Instant` to adjust from UTC.
*
* The `ZoneId` object comes from one of three places:
* - Passed implicitly by being set as a property on a `DateTimeFormatter`
* object passed as an argument. This is the best case.
* - Defaults to calling `ZoneId.systemDefault` if not found
* on the `DateTimeFormatter` object (where `getZone` returns null).
*
* I deleted the constructors taking a formatting pattern string. Parsing such a string
* and instantiating a `DateTimeFormatter` and handling resulting error conditions
* should *not* be the job of this class. I believe the Vaadin team made a poor choice
* in having constructors taking a string formatting pattern rather than just a
* `DateTimeFormatter` object.
*
* Locale is another critical issue. A `Locale` object determines:
*
* (a) The human language used for translating items such as name of month and
* name of day.
*
* (b) The cultural norms used in deciding localization issues such as the ordering
* of elements (ex: day comes before or after month), abbreviation, capitalization,
* punctuation, and so on.
*
* Again, I deleted the constructors taking a `Locale` object. The `DateTimeFormatter`
* object passed by the calling programmer carries a `Locale`. That calling programmer
* should have attached their intended locale object to that `DateTimeFormatter` object
* by calling `DateTimeFormatter::withLocale`. Usually a `DateTimeFormatter` has a default
* `Locale` assigned. But if found lacking, here we attach the JVM’s current default locale.
*
* Following the logic discussed above, I chose to not take a `ZoneId` as an argument.
* A `ZoneId` can be attached to the `DateTimeFormatter` by calling `withZoneId`.
* If the passed `DateTimeFormatter` is found lacking, here we attach the JVM’s current
* default time zone.
*
* Typical usage, passing 2 arguments, a method reference and a `DateTimeFormatter` object
* while omitting 3rd optional argument for null-representation to go with an blank empty string:
*
* myGrid
* .addColumn(
* new InstantRenderer <>( TheBusinessObject :: getWhenCreated ,
* DateTimeFormatter
* .ofLocalizedDateTime( FormatStyle.SHORT , FormatStyle.MEDIUM )
* .withLocale( Locale.CANADA_FRENCH )
* .withZone( ZoneId.of( "America/Montreal" ) )
* )
* )
*
* This code is written for Java 8 or later.
*
* For criticisms and suggestions, contact me via LinkedIn at: basilbourque
*/
/**
* A template renderer for presenting {@code Instant} objects.
*
* @param <SOURCE> the type of the input item, from which the {@link Instant}
* is extracted
* @author Vaadin Ltd
* @since 1.0.
*/
public class InstantRenderer < SOURCE >
extends BasicRenderer < SOURCE, Instant >
{
private DateTimeFormatter formatter;
private String nullRepresentation;
/**
* Creates a new InstantRenderer.
* <p>
* The renderer is configured to render with the format style
* {@code FormatStyle.LONG} for the date and {@code FormatStyle.SHORT} for
* time, with an empty string as its null representation.
*
* @param valueProvider the callback to provide a {@link Instant} to the
* renderer, not <code>null</code>
* @see <a href=
* "https://docs.oracle.com/javase/8/docs/api/java/time/format/FormatStyle.html#LONG">
* FormatStyle.LONG</a>
* @see <a href=
* "https://docs.oracle.com/javase/8/docs/api/java/time/format/FormatStyle.html#SHORT">
* FormatStyle.SHORT</a>
*/
public InstantRenderer (
ValueProvider < SOURCE, Instant > valueProvider )
{
this(
valueProvider ,
DateTimeFormatter
.ofLocalizedDateTime( FormatStyle.LONG )
.withZone( ZoneId.systemDefault() )
.withLocale( Locale.getDefault() ) ,
""
);
}
/**
* Creates a new InstantRenderer.
* <p>
* The renderer is configured to render with the given formatter, with the
* empty string as its null representation.
*
* @param valueProvider the callback to provide a {@link Instant} to the
* renderer, not <code>null</code>
* @param formatter the formatter to use, not <code>null</code>
*/
public InstantRenderer (
ValueProvider < SOURCE, Instant > valueProvider ,
DateTimeFormatter formatter
)
{
this(
valueProvider ,
formatter ,
""
);
}
/**
* Creates a new InstantRenderer.
* <p>
* The renderer is configured to render with the given formatter.
*
* @param valueProvider the callback to provide a {@link Instant} to the
* renderer, not <code>null</code>
* @param formatter the formatter to use, not <code>null</code>
* @param nullRepresentation the textual representation of the <code>null</code> value
*/
public InstantRenderer (
final ValueProvider < SOURCE, Instant > valueProvider ,
final DateTimeFormatter formatter ,
final String nullRepresentation
)
{
super( valueProvider );
this.formatter = Objects.requireNonNull( formatter , "formatter may not be null" );
this.nullRepresentation = Objects.requireNonNull( nullRepresentation , "null-representation may not be null" );
// If the formatter provided by the calling programmer lacks a time zone, apply the JVM's current default zone.
// This condition is less than ideal. The calling programmer should have set an appropriate zone.
// Often the appropriate zone is one specifically chosen or confirmed by the user.
if ( Objects.isNull( this.formatter.getZone() ) )
{
this.formatter = this.formatter.withZone( ZoneId.systemDefault() );
}
// If the formatter provided by the calling programmer lacks a locale, apply the JVM's current default locale.
// This condition is less than ideal. The calling programmer should have set an appropriate locale.
// Often the appropriate locale is one specifically chosen or confirmed by the user.
if ( Objects.isNull( this.formatter.getLocale() ) )
{
this.formatter = this.formatter.withLocale( Locale.getDefault() );
}
}
@Override
protected String getFormattedValue ( final Instant instant )
{
// If null, return the null representation.
// If not null, adjust the `Instant` from UTC into the time zone attached to the `DateTimeFormatter` object.
// This adjustment, made by calling `Instant::atZone`, produces a `ZonedDateTime` object.
// We then create a `String` with text representing the value of that `ZonedDateTime` object.
// That text is automatically localized per the `Locale` attached to the `DateTimeFormatter` object.
String s = Objects.isNull( instant ) ? nullRepresentation : formatter.format( instant.atZone( this.formatter.getZone() ) );
return s;
}
}
也许我以后可以对问题中列出的其他 java.time 类型执行类似的操作。