001    /*
002     // This software is subject to the terms of the Common Public License
003     // Agreement, available at the following URL:
004     // http://www.opensource.org/licenses/cpl.html.
005     // Copyright (C) 2007-2008 Julian Hyde
006     // All Rights Reserved.
007     // You must accept the terms of that agreement to use this software.
008     */
009    package mondrian.olap.fun.vba;
010    
011    import mondrian.olap.Util;
012    import mondrian.olap.InvalidArgumentException;
013    import static mondrian.olap.fun.JavaFunDef.*;
014    
015    import java.util.*;
016    import java.util.regex.Matcher;
017    import java.util.regex.Pattern;
018    import java.text.*;
019    
020    /**
021     * Implementations of functions in the Visual Basic for Applications (VBA)
022     * specification.
023     *
024     * @author jhyde
025     * @version $Id: //open/mondrian/src/main/mondrian/olap/fun/vba/Vba.java#8 $
026     * @since Dec 31, 2007
027     */
028    public class Vba {
029        private static final long MILLIS_IN_A_DAY = 24 * 60 * 60 * 1000;
030    
031        private static final DateFormatSymbols DATE_FORMAT_SYMBOLS
032                                = new DateFormatSymbols(Locale.getDefault());
033    
034        // Conversion
035    
036        @FunctionName("CBool")
037        @Signature("CBool(expression)")
038        @Description("Returns an expression that has been converted to a Variant of subtype Boolean.")
039        public static boolean cBool(Object expression) {
040            if (expression instanceof Boolean) {
041                return (Boolean) expression;
042            } else {
043                int i = cInt(expression);
044                return i != 0;
045            }
046        }
047    
048        // Conversion functions
049    
050        @FunctionName("CByte")
051        @Signature("CByte(expression)")
052        @Description("Returns an expression that has been converted to a Variant of subtype Byte.")
053        public static byte cByte(Object expression) {
054            if (expression instanceof Byte) {
055                return (Byte) expression;
056            } else {
057                int i = cInt(expression);
058                return (byte) i;
059            }
060        }
061    
062        // public Currency cCur(Object expression)
063    
064        @FunctionName("CDate")
065        @Signature("CDate(date)")
066        @Description("Returns an expression that has been converted to a Variant of subtype Date.")
067        public static Date cDate(Object expression) {
068            String str = String.valueOf(expression);
069            if (expression instanceof Date) {
070                return (Date) expression;
071            } else if (expression == null) {
072                return null;
073            } else {
074                // note that this currently only supports a limited set of dates and
075                // times
076                // "October 19, 1962"
077                // "4:35:47 PM"
078                try {
079                    return DateFormat.getTimeInstance().parse(str);
080                } catch (ParseException ex0) {
081                    try {
082                        return DateFormat.getDateTimeInstance().parse(str);
083                    } catch (ParseException ex1) {
084                        try {
085                            return DateFormat.getDateInstance().parse(str);
086                        } catch (ParseException ex2) {
087                            throw new InvalidArgumentException(
088                                "Invalid parameter. "
089                                + "expression parameter of CDate function must be "
090                                + "formatted correctly ("
091                                + String.valueOf(expression) + ")");
092                        }
093                    }
094                }
095            }
096        }
097    
098        @FunctionName("CDbl")
099        @Signature("CDbl(expression)")
100        @Description("Returns an expression that has been converted to a Variant of subtype Double.")
101        public static double cDbl(Object expression) {
102            if (expression instanceof Number) {
103                Number number = (Number) expression;
104                return number.doubleValue();
105            } else {
106                final String s = String.valueOf(expression);
107                return new Double(s).intValue();
108            }
109        }
110    
111        @FunctionName("CInt")
112        @Signature("CInt(expression)")
113        @Description("Returns an expression that has been converted to a Variant of subtype Integer.")
114        public static int cInt(Object expression) {
115            if (expression instanceof Number) {
116                Number number = (Number) expression;
117                final int intValue = number.intValue();
118                if (number instanceof Float || number instanceof Double) {
119                    final double doubleValue = number.doubleValue();
120                    if (doubleValue == (double) intValue) {
121                        // Number is already an integer
122                        return intValue;
123                    }
124                    final double doubleDouble = doubleValue * 2d;
125                    if (doubleDouble == Math.floor(doubleDouble)) {
126                        // Number ends in .5 - round towards even required
127                        return (int) Math.round(doubleValue / 2d) * 2;
128                    }
129                    return (int) Math.round(doubleValue);
130                }
131                return intValue;
132            } else {
133                // Try to parse as integer before parsing as double. More
134                // efficient, and avoids loss of precision.
135                final String s = String.valueOf(expression);
136                try {
137                    return Integer.parseInt(s);
138                } catch (NumberFormatException e) {
139                    return new Double(s).intValue();
140                }
141            }
142        }
143    
144        // public int cLng(Object expression)
145        // public float cSng(Object expression)
146        // public String cStr(Object expression)
147        // public Object cVDate(Object expression)
148        // public Object cVErr(Object expression)
149        // public Object cVar(Object expression)
150        // public String error$(Object errorNumber)
151        // public Object error(Object errorNumber)
152    
153        @FunctionName("Fix")
154        @Signature("Fix(number)")
155        @Description("Returns the integer portion of a number. If negative, returns the negative number greater than or equal to the number.")
156        public static int fix(Object number) {
157            if (number instanceof Number) {
158                int v = ((Number) number).intValue();
159                double dv = ((Number) number).doubleValue();
160                if (v < 0 && v < dv) {
161                    v++;
162                }
163                return v;
164            } else {
165                throw new InvalidArgumentException("Invalid parameter. "
166                        + "number parameter " + number
167                        + " of Int function must be " + "of type number");
168            }
169        }
170    
171        @FunctionName("Hex")
172        @Description("Returns a String representing the hexadecimal value of a number.")
173        @Signature("Hex(number)")
174        public static String hex(Object number) {
175            if (number instanceof Number) {
176                return Integer.toHexString(((Number) number).intValue())
177                        .toUpperCase();
178            } else {
179                throw new InvalidArgumentException("Invalid parameter. "
180                        + "number parameter " + number
181                        + " of Hex function must be " + "of type number");
182            }
183        }
184    
185        @FunctionName("Int")
186        @Signature("Int(number)")
187        @Description("Returns the integer portion of a number. If negative, returns the negative number less than or equal to the number.")
188        public static int int_(Object number) {
189            if (number instanceof Number) {
190                int v = ((Number) number).intValue();
191                double dv = ((Number) number).doubleValue();
192                if (v < 0 && v > dv) {
193                    v--;
194                }
195                return v;
196            } else {
197                throw new InvalidArgumentException("Invalid parameter. "
198                        + "number parameter " + number
199                        + " of Int function must be " + "of type number");
200            }
201        }
202    
203        // public String oct$(Object number)
204    
205        @FunctionName("Oct")
206        @Signature("Oct(number)")
207        @Description("Returns a Variant (String) representing the octal value of a number.")
208        public static String oct(Object number) {
209            if (number instanceof Number) {
210                return Integer.toOctalString(((Number) number).intValue());
211            } else {
212                throw new InvalidArgumentException("Invalid parameter. "
213                        + "number parameter " + number
214                        + " of Oct function must be " + "of type number");
215            }
216        }
217    
218        // public String str$(Object number)
219    
220        @FunctionName("Str")
221        @Signature("Str(number)")
222        @Description("Returns a Variant (String) representation of a number.")
223        public static String str(Object number) {
224            // When numbers are converted to strings, a leading space is always
225            // reserved for the sign of number. If number is positive, the returned
226            // string contains a leading space and the plus sign is implied.
227            //
228            // Use the Format function to convert numeric values you want formatted
229            // as dates, times, or currency or in other user-defined formats.
230            // Unlike Str, the Format function doesn't include a leading space for
231            // the sign of number.
232            //
233            // Note The Str function recognizes only the period (.) as a valid
234            // decimal separator. When different decimal separators may be used
235            // (for example, in international applications), use CStr to convert a
236            // number to a string.
237            if (number instanceof Number) {
238                if (((Number) number).doubleValue() >= 0) {
239                    return " " + number.toString();
240                } else {
241                    return number.toString();
242                }
243            } else {
244                throw new InvalidArgumentException("Invalid parameter. "
245                        + "number parameter " + number
246                        + " of Str function must be " + "of type number");
247            }
248        }
249    
250        @FunctionName("Val")
251        @Signature("Val(string)")
252        @Description("Returns the numbers contained in a string as a numeric value of appropriate type.")
253        public static double val(String string) {
254            // The Val function stops reading the string at the first character it
255            // can't recognize as part of a number. Symbols and characters that are
256            // often considered parts of numeric values, such as dollar signs and
257            // commas, are not recognized. However, the function recognizes the
258            // radix prefixes &O (for octal) and &H (for hexadecimal). Blanks,
259            // tabs, and linefeed characters are stripped from the argument.
260            //
261            // The following returns the value 1615198:
262            //
263            // Val(" 1615 198th Street N.E.")
264            // In the code below, Val returns the decimal value -1 for the
265            // hexadecimal value shown:
266            //
267            // Val("&HFFFF")
268            // Note The Val function recognizes only the period (.) as a valid
269            // decimal separator. When different decimal separators are used, as in
270            // international applications, use CDbl instead to convert a string to
271            // a number.
272    
273            string = string.replaceAll("\\s", ""); // remove all whitespace
274            if (string.startsWith("&H")) {
275                string = string.substring(2);
276                Pattern p = Pattern.compile("[0-9a-fA-F]*");
277                Matcher m = p.matcher(string);
278                m.find();
279                return Integer.parseInt(m.group(), 16);
280            } else if (string.startsWith("&O")) {
281                string = string.substring(2);
282                Pattern p = Pattern.compile("[0-7]*");
283                Matcher m = p.matcher(string);
284                m.find();
285                return Integer.parseInt(m.group(), 8);
286            } else {
287                // find the first number
288                Pattern p = Pattern.compile("-?[0-9]*[.]?[0-9]*");
289                Matcher m = p.matcher(string);
290                m.find();
291                return Double.parseDouble(m.group());
292    
293            }
294        }
295    
296        // DateTime
297    
298        // public Calendar calendar()
299        // public void calendar(Calendar val)
300        // public String date$()
301        // public void date$(String val)
302    
303        @FunctionName("DateAdd")
304        @Signature("DateAdd(interval, number, date)")
305        @Description("Returns a Variant (Date) containing a date to which a specified time interval has been added.")
306        public static Date dateAdd(String intervalName, double number, Date date) {
307            Interval interval = Interval.valueOf(intervalName);
308            final double floor = Math.floor(number);
309            Calendar calendar = Calendar.getInstance();
310            calendar.setTime(date);
311            if (floor != number) {
312                final double ceil = Math.ceil(number);
313                interval.add(calendar, (int) ceil);
314                final long ceilMillis = calendar.getTimeInMillis();
315    
316                calendar.setTime(date);
317                interval.add(calendar, (int) floor);
318                final long floorMillis = calendar.getTimeInMillis();
319    
320                final long amount =
321                    (long)
322                        (((double) (ceilMillis - floorMillis)) * (number - floor));
323                calendar
324                        .add(Calendar.DAY_OF_YEAR, (int) (amount / MILLIS_IN_A_DAY));
325                calendar
326                        .add(Calendar.MILLISECOND, (int) (amount % MILLIS_IN_A_DAY));
327            } else {
328                interval.add(calendar, (int) floor);
329            }
330            return calendar.getTime();
331        }
332    
333        @FunctionName("DateDiff")
334        @Signature("DateDiff(interval, date1, date2[, firstdayofweek[, firstweekofyear]])")
335        @Description("Returns a Variant (Long) specifying the number of time intervals between two specified dates.")
336        public static long dateDiff(String interval, Date date1, Date date2) {
337            return _dateDiff(interval, date1, date2, Calendar.SUNDAY,
338                    FirstWeekOfYear.vbFirstJan1);
339        }
340    
341        @FunctionName("DateDiff")
342        @Signature("DateDiff(interval, date1, date2[, firstdayofweek[, firstweekofyear]])")
343        @Description("Returns a Variant (Long) specifying the number of time intervals between two specified dates.")
344        public static long dateDiff(String interval, Date date1, Date date2,
345                int firstDayOfWeek) {
346            return _dateDiff(interval, date1, date2, firstDayOfWeek,
347                    FirstWeekOfYear.vbFirstJan1);
348        }
349    
350        @FunctionName("DateDiff")
351        @Signature("DateDiff(interval, date1, date2[, firstdayofweek[, firstweekofyear]])")
352        @Description("Returns a Variant (Long) specifying the number of time intervals between two specified dates.")
353        public static long dateDiff(String interval, Date date1, Date date2,
354                int firstDayOfWeek, int firstWeekOfYear) {
355            return _dateDiff(interval, date1, date2, firstDayOfWeek,
356                    FirstWeekOfYear.values()[firstWeekOfYear]);
357        }
358    
359        private static long _dateDiff(String intervalName, Date date1, Date date2,
360                int firstDayOfWeek, FirstWeekOfYear firstWeekOfYear) {
361            Interval interval = Interval.valueOf(intervalName);
362            Calendar calendar1 = Calendar.getInstance();
363            firstWeekOfYear.apply(calendar1);
364            calendar1.setTime(date1);
365            Calendar calendar2 = Calendar.getInstance();
366            firstWeekOfYear.apply(calendar2);
367            calendar2.setTime(date2);
368            return interval.diff(calendar1, calendar2, firstDayOfWeek);
369        }
370    
371        @FunctionName("DatePart")
372        @Signature("DatePart(interval, date[,firstdayofweek[, firstweekofyear]])")
373        @Description("Returns a Variant (Integer) containing the specified part of a given date.")
374        public static int datePart(String interval, Date date) {
375            return _datePart(interval, date, Calendar.SUNDAY,
376                    FirstWeekOfYear.vbFirstJan1);
377        }
378    
379        @FunctionName("DatePart")
380        @Signature("DatePart(interval, date[,firstdayofweek[, firstweekofyear]])")
381        @Description("Returns a Variant (Integer) containing the specified part of a given date.")
382        public static int datePart(String interval, Date date, int firstDayOfWeek) {
383            return _datePart(interval, date, firstDayOfWeek,
384                    FirstWeekOfYear.vbFirstJan1);
385        }
386    
387        @FunctionName("DatePart")
388        @Signature("DatePart(interval, date[,firstdayofweek[, firstweekofyear]])")
389        @Description("Returns a Variant (Integer) containing the specified part of a given date.")
390        public static int datePart(String interval, Date date, int firstDayOfWeek,
391                int firstWeekOfYear) {
392            return _datePart(interval, date, firstDayOfWeek, FirstWeekOfYear
393                    .values()[firstWeekOfYear]);
394        }
395    
396        private static int _datePart(String intervalName, Date date,
397                int firstDayOfWeek, FirstWeekOfYear firstWeekOfYear) {
398            Interval interval = Interval.valueOf(intervalName);
399            Calendar calendar = Calendar.getInstance();
400            calendar.setTime(date);
401            switch (interval) {
402            case w:
403            case ww:
404                // firstWeekOfYear and firstDayOfWeek only matter for 'w' and 'ww'
405                firstWeekOfYear.apply(calendar);
406                calendar.setFirstDayOfWeek(firstDayOfWeek);
407                break;
408            }
409            return interval.datePart(calendar);
410        }
411    
412        @FunctionName("Date")
413        @Signature("Date")
414        @Description("Returns a Variant (Date) containing the current system date.")
415        public static Date date() {
416            Calendar calendar = Calendar.getInstance();
417            calendar.clear();
418            calendar.set(Calendar.HOUR_OF_DAY, 0);
419            calendar.set(Calendar.MINUTE, 0);
420            calendar.set(Calendar.SECOND, 0);
421            calendar.set(Calendar.MILLISECOND, 0);
422            return calendar.getTime();
423        }
424    
425        // public void date(Object val)
426    
427        @FunctionName("DateSerial")
428        @Signature("DateSerial(year, month, day)")
429        @Description("Returns a Variant (Date) for a specified year, month, and day.")
430        public static Date dateSerial(int year, int month, int day) {
431            Calendar calendar = Calendar.getInstance();
432            calendar.clear();
433            calendar.set(year, month - 1, day);
434            return calendar.getTime();
435        }
436    
437        @FunctionName("DateValue")
438        @Signature("DateValue(date)")
439        @Description("Returns a Variant (Date).")
440        public static Date dateValue(Date date) {
441            final Calendar calendar = Calendar.getInstance();
442            calendar.clear();
443            calendar.setTime(date);
444            calendar.set(Calendar.HOUR_OF_DAY, 0);
445            calendar.set(Calendar.MINUTE, 0);
446            calendar.set(Calendar.SECOND, 0);
447            calendar.set(Calendar.MILLISECOND, 0);
448            return calendar.getTime();
449        }
450    
451        @FunctionName("Day")
452        @Signature("Day(date)")
453        @Description("Returns a Variant (Integer) specifying a whole number between 1 and 31, inclusive, representing the day of the month.")
454        public static int day(Date date) {
455            final Calendar calendar = Calendar.getInstance();
456            calendar.setTime(date);
457            return calendar.get(Calendar.DAY_OF_MONTH);
458        }
459    
460        @FunctionName("Hour")
461        @Signature("Hour(time)")
462        @Description("Returns a Variant (Integer) specifying a whole number between 0 and 23, inclusive, representing the hour of the day.")
463        public static int hour(Date time) {
464            final Calendar calendar = Calendar.getInstance();
465            calendar.setTime(time);
466            return calendar.get(Calendar.HOUR_OF_DAY);
467        }
468    
469        @FunctionName("Minute")
470        @Signature("Minute(time)")
471        @Description("Returns a Variant (Integer) specifying a whole number between 0 and 59, inclusive, representing the minute of the hour.")
472        public static int minute(Date time) {
473            final Calendar calendar = Calendar.getInstance();
474            calendar.setTime(time);
475            return calendar.get(Calendar.MINUTE);
476        }
477    
478        @FunctionName("Month")
479        @Signature("Month(date)")
480        @Description("Returns a Variant (Integer) specifying a whole number between 1 and 12, inclusive, representing the month of the year.")
481        public static int month(Date date) {
482            final Calendar calendar = Calendar.getInstance();
483            calendar.setTime(date);
484            final int month = calendar.get(Calendar.MONTH);
485            return month + 1; // convert from 0- to 1-based
486        }
487    
488        @FunctionName("Now")
489        @Signature("Now()")
490        @Description("Returns a Variant (Date) specifying the current date and time according your computer's system date and time.")
491        public static Date now() {
492            return new Date();
493        }
494    
495        @FunctionName("Second")
496        @Signature("Second(time)")
497        @Description("Returns a Variant (Integer) specifying a whole number between 0 and 59, inclusive, representing the second of the minute.")
498        public static int second(Date time) {
499            final Calendar calendar = Calendar.getInstance();
500            calendar.setTime(time);
501            return calendar.get(Calendar.SECOND);
502        }
503    
504        // public String time$()
505        // public void time$(String val)
506    
507        @FunctionName("Time")
508        @Signature("Time()")
509        @Description("Returns a Variant (Date) indicating the current system time.")
510        public static Date time() {
511            return new Date();
512        }
513    
514        // public void time(Object val)
515    
516        @FunctionName("TimeSerial")
517        @Signature("TimeSerial(hour, minute, second)")
518        @Description("Returns a Variant (Date) containing the time for a specific hour, minute, and second.")
519        public static Date timeSerial(int hour, int minute, int second) {
520            final Calendar calendar = Calendar.getInstance();
521            calendar.clear();
522            calendar.set(Calendar.HOUR_OF_DAY, hour);
523            calendar.set(Calendar.MINUTE, minute);
524            calendar.set(Calendar.SECOND, second);
525            return calendar.getTime();
526        }
527    
528        @FunctionName("TimeValue")
529        @Signature("TimeValue(time)")
530        @Description("Returns a Variant (Date) containing the time.")
531        public static Date timeValue(Date time) {
532            final Calendar calendar = Calendar.getInstance();
533            calendar.clear();
534            calendar.setTime(time);
535            calendar.set(1970, 0, 1);
536            return calendar.getTime();
537        }
538    
539        @FunctionName("Timer")
540        @Signature("Timer()")
541        @Description("Returns a Single representing the number of seconds elapsed since midnight.")
542        public static float timer() {
543            final Calendar calendar = Calendar.getInstance();
544            final long now = calendar.getTimeInMillis();
545            calendar.set(Calendar.HOUR_OF_DAY, 0);
546            calendar.set(Calendar.MINUTE, 0);
547            calendar.set(Calendar.SECOND, 0);
548            calendar.set(Calendar.MILLISECOND, 0);
549            final long midnight = calendar.getTimeInMillis();
550            return ((float) (now - midnight)) / 1000f;
551        }
552    
553        @FunctionName("Weekday")
554        @Signature("Weekday(date[, firstDayOfWeek])")
555        @Description("Returns a Variant (Integer) containing a whole number representing the day of the week.")
556        public static int weekday(Date date) {
557            return weekday(date, Calendar.SUNDAY);
558        }
559    
560        @FunctionName("Weekday")
561        @Signature("Weekday(date[, firstDayOfWeek])")
562        @Description("Returns a Variant (Integer) containing a whole number representing the day of the week.")
563        public static int weekday(Date date, int firstDayOfWeek) {
564            final Calendar calendar = Calendar.getInstance();
565            calendar.setTime(date);
566            int weekday = calendar.get(Calendar.DAY_OF_WEEK);
567            // adjust for start of week
568            weekday -= (firstDayOfWeek - 1);
569            // bring into range 1..7
570            weekday = (weekday + 6) % 7 + 1;
571            return weekday;
572        }
573    
574        @FunctionName("Year")
575        @Signature("Year(date)")
576        @Description("Returns a Variant (Integer) containing a whole number representing the year.")
577        public static int year(Date date) {
578            final Calendar calendar = Calendar.getInstance();
579            calendar.setTime(date);
580            return calendar.get(Calendar.YEAR);
581        }
582    
583        //
584        // /* FileSystem */
585        // public void chDir(String path)
586        // public void chDrive(String drive)
587        // public String curDir$(Object drive)
588        // public Object curDir(Object drive)
589        // public String dir(Object pathName, FileAttribute attributes /* default
590        // FileAttribute.Normal */)
591        // public boolean EOF(int fileNumber)
592        // public int fileAttr(int fileNumber, int returnType /* default 1 */)
593        // public void fileCopy(String source, String destination)
594        // public Object fileDateTime(String pathName)
595        // public int fileLen(String pathName)
596        // public int freeFile(Object rangeNumber)
597        // public FileAttribute getAttr(String pathName)
598        // public void kill(Object pathName)
599        // public int LOF(int fileNumber)
600        // public int loc(int fileNumber)
601        // public void mkDir(String path)
602        // public void reset()
603        // public void rmDir(String path)
604        // public int seek(int fileNumber)
605        // public void setAttr(String pathName, FileAttribute attributes)
606        //
607        // Financial
608    
609        @FunctionName("DDB")
610        @Signature("DDB(cost, salvage, life, period[, factor])")
611        @Description("Returns a Double specifying the depreciation of an asset for a specific time period using the double-declining balance method or some other method you specify.")
612        public static double dDB(double cost, double salvage, double life,
613                double period) {
614            return dDB(cost, salvage, life, period, 2.0);
615        }
616    
617        @FunctionName("DDB")
618        @Signature("DDB(cost, salvage, life, period[, factor])")
619        @Description("Returns a Double specifying the depreciation of an asset for a specific time period using the double-declining balance method or some other method you specify.")
620        public static double dDB(double cost, double salvage, double life,
621                double period, double factor) {
622            return (((cost - salvage) * factor) / life) * period;
623        }
624    
625        @FunctionName("FV")
626        @Signature("FV(rate, nper, pmt[, pv[, type]])")
627        @Description("Returns a Double specifying the future value of an annuity based on periodic, fixed payments and a fixed interest rate.")
628        public static double fV(double rate, double nPer, double pmt) {
629            return fV(rate, nPer, pmt, 0d, false);
630        }
631    
632        @FunctionName("FV")
633        @Signature("FV(rate, nper, pmt[, pv[, type]])")
634        @Description("Returns a Double specifying the future value of an annuity based on periodic, fixed payments and a fixed interest rate.")
635        public static double fV(double rate, double nPer, double pmt, double pv) {
636            return fV(rate, nPer, pmt, pv, false);
637        }
638    
639        @FunctionName("FV")
640        @Signature("FV(rate, nper, pmt[, pv[, type]])")
641        @Description("Returns a Double specifying the future value of an annuity based on periodic, fixed payments and a fixed interest rate.")
642        public static double fV(double rate, double nPer, double pmt, double pv,
643                boolean type) {
644            if (rate == 0) {
645                return -(pv + (nPer * pmt));
646            } else {
647                double r1 = rate + 1;
648                return ((1 - Math.pow(r1, nPer)) * (type ? r1 : 1) * pmt) / rate
649                        - pv * Math.pow(r1, nPer);
650            }
651        }
652    
653        @FunctionName("IPmt")
654        @Signature("IPmt(rate, per, nper, pv[, fv[, type]])")
655        @Description("Returns a Double specifying the interest payment for a given period of an annuity based on periodic, fixed payments and a fixed interest rate.")
656        public static double iPmt(double rate, double per, double nPer, double PV) {
657            return iPmt(rate, per, nPer, PV, 0);
658        }
659    
660    
661        @FunctionName("IPmt")
662        @Signature("IPmt(rate, per, nper, pv[, fv[, type]])")
663        @Description("Returns a Double specifying the interest payment for a given period of an annuity based on periodic, fixed payments and a fixed interest rate.")
664        public static double iPmt(double rate, double per, double nPer, double PV,
665                double fv) {
666            return iPmt(rate, per, nPer, PV, fv, false);
667        }
668    
669    
670        @FunctionName("IPmt")
671        @Signature("IPmt(rate, per, nper, pv[, fv[, type]])")
672        @Description("Returns a Double specifying the interest payment for a given period of an annuity based on periodic, fixed payments and a fixed interest rate.")
673        public static double iPmt(double rate, double per, double nPer, double PV,
674                double fv, boolean due) {
675            double pmtVal = pmt(rate, nPer, PV, fv, due);
676            double pValm1 = PV - pV(rate, per - 1, pmtVal, fv, due);
677            return - pValm1 * rate;
678        }
679    
680        @FunctionName("IRR")
681        @Signature("IRR(values()[, guess])")
682        @Description("Returns a Double specifying the internal rate of return for a series of periodic cash flows (payments and receipts).")
683        public static double IRR(double[] valueArray) {
684            return IRR(valueArray, 0.10);
685        }
686    
687    
688        @FunctionName("IRR")
689        @Signature("IRR(values()[, guess])")
690        @Description("Returns a Double specifying the internal rate of return for a series of periodic cash flows (payments and receipts).")
691        public static double IRR(double[] valueArray, double guess) {
692    
693            // calc pV of stream (sum of pV's for valueArray) ((1 + guess) ^ index)
694            double minGuess = 0.0;
695            double maxGuess = 1.0;
696    
697            // i'm not certain
698            int r = 1;
699            if (valueArray[0] > 0) {
700                r = -1;
701            }
702    
703            for (int i = 0; i < 30; i++) {
704                // first calculate overall return based on guess
705                double totalPv = 0;
706                for (int j = 0; j < valueArray.length; j++) {
707                    totalPv += valueArray[j] / Math.pow(1.0 + guess, j);
708                }
709                if ((maxGuess - minGuess) < 0.0000001) {
710                    return guess;
711                } else if (totalPv * r < 0) {
712                    maxGuess = guess;
713                } else {
714                    minGuess = guess;
715                }
716                // avg max min to determine next step
717                guess = (maxGuess + minGuess) / 2;
718            }
719            // unable to find a match
720            return -1;
721        }
722    
723        @FunctionName("MIRR")
724        @Signature("MIRR(values(), finance_rate, reinvest_rate)")
725        @Description("Returns a Double specifying the modified internal rate of return for a series of periodic cash flows (payments and receipts).")
726        public static double MIRR(double valueArray[], double financeRate,
727                double reinvestRate) {
728            // based on http://en.wikipedia.org/wiki/Modified_Internal_Rate_of_Return
729            double reNPV = 0.0;
730            double fiNPV = 0.0;
731            for (int j = 0; j < valueArray.length; j++) {
732                if (valueArray[j] > 0) {
733                    reNPV += valueArray[j] / Math.pow(1.0 + reinvestRate, j);
734                } else {
735                    fiNPV += valueArray[j] / Math.pow(1.0 + financeRate, j);
736                }
737            }
738    
739            double ratio = (- reNPV * Math.pow(1 + reinvestRate, valueArray.length)) /
740            (fiNPV * (1 + financeRate));
741    
742            return Math.pow(ratio, 1.0 / (valueArray.length - 1)) - 1.0;
743    
744        }
745    
746        @FunctionName("NPer")
747        @Signature("NPer(rate, pmt, pv[, fv[, type]])")
748        @Description("Returns a Double specifying the number of periods for an annuity based on periodic, fixed payments and a fixed interest rate.")
749        public static double nPer(double rate, double pmt, double pv, double fv,
750                boolean due) {
751            if (rate == 0) {
752                return -(fv + pv) / pmt;
753            } else {
754                double r1 = rate + 1;
755                double ryr = (due ? r1 : 1) * pmt / rate;
756                double a1 = ((ryr - fv) < 0) ? Math.log(fv - ryr) : Math.log(ryr
757                        - fv);
758                double a2 = ((ryr - fv) < 0) ? Math.log(-pv - ryr) : Math.log(pv
759                        + ryr);
760                double a3 = Math.log(r1);
761                return (a1 - a2) / a3;
762            }
763        }
764    
765        @FunctionName("NPV")
766        @Signature("NPV(rate, values())")
767        @Description("Returns a Double specifying the net present value of an investment based on a series of periodic cash flows (payments and receipts) and a discount rate.")
768        public static double nPV(double r, double[] cfs) {
769            double npv = 0;
770            double r1 = r + 1;
771            double trate = r1;
772            for (int i = 0, iSize = cfs.length; i < iSize; i++) {
773                npv += cfs[i] / trate;
774                trate *= r1;
775            }
776            return npv;
777        }
778    
779        @FunctionName("PPmt")
780        @Signature("PPmt(rate, per, nper, pv[, fv[, type]])")
781        @Description("Returns a Double specifying the principal payment for a given period of an annuity based on periodic, fixed payments and a fixed interest rate.")
782        public static double pPmt(double rate, double per, double nPer, double PV) {
783            return pPmt(rate, per, nPer, PV, 0);
784        }
785    
786        @FunctionName("PPmt")
787        @Signature("PPmt(rate, per, nper, pv[, fv[, type]])")
788        @Description("Returns a Double specifying the principal payment for a given period of an annuity based on periodic, fixed payments and a fixed interest rate.")
789        public static double pPmt(double rate, double per, double nPer, double PV,
790                double fv) {
791            return pPmt(rate, per, nPer, PV, fv, false);
792        }
793    
794        @FunctionName("PPmt")
795        @Signature("PPmt(rate, per, nper, pv[, fv[, type]])")
796        @Description("Returns a Double specifying the principal payment for a given period of an annuity based on periodic, fixed payments and a fixed interest rate.")
797        public static double pPmt(double rate, double per, double nPer, double PV,
798                double fv, boolean due) {
799            return pmt(rate, nPer, PV, fv, due) - iPmt(rate, per, nPer, PV, fv, due);
800        }
801    
802        @FunctionName("Pmt")
803        @Signature("Pmt(rate, nper, pv[, fv[, type]])")
804        @Description("Returns a Double specifying the payment for an annuity based on periodic, fixed payments and a fixed interest rate.")
805        public static double pmt(double rate, double nPer, double pv, double fv,
806                boolean due) {
807            if (rate == 0) {
808                return -(fv + pv) / nPer;
809            } else {
810                double r1 = rate + 1;
811                return (fv + pv * Math.pow(r1, nPer)) * rate
812                        / ((due ? r1 : 1) * (1 - Math.pow(r1, nPer)));
813            }
814        }
815    
816        @FunctionName("PV")
817        @Signature("PV(rate, nper, pmt[, fv[, type]])")
818        @Description("Returns a Double specifying the present value of an annuity based on periodic, fixed payments to be paid in the future and a fixed interest rate.")
819        public static double pV(double rate, double nper, double pmt, double fv,
820                boolean due) {
821            if (rate == 0) {
822                return -((nper * pmt) + fv);
823            } else {
824                double r1 = rate + 1;
825                return
826                    (((1 - Math.pow(r1, nper)) / rate) * (due ? r1 : 1) * pmt - fv)
827                        / Math.pow(r1, nper);
828            }
829        }
830    
831        @FunctionName("Rate")
832        @Signature("Rate(nper, pmt, pv[, fv[, type[, guess]]])")
833        @Description("Returns a Double specifying the interest rate per period for an annuity.")
834        public static double rate(double nPer, double pmt, double PV) {
835            return rate(nPer, pmt, PV, 0);
836        }
837    
838        @FunctionName("Rate")
839        @Signature("Rate(nper, pmt, pv[, fv[, type[, guess]]])")
840        @Description("Returns a Double specifying the interest rate per period for an annuity.")
841        public static double rate(double nPer, double pmt, double PV, double fv) {
842            return rate(nPer, pmt, PV, fv, false);
843        }
844    
845        @FunctionName("Rate")
846        @Signature("Rate(nper, pmt, pv[, fv[, type[, guess]]])")
847        @Description("Returns a Double specifying the interest rate per period for an annuity.")
848        public static double rate(double nPer, double pmt, double PV, double fv,
849                boolean type) {
850            return rate(nPer, pmt, PV, fv, type, 0.1);
851        }
852    
853        @FunctionName("Rate")
854        @Signature("Rate(nper, pmt, pv[, fv[, type[, guess]]])")
855        @Description("Returns a Double specifying the interest rate per period for an annuity.")
856        public static double rate(double nPer, // specifies the number of payment
857                                                // periods
858                double pmt, // payment per period of annuity
859                double PV, // the present value of the annuity (0 if a loan)
860                double fv, // the future value of the annuity ($ if savings)
861                boolean due, double guess) {
862            if (nPer <= 0) {
863                throw new InvalidArgumentException(
864                        "number of payment periods must be larger than 0");
865            }
866            double minGuess = 0.0;
867            double maxGuess = 1.0;
868    
869            // converge on the correct answer should use Newton's Method
870            // for now use a binary search
871            int r = 1;
872            if (PV < fv) {
873                r = -1;
874            }
875    
876            // the vb method uses 20 iterations, but they also probably use newton's
877            // method,
878            // so i've bumped it up to 30 iterations.
879            for (int n = 0; n < 30; n++) {
880                double gFV = fV(guess, nPer, pmt, PV, due);
881                double diff = gFV - fv;
882                if ((maxGuess - minGuess) < 0.0000001) {
883                    return guess;
884                } else {
885                    if (diff * r < 0) {
886                        maxGuess = guess;
887                    } else {
888                        minGuess = guess;
889                    }
890                    guess = (maxGuess + minGuess) / 2;
891                }
892            }
893            // fail, not sure how VB fails
894            return -1;
895        }
896    
897        @FunctionName("SLN")
898        @Signature("SLN(cost, salvage, life)")
899        @Description("Returns a Double specifying the straight-line depreciation of an asset for a single period.")
900        public static double sLN(double cost, double salvage, double life) {
901            return (cost - salvage) / life;
902        }
903    
904        @FunctionName("SYD")
905        @Signature("SYD(cost, salvage, life, period)")
906        @Description("Returns a Double specifying the sum-of-years' digits depreciation of an asset for a specified period.")
907        public static double sYD(double cost, double salvage, double life,
908                double period) {
909            return (cost - salvage) * (life / (period * (period + 1) / 2));
910        }
911    
912        // Information
913    
914        // public Throwable err()
915        // public Object iMEStatus()
916    
917        @FunctionName("IsArray")
918        @Signature("IsArray(varname)")
919        @Description("Returns a Boolean value indicating whether a variable is an array.")
920        public boolean isArray(Object varName) {
921            // arrays are not supported at present
922            return false;
923        }
924    
925        @FunctionName("IsDate")
926        @Signature("IsDate(varname)")
927        @Description("Returns a Boolean value indicating whether an expression can be converted to a date..")
928        public static boolean isDate(Object expression) {
929            // IsDate returns True if Expression represents a valid date, a valid
930            // time, or a valid date and time.
931            try {
932                Date val = cDate(expression);
933                return (val != null);
934            } catch (InvalidArgumentException e) {
935                return false;
936            }
937        }
938    
939        // use mondrian's implementation of IsEmpty
940        // public boolean isEmpty(Object expression)
941    
942        @FunctionName("IsError")
943        @Signature("IsError(varname)")
944        @Description("Returns a Boolean value indicating whether an expression is an error value.")
945        public boolean isError(Object expression) {
946            return expression instanceof Throwable;
947        }
948    
949        @FunctionName("IsMissing")
950        @Signature("IsMissing(varname)")
951        @Description("Returns a Boolean value indicating whether an optional Variant argument has been passed to a procedure.")
952        public boolean isMissing(Object argName) {
953            // We have no way to detect missing arguments.
954            return false;
955        }
956    
957        @FunctionName("IsNull")
958        @Signature("IsNull(varname)")
959        @Description("Returns a Boolean value that indicates whether an expression contains no valid data (Null).")
960        public boolean isNull(Object expression) {
961            return expression == null;
962        }
963    
964        @FunctionName("IsNumeric")
965        @Signature("IsNumeric(varname)")
966        @Description("Returns a Boolean value indicating whether an expression can be evaluated as a number.")
967        public boolean isNumeric(Object expression) {
968            return expression instanceof Number;
969        }
970    
971        @FunctionName("IsObject")
972        @Signature("IsObject(varname)")
973        @Description("Returns a Boolean value indicating whether an identifier represents an object variable.")
974        public boolean isObject(Object expression) {
975            return false;
976        }
977    
978        // public int qBColor(int color)
979        // public int RGB(int red, int green, int blue)
980    
981        @FunctionName("TypeName")
982        @Signature("TypeName(varname)")
983        @Description("Returns a String that provides information about a variable.")
984        public static String typeName(Object varName) {
985            // The string returned by TypeName can be any one of the following:
986            //
987            // String returned Variable
988            // object type An object whose type is objecttype
989            // Byte Byte value
990            // Integer Integer
991            // Long Long integer
992            // Single Single-precision floating-point number
993            // Double Double-precision floating-point number
994            // Currency Currency value
995            // Decimal Decimal value
996            // Date Date value
997            // String String
998            // Boolean Boolean value
999            // Error An error value
1000            // Empty Uninitialized
1001            // Null No valid data
1002            // Object An object
1003            // Unknown An object whose type is unknown
1004            // Nothing Object variable that doesn't refer to an object
1005    
1006            if (varName == null) {
1007                return "NULL";
1008            } else {
1009                // strip off the package information
1010                String name = varName.getClass().getName();
1011                if (name.lastIndexOf(".") >= 0) {
1012                    name = name.substring(name.lastIndexOf(".") + 1);
1013                }
1014                return name;
1015            }
1016        }
1017    
1018        // public VarType varType(Object varName)
1019    
1020        // Interaction
1021    
1022        // public void appActivate(Object title, Object wait)
1023        // public void beep()
1024        // public Object callByName(Object object, String procName, CallType
1025        // callType, Object args, int lcid)
1026        // public Object choose(float index, Object choice)
1027        // public String command$()
1028        // public Object command()
1029        // public Object createObject(String Class, String serverName)
1030        // public int doEvents()
1031        // public String environ$(Object expression)
1032        // public Object environ(Object expression)
1033        // public Object getAllSettings(String appName, String section)
1034        // public Object getObject(Object pathName, Object Class)
1035        // public String getSetting(String appName, String section, String key,
1036        // Object Default)
1037        // public Object iIf(Object expression, Object truePart, Object falsePart)
1038        // public String inputBox(Object prompt, Object title, Object Default,
1039        // Object xPos, Object yPos, Object helpFile, Object context)
1040        // public String macScript(String script)
1041        // public MsgBoxResult msgBox(Object prompt, MsgBoxStyle buttons /* default
1042        // MsgBoxStyle.OKOnly */, Object title, Object helpFile, Object context)
1043        // public Object partition(Object number, Object start, Object stop, Object
1044        // interval)
1045        // public void saveSetting(String appName, String section, String key,
1046        // String setting)
1047        // public void sendKeys(String string, Object wait)
1048        // public double shell(Object pathName, AppWinStyle windowStyle /* default
1049        // AppWinStyle.MinimizedFocus */)
1050        // public Object Switch(Object varExpr)
1051    
1052        // Mathematical
1053    
1054        @FunctionName("Abs")
1055        @Signature("Abs(number)")
1056        @Description("Returns a value of the same type that is passed to it specifying the absolute value of a number.")
1057        public static double abs(double number) {
1058            return Math.abs(number);
1059        }
1060    
1061        @FunctionName("Atn")
1062        @Signature("Atn(number)")
1063        @Description("Returns a Double specifying the arctangent of a number.")
1064        public static double atn(double number) {
1065            return Math.atan(number);
1066        }
1067    
1068        @FunctionName("Cos")
1069        @Signature("Cos(number)")
1070        @Description("Returns a Double specifying the cosine of an angle.")
1071        public static double cos(double number) {
1072            return Math.cos(number);
1073        }
1074    
1075        @FunctionName("Exp")
1076        @Signature("Exp(number)")
1077        @Description("Returns a Double specifying e (the base of natural logarithms) raised to a power.")
1078        public static double exp(double number) {
1079            return Math.exp(number);
1080        }
1081    
1082        @FunctionName("Log")
1083        @Signature("Log(number)")
1084        @Description("Returns a Double specifying the natural logarithm of a number.")
1085        public static double log(double number) {
1086            return Math.log(number);
1087        }
1088    
1089        // Cannot implement randomize and rnd - we require context to hold the
1090        // seed
1091    
1092        // public void randomize(Object number)
1093        // public float rnd(Object number)
1094    
1095        @FunctionName("Round")
1096        @Signature("Round(number[, numDigitsAfterDecimal])")
1097        @Description("Returns a number rounded to a specified number of decimal places.")
1098        public static double round(double number) {
1099            return Math.round(number);
1100        }
1101    
1102        @FunctionName("Round")
1103        @Signature("Round(number[, numDigitsAfterDecimal])")
1104        @Description("Returns a number rounded to a specified number of decimal places.")
1105        public static double round(double number, int numDigitsAfterDecimal) {
1106            if (numDigitsAfterDecimal == 0) {
1107                return Math.round(number);
1108            }
1109            final double shift = Math.pow(10d, numDigitsAfterDecimal);
1110            double numberScaled = number * shift;
1111            double resultScaled = Math.round(numberScaled);
1112            return resultScaled / shift;
1113        }
1114    
1115        @FunctionName("Sgn")
1116        @Signature("Sgn(number)")
1117        @Description("Returns a Variant (Integer) indicating the sign of a number.")
1118        public static int sgn(double number) {
1119            // We could use Math.signum(double) from JDK 1.5 onwards.
1120            return number < 0.0d ? -1 : number > 0.0d ? 1 : 0;
1121        }
1122    
1123        @FunctionName("Sin")
1124        @Signature("Sin(number)")
1125        @Description("Returns a Double specifying the sine of an angle.")
1126        public static double sin(double number) {
1127            return Math.sin(number);
1128        }
1129    
1130        @FunctionName("Sqr")
1131        @Signature("Sqr(number)")
1132        @Description("Returns a Double specifying the square root of a number.")
1133        public static double sqr(double number) {
1134            return Math.sqrt(number);
1135        }
1136    
1137        @FunctionName("Tan")
1138        @Signature("Tan(number)")
1139        @Description("Returns a Double specifying the tangent of an angle.")
1140        public static double tan(double number) {
1141            return Math.tan(number);
1142        }
1143    
1144        // Strings
1145    
1146        @FunctionName("Asc")
1147        @Signature("Asc(string)")
1148        @Description("Returns an Integer representing the character code corresponding to the first letter in a string.")
1149        public static int asc(String string) {
1150            return string.charAt(0);
1151        }
1152    
1153        @FunctionName("AscB")
1154        @Signature("AscB(string)")
1155        @Description("See Asc.")
1156        public static int ascB(String string) {
1157            return (byte) string.charAt(0);
1158        }
1159    
1160        @FunctionName("AscW")
1161        @Signature("AscW(string)")
1162        @Description("See Asc.")
1163        public static int ascW(String string) {
1164            return asc(string);
1165        }
1166    
1167        // public String chr$(int charCode)
1168        // public String chrB$(int charCode)
1169    
1170        @FunctionName("Chr")
1171        @Signature("Chr(charcode)")
1172        @Description("Returns a String containing the character associated with the specified character code.")
1173        public static String chr(int charCode) {
1174            return new String(new char[] { (char) charCode });
1175        }
1176    
1177        @FunctionName("ChrB")
1178        @Signature("ChrB(charcode)")
1179        @Description("See Chr.")
1180        public static String chrB(int charCode) {
1181            return new String(new byte[] { (byte) charCode });
1182        }
1183    
1184        // public String chrW$(int charCode)
1185    
1186        @FunctionName("ChrW")
1187        @Signature("ChrW(charcode)")
1188        @Description("See Chr.")
1189        public static String chrW(int charCode) {
1190            return new String(new char[] { (char) charCode });
1191        }
1192    
1193        // public Object filter(Object sourceArray, String match, boolean include /*
1194        // default 1 */, int compare /* default BinaryCompare */)
1195        // public String format$(Object expression, Object format, int
1196        // firstDayOfWeek /* default Sunday */, int firstWeekOfYear /* default
1197        // FirstJan1 */)
1198    
1199        @FunctionName("FormatCurrency")
1200        @Signature("FormatCurrency(Expression[,NumDigitsAfterDecimal [,IncludeLeadingDigit [,UseParensForNegativeNumbers [,GroupDigits]]]])")
1201        @Description("Returns an expression formatted as a currency value using the currency symbol defined in the system control panel.")
1202        public static String formatCurrency(Object expression) {
1203            return formatCurrency(expression, -1, -2, -2, -2);
1204        }
1205    
1206        @FunctionName("FormatCurrency")
1207        @Signature("FormatCurrency(Expression[,NumDigitsAfterDecimal [,IncludeLeadingDigit [,UseParensForNegativeNumbers [,GroupDigits]]]])")
1208        @Description("Returns an expression formatted as a currency value using the currency symbol defined in the system control panel.")
1209        public static String formatCurrency(Object expression,
1210                int numDigitsAfterDecimal) {
1211            return formatCurrency(expression, numDigitsAfterDecimal, -2, -2, -2);
1212        }
1213    
1214        @FunctionName("FormatCurrency")
1215        @Signature("FormatCurrency(Expression[,NumDigitsAfterDecimal [,IncludeLeadingDigit [,UseParensForNegativeNumbers [,GroupDigits]]]])")
1216        @Description("Returns an expression formatted as a currency value using the currency symbol defined in the system control panel.")
1217        public static String formatCurrency(Object expression,
1218                int numDigitsAfterDecimal, int includeLeadingDigit) {
1219            return formatCurrency(expression, numDigitsAfterDecimal,
1220                    includeLeadingDigit, -2, -2);
1221        }
1222    
1223        @FunctionName("FormatCurrency")
1224        @Signature("FormatCurrency(Expression[,NumDigitsAfterDecimal [,IncludeLeadingDigit [,UseParensForNegativeNumbers [,GroupDigits]]]])")
1225        @Description("Returns an expression formatted as a currency value using the currency symbol defined in the system control panel.")
1226        public static String formatCurrency(Object expression,
1227                int numDigitsAfterDecimal, int includeLeadingDigit,
1228                int useParensForNegativeNumbers) {
1229            return formatCurrency(expression, numDigitsAfterDecimal,
1230                    includeLeadingDigit, useParensForNegativeNumbers, -2);
1231        }
1232    
1233        @FunctionName("FormatCurrency")
1234        @Signature("FormatCurrency(Expression[,NumDigitsAfterDecimal [,IncludeLeadingDigit [,UseParensForNegativeNumbers [,GroupDigits]]]])")
1235        @Description("Returns an expression formatted as a currency value using the currency symbol defined in the system control panel.")
1236        public static String formatCurrency(Object expression,
1237                int numDigitsAfterDecimal, int includeLeadingDigit,
1238                int useParensForNegativeNumbers, int groupDigits) {
1239            DecimalFormat format = (DecimalFormat) NumberFormat
1240                    .getCurrencyInstance();
1241            if (numDigitsAfterDecimal != -1) {
1242                format.setMaximumFractionDigits(numDigitsAfterDecimal);
1243                format.setMinimumFractionDigits(numDigitsAfterDecimal);
1244            }
1245            if (includeLeadingDigit != -2) {
1246                if (includeLeadingDigit != 0) {
1247                    format.setMinimumIntegerDigits(1);
1248                } else {
1249                    format.setMinimumIntegerDigits(0);
1250                }
1251            }
1252            if (useParensForNegativeNumbers != -2) {
1253                // todo: implement.
1254                // This will require tweaking of the currency expression
1255            }
1256            if (groupDigits != -2) {
1257                if (groupDigits != 0) {
1258                    format.setGroupingUsed(false);
1259                } else {
1260                    format.setGroupingUsed(true);
1261                }
1262            }
1263            return format.format(expression);
1264        }
1265    
1266        @FunctionName("FormatDateTime")
1267        @Signature("FormatDateTime(Date[,NamedFormat])")
1268        @Description("Returns an expression formatted as a date or time.")
1269        public static String formatDateTime(Date date) {
1270            return formatDateTime(date, 0);
1271    
1272        }
1273    
1274        @FunctionName("FormatDateTime")
1275        @Signature("FormatDateTime(Date[,NamedFormat])")
1276        @Description("Returns an expression formatted as a date or time.")
1277        public static String formatDateTime(
1278            Date date,
1279            int namedFormat /* default 0, GeneralDate */)
1280        {
1281            // todo: test
1282            // todo: how do we support VB Constants? Strings or Ints?
1283            switch (namedFormat) {
1284                // vbLongDate, 1
1285                // Display a date using the long date format specified in your
1286                // computer's regional settings.
1287    
1288            case 1:
1289                return DateFormat.getDateInstance(DateFormat.LONG).format(date);
1290    
1291                // vbShortDate, 2
1292                // Display a date using the short date format specified in your
1293                // computer's regional settings.
1294            case 2:
1295                return DateFormat.getDateInstance(DateFormat.SHORT).format(date);
1296    
1297                // vbLongTime, 3
1298                // Display a time using the time format specified in your computer's
1299                // regional settings.
1300            case 3:
1301                return DateFormat.getTimeInstance(DateFormat.LONG).format(date);
1302    
1303                // vbShortTime, 4
1304                // Display a time using the 24-hour format (hh:mm).
1305            case 4:
1306                return DateFormat.getTimeInstance(DateFormat.SHORT).format(date);
1307    
1308                // vbGeneralDate, 0
1309                // Display a date and/or time. If there is a date part, display it as a
1310                // short date. If there is a time part, display it as a long time. If
1311                // present, both parts are displayed.
1312    
1313                // todo: how do we determine if there is a "time part" in java?
1314            case 0:
1315            default:
1316                return DateFormat.getDateTimeInstance().format(date);
1317            }
1318        }
1319    
1320        // Format is implemented with FormatFunDef, third and fourth params are not
1321        // supported
1322        // @FunctionName("Format")
1323        // @Signature("Format(expression[, format[, firstdayofweek[,
1324        // firstweekofyear]]])")
1325        // @Description("Returns a Variant (String) containing an expression
1326        // formatted according to instructions contained in a format expression.")
1327    
1328    
1329        @FunctionName("FormatNumber")
1330        @Signature("FormatNumber(Expression[,NumDigitsAfterDecimal [,IncludeLeadingDigit [,UseParensForNegativeNumbers [,GroupDigits]]]])")
1331        @Description("Returns an expression formatted as a number.")
1332        public static String formatNumber(Object expression) {
1333            return formatNumber(expression, -1);
1334        }
1335    
1336        @FunctionName("FormatNumber")
1337        @Signature("FormatNumber(Expression[,NumDigitsAfterDecimal [,IncludeLeadingDigit [,UseParensForNegativeNumbers [,GroupDigits]]]])")
1338        @Description("Returns an expression formatted as a number.")
1339        public static String formatNumber(Object expression,
1340                int numDigitsAfterDecimal) {
1341            return formatNumber(expression, numDigitsAfterDecimal, -1);
1342        }
1343    
1344        @FunctionName("FormatNumber")
1345        @Signature("FormatNumber(Expression[,NumDigitsAfterDecimal [,IncludeLeadingDigit [,UseParensForNegativeNumbers [,GroupDigits]]]])")
1346        @Description("Returns an expression formatted as a number.")
1347        public static String formatNumber(
1348                Object expression,
1349                int numDigitsAfterDecimal,
1350                int includeLeadingDigit)
1351        {
1352            return formatNumber(expression, numDigitsAfterDecimal,
1353                    includeLeadingDigit, -1);
1354        }
1355    
1356        @FunctionName("FormatNumber")
1357        @Signature("FormatNumber(Expression[,NumDigitsAfterDecimal [,IncludeLeadingDigit [,UseParensForNegativeNumbers [,GroupDigits]]]])")
1358        @Description("Returns an expression formatted as a number.")
1359        public static String formatNumber(
1360                Object expression,
1361                int numDigitsAfterDecimal,
1362                int includeLeadingDigit,
1363                int useParensForNegativeNumbers)
1364        {
1365            return formatNumber(expression, numDigitsAfterDecimal,
1366                    includeLeadingDigit, useParensForNegativeNumbers, -1);
1367        }
1368    
1369        @FunctionName("FormatNumber")
1370        @Signature("FormatNumber(Expression[,NumDigitsAfterDecimal [,IncludeLeadingDigit [,UseParensForNegativeNumbers [,GroupDigits]]]])")
1371        @Description("Returns an expression formatted as a number.")
1372        public static String formatNumber(
1373                Object expression,
1374                int numDigitsAfterDecimal /* default -1 */,
1375                int includeLeadingDigit /* default usedefault */,
1376                int useParensForNegativeNumbers /* default UseDefault */,
1377                int groupDigits /* default UseDefault */)
1378        {
1379            NumberFormat format = NumberFormat.getNumberInstance();
1380            if (numDigitsAfterDecimal != -1) {
1381                format.setMaximumFractionDigits(numDigitsAfterDecimal);
1382                format.setMinimumFractionDigits(numDigitsAfterDecimal);
1383            }
1384    
1385            if (includeLeadingDigit != -1) {
1386                if (includeLeadingDigit != 0) {
1387                    // true
1388                    format.setMinimumIntegerDigits(1);
1389                } else {
1390                    format.setMinimumIntegerDigits(0);
1391                }
1392            }
1393    
1394            if (useParensForNegativeNumbers != -1) {
1395                if (useParensForNegativeNumbers != 0) {
1396                    DecimalFormat dformat = (DecimalFormat)format;
1397                    dformat.setNegativePrefix("(");
1398                    dformat.setNegativeSuffix(")");
1399                } else {
1400                    DecimalFormat dformat = (DecimalFormat)format;
1401                    dformat.setNegativePrefix(
1402                            "" + dformat.getDecimalFormatSymbols().getMinusSign());
1403                    dformat.setNegativeSuffix("");
1404                }
1405            }
1406    
1407            if (groupDigits != -1) {
1408                format.setGroupingUsed(groupDigits != 0);
1409            }
1410    
1411            return format.format(expression);
1412        }
1413    
1414        @FunctionName("FormatPercent")
1415        @Signature("FormatPercent(Expression[,NumDigitsAfterDecimal [,IncludeLeadingDigit [,UseParensForNegativeNumbers [,GroupDigits]]]])")
1416        @Description("Returns an expression formatted as a percentage (multipled by 100) with a trailing % character.")
1417        public static String formatPercent(Object expression) {
1418            return formatPercent(expression, -1);
1419        }
1420    
1421        @FunctionName("FormatPercent")
1422        @Signature("FormatPercent(Expression[,NumDigitsAfterDecimal [,IncludeLeadingDigit [,UseParensForNegativeNumbers [,GroupDigits]]]])")
1423        @Description("Returns an expression formatted as a percentage (multipled by 100) with a trailing % character.")
1424        public static String formatPercent(
1425                // todo: impl & test
1426                Object expression, int numDigitsAfterDecimal /* default -1 */) {
1427            return formatPercent(expression, numDigitsAfterDecimal, -1);
1428    
1429        }
1430    
1431        @FunctionName("FormatPercent")
1432        @Signature("FormatPercent(Expression[,NumDigitsAfterDecimal [,IncludeLeadingDigit [,UseParensForNegativeNumbers [,GroupDigits]]]])")
1433        @Description("Returns an expression formatted as a percentage (multipled by 100) with a trailing % character.")
1434        public static String formatPercent(
1435                // todo: impl & test
1436                Object expression, int numDigitsAfterDecimal /* default -1 */,
1437                int includeLeadingDigit /* default UseDefault */) {
1438            return formatPercent(expression, numDigitsAfterDecimal,
1439                    includeLeadingDigit, -1);
1440    
1441        }
1442    
1443        @FunctionName("FormatPercent")
1444        @Signature("FormatPercent(Expression[,NumDigitsAfterDecimal [,IncludeLeadingDigit [,UseParensForNegativeNumbers [,GroupDigits]]]])")
1445        @Description("Returns an expression formatted as a percentage (multipled by 100) with a trailing % character.")
1446        public static String formatPercent(
1447                // todo: impl & test
1448                Object expression, int numDigitsAfterDecimal /* default -1 */,
1449                int includeLeadingDigit /* default UseDefault */,
1450                int useParensForNegativeNumbers /* default UseDefault */) {
1451            return formatPercent(expression, numDigitsAfterDecimal,
1452                    includeLeadingDigit, useParensForNegativeNumbers, -1);
1453        }
1454    
1455        @FunctionName("FormatPercent")
1456        @Signature("FormatPercent(Expression[,NumDigitsAfterDecimal [,IncludeLeadingDigit [,UseParensForNegativeNumbers [,GroupDigits]]]])")
1457        @Description("Returns an expression formatted as a percentage (multipled by 100) with a trailing % character.")
1458        public static String formatPercent(
1459                Object expression, int numDigitsAfterDecimal /* default -1 */,
1460                int includeLeadingDigit /* default UseDefault */,
1461                int useParensForNegativeNumbers /* default UseDefault */,
1462                int groupDigits /* default UseDefault */) {
1463    
1464            NumberFormat format = NumberFormat.getPercentInstance();
1465            if (numDigitsAfterDecimal != -1) {
1466                format.setMaximumFractionDigits(numDigitsAfterDecimal);
1467                format.setMinimumFractionDigits(numDigitsAfterDecimal);
1468            }
1469    
1470            if (includeLeadingDigit != -1) {
1471                if (includeLeadingDigit != 0) {
1472                    // true
1473                    format.setMinimumIntegerDigits(1);
1474                } else {
1475                    format.setMinimumIntegerDigits(0);
1476                }
1477            }
1478    
1479            if (useParensForNegativeNumbers != -1) {
1480                if (useParensForNegativeNumbers != 0) {
1481                    DecimalFormat dformat = (DecimalFormat)format;
1482                    dformat.setNegativePrefix("(");
1483                    dformat.setNegativeSuffix(
1484                            "" + dformat.getDecimalFormatSymbols().getPercent() +
1485                            ")");
1486                } else {
1487                    DecimalFormat dformat = (DecimalFormat)format;
1488                    dformat.setNegativePrefix(
1489                            "" + dformat.getDecimalFormatSymbols().getMinusSign());
1490                    dformat.setNegativeSuffix(
1491                            "" + dformat.getDecimalFormatSymbols().getPercent());
1492                }
1493            }
1494    
1495            if (groupDigits != -1) {
1496                format.setGroupingUsed(groupDigits != 0);
1497            }
1498    
1499            return format.format(expression);
1500    
1501        }
1502    
1503        // instr is already implemented in BuiltinFunTable... defer
1504    
1505        // public Object inStrB(Object start, Object string1, Object string2, int
1506        // compare /* default BinaryCompare */)
1507    
1508        // @FunctionName("InStr")
1509        // @Signature("InStr([start, ]string1, string2[, compare])")
1510        // @Description("Returns a Variant (Long) specifying the position of the first occurrence of one string within another.")
1511    
1512        @FunctionName("InStrRev")
1513        @Signature("InstrRev(stringcheck, stringmatch[, start[, compare]])")
1514        @Description("Returns the position of an occurrence of one string within another, from the end of string.")
1515        public static int inStrRev(String stringCheck, String stringMatch) {
1516            return inStrRev(stringCheck, stringMatch, -1);
1517        }
1518    
1519        @FunctionName("InStrRev")
1520        @Signature("InstrRev(stringcheck, stringmatch[, start[, compare]])")
1521        @Description("Returns the position of an occurrence of one string within another, from the end of string.")
1522        public static int inStrRev(String stringCheck, String stringMatch, int start /*
1523                                                                                         * default
1524                                                                                         * -1
1525                                                                                         */) {
1526            return inStrRev(stringCheck, stringMatch, start, 0);
1527        }
1528    
1529        @FunctionName("InStrRev")
1530        @Signature("InstrRev(stringcheck, stringmatch[, start[, compare]])")
1531        @Description("Returns the position of an occurrence of one string within another, from the end of string.")
1532        public static int inStrRev(String stringCheck, String stringMatch,
1533                int start /* default -1 */, int compare /* default BinaryCompare */) {
1534            // todo: implement binary vs. text compare
1535            if (start == 0 || start < -1) {
1536                throw new InvalidArgumentException(
1537                        "start must be -1 or a location in the string to start");
1538            }
1539            if (start != -1) {
1540                return stringCheck.lastIndexOf(stringMatch, start - 1) + 1;
1541            } else {
1542                return stringCheck.lastIndexOf(stringMatch) + 1;
1543            }
1544        }
1545    
1546        // public String join(Object sourceArray, Object delimiter)
1547    
1548        @FunctionName("LCase")
1549        @Signature("LCase(string)")
1550        @Description("Returns a String that has been converted to lowercase.")
1551        public static String lCase(String string) {
1552            return string.toLowerCase();
1553        }
1554    
1555        // public Object lCase$(Object string)
1556        // public String lTrim$(String string)
1557    
1558        @FunctionName("LTrim")
1559        @Signature("LTrim(string)")
1560        @Description("Returns a Variant (String) containing a copy of a specified string without leading spaces.")
1561        public static String lTrim(String string) {
1562            int i = 0, n = string.length();
1563            while (i < n) {
1564                if (string.charAt(i) > ' ') {
1565                    break;
1566                }
1567                i++;
1568            }
1569            return string.substring(i);
1570        }
1571    
1572        // public String left$(String string, int length)
1573        // public String leftB$(String string, int length)
1574        // public Object leftB(Object string, int length)
1575    
1576        @FunctionName("Left")
1577        @Signature("Left(string, length)")
1578        @Description("Returns a specified number of characters from the left side of a string.")
1579        public static String left(String string, int length) {
1580            final int stringLength = string.length();
1581            if (length >= stringLength) {
1582                return string;
1583            }
1584            return string.substring(0, length);
1585        }
1586    
1587        // public Object lenB(Object expression)
1588    
1589        // len is already implemented in BuiltinFunTable... defer
1590    
1591        // @FunctionName("Len")
1592        // @Signature("Len(String)")
1593        // @Description("Returns a Long containing the number of characters in a
1594        // string.")
1595        // public static int len(String expression) {
1596        // return expression.length();
1597        // }
1598    
1599        // public String mid$(String string, int start, Object length)
1600        // public String midB$(String string, int start, Object length)
1601        // public Object midB(Object string, int start, Object length)
1602    
1603        @FunctionName("Mid")
1604        @Signature("Mid(value, beginIndex[, length])")
1605        @Description("Returns a specified number of characters from a string.")
1606        public static String mid(String value, int beginIndex) {
1607            // If we used 'value.length() - beginIndex' as the default value for
1608            // length, we'd have problems if beginIndex is huge;
1609            // so 'value.length()' looks like an overestimate - but will always
1610            // return the correct result.
1611            final int length = value.length();
1612            return mid(value, beginIndex, length);
1613        }
1614    
1615        @FunctionName("Mid")
1616        @Signature("Mid(value, beginIndex[, length])")
1617        @Description("Returns a specified number of characters from a string.")
1618        public static String mid(String value, int beginIndex, int length) {
1619            if (beginIndex < 0) {
1620                throw new InvalidArgumentException("Invalid parameter. "
1621                        + "Start parameter of Mid function can't " + "be negative");
1622            }
1623            if (length < 0) {
1624                throw new InvalidArgumentException("Invalid parameter. "
1625                        + "Length parameter of Mid function can't " + "be negative");
1626            }
1627    
1628            if (beginIndex >= value.length()) {
1629                return "";
1630            }
1631    
1632            if (beginIndex != 0) {
1633                --beginIndex;
1634            }
1635            int endIndex = beginIndex + length;
1636            return endIndex >= value.length() ? value.substring(beginIndex) : value
1637                    .substring(beginIndex, endIndex);
1638        }
1639    
1640        @FunctionName("MonthName")
1641        @Signature("MonthName(month, abbreviate)")
1642        @Description("Returns a string indicating the specified month.")
1643        public static String monthName(int month, boolean abbreviate) {
1644            // VB months are 1-based, Java months are 0-based
1645            --month;
1646            return (abbreviate ? getDateFormatSymbols().getShortMonths()
1647                    : getDateFormatSymbols().getMonths())[month];
1648        }
1649    
1650        /**
1651         * Returns an instance of {@link DateFormatSymbols} for the current locale.
1652         *
1653         * <p>
1654         * Todo: inherit locale from connection.
1655         *
1656         * @return a DateFormatSymbols object
1657         */
1658        private static DateFormatSymbols getDateFormatSymbols() {
1659            // We would use DataFormatSymbols.getInstance(), but it is only
1660            // available from JDK 1.6 onwards.
1661            return DATE_FORMAT_SYMBOLS;
1662        }
1663    
1664        // public String rTrim$(String string)
1665    
1666        @FunctionName("RTrim")
1667        @Signature("RTrim(string)")
1668        @Description("Returns a Variant (String) containing a copy of a specified string without trailing spaces.")
1669        public static String rTrim(String string) {
1670            int i = string.length() - 1;
1671            while (i >= 0) {
1672                if (string.charAt(i) > ' ') {
1673                    break;
1674                }
1675                i--;
1676            }
1677            return string.substring(0, i + 1);
1678        }
1679    
1680        @FunctionName("Replace")
1681        @Signature("Replace(expression, find, replace[, start[, count[, compare]]])")
1682        @Description("Returns a string in which a specified substring has been replaced with another substring a specified number of times.")
1683        public static String replace(String expression, String find,
1684                String replace, int start, int count, int compare) {
1685            // compare is currently ignored
1686            Util.discard(compare);
1687            return _replace(expression, find, replace, start, count);
1688        }
1689    
1690        @FunctionName("Replace")
1691        @Signature("Replace(expression, find, replace[, start[, count[, compare]]])")
1692        @Description("Returns a string in which a specified substring has been replaced with another substring a specified number of times.")
1693        public static String replace(String expression, String find,
1694                String replace, int start, int count) {
1695            return _replace(expression, find, replace, start, count);
1696        }
1697    
1698        @FunctionName("Replace")
1699        @Signature("Replace(expression, find, replace[, start[, count[, compare]]])")
1700        @Description("Returns a string in which a specified substring has been replaced with another substring a specified number of times.")
1701        public static String replace(String expression, String find,
1702                String replace, int start) {
1703            return _replace(expression, find, replace, start, -1);
1704        }
1705    
1706        @FunctionName("Replace")
1707        @Signature("Replace(expression, find, replace[, start[, count[, compare]]])")
1708        @Description("")
1709        public static String replace(String expression, String find, String replace) {
1710            // compare is currently ignored
1711            return _replace(expression, find, replace, 1, -1);
1712        }
1713    
1714        private static String _replace(
1715            String expression,
1716            String find,
1717            String replace,
1718            int start /* default 1 */,
1719            int count /* default -1 */)
1720        {
1721            final StringBuilder buf = new StringBuilder(expression);
1722            int i = 0;
1723            int pos = start - 1;
1724            while (true) {
1725                if (i++ == count) {
1726                    break;
1727                }
1728                final int j = buf.indexOf(find, pos);
1729                if (j == -1) {
1730                    break;
1731                }
1732                buf.replace(j, j + find.length(), replace);
1733                pos = j + replace.length();
1734            }
1735            return buf.toString();
1736        }
1737    
1738        // public String right$(String string, int length)
1739        // public String rightB$(String string, int length)
1740        // public Object rightB(Object string, int length)
1741    
1742        @FunctionName("Right")
1743        @Signature("Right(string, length)")
1744        @Description("Returns a Variant (String) containing a specified number of characters from the right side of a string.")
1745        public static String right(String string, int length) {
1746            final int stringLength = string.length();
1747            if (length >= stringLength) {
1748                return string;
1749            }
1750            return string.substring(stringLength - length, stringLength);
1751        }
1752    
1753        // public String space$(int number)
1754    
1755        @FunctionName("Space")
1756        @Signature("Space(number)")
1757        @Description("Returns a Variant (String) consisting of the specified number of spaces.")
1758        public static String space(int number) {
1759            return string(number, ' ');
1760        }
1761    
1762        // public Object split(String expression, Object delimiter, int limit /*
1763        // default -1 */, int compare /* default BinaryCompare */)
1764    
1765        @FunctionName("StrComp")
1766        @Signature("StrComp(string1, string2[, compare])")
1767        @Description("Returns a Variant (Integer) indicating the result of a string comparison.")
1768        public static int strComp(String string1, String string2) {
1769            return strComp(string1, string2, 0);
1770        }
1771    
1772        @FunctionName("StrComp")
1773        @Signature("StrComp(string1, string2[, compare])")
1774        @Description("Returns a Variant (Integer) indicating the result of a string comparison.")
1775        public static int strComp(String string1, String string2, int compare/*
1776                                                                         * default
1777                                                                         * BinaryCompare
1778                                                                         */) {
1779            // Note: compare is currently ignored
1780            // Wrapper already checked whether args are null
1781            assert string1 != null;
1782            assert string2 != null;
1783            return string1.compareTo(string2);
1784        }
1785    
1786        // public Object strConv(Object string, StrConv conversion, int localeID)
1787    
1788        @FunctionName("StrReverse")
1789        @Signature("StrReverse(string)")
1790        @Description("Returns a string in which the character order of a specified string is reversed.")
1791        public static String strReverse(String expression) {
1792            final char[] chars = expression.toCharArray();
1793            for (int i = 0, j = chars.length - 1; i < j; i++, j--) {
1794                char c = chars[i];
1795                chars[i] = chars[j];
1796                chars[j] = c;
1797            }
1798            return new String(chars);
1799        }
1800    
1801        // public String string$(int number, Object character)
1802    
1803        @FunctionName("String")
1804        @Signature("String(number, character)")
1805        @Description("")
1806        public static String string(int number, char character) {
1807            if (character == 0) {
1808                return "";
1809            }
1810            final char[] chars = new char[number];
1811            Arrays.fill(chars, (char) (character % 256));
1812            return new String(chars);
1813        }
1814    
1815        // public String trim$(String string)
1816    
1817        @FunctionName("Trim")
1818        @Signature("Trim(string)")
1819        @Description("Returns a Variant (String) containing a copy of a specified string without leading and trailing spaces.")
1820        public static String trim(String string) {
1821            // JDK has a method for trim, but not ltrim or rtrim
1822            return string.trim();
1823        }
1824    
1825        // ucase is already implemented in BuiltinFunTable... defer
1826    
1827        // public String uCase$(String string)
1828    
1829    //    @FunctionName("UCase")
1830    //    @Signature("UCase(string)")
1831    //    @Description("Returns a String that has been converted to uppercase.")
1832    //    public String uCase(String string) {
1833    //        return string.toUpperCase();
1834    //    }
1835    
1836        // TODO: should use connection's locale to determine first day of week,
1837        // not the JVM's default
1838    
1839        @FunctionName("WeekdayName")
1840        @Signature("WeekdayName(weekday, abbreviate, firstdayofweek)")
1841        @Description("Returns a string indicating the specified day of the week.")
1842        public static String weekdayName(int weekday, boolean abbreviate,
1843                int firstDayOfWeek) {
1844            // Java and VB agree: SUNDAY = 1, ... SATURDAY = 7
1845            final Calendar calendar = Calendar.getInstance();
1846            if (firstDayOfWeek == 0) {
1847                firstDayOfWeek = calendar.getFirstDayOfWeek();
1848            }
1849            // compensate for start of week
1850            weekday += (firstDayOfWeek - 1);
1851            // bring into range 1..7
1852            weekday = (weekday - 1) % 7 + 1;
1853            if (weekday <= 0) {
1854                // negative numbers give negative modulo
1855                weekday += 7;
1856            }
1857            return (abbreviate ? getDateFormatSymbols().getShortWeekdays()
1858                    : getDateFormatSymbols().getWeekdays())[weekday];
1859        }
1860    
1861        // Misc
1862    
1863        // public Object array(Object argList)
1864        // public String input$(int number, int fileNumber)
1865        // public String inputB$(int number, int fileNumber)
1866        // public Object inputB(int number, int fileNumber)
1867        // public Object input(int number, int fileNumber)
1868        // public void width(int fileNumber, int width)
1869    
1870        // ~ Inner classes
1871    
1872        private enum Interval {
1873            yyyy("Year", Calendar.YEAR), q("Quarter", -1), m("Month",
1874                    Calendar.MONTH), y("Day of year", Calendar.DAY_OF_YEAR), d(
1875                    "Day", Calendar.DAY_OF_MONTH), w("Weekday",
1876                    Calendar.DAY_OF_WEEK), ww("Week", Calendar.WEEK_OF_YEAR), h(
1877                    "Hour", Calendar.HOUR_OF_DAY), n("Minute", Calendar.MINUTE), s(
1878                    "Second", Calendar.SECOND);
1879            private final int dateField;
1880    
1881            Interval(String desc, int dateField) {
1882                Util.discard(desc);
1883                this.dateField = dateField;
1884            }
1885    
1886            void add(Calendar calendar, int amount) {
1887                switch (this) {
1888                case q:
1889                    calendar.add(Calendar.MONTH, amount * 3);
1890                    break;
1891                default:
1892                    calendar.add(dateField, amount);
1893                    break;
1894                }
1895            }
1896    
1897            Calendar floor(Calendar calendar) {
1898                Calendar calendar2 = Calendar.getInstance();
1899                calendar2.setTime(calendar.getTime());
1900                floorInplace(calendar2);
1901                return calendar2;
1902            }
1903    
1904            private void floorInplace(Calendar calendar) {
1905                switch (this) {
1906                case yyyy:
1907                    calendar.set(Calendar.DAY_OF_YEAR, 1);
1908                    d.floorInplace(calendar);
1909                    break;
1910                case q:
1911                    int month = calendar.get(Calendar.MONTH);
1912                    month -= month % 3;
1913                    calendar.set(Calendar.MONTH, month);
1914                    calendar.set(Calendar.DAY_OF_MONTH, 1);
1915                    d.floorInplace(calendar);
1916                    break;
1917                case m:
1918                    calendar.set(Calendar.DAY_OF_MONTH, 1);
1919                    d.floorInplace(calendar);
1920                    break;
1921                case w:
1922                    final int dow = calendar.get(Calendar.DAY_OF_WEEK);
1923                    final int firstDayOfWeek = calendar.getFirstDayOfWeek();
1924                    if (dow == firstDayOfWeek) {
1925                        // nothing to do
1926                    } else if (dow > firstDayOfWeek) {
1927                        final int roll = firstDayOfWeek - dow;
1928                        assert roll < 0;
1929                        calendar.roll(Calendar.DAY_OF_WEEK, roll);
1930                    } else {
1931                        final int roll = firstDayOfWeek - dow - 7;
1932                        assert roll < 0;
1933                        calendar.roll(Calendar.DAY_OF_WEEK, roll);
1934                    }
1935                    d.floorInplace(calendar);
1936                    break;
1937                case y:
1938                case d:
1939                    calendar.set(Calendar.HOUR_OF_DAY, 0);
1940                    calendar.set(Calendar.MINUTE, 0);
1941                    calendar.set(Calendar.SECOND, 0);
1942                    calendar.set(Calendar.MILLISECOND, 0);
1943                    break;
1944                case h:
1945                    calendar.set(Calendar.MINUTE, 0);
1946                    calendar.set(Calendar.SECOND, 0);
1947                    calendar.set(Calendar.MILLISECOND, 0);
1948                    break;
1949                case n:
1950                    calendar.set(Calendar.SECOND, 0);
1951                    calendar.set(Calendar.MILLISECOND, 0);
1952                    break;
1953                case s:
1954                    calendar.set(Calendar.MILLISECOND, 0);
1955                    break;
1956                }
1957            }
1958    
1959            int diff(Calendar calendar1, Calendar calendar2, int firstDayOfWeek) {
1960                switch (this) {
1961                case q:
1962                    return m.diff(calendar1, calendar2, firstDayOfWeek) / 3;
1963                default:
1964                    return floor(calendar1).get(dateField)
1965                            - floor(calendar2).get(dateField);
1966                }
1967            }
1968    
1969            int datePart(Calendar calendar) {
1970                switch (this) {
1971                case q:
1972                    return (m.datePart(calendar) + 2) / 3;
1973                case m:
1974                    return calendar.get(dateField) + 1;
1975                case w:
1976                    int dayOfWeek = calendar.get(dateField);
1977                    dayOfWeek -= (calendar.getFirstDayOfWeek() - 1);
1978                    dayOfWeek = dayOfWeek % 7;
1979                    if (dayOfWeek <= 0) {
1980                        dayOfWeek += 7;
1981                    }
1982                    return dayOfWeek;
1983                default:
1984                    return calendar.get(dateField);
1985                }
1986            }
1987        }
1988    
1989        private enum FirstWeekOfYear {
1990            vbUseSystem(0, "Use the NLS API setting."), vbFirstJan1(1,
1991                    "Start with week in which January 1 occurs (default)."), vbFirstFourDays(
1992                    2,
1993                    "Start with the first week that has at least four days in the new year."), vbFirstFullWeek(
1994                    3, "Start with first full week of the year.");
1995    
1996            FirstWeekOfYear(int code, String desc) {
1997                assert code == ordinal();
1998                assert desc != null;
1999            }
2000    
2001            void apply(Calendar calendar) {
2002                switch (this) {
2003                case vbUseSystem:
2004                    break;
2005                case vbFirstJan1:
2006                    calendar.setMinimalDaysInFirstWeek(1);
2007                    break;
2008                case vbFirstFourDays:
2009                    calendar.setMinimalDaysInFirstWeek(4);
2010                    break;
2011                case vbFirstFullWeek:
2012                    calendar.setMinimalDaysInFirstWeek(7);
2013                    break;
2014                }
2015            }
2016        }
2017    }
2018    
2019    // End Vba.java