【问题标题】:Vaadin Flow renderer for java.time date-time types beyond just LocalDateTime & LocalDate classes用于 java.time 日期时间类型的 Vaadin Flow 渲染器,不仅限于 LocalDateTime 和 LocalDate 类
【发布时间】:2020-05-06 03:04:35
【问题描述】:

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

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

第二个是LocalDateTime 类,表示带有时间的日期,但故意缺少time zoneoffset-from-UTC 的上下文。够了。

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

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

要表示一个时刻,我们必须使用InstantOffsetDateTimeZonedDateTime 类。 InstantUTC 中的一个时刻,根据定义总是在 UTC 中。 OffsetDateTime 表示与 UTC 有一定小时-分钟-秒数的偏移量的时刻。 ZonedDateTime 是通过特定地区(时区)的人们使用的挂钟时间看到的时刻。这样的时区是该地区使用的偏移量的过去、现在和未来变化的历史记录。

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

【问题讨论】:

    标签: java vaadin java-time vaadin-flow dateformatter


    【解决方案1】:

    我的InstantRenderer 班级

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

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

    这里的想法是我们获取Instant 对象,应用指定的ZoneId 来获得ZonedDateTime 对象。该ZonedDateTime 对象使用指定的DateTimeFormatter 对象在String 对象中生成文本。该文本将ZonedDateTime对象automatically localized的内容表示为指定Locale对象的人类语言和文化规范。

    ZoneIdLocale 附加到调用程序员传递的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/MontrealAfrica/CasablancaPacific/Auckland。切勿使用 2-4 个字母的缩写,例如 ESTIST,因为它们不是真正的时区,没有标准化,甚至不是唯一的 (!)。

    请注意,java.time 类使用immutable objectswithZonewithLocale 方法产生一个新的 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 对象为空的情况下使用。默认是不向用户显示任何文本,一个空的"" 字符串。如果您愿意,您可以传递一些其他字符串,例如nullvoid

    这是我班级的源代码。请注意,我在顶部附近的 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 类型执行类似的操作。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-04-21
      • 1970-01-01
      • 2018-01-25
      • 2020-02-05
      • 1970-01-01
      • 2022-01-06
      • 2021-11-10
      相关资源
      最近更新 更多