/* * © Copyright FOCONIS AG, 2014 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. See the License for the specific language governing * permissions and limitations under the License. * */ package org.openntf.formula.impl; import static com.ibm.icu.util.Calendar.DAY_OF_MONTH; import static com.ibm.icu.util.Calendar.HOUR; import static com.ibm.icu.util.Calendar.HOUR_OF_DAY; import static com.ibm.icu.util.Calendar.MINUTE; import static com.ibm.icu.util.Calendar.MONTH; import static com.ibm.icu.util.Calendar.SECOND; import static com.ibm.icu.util.Calendar.YEAR; import static com.ibm.icu.util.Calendar.getInstance; import java.text.ParsePosition; import java.util.Date; import java.util.Locale; import org.openntf.formula.DateTime; import org.openntf.formula.Formatter; import com.ibm.icu.text.DateFormat; import com.ibm.icu.text.NumberFormat; import com.ibm.icu.text.SimpleDateFormat; import com.ibm.icu.util.Calendar; public class FormatterImpl implements Formatter { private Locale iLocale; public FormatterImpl(final Locale loc) { iLocale = loc; } public Locale getLocale() { return iLocale; } /*----------------------------------------------------------------------------*/ public DateTime getNewSDTInstance() { return new DateTimeImpl(iLocale); } public DateTime getNewInitializedSDTInstance(final Date date, final boolean noDate, final boolean noTime) { DateTime sdt = getNewSDTInstance(); sdt.setLocalTime(date); if (noDate) sdt.setAnyDate(); if (noTime) sdt.setAnyTime(); return sdt; } public DateTime getCopyOfSDTInstance(final DateTime sdt) { return getNewInitializedSDTInstance(sdt.toJavaDate(), sdt.isAnyDate(), sdt.isAnyTime()); } /*----------------------------------------------------------------------------*/ public DateTime parseDate(final String image) { return parseDate(image, false); } public DateTime parseDate(final String image, final boolean parseLenient) { DateTime ret = getNewSDTInstance(); ret.setLocalTime(image, parseLenient); return ret; } public DateTime parseDateWithFormat(final String image, final String format, final boolean parseLenient) { boolean[] noDT = new boolean[2]; Calendar cal = parseDateToCalWithFormat(image, format, noDT, parseLenient); DateTime ret = getNewInitializedSDTInstance(cal.getTime(), noDT[0], noDT[1]); return ret; } /*----------------------------------------------------------------------------*/ public Calendar parseDateToCal(String image, final boolean[] noDT, final boolean parseLenient) { image = image.trim(); Calendar ret = getInstance(iLocale); // Should an empty string lead to a DateTime with noDate=noTime=true? // (Lotus doesn't accept empty strings here.) char spec = 0; if (image.equalsIgnoreCase("TODAY")) spec = 'H'; else if (image.equalsIgnoreCase("TOMORROW")) spec = 'M'; else if (image.equalsIgnoreCase("YESTERDAY")) spec = 'G'; if (spec != 0) { ret.setTime(new Date()); if (spec == 'M') ret.add(DAY_OF_MONTH, 1); else if (spec == 'G') ret.add(DAY_OF_MONTH, -1); noDT[0] = false; noDT[1] = true; return ret; } ret.setLenient(false); ParsePosition p = new ParsePosition(0); boolean illegalDateString = false; for (;;) { ret.clear(); /* * First attempt: Take a full date-time format MEDIUM */ DateFormat df = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM, iLocale); df.parse(image, ret, p); if (p.getErrorIndex() < 0) break; if (!ret.isSet(DAY_OF_MONTH) || !ret.isSet(MONTH)) { //Try with SHORT format ret.clear(); df = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM, iLocale); p.setIndex(0); p.setErrorIndex(-1); df.parse(image, ret, p); if (!ret.isSet(DAY_OF_MONTH) || !ret.isSet(MONTH)) { // Give up with date ret.clear(); p.setErrorIndex(0); } } if (ret.isSet(MINUTE)) break; /* * If no time found yet (i.e. at least hour+minute like Lotus), try to fish it */ p.setIndex(p.getErrorIndex()); p.setErrorIndex(-1); df = DateFormat.getTimeInstance(DateFormat.MEDIUM, iLocale); df.parse(image, ret, p); if (ret.isSet(MINUTE)) break; if (ret.isSet(DAY_OF_MONTH)) { // Set back possible hour (in accordance with Lotus) ret.clear(HOUR); ret.clear(HOUR_OF_DAY); break; } /* * Left: No date found, no time found */ illegalDateString = true; break; } // System.out.println("Lh=" + image.length() + " Index=" + p.getIndex() + " ErrIndex=" + p.getErrorIndex()); if (!illegalDateString && !parseLenient) { int lh = image.length(); int errInd = p.getErrorIndex(); illegalDateString = (errInd < 0 && p.getIndex() < lh) || (errInd >= 0 && errInd < lh); } if (illegalDateString) throw new IllegalArgumentException("Illegal date string '" + image + "'"); boolean contDate = ret.isSet(DAY_OF_MONTH); boolean contTime = ret.isSet(MINUTE); if (ret.isSet(YEAR)) { if (!contTime) ret.set(HOUR_OF_DAY, 0); } else { Calendar now = getInstance(iLocale); now.setTime(new Date()); ret.set(YEAR, now.get(YEAR)); if (!contDate) { ret.set(DAY_OF_MONTH, now.get(DAY_OF_MONTH)); ret.set(MONTH, now.get(MONTH)); } } if (!ret.isSet(MINUTE)) ret.set(MINUTE, 0); if (!ret.isSet(SECOND)) ret.set(SECOND, 0); try { ret.getTime(); } catch (IllegalArgumentException e) { throw new IllegalArgumentException("Illegal date string '" + image + "': " + e.getMessage()); } noDT[0] = !contDate; noDT[1] = !contTime; return ret; } /*----------------------------------------------------------------------------*/ public Calendar parseDateToCalWithFormat(final String image, final String format, final boolean[] noDT, final boolean parseLenient) { Calendar ret = getInstance(iLocale); ret.setLenient(false); ParsePosition p = new ParsePosition(0); ret.clear(); SimpleDateFormat sdf = new SimpleDateFormat(format, iLocale); sdf.parse(image, ret, p); boolean contDate = ret.isSet(YEAR) || ret.isSet(MONTH) || ret.isSet(DAY_OF_MONTH); boolean contTime = ret.isSet(HOUR_OF_DAY) || ret.isSet(HOUR) || ret.isSet(MINUTE) || ret.isSet(SECOND); boolean illegalDateString = !contDate && !contTime; if (!illegalDateString && !parseLenient) { int lh = image.length(); int errInd = p.getErrorIndex(); illegalDateString = (errInd < 0 && p.getIndex() < lh) || (errInd >= 0 && errInd < lh); } if (illegalDateString) throw new IllegalArgumentException("Illegal date string '" + image + "' for format '" + format + "'"); // System.out.println("Y=" + ret.isSet(YEAR) + " M=" + ret.isSet(MONTH) + " D=" + ret.isSet(DAY_OF_MONTH) // + " H=" + ret.isSet(HOUR_OF_DAY) + "m=" + ret.isSet(MINUTE) + " S=" + ret.isSet(SECOND)); if (!ret.isSet(YEAR)) ret.set(YEAR, 1970); if (!ret.isSet(MONTH)) ret.set(MONTH, 0); if (!ret.isSet(DAY_OF_MONTH)) ret.set(DAY_OF_MONTH, 1); if (!ret.isSet(HOUR_OF_DAY) && !ret.isSet(HOUR)) ret.set(HOUR_OF_DAY, 0); if (!ret.isSet(MINUTE)) ret.set(MINUTE, 0); if (!ret.isSet(SECOND)) ret.set(SECOND, 0); try { ret.getTime(); } catch (IllegalArgumentException e) { throw new IllegalArgumentException("Parsing '" + image + "' against '" + format + "' gives Calendar exception: " + e.getMessage()); } noDT[0] = !contDate; noDT[1] = !contTime; return ret; } /*----------------------------------------------------------------------------*/ public Number parseNumber(final String image) { return parseNumber(image, false); } public Number parseNumber(String image, final boolean lenient) { image = image.trim(); if (!image.isEmpty()) { String toParse = image; if (toParse.length() > 1 && toParse.charAt(0) == '+') toParse = toParse.substring(1); NumberFormat nf = NumberFormat.getNumberInstance(iLocale); ParsePosition p = new ParsePosition(0); Number ret = nf.parse(toParse, p); int errIndex = p.getErrorIndex(); //System.out.println("Ind=" + index + " ErrInd=" + errIndex); if (errIndex == -1) { if (p.getIndex() >= toParse.length() || lenient) return ret; } else if (errIndex != 0 && lenient) return ret; } throw new IllegalArgumentException("Illegal number string '" + image + "'"); } /*----------------------------------------------------------------------------*/ public String formatDateTime(final DateTime sdt) { return sdt.getLocalTime(); } public String formatDateTime(final DateTime sdt, final LotusDateTimeOptions ldto) { if (ldto.nothingSet()) return formatDateTime(sdt); String notSupported = ""; if (ldto.dOption != -1 && ldto.dOption != LotusDateTimeOptions.D_YMD) notSupported += "," + ldto.dOptToStr(); if (ldto.zOption != -1) notSupported += "," + ldto.zOptToStr(); if (ldto.sOption == LotusDateTimeOptions.S_DT_TY) notSupported += "," + ldto.sOptToStr(); if (!notSupported.isEmpty()) throw new UnsupportedOperationException("Not yet supported formatting option(s): " + notSupported.substring(1)); if (ldto.sOption == LotusDateTimeOptions.S_D_ONLY || sdt.isAnyTime()) return sdt.getDateOnly(); Calendar cal = sdt.toJavaCal(); if (ldto.sOption == LotusDateTimeOptions.S_T_ONLY || sdt.isAnyDate()) { if (ldto.tOption != LotusDateTimeOptions.T_HM) return sdt.getTimeOnly(); return sdt.isAnyTime() ? "" : formatCalTimeOnly(cal, TIMEFORMAT_SHORT); } int timeFormat = (ldto.tOption == LotusDateTimeOptions.T_HM) ? TIMEFORMAT_SHORT : TIMEFORMAT_MEDIUM; return formatCalDateTime(cal, timeFormat); } public String formatCalDateTime(final Calendar cal) { return formatCalDateTime(cal, TIMEFORMAT_MEDIUM); } public String formatCalDateTime(final Calendar cal, final int timeFormat) { DateFormat df = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, transTimeFormat(timeFormat), iLocale); df.setCalendar(cal); return df.format(cal.getTime()); } public String formatCalDateOnly(final Calendar cal) { DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM, iLocale); df.setCalendar(cal); return df.format(cal.getTime()); } public String formatCalTimeOnly(final Calendar cal) { return formatCalTimeOnly(cal, TIMEFORMAT_MEDIUM); } public String formatCalTimeOnly(final Calendar cal, final int timeFormat) { DateFormat df = DateFormat.getTimeInstance(transTimeFormat(timeFormat), iLocale); df.setCalendar(cal); return df.format(cal.getTime()); } private int transTimeFormat(final int timeFormat) { if (timeFormat == TIMEFORMAT_MEDIUM) return DateFormat.MEDIUM; if (timeFormat == TIMEFORMAT_SHORT) return DateFormat.SHORT; return DateFormat.LONG; } public String formatDateTimeWithFormat(final DateTime sdt, final String format) { Calendar cal = sdt.toJavaCal(); if (sdt.isAnyDate() || sdt.isAnyTime()) { Calendar calCopy = (Calendar) cal.clone(); if (sdt.isAnyDate()) { calCopy.set(YEAR, 1970); calCopy.set(MONTH, 0); calCopy.set(DAY_OF_MONTH, 1); } if (sdt.isAnyTime()) { calCopy.set(HOUR_OF_DAY, 0); calCopy.set(MINUTE, 0); calCopy.set(SECOND, 0); } cal = calCopy; } return formatCalWithFormat(cal, format); } public String formatCalWithFormat(final Calendar cal, final String format) { SimpleDateFormat sdf = new SimpleDateFormat(format, iLocale); sdf.setCalendar(cal); return sdf.format(cal.getTime()); } /*----------------------------------------------------------------------------*/ public String formatNumber(final Number n) { LotusNumberOptions lno = new LotusNumberOptions(); lno.setDefault(); return formatNumber(n, lno); } public String formatNumber(final Number n, final LotusNumberOptions lno) { NumberFormat nf; /* * It would have been more convenient to use NumberFormat.getInstance(locale, style), * but this method is private in com.ibm.icu_3.8.1.v20120530.jar. * (Seems to be public as of ICU 4.2.) */ if (lno.format == 'C') nf = NumberFormat.getCurrencyInstance(iLocale); else if (lno.format == 'S') nf = NumberFormat.getScientificInstance(iLocale); else if (lno.format == '%') nf = NumberFormat.getPercentInstance(iLocale); else nf = NumberFormat.getNumberInstance(iLocale); nf.setGroupingUsed(lno.useGrouping); nf.setMaximumIntegerDigits(1000); if (lno.fractionDigits != -1) { nf.setMinimumFractionDigits(lno.fractionDigits); nf.setMaximumFractionDigits(lno.fractionDigits); } else nf.setMaximumFractionDigits(1000); String ret = nf.format(n); do { if (lno.format != 'G' || ret.length() <= 15) break; /* * In this case, Lotus implicitly switches to scientific style. * When useGrouping is in effect, the limit decreases from 15 to 12 in Lotus * (i.e. the grouping bytes are likewise counted), but we are not going to * imitate this strange behaviour. */ String tester = ret; if (lno.useGrouping) { nf.setGroupingUsed(false); tester = nf.format(n); } int minus = (tester.charAt(0) == '-') ? 1 : 0; int lh = tester.length(); if (lh - minus <= 15) break; int komma = minus; for (; komma < lh; komma++) if (!Character.isDigit(tester.charAt(komma))) break; if (komma - minus <= 15) break; nf = NumberFormat.getScientificInstance(iLocale); nf.setGroupingUsed(lno.useGrouping); ret = nf.format(n); } while (false); if (lno.negativeAsParentheses && ret.charAt(0) == '-') ret = '(' + ret.substring(1) + ')'; return ret; } /*----------------------------------------------------------------------------*/ }