Looks like I'm ending up spending as much time on date parsing as Martin...

Parsing of MMM and MMMM starts to work now, too. Additionally I looked into parsing and formatting of yy vs. yyyy.

Regards,
Arvid
/*
 * Copyright 2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var DateFormatSymbols = Class.create();
DateFormatSymbols.prototype = {
    initialize: function()
    {
        this.eras = new Array('BC', 'AD');
        this.months = new Array('January', 'February', 'March', 'April',
                'May', 'June', 'July', 'August', 'September', 'October',
                'November', 'December', 'Undecimber');
        this.shortMonths = new Array('Jan', 'Feb', 'Mar', 'Apr',
                'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct',
                'Nov', 'Dec', 'Und');
        this.weekdays = new Array('Sunday', 'Monday', 'Tuesday',
                'Wednesday', 'Thursday', 'Friday', 'Saturday');
        this.shortWeekdays = new Array('Sun', 'Mon', 'Tue',
                'Wed', 'Thu', 'Fri', 'Sat');
        this.ampms = new Array('AM', 'PM');
        this.zoneStrings = new Array(new Array(0, 'long-name', 'short-name'));
    },
}

var SimpleDateFormatParserContext = Class.create();
SimpleDateFormatParserContext.prototype = {
    initialize: function()
    {
        this.newIndex=0;
        this.retValue=0;
        this.year=0;
        this.month=0;
        this.day=0;
        this.dayOfWeek=0;
        this.hour=0;
        this.min=0;
        this.sec=0;
        this.ampm=0;
        this.dateStr="";
    },
}

var SimpleDateFormat = Class.create();
SimpleDateFormat.prototype = {
    initialize: function(pattern, dateFormatSymbols)
    {
        this.pattern = pattern;
        this.dateFormatSymbols = dateFormatSymbols ? dateFormatSymbols : new 
DateFormatSymbols();
    },


    _handle: function(dateStr, date, parse)
    {
        var patternIndex = 0;
        var dateIndex = 0;
        var commentMode = false;
        var lastChar = 0;
        var currentChar=0;
        var nextChar=0;
        var patternSub = null;

        var context = new SimpleDateFormatParserContext();

        if(date != null)
        {
            var yearStr = date.getYear()+"";

            if (yearStr.length < 4)
            {
                  yearStr=(parseInt(yearStr, 10)+1900)+"";
            }

            context.year = parseInt(yearStr, 10);
            context.month = date.getMonth();
            context.day = date.getDate();
            context.dayOfWeek = date.getDay();
            context.hour = date.getHours();
            context.min = date.getMinutes();
            context.sec = date.getSeconds();
        }

        while (patternIndex < this.pattern.length)
        {
            currentChar = this.pattern.charAt(patternIndex);

            if(patternIndex<(this.pattern.length-1))
            {
                nextChar = this.pattern.charAt(patternIndex+1);
            }
            else
            {
                nextChar = 0;
            }


            if (currentChar == '\'' && lastChar!='\\')
            {
                commentMode = !commentMode;
                patternIndex++;
            }
            else
            {
                if(!commentMode)
                {
                    if (currentChar == '\\' && lastChar!='\\')
                    {
                        patternIndex++;
                    }
                    else
                    {
                        if(patternSub == null)
                            patternSub = "";

                        patternSub+=currentChar;

                        if(currentChar != nextChar)
                        {
                            this._handlePatternSub(context, patternSub,
                                    dateStr, dateIndex, parse);

                            patternSub = null;

                            if(context.newIndex<0)
                                break;

                            dateIndex = context.newIndex;
                        }

                        patternIndex++;
                    }
                }
                else
                {
                    if(parse)
                    {
                        
if(this.pattern.charAt(patternIndex)!=dateStr.charAt(dateIndex))
                        {
                            //invalid character in dateString
                            return null;
                        }
                    }
                    else
                    {
                        context.dateStr+=this.pattern.charAt(patternIndex);
                    }

                    patternIndex++;
                    dateIndex++;
                }
            }

            lastChar = currentChar;
        }

        this._handlePatternSub(context, patternSub,
                dateStr, dateIndex, parse);

        return context;
    },

    parse: function(dateStr)
    {
        var context = this._handle(dateStr, null, true);

        return new Date(context.year, context.month,
                context.day,context.hour,context.min,context.sec);
    },

    format: function(date)
    {
        var context = this._handle(null, date, false);

        return context.dateStr;
    },

    parseString: function(context, dateStr, dateIndex, strings)
    {
        for(var i=0; i<strings.length;i++)
        {
            var currentStrings = strings[0];

            for (var j = 0; j < currentStrings.length; j++)
            {
                var currentString = currentStrings[j];

                
if(dateStr.substring(dateIndex,dateIndex+currentString.length).equals(currentString))
                {
                    context.newIndex=dateIndex+currentString.length;
                    context.retValue=j;
                    return context;
                }
            }
        }

        context.newIndex=-1;
        return context;
    },

    _parseNum: function(context, dateStr, posCount, dateIndex)
    {
        for(var i=posCount;i>0;i--)
        {
            var numStr = dateStr.substring(dateIndex,dateIndex+posCount);

            context.retValue = parseInt(numStr, 10);

            if(context.retValue == Number.NaN)
                continue;

            context.newIndex = dateIndex+posCount;
            return context;
        }

        context.newIndex = -1;
        return context;
    },

    _handlePatternSub: function(context, patternSub, dateStr,
                                  dateIndex, parse)
    {
        if(patternSub==null || patternSub.length==0)
            return;

        if(patternSub.charAt(0)=='y')
        {
            if(parse)
            {
                if (patternSub.length <= 3) {
                  this._parseNum(context, dateStr,2,dateIndex);
                  context.year = (context.retValue < 26)
                      ? 2000 + context.retValue : 1900 + context.retValue;
                } else {
                  this._parseNum(context, dateStr,4,dateIndex);
                  context.year = context.retValue;
                }
            }
            else
            {
                this._formatNum(context,context.year,patternSub.length <= 3 ? 2 
: 4);
            }
        }
        else if(patternSub.charAt(0)=='M')
        {
            if(parse)
            {
              if (patternSub.length == 3) {
                var fragment = dateStr.substr(dateIndex, 3);
                var index = this._indexOf(this.dateFormatSymbols.shortMonths, 
fragment);
                if (index != -1) {
                  context.month = index;
                  context.newIndex = dateIndex + 3;
                }
              } else if (patternSub.length >= 4) {
                var fragment = dateStr.substr(dateIndex);
                var index = this._prefixOf(this.dateFormatSymbols.months, 
fragment);
                if (index != -1) {
                  context.month = index;
                  context.newIndex = dateIndex + 
this.dateFormatSymbols.months[index].length;
                }
              } else {
                this._parseNum(context, dateStr,2,dateIndex);
                context.month = context.retValue-1;
              }
            }
            else
            {
                if (patternSub.length == 3) {
                  context.dateStr += 
this.dateFormatSymbols.shortMonths[context.month];
                } else if (patternSub.length >= 4) {
                  context.dateStr += 
this.dateFormatSymbols.months[context.month];
                } else {
                  this._formatNum(context,context.month+1,patternSub.length);
                }
            }
        }
        else if(patternSub.charAt(0)=='d')
        {
            if(parse)
            {
                this._parseNum(context, dateStr,2,dateIndex);
                context.day = context.retValue;
            }
            else
            {
                this._formatNum(context,context.day,patternSub.length);
            }
        }
        else if(patternSub.charAt(0)=='E')
        {
            if(parse)
            {
              // XXX dayOfWeek is not used to generate date at the moment
              if (patternSub.length <= 3) {
                var fragment = dateStr.substr(dateIndex, 3);
                var index = this._indexOf(this.dateFormatSymbols.shortWeekdays, 
fragment);
                if (index != -1) {
                  context.dayOfWeek = index;
                  context.newIndex = dateIndex + 3;
                }
              } else {
                var fragment = dateStr.substr(dateIndex);
                var index = this._prefixOf(this.dateFormatSymbols.weekdays, 
fragment);
                if (index != -1) {
                  context.dayOfWeek = index;
                  context.newIndex = dateIndex + 
this.dateFormatSymbols.weekdays[index].length;
                }
              }
            }
            else
            {
              if (patternSub.length <= 3) {
                context.dateStr += 
this.dateFormatSymbols.shortWeekdays[context.dayOfWeek];
              } else {
                context.dateStr += 
this.dateFormatSymbols.weekdays[context.dayOfWeek];
              }
            }
        }
        else if(patternSub.charAt(0)=='H' ||
                patternSub.charAt(0)=='h')
        {
            if(parse)
            {
                this._parseNum(context, dateStr,2,dateIndex);
                context.hour = context.retValue;
            }
            else
            {
                this._formatNum(context,context.hour,patternSub.length);
            }
        }
        else if(patternSub.charAt(0)=='m')
        {
            if(parse)
            {
                this._parseNum(context, dateStr,2,dateIndex);
                context.min = context.retValue;
            }
            else
            {
                this._formatNum(context,context.min,patternSub.length);
            }
        }
        else if(patternSub.charAt(0)=='s')
        {
            if(parse)
            {
                this._parseNum(context, dateStr,2,dateIndex);
                context.sec = context.retValue;
            }
            else
            {
                this._formatNum(context,context.sec,patternSub.length);
            }
        }
        else if(patternSub.charAt(0)=='a')
        {
            if(parse)
            {
                parseString(context, dateStr,dateIndex,new Array(ampms));
                context.ampm = context.retValue;
            }
            else
            {
                this._formatNum(context,context.ampm,patternSub.length);
            }
        }
        else
        {
            if(parse)
            {
                context.newIndex=dateIndex+patternSub.length;
            }
            else
            {
                context.dateStr+=patternSub;
            }

        }
    },

    _formatNum: function (context, num, length)
    {
        var str = num+"";

        while(str.length<length)
            str="0"+str;

        // XXX dow we have to distinguish left and right 'cutting'
        if (str.length > length) {
          str = str.substr(str.length - length);
        }

        context.dateStr+=str;
    },

    // perhaps add to Array.prototype
    _indexOf: function (array, value) {
      for (var i = 0; i < array.length; ++i) {
        if (array[i] == value) {
          return i;
        }
      }
      return -1;
    },

    _prefixOf: function (array, value) {
      for (var i = 0; i < array.length; ++i) {
        if (value.indexOf(array[i]) == 0) {
          return i;
        }
      }
      return -1;
    }
}
import org.mozilla.javascript.JavaScriptException;

import java.text.SimpleDateFormat;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.util.Date;
import java.util.Locale;
import java.util.Calendar;
import java.io.IOException;

public class DateTest extends JavaScriptTestCase {

  private static final int[] YEAR_MONTH_DAY
      = {Calendar.YEAR, Calendar.MONTH, Calendar.DAY_OF_MONTH};

  protected void setUp() throws Exception {
    super.setUp();
    // loadScriptFile("debug.js");
    loadScriptFile("prototype-basics.js");
    loadScriptFile("date.js");
  }

  public void testNumberOnlyDateFormats() throws IOException, 
JavaScriptException {
    Date date = new Date();

    checkFormat("yyyyMMdd", date, YEAR_MONTH_DAY);
    checkFormat("ddMMyyyy", date, YEAR_MONTH_DAY);
    checkFormat("dd/MM/yyyy", date, YEAR_MONTH_DAY);
    checkFormat("dd/MM/yyy", date, YEAR_MONTH_DAY);
    checkFormat("dd/MM/yy", date, YEAR_MONTH_DAY);
    checkFormat("EddMMyyyy", date, YEAR_MONTH_DAY);
  }

  public void testEnglishMonths() throws IOException {
    for (int month = 0; month < 12; ++month) {
      Calendar calendar = Calendar.getInstance(Locale.ENGLISH);
      calendar.set(Calendar.MONTH, month);
      Date date = calendar.getTime();

      StringBuffer format = new StringBuffer("M");
      for (int i = 0; i < 4; ++i) {
        format.append('M');
        checkFormat(format.toString(), date, new int[] {Calendar.MONTH});
      }
    }
  }

  public void testTwoDigitYears() throws IOException, ParseException {
    DecimalFormat decimalFormat = new DecimalFormat("00");
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yy", 
Locale.ENGLISH);
    Calendar calendar = Calendar.getInstance(Locale.ENGLISH);
    for (int year = 0; year < 100; ++year) {
      String yearString = decimalFormat.format(year);
      calendar.setTime(simpleDateFormat.parse(yearString));
      assertEquals(calendar.get(Calendar.YEAR), evalParseDate(yearString, 
"yy").get(Calendar.YEAR));
    }
  }

  public void testEnglishWeekDays() throws IOException {
    for (int day = 1; day <= 7; ++day) {
      Calendar calendar = Calendar.getInstance();
      calendar.set(Calendar.DAY_OF_WEEK, day);
      Date date = calendar.getTime();

      StringBuffer format = new StringBuffer();
      for (int i = 0; i < 4; ++i) {
        format.append('E');
        checkFormat(format.toString(), date, new int[0]); // XXX new int[] 
{Calendar.DAY_OF_WEEK}
      }
    }
  }

  private Object evalFormatDate(Date date, String format) {
    return eval("new SimpleDateFormat(\"" + format + "\")"
        + ".format(new Date(" + date.getTime() + "))");
  }

  private Calendar evalParseDate(String input, String format) {
    long time = evalLong("new SimpleDateFormat(\"" + format + "\")"
        + ".parse(\"" + input + "\").getTime()");
    Calendar calendar = Calendar.getInstance(Locale.ENGLISH);
    calendar.setTime(new Date(time));
    return calendar;
  }

  private void checkFormat(String format, Date date, int[] fields) {
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format, 
Locale.ENGLISH);
    assertEquals(simpleDateFormat.format(date), evalFormatDate(date, format));
    Calendar calendar1 = Calendar.getInstance(Locale.ENGLISH);
    calendar1.setTime(date);
    Calendar calendar2 = evalParseDate(simpleDateFormat.format(date), 
format.toString());
    for (int i = 0; i < fields.length; i++) {
      int field = fields[i];
      checkField(calendar1, calendar2, field);
    }
  }

  private void checkField(Calendar calendar1, Calendar calendar2, int field) {
    assertEquals(calendar1.get(field), calendar2.get(field));
  }

}
import junit.framework.TestCase;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.JavaScriptException;

import java.io.IOException;
import java.io.FileReader;
import java.util.Date;

public class JavaScriptTestCase extends TestCase {

  protected Context cx;
  protected Scriptable scope;

  protected void setUp() throws Exception {
    super.setUp();
    cx = Context.enter();
    scope = cx.initStandardObjects(null);
  }

  protected void tearDown() throws Exception {
    Context.exit();
  }

  protected Object eval(String script) throws JavaScriptException {
    return cx.evaluateString(scope, script, "test", 1, null);
  }

  protected int evalInt(String script) throws JavaScriptException {
    Object o = eval(script);
    if (o instanceof Number) {
      return ((Number) o).intValue();
    }
    throw new JavaScriptException(null, "invalid return type "
        + o.getClass().getName() + " with value " + o, 0);
  }

  protected long evalLong(String script) throws JavaScriptException {
    Object o = eval(script);
    if (o instanceof Number) {
      return ((Number) o).longValue();
    }
    throw new JavaScriptException(null, "invalid return type "
        + o.getClass().getName() + " with value " + o, 0);
  }

  protected boolean evalBoolean(String script) throws JavaScriptException {
    Object o = eval(script);
    if (o instanceof Boolean) {
      return ((Boolean) o).booleanValue();
    }
    throw new JavaScriptException(null, "invalid return type "
        + o.getClass().getName() + " with value " + o, 0);
  }

  // XXX directory handling
  protected void loadScriptFile(String jsFile)
      throws IOException, JavaScriptException {
    cx.evaluateReader(scope, new FileReader(jsFile), jsFile, 0, null);
  }
}

Reply via email to