TimeField DateTimeParseException - Datatype Format Strings bug

Hello Everyone

I have encountered a known bug/issue and need more information about when I can expect a permanent resolution.

Operating System: macOS Catalina 10.15.7 (19H2)
Browser: Safari 14.0 (15610.1.28.1.9, 15610)
File System: Case-Sensitive Journaled HFS+ (APFS)
Datebase: MySQL 8.0.19

CUBA Platform version: 7.2.9
CUBA Studio plugin version: 14.0-193
IntelliJ version: CUBA Studio 2019.3

Use Case: Internationalization using various locales and UI TimeField usage.

Background & Problem: When I use the following Datatype Format String in the Main Message Pack for my English|en locale…

timeFormat = hh:mm a

I receive this exception during data entry “DateTimeParseException: Text ‘02:03 A’ could not be parsed at index 6”.

Searching the forum I found the following post that already describes this problem and a possible workaround…

DateTime Patterns

with the reference to this issue:

Support 12 hours format for TimeField #2347

I have downloaded and tested the “cuba-timefield-ampm-demo-master” application from issue 2347, however, I do not find the new TimeMode methods or format strings anywhere else; they appear to only be in the 7.2-SNAPSHOT version in this demo. Furthermore, this implementation would require checking for each locale in the screen code for each TimeField, which is not desirable. It is certainly nice to have more flexibility in the UI code but I would prefer to set the formats in the Main Message Pack as originally intended.

Can you please tell me what the status of this situation is, e.g. what will be implemented and when?

Many thanks in advance.

Best regards
Chris

Hello,

as you noticed, timeFormat with AM/PM resolution is not supported by TimeField but provides API for enabling time mode (12h and 24h). It is available since the 7.2.0 version.

Considering your case, I can suggest you create custom Datatype and custom ComponentGenerationStrategy. It will generate component for Form, Table, DataGrid depending on Datatype and locale.

For instance:

  1. Custom Datatype:
    @JavaClass(Date.class)
    public class CustomTimeDatatype extends TimeDatatype implements Datatype<Date> {
    
       public CustomTimeDatatype(Element element) {
           super(element);
       }
    }
    
  2. Custom ComponentGenerationStrategy:
    @Nullable
    @Override
    public Component createComponent(ComponentGenerationContext context) {
        MetaClass metaClass = context.getMetaClass();
        MetaProperty metaProperty = metaClass.getPropertyNN(context.getProperty());
    
        if (metaProperty.getRange().isDatatype()) {
            Datatype datatype = metaProperty.getRange().asDatatype();
            if (datatype instanceof CustomTimeDatatype) {
                TimeField timeField = uiComponents.create(TimeField.NAME);
    
                Locale locale = userSessionSource.getLocale();
                if (locale == Locale.ENGLISH) {
                    timeField.setTimeMode(TimeField.TimeMode.H_12);
                }
    
                ValueSource valueSource = context.getValueSource();
                if (valueSource != null) {
                    timeField.setValueSource(valueSource);
                }
                return timeField;
            }
        }
        return null;
    }
    

Full example: ftimefield.zip (86.1 KB)

@Pinyazhin
Hello Roman, Thank you very much for the valuable information and all of your efforts! After reading your comment “It is available since the 7.2.0 version.” I was very confused, because I could not find the API while experimenting with a petclinic example before I posted my problem. I went back and checked everything and found that I had had two petclinic examples open, one with version 7.2.9 and one with 7.1.6 and unfortunately, I was looking for the API in the 7.1.6 version. That’s why I could not find it originally and had thought that it was only available in the 7.2-SNAPSHOT. My mistake! I’ll be more careful next time.

Your custom Datatype is extremely interesting and I will definitely consider this. I was also thinking of creating a special Distance field to either use kilometers or miles dependent upon the locale and this method might also solve that situation. I must give it some more thought and thoroughly understand all of the implications.

I will update this post as soon as I have decided upon the final solution.

Best regards
Chris

@Pinyazhin
Hello Roman, I have spent some more time with your ftimefield example (thanks again for that) and with the TimeField API (TimeMode.H_12, TimeMode.H_24) and here is what I have found…

ftimefield Example - Problem: If the displayed “customTime” is “02:44 PM” and I remove the “editable=true” attribute in your order-browse.xml file, then the column display reverts back to a HH:mm format and displays “14:44”. I would expect a non-editable field displaying “02:44 PM”. There must be an internal side-effect preventing the correct display; this appears to be a bug. However, setting the customTimeField to editable=“false" in the order-edit.xml works as expected.

TimeMode_H_12_editable-false TimeMode_H_12_editable-true

Furthermore, your CustomTimeDatatype.java file does not appear in my CUBA project view; it is only shown when I select the Packages view. Even after invalidating the cache and restarting, it does not appear in the CUBA entity tree structure. This may be a known bug in CUBA Studio or something in the studio settings that I am missing. Is CustomTimeDatatype.java displayed correctly in your project? DO you know how I can correct this?

