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