View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.html;
16  
17  import java.time.LocalTime;
18  import java.time.format.DateTimeFormatter;
19  import java.time.format.DateTimeFormatterBuilder;
20  import java.time.format.DateTimeParseException;
21  import java.time.temporal.ChronoField;
22  import java.time.temporal.TemporalAccessor;
23  import java.util.Locale;
24  import java.util.Map;
25  
26  import org.apache.commons.lang3.StringUtils;
27  import org.htmlunit.BrowserVersion;
28  import org.htmlunit.SgmlPage;
29  
30  /**
31   * Wrapper for the HTML element "input" where type is "time".
32   *
33   * @author Ahmed Ashour
34   * @author Frank Danek
35   * @author Anton Demydenko
36   * @author Ronald Brill
37   */
38  public class HtmlTimeInput extends HtmlSelectableTextInput implements LabelableElement {
39  
40      private static final DateTimeFormatter INPUT_FORMATTER_ =
41              new DateTimeFormatterBuilder()
42                      .parseCaseInsensitive()
43                      .appendPattern("hh[:]mm[a]")
44                      .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
45                      .parseDefaulting(ChronoField.AMPM_OF_DAY, 0)
46                      .toFormatter(Locale.US);
47      private static final DateTimeFormatter OUTPUT_FORMATTER_ = DateTimeFormatter.ofPattern("HH:mm");
48  
49      /**
50       * Creates an instance.
51       *
52       * @param qualifiedName the qualified name of the element type to instantiate
53       * @param page the page that contains this element
54       * @param attributes the initial attributes
55       */
56      HtmlTimeInput(final String qualifiedName, final SgmlPage page,
57              final Map<String, DomAttr> attributes) {
58          super(qualifiedName, page, attributes);
59      }
60  
61      /**
62       * {@inheritDoc}
63       */
64      @Override
65      public void setDefaultChecked(final boolean defaultChecked) {
66          // Empty.
67      }
68  
69      /**
70       * {@inheritDoc}
71       */
72      @Override
73      public String getValue() {
74          String raw = getRawValue();
75          if (StringUtils.isBlank(raw)) {
76              return "";
77          }
78  
79          raw = raw.trim();
80          try {
81              final TemporalAccessor time = INPUT_FORMATTER_.parse(raw);
82              return OUTPUT_FORMATTER_.format(time);
83          }
84          catch (final DateTimeParseException ignored) {
85              // ignore
86          }
87          return "";
88      }
89  
90      /**
91       * {@inheritDoc}
92       */
93      @Override
94      public boolean isValid() {
95          return super.isValid() && isMaxValid() && isMinValid();
96      }
97  
98      /**
99       * Returns if the input element has a valid min value. Refer to the
100      * <a href="https://www.w3.org/TR/html5/sec-forms.html">HTML 5</a>
101      * documentation for details.
102      *
103      * @return if the input element has a valid min value
104      */
105     private boolean isMinValid() {
106         if (!getMin().isEmpty()) {
107             try {
108                 final LocalTime timeValue = LocalTime.parse(getRawValue(), INPUT_FORMATTER_);
109                 final LocalTime minTime = LocalTime.parse(getMin(), INPUT_FORMATTER_);
110                 return minTime.equals(timeValue) || minTime.isBefore(timeValue);
111             }
112             catch (final DateTimeParseException ignored) {
113                 // ignore
114             }
115         }
116         return true;
117     }
118 
119     /**
120      * Returns if the input element has a valid max value. Refer to the
121      * <a href="https://www.w3.org/TR/html5/sec-forms.html">HTML 5</a>
122      * documentation for details.
123      *
124      * @return if the input element has a valid max value
125      */
126     private boolean isMaxValid() {
127         if (!getMax().isEmpty()) {
128             try {
129                 final LocalTime timeValue = LocalTime.parse(getRawValue(), INPUT_FORMATTER_);
130                 final LocalTime maxTime = LocalTime.parse(getMax(), INPUT_FORMATTER_);
131                 return maxTime.equals(timeValue) || maxTime.isAfter(timeValue);
132             }
133             catch (final DateTimeParseException ignored) {
134                 // ignore
135             }
136         }
137         return true;
138     }
139 
140     @Override
141     protected void adjustValueAfterTypeChange(final HtmlInput oldInput, final BrowserVersion browserVersion) {
142         setRawValue("");
143     }
144 }