Line 1... |
Line 1... |
1 |
/*
|
1 |
/*
|
2 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
|
2 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
|
3 |
*
|
3 |
*
|
4 |
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
|
4 |
* Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
|
5 |
*
|
5 |
*
|
6 |
* The contents of this file are subject to the terms of the GNU General Public License Version 3
|
6 |
* The contents of this file are subject to the terms of the GNU General Public License Version 3
|
7 |
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
|
7 |
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
|
8 |
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
|
8 |
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
|
9 |
* language governing permissions and limitations under the License.
|
9 |
* language governing permissions and limitations under the License.
|
Line 16... |
Line 16... |
16 |
import org.openconcerto.openoffice.ODPackage;
|
16 |
import org.openconcerto.openoffice.ODPackage;
|
17 |
import org.openconcerto.openoffice.ODValueType;
|
17 |
import org.openconcerto.openoffice.ODValueType;
|
18 |
import org.openconcerto.openoffice.StyleProperties;
|
18 |
import org.openconcerto.openoffice.StyleProperties;
|
19 |
import org.openconcerto.openoffice.XMLVersion;
|
19 |
import org.openconcerto.openoffice.XMLVersion;
|
20 |
import org.openconcerto.openoffice.spreadsheet.CellStyle;
|
20 |
import org.openconcerto.openoffice.spreadsheet.CellStyle;
|
- |
|
21 |
import org.openconcerto.utils.CollectionUtils;
|
21 |
import org.openconcerto.utils.convertor.NumberConvertor;
|
22 |
import org.openconcerto.utils.convertor.NumberConvertor;
|
22 |
|
23 |
|
23 |
import java.math.BigDecimal;
|
24 |
import java.math.BigDecimal;
|
24 |
import java.text.DecimalFormat;
|
25 |
import java.text.DecimalFormat;
|
25 |
import java.text.DecimalFormatSymbols;
|
26 |
import java.text.DecimalFormatSymbols;
|
26 |
import java.text.SimpleDateFormat;
|
27 |
import java.text.SimpleDateFormat;
|
- |
|
28 |
import java.util.ArrayList;
|
27 |
import java.util.Calendar;
|
29 |
import java.util.Calendar;
|
28 |
import java.util.Date;
|
30 |
import java.util.Date;
|
29 |
import java.util.GregorianCalendar;
|
31 |
import java.util.GregorianCalendar;
|
30 |
import java.util.List;
|
32 |
import java.util.List;
|
31 |
import java.util.Locale;
|
33 |
import java.util.Locale;
|
- |
|
34 |
import java.util.SortedMap;
|
- |
|
35 |
import java.util.TreeMap;
|
- |
|
36 |
import java.util.function.Consumer;
|
32 |
|
37 |
|
33 |
import org.jdom.Attribute;
|
38 |
import org.jdom.Attribute;
|
34 |
import org.jdom.Element;
|
39 |
import org.jdom.Element;
|
35 |
import org.jdom.Namespace;
|
40 |
import org.jdom.Namespace;
|
36 |
|
41 |
|
- |
|
42 |
import com.ibm.icu.text.DateTimePatternGenerator;
|
- |
|
43 |
|
37 |
// from section 16.27.10 in v1.2-cs01-part1
|
44 |
// from section 16.27.10 in v1.2-cs01-part1
|
38 |
public class DateStyle extends DataStyle {
|
45 |
public class DateStyle extends DataStyle {
|
39 |
|
46 |
|
- |
|
47 |
private static final String AUTOMATIC_ORDER_ATTRNAME = "automatic-order";
|
- |
|
48 |
|
- |
|
49 |
private static final String FORMAT_SOURCE_ATTRNAME = "format-source";
|
- |
|
50 |
private static final String FORMAT_SOURCE_FIXED = "fixed";
|
- |
|
51 |
private static final String FORMAT_SOURCE_LANG = "language";
|
- |
|
52 |
|
40 |
// see http://download.oracle.com/javase/6/docs/technotes/guides/intl/calendar.doc.html
|
53 |
// see http://download.oracle.com/javase/6/docs/technotes/guides/intl/calendar.doc.html
|
41 |
private static final Calendar BUDDHIST_CAL = Calendar.getInstance(new Locale("th", "TH"));
|
54 |
private static final Calendar BUDDHIST_CAL = Calendar.getInstance(new Locale("th", "TH"));
|
42 |
private static final Calendar JAPANESE_CAL = Calendar.getInstance(new Locale("ja", "JP", "JP"));
|
55 |
private static final Calendar JAPANESE_CAL = Calendar.getInstance(new Locale("ja", "JP", "JP"));
|
43 |
private static final Calendar GREGORIAN_CAL = new GregorianCalendar();
|
56 |
private static final Calendar GREGORIAN_CAL = new GregorianCalendar();
|
44 |
|
57 |
|
Line 52... |
Line 65... |
52 |
static final boolean isShort(final Element elem) {
|
65 |
static final boolean isShort(final Element elem) {
|
53 |
// in OOo the default is short
|
66 |
// in OOo the default is short
|
54 |
return !"long".equals(elem.getAttributeValue("style", elem.getNamespace("number")));
|
67 |
return !"long".equals(elem.getAttributeValue("style", elem.getNamespace("number")));
|
55 |
}
|
68 |
}
|
56 |
|
69 |
|
- |
|
70 |
// with LO 6.4 only year,month,day,day-of-week have variable length
|
- |
|
71 |
static final boolean isShort(final Element elem, final boolean fixed, final Locale locale) {
|
- |
|
72 |
if (fixed)
|
- |
|
73 |
return isShort(elem);
|
- |
|
74 |
else
|
- |
|
75 |
return Locale.US.equals(locale);
|
- |
|
76 |
}
|
- |
|
77 |
|
57 |
private static final Calendar getCalendar(final Element elem, Calendar defaultCal) {
|
78 |
private static final Calendar getCalendar(final Element elem, Calendar defaultCal) {
|
58 |
final Calendar res;
|
79 |
final Calendar res;
|
59 |
final String cal = elem.getAttributeValue("calendar", elem.getNamespace());
|
80 |
final String cal = elem.getAttributeValue("calendar", elem.getNamespace());
|
60 |
if (cal == null) {
|
81 |
if (cal == null) {
|
61 |
res = defaultCal;
|
82 |
res = defaultCal;
|
Line 96... |
Line 117... |
96 |
return getEpoch().getDate(NumberConvertor.toBigDecimal((Number) o));
|
117 |
return getEpoch().getDate(NumberConvertor.toBigDecimal((Number) o));
|
97 |
else
|
118 |
else
|
98 |
return null;
|
119 |
return null;
|
99 |
}
|
120 |
}
|
100 |
|
121 |
|
- |
|
122 |
public final boolean isFormatSourceFixed() {
|
- |
|
123 |
return this.getElement().getAttributeValue(FORMAT_SOURCE_ATTRNAME, this.getElement().getNamespace(), FORMAT_SOURCE_FIXED).equals(FORMAT_SOURCE_FIXED);
|
- |
|
124 |
}
|
- |
|
125 |
|
- |
|
126 |
final void setFormatSourceFixed(final boolean b) {
|
- |
|
127 |
if (b) {
|
- |
|
128 |
this.getElement().removeAttribute(FORMAT_SOURCE_ATTRNAME, this.getElement().getNamespace());
|
- |
|
129 |
} else {
|
- |
|
130 |
this.getElement().setAttribute(FORMAT_SOURCE_ATTRNAME, FORMAT_SOURCE_LANG, this.getElement().getNamespace());
|
- |
|
131 |
}
|
- |
|
132 |
}
|
- |
|
133 |
|
- |
|
134 |
public final boolean isAutomaticOrder() {
|
- |
|
135 |
return StyleProperties.parseBoolean(this.getElement().getAttributeValue(AUTOMATIC_ORDER_ATTRNAME, this.getElement().getNamespace()), false);
|
- |
|
136 |
}
|
- |
|
137 |
|
- |
|
138 |
final void setAutomaticOrder(final boolean b) {
|
- |
|
139 |
if (b) {
|
- |
|
140 |
this.getElement().setAttribute(AUTOMATIC_ORDER_ATTRNAME, Boolean.toString(b), this.getElement().getNamespace());
|
- |
|
141 |
} else {
|
- |
|
142 |
this.getElement().removeAttribute(AUTOMATIC_ORDER_ATTRNAME, this.getElement().getNamespace());
|
- |
|
143 |
}
|
- |
|
144 |
}
|
- |
|
145 |
|
101 |
private final void format(final StringBuilder res, final StringBuilder pattern, final Locale styleLocale, final Calendar currentCalendar, final Date d) {
|
146 |
private final void format(final StringBuilder res, final List<String> skeleton, final StringBuilder pattern, final Locale styleLocale, final boolean automaticOrder, final Calendar currentCalendar,
|
- |
|
147 |
final Date d) {
|
102 |
if (pattern.length() > 0) {
|
148 |
if (pattern.length() > 0) {
|
- |
|
149 |
if (automaticOrder) {
|
- |
|
150 |
// ask ICU the preferred order for our skeleton,
|
- |
|
151 |
// then change order of components in our pattern (thus keeping literals)
|
- |
|
152 |
|
- |
|
153 |
// e.g. [EEEE, dd, MM, yyyy] => "EEEE, MM dd yyyy"
|
- |
|
154 |
final String bestPattern = DateTimePatternGenerator.getInstance(styleLocale).getBestPattern(CollectionUtils.join(skeleton, ""), DateTimePatternGenerator.MATCH_ALL_FIELDS_LENGTH);
|
- |
|
155 |
final SortedMap<Integer, String> bestOrder = new TreeMap<>();
|
- |
|
156 |
for (final String comp : skeleton) {
|
- |
|
157 |
final int indexOf = bestPattern.indexOf(comp);
|
- |
|
158 |
if (indexOf < 0)
|
- |
|
159 |
throw new IllegalStateException("Missing " + comp + " in best pattern : " + bestPattern);
|
- |
|
160 |
final String prev = bestOrder.put(indexOf, comp);
|
- |
|
161 |
if (prev != null)
|
- |
|
162 |
throw new IllegalStateException("Duplicate " + comp + " in best pattern : " + bestPattern);
|
- |
|
163 |
}
|
- |
|
164 |
assert bestOrder.size() == skeleton.size();
|
- |
|
165 |
// e.g. [EEEE, MM, dd, yyyy]
|
- |
|
166 |
final List<String> reorderedSkeleton = new ArrayList<>(bestOrder.values());
|
- |
|
167 |
|
- |
|
168 |
int indexOfStart = 0;
|
- |
|
169 |
for (int i = 0; i < skeleton.size(); i++) {
|
- |
|
170 |
// e.g. "dd"
|
- |
|
171 |
final String comp = skeleton.get(i);
|
- |
|
172 |
final int reorderedIndex = reorderedSkeleton.indexOf(comp);
|
- |
|
173 |
assert reorderedIndex >= 0;
|
- |
|
174 |
if (i != reorderedIndex) {
|
- |
|
175 |
// e.g. "MM"
|
- |
|
176 |
final String compToSwap = reorderedSkeleton.get(i);
|
- |
|
177 |
|
- |
|
178 |
// replace "dd" by "MM"
|
- |
|
179 |
final int indexOfComp = pattern.indexOf(comp, indexOfStart);
|
- |
|
180 |
pattern.replace(indexOfComp, indexOfComp + comp.length(), compToSwap);
|
- |
|
181 |
// avoid swapping back
|
- |
|
182 |
indexOfStart = indexOfComp + compToSwap.length();
|
- |
|
183 |
}
|
- |
|
184 |
}
|
- |
|
185 |
}
|
103 |
final SimpleDateFormat fmt = new SimpleDateFormat(pattern.toString(), styleLocale);
|
186 |
final SimpleDateFormat fmt = new SimpleDateFormat(pattern.toString(), styleLocale);
|
104 |
pattern.setLength(0);
|
187 |
pattern.setLength(0);
|
105 |
fmt.setCalendar((Calendar) currentCalendar.clone());
|
188 |
fmt.setCalendar((Calendar) currentCalendar.clone());
|
106 |
res.append(fmt.format(d));
|
189 |
res.append(fmt.format(d));
|
107 |
}
|
190 |
}
|
Line 109... |
Line 192... |
109 |
|
192 |
|
110 |
@Override
|
193 |
@Override
|
111 |
public String format(Object o, CellStyle defaultStyle, boolean lenient) {
|
194 |
public String format(Object o, CellStyle defaultStyle, boolean lenient) {
|
112 |
final Date d = o instanceof Calendar ? ((Calendar) o).getTime() : (Date) o;
|
195 |
final Date d = o instanceof Calendar ? ((Calendar) o).getTime() : (Date) o;
|
113 |
final Namespace numberNS = this.getElement().getNamespace();
|
196 |
final Namespace numberNS = this.getElement().getNamespace();
|
- |
|
197 |
final boolean automaticOrder = this.isAutomaticOrder();
|
- |
|
198 |
final boolean fixedLength = this.isFormatSourceFixed();
|
114 |
final Locale styleLocale = this.getLocale();
|
199 |
final Locale styleLocale = this.getLocale();
|
115 |
final Calendar styleCalendar = Calendar.getInstance(styleLocale);
|
200 |
final Calendar styleCalendar = Calendar.getInstance(styleLocale);
|
116 |
final StringBuilder res = new StringBuilder();
|
201 |
final StringBuilder res = new StringBuilder();
|
117 |
|
202 |
|
118 |
Calendar currentCalendar = styleCalendar;
|
203 |
Calendar currentCalendar = styleCalendar;
|
- |
|
204 |
final List<String> skeleton = automaticOrder ? new ArrayList<>() : null;
|
119 |
final StringBuilder sb = new StringBuilder();
|
205 |
final StringBuilder sb = new StringBuilder();
|
120 |
|
206 |
|
- |
|
207 |
final Consumer<String> addComp = (s) -> {
|
- |
|
208 |
sb.append(s);
|
- |
|
209 |
if (skeleton != null)
|
- |
|
210 |
skeleton.add(s);
|
- |
|
211 |
};
|
- |
|
212 |
|
121 |
@SuppressWarnings("unchecked")
|
213 |
@SuppressWarnings("unchecked")
|
122 |
final List<Element> children = this.getElement().getChildren();
|
214 |
final List<Element> children = this.getElement().getChildren();
|
123 |
for (final Element elem : children) {
|
215 |
for (final Element elem : children) {
|
124 |
if (elem.getNamespace().equals(numberNS)) {
|
216 |
if (elem.getNamespace().equals(numberNS)) {
|
125 |
final Calendar calendarLocaleElem = getCalendar(elem, styleCalendar);
|
217 |
final Calendar calendarLocaleElem = getCalendar(elem, styleCalendar);
|
126 |
if (!calendarLocaleElem.equals(currentCalendar)) {
|
218 |
if (!calendarLocaleElem.equals(currentCalendar)) {
|
127 |
format(res, sb, styleLocale, currentCalendar, d);
|
219 |
format(res, skeleton, sb, styleLocale, automaticOrder, currentCalendar, d);
|
128 |
currentCalendar = calendarLocaleElem;
|
220 |
currentCalendar = calendarLocaleElem;
|
129 |
}
|
221 |
}
|
130 |
|
222 |
|
131 |
if (elem.getName().equals("text")) {
|
223 |
if (elem.getName().equals("text")) {
|
132 |
DataStyle.addStringLiteral(sb, elem.getText());
|
224 |
DataStyle.addStringLiteral(sb, elem.getText());
|
133 |
} else if (elem.getName().equals("era")) {
|
225 |
} else if (elem.getName().equals("era")) {
|
134 |
sb.append(isShort(elem) ? "G" : "GGGG");
|
226 |
addComp.accept(isShort(elem) ? "G" : "GGGG");
|
135 |
} else if (elem.getName().equals("year")) {
|
227 |
} else if (elem.getName().equals("year")) {
|
136 |
sb.append(isShort(elem) ? "yy" : "yyyy");
|
228 |
addComp.accept(isShort(elem, fixedLength, styleLocale) ? "yy" : "yyyy");
|
137 |
} else if (elem.getName().equals("quarter")) {
|
229 |
} else if (elem.getName().equals("quarter")) {
|
138 |
final Calendar cal = (Calendar) currentCalendar.clone();
|
230 |
final Calendar cal = (Calendar) currentCalendar.clone();
|
139 |
cal.setTime(d);
|
231 |
cal.setTime(d);
|
140 |
final double quarterLength = cal.getActualMaximum(Calendar.MONTH) / 4.0;
|
232 |
final double quarterLength = cal.getActualMaximum(Calendar.MONTH) / 4.0;
|
141 |
final int quarter = (int) (cal.get(Calendar.MONTH) / quarterLength + 1);
|
233 |
final int quarter = (int) (cal.get(Calendar.MONTH) / quarterLength + 1);
|
Line 146... |
Line 238... |
146 |
} else if (elem.getName().equals("month")) {
|
238 |
} else if (elem.getName().equals("month")) {
|
147 |
final Attribute possessive = elem.getAttribute("possessive-form", numberNS);
|
239 |
final Attribute possessive = elem.getAttribute("possessive-form", numberNS);
|
148 |
if (possessive != null)
|
240 |
if (possessive != null)
|
149 |
reportError("Ignoring " + possessive, lenient);
|
241 |
reportError("Ignoring " + possessive, lenient);
|
150 |
if (!StyleProperties.parseBoolean(elem.getAttributeValue("textual", numberNS), false))
|
242 |
if (!StyleProperties.parseBoolean(elem.getAttributeValue("textual", numberNS), false))
|
151 |
sb.append(isShort(elem) ? "M" : "MM");
|
243 |
addComp.accept(isShort(elem, fixedLength, styleLocale) ? "M" : "MM");
|
152 |
else
|
244 |
else
|
153 |
sb.append(isShort(elem) ? "MMM" : "MMMM");
|
245 |
addComp.accept(isShort(elem) ? "MMM" : "MMMM");
|
154 |
} else if (elem.getName().equals("week-of-year")) {
|
246 |
} else if (elem.getName().equals("week-of-year")) {
|
155 |
sb.append("w");
|
247 |
addComp.accept("w");
|
156 |
} else if (elem.getName().equals("day")) {
|
248 |
} else if (elem.getName().equals("day")) {
|
157 |
sb.append(isShort(elem) ? "d" : "dd");
|
249 |
addComp.accept(isShort(elem, fixedLength, styleLocale) ? "d" : "dd");
|
158 |
} else if (elem.getName().equals("day-of-week")) {
|
250 |
} else if (elem.getName().equals("day-of-week")) {
|
159 |
sb.append(isShort(elem) ? "E" : "EEEE");
|
251 |
addComp.accept(isShort(elem, fixedLength, styleLocale) ? "E" : "EEEE");
|
160 |
} else if (elem.getName().equals("am-pm")) {
|
252 |
} else if (elem.getName().equals("am-pm")) {
|
161 |
sb.append("a");
|
253 |
addComp.accept("a");
|
162 |
} else if (elem.getName().equals("hours")) {
|
254 |
} else if (elem.getName().equals("hours")) {
|
163 |
// see 16.27.22 : If a <number:am-pm> element is contained in a date or time
|
255 |
// see 16.27.22 : If a <number:am-pm> element is contained in a date or time
|
164 |
// style, hours are displayed using values from 1 to 12 only.
|
256 |
// style, hours are displayed using values from 1 to 12 only.
|
165 |
if (getElement().getChild("am-pm", numberNS) == null)
|
257 |
if (getElement().getChild("am-pm", numberNS) == null)
|
166 |
sb.append(isShort(elem) ? "H" : "HH");
|
258 |
addComp.accept(isShort(elem) ? "H" : "HH");
|
167 |
else
|
259 |
else
|
168 |
sb.append(isShort(elem) ? "h" : "hh");
|
260 |
addComp.accept(isShort(elem) ? "h" : "hh");
|
169 |
} else if (elem.getName().equals("minutes")) {
|
261 |
} else if (elem.getName().equals("minutes")) {
|
170 |
sb.append(isShort(elem) ? "m" : "mm");
|
262 |
addComp.accept(isShort(elem) ? "m" : "mm");
|
171 |
} else if (elem.getName().equals("seconds")) {
|
263 |
} else if (elem.getName().equals("seconds")) {
|
172 |
sb.append(isShort(elem) ? "s" : "ss");
|
264 |
addComp.accept(isShort(elem) ? "s" : "ss");
|
173 |
final int decPlaces = StyleProperties.parseInt(elem.getAttributeValue("decimal-places", numberNS), 0);
|
265 |
final int decPlaces = StyleProperties.parseInt(elem.getAttributeValue("decimal-places", numberNS), 0);
|
174 |
if (decPlaces > 0) {
|
266 |
if (decPlaces > 0) {
|
175 |
// use styleLocale since <seconds> hasn't @calendar
|
267 |
// use styleLocale since <seconds> hasn't @calendar
|
176 |
final Calendar cal = Calendar.getInstance(styleLocale);
|
268 |
final Calendar cal = Calendar.getInstance(styleLocale);
|
177 |
cal.setTime(d);
|
269 |
cal.setTime(d);
|
Line 181... |
Line 273... |
181 |
DataStyle.addStringLiteral(sb, fractionPart);
|
273 |
DataStyle.addStringLiteral(sb, fractionPart);
|
182 |
}
|
274 |
}
|
183 |
}
|
275 |
}
|
184 |
}
|
276 |
}
|
185 |
}
|
277 |
}
|
186 |
format(res, sb, styleLocale, currentCalendar, d);
|
278 |
format(res, skeleton, sb, styleLocale, automaticOrder, currentCalendar, d);
|
187 |
return res.toString();
|
279 |
return res.toString();
|
188 |
}
|
280 |
}
|
189 |
}
|
281 |
}
|