1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package dev.metaschema.core.metapath.function.library;
7   
8   import java.util.List;
9   import java.util.Set;
10  
11  import dev.metaschema.core.metapath.DynamicContext;
12  import dev.metaschema.core.metapath.MetapathConstants;
13  import dev.metaschema.core.metapath.function.FunctionUtils;
14  import dev.metaschema.core.metapath.function.IArgument;
15  import dev.metaschema.core.metapath.function.IFunction;
16  import dev.metaschema.core.metapath.item.IItem;
17  import dev.metaschema.core.metapath.item.ISequence;
18  import dev.metaschema.core.metapath.item.atomic.IStringItem;
19  import dev.metaschema.core.metapath.item.atomic.ITimeItem;
20  import dev.metaschema.core.util.ObjectUtils;
21  import edu.umd.cs.findbugs.annotations.NonNull;
22  import edu.umd.cs.findbugs.annotations.Nullable;
23  
24  /**
25   * Implements the XPath 3.1 <a href=
26   * "https://www.w3.org/TR/xpath-functions-31/#func-format-time">fn:format-time</a>
27   * functions.
28   *
29   * @see <a href=
30   *      "https://www.w3.org/TR/xpath-functions-31/#func-format-time">XPath 3.1
31   *      fn:format-time</a>
32   */
33  public final class FnFormatTime {
34    private static final String NAME = "format-time";
35  
36    /**
37     * The set of component specifiers allowed for time values, which excludes
38     * date-only markers (Y, M, D, d, F, W, w) and era (E), since era is defined as
39     * "a baseline for the numbering of years" and is not available in
40     * {@code xs:time} values.
41     */
42    @NonNull
43    static final Set<Character> TIME_MARKERS = Set.of(
44        'H', 'h', 'P', 'm', 's', 'f',
45        'Z', 'z', 'C');
46  
47    @NonNull
48    static final IFunction SIGNATURE_TWO_ARG = IFunction.builder()
49        .name(NAME)
50        .namespace(MetapathConstants.NS_METAPATH_FUNCTIONS)
51        .deterministic()
52        .contextDependent()
53        .focusIndependent()
54        .argument(IArgument.builder()
55            .name("value")
56            .type(ITimeItem.type())
57            .zeroOrOne()
58            .build())
59        .argument(IArgument.builder()
60            .name("picture")
61            .type(IStringItem.type())
62            .one()
63            .build())
64        .returnType(IStringItem.type())
65        .returnZeroOrOne()
66        .functionHandler(FnFormatTime::executeTwoArg)
67        .build();
68  
69    @NonNull
70    static final IFunction SIGNATURE_FIVE_ARG = IFunction.builder()
71        .name(NAME)
72        .namespace(MetapathConstants.NS_METAPATH_FUNCTIONS)
73        .deterministic()
74        .contextDependent()
75        .focusIndependent()
76        .argument(IArgument.builder()
77            .name("value")
78            .type(ITimeItem.type())
79            .zeroOrOne()
80            .build())
81        .argument(IArgument.builder()
82            .name("picture")
83            .type(IStringItem.type())
84            .one()
85            .build())
86        .argument(IArgument.builder()
87            .name("language")
88            .type(IStringItem.type())
89            .zeroOrOne()
90            .build())
91        .argument(IArgument.builder()
92            .name("calendar")
93            .type(IStringItem.type())
94            .zeroOrOne()
95            .build())
96        .argument(IArgument.builder()
97            .name("place")
98            .type(IStringItem.type())
99            .zeroOrOne()
100           .build())
101       .returnType(IStringItem.type())
102       .returnZeroOrOne()
103       .functionHandler(FnFormatTime::executeFiveArg)
104       .build();
105 
106   private FnFormatTime() {
107     // disable construction
108   }
109 
110   @SuppressWarnings("unused")
111   @NonNull
112   private static ISequence<IStringItem> executeTwoArg(
113       @NonNull IFunction function,
114       @NonNull List<ISequence<?>> arguments,
115       @NonNull DynamicContext dynamicContext,
116       IItem focus) {
117 
118     ITimeItem value = FunctionUtils.asTypeOrNull(arguments.get(0).getFirstItem(true));
119     if (value == null) {
120       return ISequence.empty();
121     }
122 
123     IStringItem picture = FunctionUtils.asType(
124         ObjectUtils.requireNonNull(arguments.get(1).getFirstItem(true)));
125 
126     String lang = dynamicContext.getStaticContext().getDefaultLanguage();
127 
128     return ISequence.of(IStringItem.valueOf(
129         formatTime(value, picture.asString(), lang, null, null)));
130   }
131 
132   @SuppressWarnings("unused")
133   @NonNull
134   private static ISequence<IStringItem> executeFiveArg(
135       @NonNull IFunction function,
136       @NonNull List<ISequence<?>> arguments,
137       @NonNull DynamicContext dynamicContext,
138       IItem focus) {
139 
140     ITimeItem value = FunctionUtils.asTypeOrNull(arguments.get(0).getFirstItem(true));
141     if (value == null) {
142       return ISequence.empty();
143     }
144 
145     IStringItem picture = FunctionUtils.asType(
146         ObjectUtils.requireNonNull(arguments.get(1).getFirstItem(true)));
147     IStringItem language = FunctionUtils.asTypeOrNull(arguments.get(2).getFirstItem(true));
148     IStringItem calendar = FunctionUtils.asTypeOrNull(arguments.get(3).getFirstItem(true));
149     IStringItem place = FunctionUtils.asTypeOrNull(arguments.get(4).getFirstItem(true));
150 
151     return ISequence.of(IStringItem.valueOf(
152         formatTime(
153             value,
154             picture.asString(),
155             language == null ? dynamicContext.getStaticContext().getDefaultLanguage() : language.asString(),
156             calendar == null ? null : calendar.asString(),
157             place == null ? null : place.asString())));
158   }
159 
160   /**
161    * Format a time value using a picture string per the XPath 3.1 <a href=
162    * "https://www.w3.org/TR/xpath-functions-31/#func-format-time">fn:format-time</a>
163    * specification.
164    *
165    * @param value
166    *          the time value to format
167    * @param picture
168    *          the picture string
169    * @param language
170    *          the language, or {@code null}
171    * @param calendar
172    *          the calendar, or {@code null}
173    * @param place
174    *          the place, or {@code null}
175    * @return the formatted string
176    */
177   @NonNull
178   public static String formatTime(
179       @NonNull ITimeItem value,
180       @NonNull String picture,
181       @Nullable String language,
182       @Nullable String calendar,
183       @Nullable String place) {
184     return DateTimeFormatUtil.formatDateTime(value, picture, language, calendar, place, TIME_MARKERS);
185   }
186 }