CustomTimeDatatype.java_Found CustomTimeDatatype.java_Not-Found

Pet Clinic Example - Problem: I modified the “cuba-petclinic-i18n-messages-master” example to include the time with the following attribute…

@ Temporal(TemporalType. TIME )
@ NotNull
@ Column(name = “VISIT_TIME” , nullable = false )
private Date visitTime;

and here is what I have found….

If I want to display the local time in the browse column dependent upon the locale, then I need to set the Datatype format to e.g. “timeFormat=hh:mm a” in the “Main Message Pack”, but when I do this, this format is also used in the TimeField in the visit-edit.xml screen even when the TimeMode is explicitly set to H_12. So in my edit screen the TimeField display looks like this “02:44 P PM” and this causes an error when the input is validated.

TimeMode_H_12_with_Format_in_MainMsgPack_Error TimeMode_H_12_with_Format_in_MainMsgPack

If I remove the “timeFormat=hh:mm a” from the “Main Message Pack” and add the following formatter to the screen.xml file, then the display is static for only one selected locale.

< column id =“visitTime” >
< formatter class ="com.haulmont.cuba.gui.components.formatters.DateFormatter"
format ="hh:mm a"
useUserTimezone =“true” />
</ column >

Unfortunately, I have not found a description in the documentation or the forum how to programmatically alter/exchange this formatter at runtime.

I had the idea to use the (see above) for one column and then add a second column with a different formatter for format=“HH:mm” and programmatically set one of them to “visible=false” depending upon the locale, because I really only need two: “HH:mm” and “hh:mm a”. But since the column does not have a property field and uses its id to determine the attribute, it is no possible to add a second column. Doing this generates a runtime error.

I therefore decided to try a variant of this idea for testing purposes and added a second customer column for the format=“HH:mm” using the following code in the VisitBrowse screen controller:

@ Install(to = “visitsTable.visitTimeHHmm” , subject = “columnGenerator” )
private Component visitsTableVisitTimeHHmmColumnGenerator(Visit visit) {

Label label = uiComponents.create(Label. NAME );
String visitTimeStr = visit.getVisitTime().toString();
CharSequence charSequence = (CharSequence) visitTimeStr;
LocalDateTime localDateTime = LocalDateTime. parse (charSequence, DateTimeFormatter. ofPattern ( “HH:mm” ));
label.setValue(localDateTime.toString());
return label;
}

I think that this is probably the right idea but this method however generates a parsing error currently:

DateTimeParseException: Text ‘Thu Jan 01 14:04:00 CET 1970’ could not be parsed at index 0

and I have not yet been able to find a solution to this. This is my first experience with formatters in Java, so I am certainly doing something wrong. Please note, that I have tried several variations of this formatter, respectively, this method of formatting but I have yet to find the right combination.

Do you have any suggestions, how I can best solve this current problem?

Best regards
Chris

Hello Chris,

CustomTimeDatatype does not override methods for parsing and formatting values. I reworked ftimefield project and changed CustomTimeDatatype with LocalTime type to simplify parsing and formatting.
ftimefield.zip (86.5 KB)

Yes, Studio does not show custom datatypes. This is a known issue that has not been resolved yet.

TimeField does not support formats with AM/PM. It uses format from timeFormat for creating the mask. DateTimeFormatter for format hh:mm a requires two symbols of AM or PM, e.g. "09:38 AM". The wrong value is 09:38 A.

Information about formatters you can find in the documentation. For instance,

@Subscribe
public void onInit(InitEvent event) {
    visitsTable.getColumn("visitTime").setFormatter(o -> {
        if (o == null) {
            return "";
        }
        Locale locale = userSessionSource.getLocale();
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern(messages.getMainMessage("timeFormat", locale));
        return dtf.format((LocalTime) o);
    });
}

Check this code for your column generator:

@Install(to = "visitsTable.visitTimeHHmm", subject = "columnGenerator")
private Component visitsTableVisitTimeHHmmColumnGenerator(Visit visit) {
    Label label = uiComponents.create(Label.NAME);
    LocalTime visitTime = visit.getVisitTime();
    String value = visitTime.format(DateTimeFormatter.ofPattern("HH:mm"));
    label.setValue(value);
    return label;
}

@Pinyazhin
Hello Roman, Many many thanks! Your information and new examples have definitely cleared up several points that I was struggling with and I am confident that I can now find an appropriate solution.

For your information, I did find one source of my confusion that I had not realized before. In the Entity Designer when you choose the “Time” DATATYPE, it actually assigns “Date” to the variable, whereas choosing a “LocalTime” DATATYPE assigns “LocalTime” to the variable.




This resulted in errors while I was coding and caused me to try other functions, that ultimately did not work and sent me in the wrong direction.

I also realized that I did consult the formatters documentation that you referred to but I was lost in all of the possiblilities at the time and did not understand it well enough.

I will put some more effort into better understanding this situation and these configurations. Thanks again!

Best regards
Chris