Рендерер Vaadin Flow для типов даты и времени java .time, кроме классов LocalDateTime и LocalDate - PullRequest
3 голосов
/ 20 января 2020

В Vaadin Flow версии 14.1 я нахожу только две реализации средства визуализации для типов даты и времени:

Первый предназначен для значения только для даты в классе LocalDate, без времени суток и без часового пояса. Это хорошо.

Второй - для LocalDateTime класса, представляющего дату с временем суток, но намеренно не имеющего контекста часового пояса или смещение от UT C. Достаточно хорошо.

Проблема в том, что я не могу найти других средств визуализации для нескольких других типов данных java .time . Вот диаграмма, которую я сделал для различных типов даты и времени, современные типы java .time , а также унаследованные классы даты и времени, которые они вытесняют, и список SQL - стандартные эквивалентные типы данных.

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

В частности, в бизнес-приложениях мы склонны использовать LocalDateTime реже, в основном для бронирования будущих встреч при определении часовые пояса могут быть изменены политиками (что, как было показано, они делают довольно часто по всему миру). Этот LocalDateTime класс не может представлять момент. Например, возьмем 23 января этого года в 3 часа дня. Без учета часового пояса или смещения от UT C мы не знаем, означает ли это 15:00 в Токио, Япония, 15:00 в Тулузе, Франция или 15:00 в Толедо, Огайо, США - три совершенно разных момента по несколько часов обособленно.

Чтобы представить момент, мы должны использовать классы Instant, OffsetDateTime или ZonedDateTime. Instant - это момент в UT C, всегда в UT C по определению. OffsetDateTime представляет момент со смещением от-UT C некоторого количества часов-минут-секунд. ZonedDateTime - это момент, который виден через часы настенного времени, используемые людьми определенного региона, часового пояса. Такой часовой пояс - это история прошлых, настоящих и будущих изменений смещения, используемого в этом регионе.

provide Предоставляет ли Vaadin 14 средства визуализации для любого из этих других типов? Если нет, есть ли обходной путь или способ сделать рендер?

1 Ответ

0 голосов
/ 20 января 2020

My InstantRenderer class

Вы можете легко создать свою собственную реализацию средства визуализации.

Вот средство визуализации, которое я написал для обработки виджета Grid, отображающего объекты, которые включают Instant объект. Instant - это момент, конкретная c точка на временной шкале, как видно из UT C (смещение нуля часов-минут-секунд). Класс Instant - это базовый класс строительных блоков c, используемый в java .time framework.

Идея в том, что мы берем объект Instant, применяем указанный ZoneId для получения объекта ZonedDateTime. Этот объект ZonedDateTime использует указанный объект DateTimeFormatter для генерации текста в String объекте. Текст представляет собой содержимое ZonedDateTime объекта , автоматически локализованного для указанного Locale человеческого языка и культурных норм объекта.

imageDateTimeFormatter object provides a ZoneId object used by the InstantRenderer to produce a ZonedDateTime which in turn uses the DateTimeFormatter to generate automatically localized text for presentation to the user">

ZoneId и Locale присоединяются к DateTimeFormatter, передаваемому вызывающим программистом.

Мой код основан на коде, опубликованном компанией Vaadin Ltd для их LocalDateTimeRenderer class ' исходного кода, найденного на их сайте GitHub .

Я сократил API этого класса. Их API позволял передавать строку шаблона форматирования, а не объект DateTimeFormatter. Я не верю, что это должно быть обязанностью средства визуализации создавать объект форматирования из такой строки и, следовательно, также обрабатывать любые возникающие условия ошибки. И их API позволяет передавать объект Locale. Объект Locale может быть присоединен к объекту DateTimeFormatter, передаваемому вызывающим программистом. Я не понимаю, как этот класс рендерера должен без необходимости участвовать в назначении переданного языкового стандарта переданному форматирующему устройству. Вызывающее программирование может выполнить это назначение перед передачей форматера нашему рендереру.

Вот типичное использование при определении InstantRenderer для рендеринга Instant объекта для отображения в пределах Grid в Vaadin 14.

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, например America/Montreal, Africa/Casablanca или Pacific/Auckland. Никогда не используйте 2-4 буквенные сокращения, такие как EST или IST, поскольку они не истинных часовых поясов, не стандартизированы и даже не уникальны (!).

Имейте в виду, что классы java .time используют неизменяемые объекты . Методы withZone и withLocale производят новый fre sh 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 равен нулю. По умолчанию пользователь вообще не представляет текст, пустая строка "". Вы можете передать некоторую другую строку, если у вас sh, например null или void.

А вот и исходный код моего класса. Обратите внимание, что я поместил некоторые обсуждения в Javado c в верхней части.

Я использую ту же лицензию Apache 2, что и Vaadin Ltd, поэтому вы можете использовать и изменять этот код для себя. Ваши отзывы приветствуются.

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 типов, перечисленных в Вопросе.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...