java - 用于 java.time 日期时间类型的 Vaadin Flow 渲染器,不仅限于 LocalDateTime 和 LocalDate 类

标签 java vaadin java-time vaadin-flow dateformatter

在 Vaadin Flow 14.1 版中,我发现只有两个日期时间类型的渲染器实现:

  • LocalDateRenderer
  • LocalDateTimeRenderer

  • 第一个是 LocalDate 中的仅日期值。类,没有时间和时区。那很好。

    第二个是 LocalDateTime 表示带有时间的日期的类,但故意缺少 time zone 的上下文或 offset-from-UTC .足够好。

    问题是我找不到其他几种 java.time 数据类型的其他渲染器。这是我制作的各种日期时间类型、现代 java.time 类型以及它们取代的旧日期时间类的图表,以及 SQL 标准等效数据类型的列表。

    Table of date-time types in Java (both legacy and modern) and in standard SQL

    具体来说,在商业应用程序中,我们倾向于使用 LocalDateTime不太频繁,主要是在政治家可以更改时区的定义时(他们已被证明在世界各地经常这样做)来预订 future 的约会。那LocalDateTime类不能代表片刻。例如,以今年 1 月 23 日下午 3 点为例。如果没有时区或与 UTC 偏移的上下文,我们不知道这是否意味着日本东京的下午 3 点、法国图卢兹的下午 3 点或美国俄亥俄州托莱多的下午 3 点——三个非常不同的时刻相隔几个小时。

    要表示一个时刻,我们必须使用 Instant , OffsetDateTime , 或 ZonedDateTime 类。一个 InstantUTC中的片刻, 根据定义,始终为 UTC。一个 OffsetDateTime表示与 UTC 的偏移量为几小时-分钟-秒的时刻。一个 ZonedDateTime是通过特定地区的人们使用的挂钟时间看到的时刻,一个时区。这样的时区是该地区使用的偏移量的过去、现在和 future 变化的历史。

    ➥ Vaadin 14 是否为这些其他类型提供渲染器?如果没有,是否有解决方法或制作渲染器的方法?

    最佳答案

    我的 InstantRenderer类(class)

    您可以轻松创建自己的渲染器实现。

    这是我为处理 Grid 而编写的渲染器显示包含 Instant 的对象的小部件目的。一个 Instant是一个时刻,时间线上的一个特定点,如 UTC 所示(零小时-分钟-秒的偏移量)。 Instant class 是 java.time 框架中使用的基本构建 block 类。

    这里的想法是我们采用 Instant对象,应用指定的 ZoneId 获取 ZonedDateTime 目的。那ZonedDateTime对象使用指定的 DateTimeFormatter String 中生成文本的对象目的。文本代表 ZonedDateTime 的内容对象 automatically localized到指定 Locale 对象的人类语言和文化规范。

    diagram showing how the <code>DateTimeFormatter</code> object provides a <code>ZoneId</code> object used by the <code>InstantRenderer</code> to produce a <code>ZonedDateTime</code> which in turn uses the <code>DateTimeFormatter</code> to generate automatically localized text for presentation to the user
    ZoneIdLocale附在 DateTimeFormatter由调用程序员传递。

    我的代码是基于 Vaadin Ltd 公司为他们的 LocalDateTimeRenderer 发布的代码。类(class)’ source-code found on their GitHub site .

    我修剪了那个类的 API。他们的 API 允许传递格式化模式字符串而不是 DateTimeFormatter。目的。我不认为渲染器有责任从这样的字符串生成格式化程序对象,因此也处理任何由此产生的错误条件。他们的 API 允许通过 Locale目的。 Locale对象可以附加到 DateTimeFormatter调用程序员传递的对象。我看不出这个渲染器类应该如何不必要地参与将传递的语言环境分配给传递的格式化程序。调用程序可以在将格式化程序传递给我们的渲染器之前完成该分配。

    这是定义 InstantRenderer 的典型用法。用于渲染 InstantGrid 中显示的对象在瓦丁 14.

    invoicesGrid
            .addColumn(
                    new InstantRenderer <>( Invoice :: getWhenCreated ,
                            DateTimeFormatter
                                    .ofLocalizedDateTime( FormatStyle.SHORT , FormatStyle.MEDIUM )
                                    .withLocale( Locale.CANADA_FRENCH )
                                    .withZone( ZoneId.of( "America/Montreal" ) )
                    )
            )
            .setHeader( "Created" )
    ;
    

    指定 proper time zone name格式为 Continent/Region ,如 America/Montreal , Africa/Casablanca , 或 Pacific/Auckland .切勿使用 2-4 个字母的缩写,例如 ESTIST因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。

    请注意 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 中使用的不可变对象(immutable对象)模式,原始对象不受影响。
    invoicesGrid
            .addColumn(
                    new InstantRenderer <>( Invoice :: getWhenCreated ,
                            f
                                    .withLocale( user.getPreferredLocale()  )
                                    .withZone( user.getPreferredZone() )
                    )
            )
            .setHeader( "Created" )
    ;
    

    顺便说一句,构造函数还有第三个可选参数:a StringInstant 的情况下使用正在渲染的对象为空。默认是不向用户显示任何文本,一个空的 ""字符串。如果您愿意,可以传递一些其他字符串,例如 nullvoid .

    这是我类(class)的源代码。请注意,我在顶部附近的 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 类型做类似的事情。

    关于java - 用于 java.time 日期时间类型的 Vaadin Flow 渲染器,不仅限于 LocalDateTime 和 LocalDate 类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59817621/

    相关文章:

    java - PSI 和索引不匹配 : PSI and index do not match on Android Studio

    Java插入排序: Counting Data Comparisons

    java - Spring 启动 : Autowired fields are none

    java - 在 Java 中定义一些特定的日期格式化程序作为枚举的成员

    java - 将包含阿拉伯字符的字符串转换为日期

    java - 为什么Java序列化占用这么大的空间?

    java - 如何从数据库 View /刷新实体重新加载内容?

    java - 立即同步 Vaadin 中的两个表

    java - 当某些条件不成立时中断文件上传

    java - JAX-RS 中的 @DefaultValue 日期为 : now() and MAX