You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by sk...@apache.org on 2008/01/02 17:48:13 UTC

svn commit: r608142 - /myfaces/tomahawk/trunk/core/src/main/resources/org/apache/myfaces/custom/calendar/resource/date.js

Author: skitching
Date: Wed Jan  2 08:48:05 2008
New Revision: 608142

URL: http://svn.apache.org/viewvc?rev=608142&view=rev
Log:
Major restructure/rewrite. This code is now a port of the java class org.apache.myfaces.dateformat.SimpleDateFormatter,
which has proper unit tests. This rewrite fixes a few bugs and adds support for "week of year" formats.

Modified:
    myfaces/tomahawk/trunk/core/src/main/resources/org/apache/myfaces/custom/calendar/resource/date.js

Modified: myfaces/tomahawk/trunk/core/src/main/resources/org/apache/myfaces/custom/calendar/resource/date.js
URL: http://svn.apache.org/viewvc/myfaces/tomahawk/trunk/core/src/main/resources/org/apache/myfaces/custom/calendar/resource/date.js?rev=608142&r1=608141&r2=608142&view=diff
==============================================================================
--- myfaces/tomahawk/trunk/core/src/main/resources/org/apache/myfaces/custom/calendar/resource/date.js (original)
+++ myfaces/tomahawk/trunk/core/src/main/resources/org/apache/myfaces/custom/calendar/resource/date.js Wed Jan  2 08:48:05 2008
@@ -18,848 +18,921 @@
  */
 
 //----------------------------------------------------------------------------
-// A javascript implementation of most of the java.text.SimpleDateFormat class,
-// written for the purposes of implementing the Apache MyFaces Tomahawk
-// calendar control.
+// This file contains a javascript implementation of most of the
+// java.text.SimpleDateFormat class, written for the purposes of implementing
+// the Apache MyFaces Tomahawk calendar control.
 //
-// This file defines a javascript class, org_apache_myfaces_SimpleDateFormat.
+// This file defines a javascript class, org_apache_myfaces_dateformat_SimpleDateFormat.
 // An instance of this class can be constructed, then used to parse strings
 // into dates, and format dates into strings.
 //
 // Note that there is one difference from SimpleDateFormat in the formatting
-// string pattern; this code also supports the JODA "xxxx" pattern for weekYear.
-// If this is used, then the ugly java.text.SimpleDateFormat mangling of the
-// purpose of the "yyyy" pattern is disabled, allowing formatting patterns like
-// "xxxx-ww  yyyy-mm-dd" to behave correctly.
-//----------------------------------------------------------------------------
-
-//----------------------------------------------------------------------------
-// Return the week# represented by the specified date (1..53).
-//
-// This implements the ISO-8601 standard for week numbering, as documented in
-// Klaus Tondering's Calendar document, version 2.8:
-//   http://www.tondering.dk/claus/calendar.html
-//
-// For dates in January and February, calculate:
-//
-//    a = year-1
-//    b = a/4 - a/100 + a/400
-//    c = (a-1)/4 - (a-1)/100 + (a-1)/400
-//    s = b-c
-//    e = 0
-//    f = day - 1 + 31*(month-1)
-//
-// For dates in March through December, calculate:
-//
-//    a = year
-//    b = a/4 - a/100 + a/400
-//    c = (a-1)/4 - (a-1)/100 + (a-1)/400
-//    s = b-c
-//    e = s+1
-//    f = day + (153*(month-3)+2)/5 + 58 + s
-//
-// Then, for any month continue thus:
-//
-//    g = (a + b) mod 7
-//    d = (f + g - e) mod 7
-//    n = f + 3 - d
-//
-// We now have three situations:
-//
-//    If n<0, the day lies in week 53-(g-s)/5 of the previous year.
-//    If n>364+s, the day lies in week 1 of the coming year.
-//    Otherwise, the day lies in week n/7 + 1 of the current year.
-//
-// This algorithm gives you a couple of additional useful values:
-//
-//    d indicates the day of the week (0=Monday, 1=Tuesday, etc.)
-//    f+1 is the ordinal number of the date within the current year.
+// string pattern; this code optionally supports ISO-8601 weeks, and the JODA
+// "xxxx" pattern for weekYear.
 //
-// Note that ISO-8601 specifies that week1 of a year is the first week in
-// which the majority of days lie in that year. An equivalent description
-// is that it is the first week including the 4th of january. This means
-// that the 1st, 2nd and 3rd of January might lie in the last week of the
-// previous year, and that the last week of a year may include the first
-// few days of the following year.
-//
-// ISO-8601 also specifies that the first day of the week is always Monday.
-//
-// This function returns the week number regardless of which year it lies in.
-// That means that asking for the week# of 01/01/yyyy might return 52 or 53,
-// and asking for the week# of 31/12/yyyy might return 1.
+// This code is a "port" of java class org.apache.myfaces.dateformat.SimpleDateFormatter
+// from Apache Myfaces Tomahawk, with the minimum number of changes needed to
+// make the code work in javascript. See that class for further documentation
+// about the code in this file. Any "fixes" needed in the code below should
+// first be implemented (and tested) in the base java version.
+//
+// The main differences between the java code and this javascript are that
+// in java a lot of methods are declared static to ensure they do not
+// accidentally change the object state. But this is very inconvenient to
+// do in javascript as static function names must include the "namespace"
+// prefix, eg org_apache_myfaces_dateformat_SimpleDateFormatter_foo().
+// Instead, all these methods are implemented as non-static member methods.
+// In addition, private methods have an underscore added to the start of
+// the name. Note also that the constructor for a java.util.Date object takes
+// a year relative to 1900, while javascript takes one relative to 0AD.
 //----------------------------------------------------------------------------
-function org_apache_myfaces_SimpleDateFormat_weekNbr(n)
+
+// Constructor for DateFormatSymbols class
+org_apache_myfaces_dateformat_DateFormatSymbols = function()
 {
-    var year = n.getFullYear();
-    var month = n.getMonth() + 1;
-    var day = n.getDate();
-
-    var a,b,c,d,e,f,g;
-
-    if (month <= 2)
-    {
-        a = year - 1;
-        b = Math.floor(a/4) - Math.floor(a/100) + Math.floor(a/400);
-        c = Math.floor((a-1)/4) - Math.floor((a-1)/100) + Math.floor((a-1)/400);
-        s = b - c;
-        e = 0;
-        f = day - 1 + 31*(month-1);
-    }
-    else
-    {
-        a = year;
-        b = Math.floor(a/4) - Math.floor(a/100) + Math.floor(a/400);
-        c = Math.floor((a-1)/4) - Math.floor((a-1)/100) + Math.floor((a-1)/400);
-        s = b - c;
-        e = s + 1;
-        f = day + Math.floor((153*(month-3) + 2)/5) + 58 + s;       
-    }
+  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 threshold = new Date();
+  threshold.setYear(threshold.getYear()-80);
+  this.twoDigitYearStart = threshold;
+}
 
-    g = (a + b) % 7;
-    d = (f + g - e) % 7;
-    n = f + 3 - d;
+// Constructor for ParserContext class
+org_apache_myfaces_dateformat_ParserContext = function(dow)
+{
+  this.newIndex=0;
+  this.invalid = false;
+  this.firstDayOfWeek = dow;
+  this.ambigousYear=false;
+  this.ambigousWeekYear=false;
+  
+  this.year=0;
+  this.month=0;
+  this.day=1;
+  this.dayOfWeek=0;
+  this.hour=0;
+  this.hourAmpm;
+  this.min=0;
+  this.sec=0;
+  this.ampm=0;
+  
+  this.weekYear=0;
+  this.weekOfWeekYear=0;
+}
 
-    var week;
-    if (n<0)
-    {
-        // previous year
-        week = 53 - Math.floor((g-s)/5);
-    }
-    else if (n > (364+s))
-    {
-        // next year
-        week = 1;
-    }
-    else
-    {
-        // current year
-        week = Math.floor(n/7) + 1;
-    }
-    
-    return week;
+// constructor for WeekDate class
+org_apache_myfaces_dateformat_WeekDate = function(year, week)
+{
+  this.year = year;
+  this.week = week;
 }
 
-//----------------------------------------------------------------------------
-// Create a new date using "week in year" rather than "month in year".
-// Weeks always begin on a monday (ISO-8601).
-//
-// This functions like
-//   new Date(year,month,day,hour,min,sec)
-// except that week (1..53) is used in place of month, and day is (1..7), not 
-// 1..31.
-//----------------------------------------------------------------------------
-org_apache_myfaces_SimpleDateFormat_newDateByWeekYear = function(year, week, day, hour, min, sec)
+// constructor for StringBuffer class
+org_apache_myfaces_dateformat_StringBuffer = function(s)
 {
-    var MSECS_PER_DAY = 24*60*60*1000;
-    
-    var date = new Date(
-        year, 0, 1,
-        hour, min, sec);
-    
-    // Nudge date to the nearest Monday from the start of the year;
-    // weeks always start on a monday. Note that it might be in the
-    // previous year. The actual date for the start of the first week
-    // in the year is guaranteed to be in the range (29dec-4jan)
-    var dow = date.getDay();
-    var daysOffset;
-    if (dow == 1)
-    {
-      // first day of year is monday, so no offset needed.
-      daysOffset = 0;
-    }
-    else if (dow <= 4)
-    {
-      // first day-of-year is tue, wed, thurs so the nearest monday is
-      // earlier (in the previous year). The offset will be negative.
-      daysOffset = 1 - dow;
-    }
-    else
-    {
-      // first day-of-year is fri,sat,sun so the nearest monday
-      // is later..
-      daysOffset = 8 - dow;
-    }
-    
-    // now add week*7 days
-    daysOffset += (week - 1) * 7 + (day - 1);
+  if (s == null)
+    this.str = "";
+  else
+    this.str = s;
+
+  var proto = org_apache_myfaces_dateformat_StringBuffer.prototype;    
+  proto.append = function(s)
+  {
+    this.str = this.str + s;
+  }
+
+  proto.toString = function()
+  {
+    return this.str;
+  }
+}
 
-    // do arithmetic
-    var msecsBase = date.getTime();
-    var msecsOffset = daysOffset * MSECS_PER_DAY;
-
-    var finalDate = new Date();
-    finalDate.setTime(msecsBase + msecsOffset);
-    return finalDate;
+// Constructor for SimpleDateFormatter class
+org_apache_myfaces_dateformat_SimpleDateFormatter = function(pattern, symbols, firstDayOfWeek)
+{
+  this._construct(pattern, symbols, firstDayOfWeek);
 }
 
-//----------------------------------------------------------------------------
-// Constructor for a simple object that contains locale-specific constants
-// used for date parsing and formatting.
-//----------------------------------------------------------------------------
-org_apache_myfaces_DateFormatSymbols = function()
+// static initialisation block; requires constructor for SimpleDateFormatter
+// to run first in order to create the prototype object that this block
+// attaches methods and constants to.
 {
-        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 threshold = new Date();
-        threshold.setYear(threshold.getYear()-80);
-        this.twoDigitYearStart = threshold;
+var proto = org_apache_myfaces_dateformat_SimpleDateFormatter.prototype;
+
+proto.MSECS_PER_SEC = 1000;
+proto.MSECS_PER_MIN = 60 * proto.MSECS_PER_SEC;
+proto.MSECS_PER_HOUR = 60 * proto.MSECS_PER_MIN;
+proto.MSECS_PER_DAY = 24 * proto.MSECS_PER_HOUR;
+proto.MSECS_PER_WEEK = 7 * proto.MSECS_PER_DAY;
+  
+proto.MONTH_LEN = 
+[
+  0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
+];
+
+proto._getIsoWeekDate = function(date)
+{
+  var year = date.getFullYear();
+  var month = date.getMonth() + 1;
+  var day = date.getDate();
+
+  var a,b,c,d,e,f,g,s,n;
+
+  if (month <= 2)
+  {
+    a = year - 1;
+    b = Math.floor(a/4) - Math.floor(a/100) + Math.floor(a/400);
+    c = Math.floor((a-1)/4) - Math.floor((a-1)/100) + Math.floor((a-1)/400);
+    s = b - c;
+    e = 0;
+    f = day - 1 + 31*(month-1);
+  }
+  else
+  {
+    a = year;
+    b = Math.floor(a/4) - Math.floor(a/100) + Math.floor(a/400);
+    c = Math.floor((a-1)/4) - Math.floor((a-1)/100) + Math.floor((a-1)/400);
+    s = b - c;
+    e = s + 1;
+    f = day + Math.floor((153*(month-3) + 2)/5) + 58 + s;       
+  }
+
+  g = (a + b) % 7;
+  d = (f + g - e) % 7;
+  n = f + 3 - d;
+
+  if (n<0)
+  {
+    // previous year
+    var resultWeek = 53 - Math.floor((g-s)/5);
+    return new org_apache_myfaces_dateformat_WeekDate(year-1, resultWeek);
+  }
+  else if (n > (364+s))
+  {
+    // next year
+    var resultWeek = 1;
+    return new org_apache_myfaces_dateformat_WeekDate(year+1, resultWeek);
+  }
+  else
+  {
+    // current year
+    var resultWeek = Math.floor(n/7) + 1;
+    return new org_apache_myfaces_dateformat_WeekDate(year, resultWeek);
+  }
 }
 
-//----------------------------------------------------------------------------
-// Constructor for a simple object that contains the current parsing or
-// formatting state. This encapsulates the properties of a SimpleDateFormat
-// which are modified during a parse or format call.
-//----------------------------------------------------------------------------
-org_apache_myfaces_SimpleDateFormatParserContext = function()
+proto._isLeapYear = function(year)
 {
-        this.newIndex=0;
-        this.retValue=0;
-        this.year=0;
-        this.ambigousYear=false;
-        this.month=0;
-        this.day=0;
-        this.dayOfWeek=0;
-        this.hour=0;
-        this.min=0;
-        this.sec=0;
-        this.ampm=0;
-        this.dateStr="";
-
-        this.weekYear=0;
-        this.weekOfWeekYear=0;
-        this.yearIsWeekYear=false;
+  return ((year%4 == 0) && (year%100 != 0)) || (year%400 == 0);
 }
 
-//----------------------------------------------------------------------------
-// Constructor method.
-//
-// param pattern defines the pattern to be used for parsing or formatting.
-//
-// param dateSymbols is optional. It defines the "locale"; if not defined
-// then default settings (english locale) are used.
-//----------------------------------------------------------------------------
-org_apache_myfaces_SimpleDateFormat = function(pattern, dateFormatSymbols)
+proto._dayOfWeek = function(year, month, day)
 {
-        this.pattern = pattern;
-        this.dateFormatSymbols = dateFormatSymbols ? dateFormatSymbols :
-                new org_apache_myfaces_DateFormatSymbols();
+  month -= 2;
+  if (month < 1)
+  {
+    month += 12;
+    --year;
+  }
+
+  var cent = Math.floor(year / 100);
+  year %= 100;
+  
+  var base =
+    Math.floor((26 * month - 2) / 10)
+    + day
+    + year
+      + Math.floor(year/4)
+      + Math.floor(cent/4)
+      + (5*cent);
+
+  var dow = base % 7;
+  return dow;  
 }
 
-//----------------------------------------------------------------------------
-// Handle both parsing and formatting of dates, by walking the pattern and
-// invoking _handlePatternSub for each "segment" of the pattern.
-//----------------------------------------------------------------------------
-org_apache_myfaces_SimpleDateFormat.prototype._handle = function(dateStr, date, parse)
+proto._getJavaWeekDate = function(date, firstDayOfWeek)
+{
+  var year = date.getFullYear();
+  var month = date.getMonth() + 1;
+  var day = date.getDate();
+  
+  var thisIsLeapYear = this._isLeapYear(year);
+
+  var dayOfYear = day + this.MONTH_LEN[month-1];
+  if (thisIsLeapYear && (month>2))
+  {
+    ++dayOfYear;
+  }
+
+  var jan1Weekday = this._dayOfWeek(year, 1, 1);
+
+  var pivotOffset = firstDayOfWeek - jan1Weekday;
+  if (pivotOffset > 3)
+  {
+    pivotOffset -= 7;
+  }
+  else if (pivotOffset < -3)
+  {
+    pivotOffset += 7;
+  }
+  
+  var dayOffset = dayOfYear-1;
+  if (dayOffset < pivotOffset)
+  {
+    var prevIsLeapYear = this._isLeapYear(year-1);
+    if ((pivotOffset==3) || ((pivotOffset==2) && prevIsLeapYear))
     {
-        var patternIndex = 0;
-        var commentMode = false;
-        var lastChar = 0;
-        var currentChar=0;
-        var nextChar=0;
-        var patternSub = null;
-        var ops = [];
-        var i;
-        var controls="yxMdEHhmsaw";
-        var wwPresent = false;
-        var xxPresent = false;
-
-        var context = new org_apache_myfaces_SimpleDateFormatParserContext();
-
-        if(date != null)
-        {
-            context.year = this._fullYearFromDate(date.getYear());
-            context.month = date.getMonth();
-            context.day = date.getDate();
-            context.dayOfWeek = date.getDay();
-            context.hour = date.getHours();
-            context.min = date.getMinutes();
-            context.sec = date.getSeconds();
-
-            context.weekOfWeekYear = this._weekNbr(date);
-            if ((context.weekOfWeekYear > 50) && (context.month==0))
-                context.weekYear = context.year - 1;
-            else if ((context.weekOfWeekYear == 1) && (context.month==12))
-                context.weekYear = context.year + 1;
-            else
-                context.weekYear = context.year;
-        }
-
-
-        // Walk through the date pattern char by char, splitting it up into
-        // "comment segments", "pattern segments" and "literal segments". 
-        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!='\\')
-            {
-                if (patternSub != null)
-                {
-                    this._append(ops, patternSub);
-                    patternSub = null;
-                }
-                commentMode = !commentMode;
-            }
-            else if (commentMode)
-            {
-                if (patternSub == null)
-                {
-                    patternSub = "c:";
-                }
-                patternSub += currentChar;
-            }
-            else
-            {
-                if (currentChar == '\\' && lastChar!='\\')
-                {
-                }
-                else
-                {
-                    if(patternSub == null)
-                    {
-                        if (controls.indexOf(currentChar)<0)
-                            patternSub = "l:";
-                        else
-                            patternSub = "f:";
-                    }
-                    patternSub+=currentChar;
-                    if(currentChar != nextChar)
-                    {
-                        this._append(ops, patternSub);
-                        patternSub = null;
-                    }
-
-                }
-            }
-
-            patternIndex++;
-            lastChar = currentChar;
-        }
-
-        if (patternSub != null)
-            this._append(ops, patternSub);
-
-        // Ugly hack to make the "yyyy" formatter behave differently if the
-        // "ww" formatter is also present in the pattern. This is what
-        // java.text.SimpleDateFormat does. The JODA library implements
-        // an "xxxx" formatter instead, and allows "yyyy" to behave normally;
-        // this is nicer so support that if formatter "xx" is present.
-        for(i=0;i<ops.length;++i)
-        {
-            var s = ops[i];
-            wwPresent = wwPresent || ("f:ww" == s.substr(0,4));
-            xxPresent = xxPresent || ("f:xx" == s.substr(0,4));
-        }
-        context.yearIsWeekYear = wwPresent && !xxPresent;
-
-        if (parse)
-            context = this._parseOps(context, ops, dateStr);
-        else
-            context = this._formatOps(context, ops);
-
-        return context;
-    };
-
-org_apache_myfaces_SimpleDateFormat.prototype._parseOps = function(context, ops, dateStr)
-{
-    var dateIndex = 0;
-    var i;
-    for(i=0;i<ops.length;++i)
-    {
-        var op = ops[i];
-        var optype = op.substr(0,2);
-        var opval = op.substr(2);
-        if (optype == "f:")
-        {
-            this._handlePatternSub(context, opval,
-                dateStr, dateIndex, true);
-
-            if(context.newIndex<0)
-                 break;
-
-            dateIndex = context.newIndex;
-        }
-        else if (optype == "l:")
-        {
-            // Just skip over the equivalent number of chars. This is what the previous
-            // parsing implementation did...
-            dateIndex += opval.length;
-        }
-        else if (optype == "c:")
-        {
-            // verify that opval matches the next chars in dateStr, then
-            // advance dateIndex by the length.
-            var s = dateStr.substr(dateIndex, opval.length);
-            if (s != opval)
-            {
-                context.retValue = -1;
-                break;
-            }
-            dateIndex += opval.length;
-        }
+      return new org_apache_myfaces_dateformat_WeekDate(year-1, 53);
     }
-    
-    return context;
+    return new org_apache_myfaces_dateformat_WeekDate(year-1, 52);
+  }
+
+  var daysFromFirstWeekStart = (dayOfYear - 1 - pivotOffset);
+  var weekNumber = Math.floor(daysFromFirstWeekStart/7) + 1;  
+
+  if ((weekNumber < 53)
+      || (pivotOffset == -3)
+      || (pivotOffset == -2 && thisIsLeapYear))
+  {
+    return new org_apache_myfaces_dateformat_WeekDate(year, weekNumber);
+  }
+  else
+  {
+    return new org_apache_myfaces_dateformat_WeekDate(year+1, 1);
+  }
 }
 
-org_apache_myfaces_SimpleDateFormat.prototype._formatOps = function(context, ops)
+proto._getStartOfWeekYear = function(year, firstDayOfWeek)
 {
-    var dateIndex = 0; // not used on formatting, only on parsing
-    var dateStr = ""; // not used on formatting, only on parsing
-    var i;
-    for(i=0;i<ops.length;++i)
-    {
-        var op = ops[i];
-        var optype = op.substr(0,2);
-        var opval = op.substr(2);
-        if (optype == "f:")
-        {
-            this._handlePatternSub(context, opval,
-                dateStr, dateIndex, false);
-        }
-        else if (optype == "l:")
-        {
-            // just output the literal sequence into the result
-            context.dateStr += opval;
-        }
-        else if (optype == "c:")
-        {
-            // just output the literal sequence into the result
-            context.dateStr += opval;
-        }
-    }
-    
-    return context;
+  var d1 = new Date(year, 0, 1, 0, 0, 0);
+  var firstDayOfYear = d1.getDay();
+  var dayDiff = firstDayOfWeek - firstDayOfYear;
+  var dayShift;
+  if (dayDiff >= 4)
+  {
+    dayShift = 7 - dayShift;
+  }
+  else if (dayDiff >= 0)
+  {
+    dayShift = dayDiff;
+  }
+  else if (dayDiff >= -3)
+  {
+    dayShift = dayDiff;
+  }
+  else
+  {
+    dayShift = 7 + dayDiff;
+  }
+
+  var weekYearStartMsecs = d1.getTime() + (dayShift * this.MSECS_PER_DAY);
+  return weekYearStartMsecs;
 }
 
-//----------------------------------------------------------------------------
-// Parse a string using the configured pattern, and return a normal javascript
-// Date object.
-//
-// Returns null if parsing failed.
-//----------------------------------------------------------------------------
-org_apache_myfaces_SimpleDateFormat.prototype.parse = function(dateStr)
-    {
-        if(!dateStr || dateStr.length==0)
-            return null;
+proto._newJavaDateByWeekYear = function(
+  year, week, day,
+  hour, min, sec,
+  firstDayOfWeek)
+{
+  var msecsBase = this._getStartOfWeekYear(year, firstDayOfWeek);
+  var msecsOffset = (week-1) * this.MSECS_PER_WEEK;
+  msecsOffset += (day-1) * this.MSECS_PER_DAY;
+  msecsOffset += hour * this.MSECS_PER_HOUR;
+  msecsOffset += min * this.MSECS_PER_MIN;
+  msecsOffset += sec * this.MSECS_PER_SEC;
+  
+  var finalDate = new Date();
+  finalDate.setTime(msecsBase + msecsOffset);
+  return finalDate;
+}
 
-        var context = this._handle(dateStr, null, true);
+proto._fullYearFromDate = function(year)
+{
+  if (year < 1900)
+  {
+    return year+1900;
+  }
+  else
+  {
+    return year;
+  }
+}
 
-        if(context.retValue==-1)
-            return null;
+proto._createDateFromContext = function(context)
+{
+  var date;
+  if (context.weekOfWeekYear != 0)
+  {
+    date = this._newJavaDateByWeekYear(
+      context.weekYear, context.weekOfWeekYear, context.day,
+      context.hour, context.min, context.sec,
+      context.firstDayOfWeek);
+  }
+  else
+  {
+    date = new Date(
+      context.year, context.month, context.day,
+      context.hour, context.min, context.sec);
+  }
+  return date;
+}
 
-        this._adjustTwoDigitYear(context);
+proto._substr = function(s, start, len)
+{
+  var s2 = s.substring(start);
+  if (s2.length <= len)
+    return s2;
+  else
+    return s2.substring(0, len);
+}
 
-        return this._createDateFromContext(context);
-    };
+proto._parseOps = function(symbols, yearIsWeekYear, firstDayOfWeek, ops, dateStr)
+{
+  var context = new org_apache_myfaces_dateformat_ParserContext(firstDayOfWeek);
 
-//----------------------------------------------------------------------------
-org_apache_myfaces_SimpleDateFormat.prototype._createDateFromContext=function(context)
+  var dateIndex = 0;
+  var dateStrLen = dateStr.length;
+  for(var i=0; (i<ops.length) && (dateIndex < dateStrLen); ++i)
+  {
+    var op = ops[i];
+    var optype = op.substring(0, 2);
+    var opval = op.substring(2);
+
+    if (optype == "f:")
     {
-        var date;
-        if (context.weekOfWeekYear != 0)
-        {
-            date = org_apache_myfaces_SimpleDateFormat_newDateByWeekYear(
-                context.weekYear, context.weekOfWeekYear, context.day,
-                context.hour, context.min, context.sec);
-        }
-        else
-        {
-            date = new Date(
-                context.year, context.month, context.day,
-                context.hour,context.min,context.sec);
-        }
-        return date;
-    };
+      this._parsePattern(symbols, yearIsWeekYear, context, opval, dateStr, dateIndex);
 
-//----------------------------------------------------------------------------
-// Accept a normal javascript Date object, and return a String.
-//----------------------------------------------------------------------------
-org_apache_myfaces_SimpleDateFormat.prototype.format = function(date)
+      if ((context.newIndex < 0) || context.invalid)
+        break;
+
+      dateIndex = context.newIndex;
+    }
+    else if (optype =="q:" || optype == "l:")
     {
-        var context = this._handle(null, date, false);
+      var oplen = opval.length;
+      var s = this._substr(dateStr, dateIndex, oplen);
+      if (opval != s)
+      {
+        context.invalid = true;
+        break;
+      }
+      dateIndex += oplen;
+    }
+  }
 
-        return context.dateStr;
-    };
+  return context;
+}
 
-//----------------------------------------------------------------------------
-// dateStr is the full string currently being parsed.
-// dateIndex is the offset within dateStr to parse from.
-//----------------------------------------------------------------------------
-org_apache_myfaces_SimpleDateFormat.prototype._parseString = function(context, dateStr, dateIndex, strings)
+proto._parsePattern = function(
+  symbols, yearIsWeekYear, context, patternSub, dateStr, dateIndex)
+{
+  var c = patternSub.charAt(0);
+  var patlen = patternSub.length;
+
+  if (c == 'y')
+  {
+    var year = this._parseNum(context, dateStr, 4, dateIndex);
+    if ((context.newIndex-dateIndex) < 4)
+    {
+      context.year = year;
+      context.ambiguousYear = true;
+    }
+    else
     {
-        var fragment = dateStr.substr(dateIndex);
-        var index = this._prefixOf(strings, fragment);
-        if (index != -1) {
-          context.retValue = index;
-          context.newIndex = dateIndex + strings[index].length;
-          return context;
-        }
-
-        context.retValue=-1;
-        context.newIndex=-1;
-        return context;
-    };
+      context.year = year;
+    }
 
-//----------------------------------------------------------------------------
-// Convert at most the next posCount characters to numeric (or stop at
-// end-of-string), starting from offset dateIndex within dateStr.
-//
-// dateStr is the full string currently being parsed.
-// dateIndex is the offset within dateStr to parse from.
-//
-// Stores the result in context.retValue
-// Updates context.newIndex to contain the next unparsed char within dateStr
-//----------------------------------------------------------------------------
-org_apache_myfaces_SimpleDateFormat.prototype._parseNum = function(context, dateStr, posCount, dateIndex)
+    if (yearIsWeekYear)
+    {
+      // There is a "ww" pattern present, so set weekYear as well as year.
+      context.weekYear = context.year;
+      context.ambiguousWeekYear = context.ambiguousYear;
+    }
+  }
+  else if (c == 'x')
+  {
+    // extension to standard java.text.SimpleDateFormat class, to support the
+    // JODA "weekYear" formatter.
+    var year = this._parseNum(context, dateStr, 4, dateIndex);
+    if ((context.newIndex-dateIndex) < 4)
+    {
+      context.weekYear = year;
+      context.ambiguousWeekYear = true;
+    }
+    else
+    {
+      context.weekYear = year;
+    }
+  }
+  else if (c == 'M')
+  {
+    if (patlen == 3)
+    {
+      var fragment = this._substr(dateStr, dateIndex, 3);
+      var index = this._parseIndexOf(context, symbols.shortMonths, fragment);
+      if (index != -1)
+      {
+        context.month = index;
+      }
+    }
+    else if (patlen >= 4)
+    {
+      var fragment = dateStr.substring(dateIndex);
+      var index = this._parsePrefixOf(context, symbols.months, fragment);
+      if (index != -1)
+      {
+        context.month = index;
+      }
+    }
+    else
     {
-        // Try to convert the most possible characters (posCount). If that fails,
-        // then try again without the last character. Repeat until successful
-        // numeric conversion occurs.
-        for(var i=Math.min(posCount,dateStr.length-dateIndex);i>0;i--)
-        {
-            var numStr = dateStr.substring(dateIndex,dateIndex+i);
-
-            context.retValue = this._parseInt(numStr);
-
-            if(context.retValue == -1)
-                continue;
-
-            context.newIndex = dateIndex+i;
-            return context;
-        }
-
-        context.retValue = -1;
-        context.newIndex = -1;
-        return context;
-    };
+      context.month = this._parseNum(context, dateStr, 2, dateIndex) - 1;
+    }
+  }
+  else if (c == 'd')
+  {
+    context.day = this._parseNum(context, dateStr, 2, dateIndex);
+  }
+  else if (c == 'E')
+  {
+    if (patlen <= 3)
+    {
+      var fragment = dateStr.substring(dateIndex, dateIndex+3);
+      var index = this._parseIndexOf(context, symbols.shortWeekdays, fragment);
+      if (index != -1)
+      {
+        context.dayOfWeek = index;
+      }
+    }
+    else
+    {
+      var fragment = dateStr.substring(dateIndex);
+      var index = this._parsePrefixOf(context, symbols.weekdays, fragment);
+      if (index != -1)
+      {
+        context.dayOfWeek = index;
+      }
+    }
+  }
+  else if (c == 'H')
+  {
+    context.hour = this._parseNum(context, dateStr, 2, dateIndex);
+  }
+  else if (c == 'h')
+  {
+    context.hourAmpm = this._parseNum(context, dateStr, 2, dateIndex);
+  }
+  else if (c == 'm')
+  {
+    context.min = this._parseNum(context, dateStr, 2, dateIndex);
+  }
+  else if (c == 's')
+  {
+    context.sec = this._parseNum(context, dateStr, 2, dateIndex);
+  }
+  else if (c == 'a')
+  {
+    context.ampm = this._parseString(context, dateStr, dateIndex, symbols.ampms);
+  }
+  else if (c == 'w')
+  {
+    context.weekOfWeekYear = this._parseNum(context, dateStr, 2, dateIndex);
+  }
+  else
+  {
+    context.invalid = true;
+  }
+}
 
-//----------------------------------------------------------------------------
-// Handles a "segment" of a pattern, for either parsing or formatting.
-//
-// patternSub contains a sequence of identical chars, eg "yyyy" or "HH".
-// returns VOID
-//----------------------------------------------------------------------------
-org_apache_myfaces_SimpleDateFormat.prototype._handlePatternSub = function(context, patternSub, dateStr, dateIndex, parse)
+proto._parseInt = function(value)
+{
+  var sum = 0;
+  for(var i=0;i<value.length;i++)
+  {
+    var c = value.charAt(i);
+
+    if(c<'0'||c>'9')
     {
-        if(patternSub==null || patternSub.length==0)
-            return;
+      return -1;
+    }
+    sum = sum*10+(c-'0');
+  }
+  return sum;
+}
 
-        if(patternSub.charAt(0)=='y')
-        {
-            if(parse)
-            {
-                this._parseNum(context, dateStr,4,dateIndex);
-
-                if((context.newIndex-dateIndex)<4)
-                {
-                    context.year = context.retValue+1900;
-                    context.ambigousYear = true;
-                }
-                else
-                {
-                    context.year = context.retValue;
-                }
-                
-                if (context.yearIsWeekYear)
-                    context.weekYear = context.year;
-            }
-            else
-            {
-                if (context.yearIsWeekYear)
-                    this._formatNum(context,context.weekYear,patternSub.length <= 3 ? 2 : 4,true);
-                else
-                    this._formatNum(context,context.year,patternSub.length <= 3 ? 2 : 4,true);
-            }
-        }
-        else if(patternSub.charAt(0)=='x')
-        {
-            if(parse)
-            {
-                this._parseNum(context, dateStr,4,dateIndex);
-
-                if((context.newIndex-dateIndex)<4)
-                {
-                    context.weekYear = context.retValue+1900;
-                    context.ambigousYear = true;
-                }
-                else
-                {
-                    context.weekYear = context.retValue;
-                }
-            }
-            else
-            {
-                this._formatNum(context,context.weekYear,patternSub.length <= 3 ? 2 : 4,true);
-            }
-        }
-        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)
-            {
-                this._parseString(context, dateStr,dateIndex,this.dateFormatSymbols.ampms);
-                context.ampm = context.retValue;
-            }
-            else
-            {
-                context.dateStr += this.dateFormatSymbols.ampms[context.ampm];
-            }
-        }
-        else if(patternSub.charAt(0)=='w')
-        {
-            if(parse)
-            {
-                this._parseNum(context, dateStr,2,dateIndex);
-                context.weekOfWeekYear = context.retValue;
-            }
-            else
-            {
-                this._formatNum(context,context.weekOfWeekYear,patternSub.length);
-            }
-        }
-        else
-        {
-            if(parse)
-            {
-                // just skip over it
-                context.newIndex=dateIndex+patternSub.length;
-            }
-            else
-            {
-                // just copy it to the output
-                context.dateStr+=patternSub;
-            }
+proto._parseNum = function(context, dateStr, nChars, dateIndex)
+{
+  // Try to convert the most possible characters (posCount). If that fails,
+  // then try again without the last character. Repeat until successful
+  // numeric conversion occurs.
+  var nToParse = Math.min(nChars, dateStr.length - dateIndex);
+  for(var i=nToParse;i>0;i--)
+  {
+    var numStr = dateStr.substring(dateIndex,dateIndex+i);
+    var value = this._parseInt(numStr);
+    if(value == -1)
+      continue;
+
+    context.newIndex = dateIndex+i;
+    return value;
+  }
+
+  context.newIndex = -1;
+  context.invalid = true;
+  return -1;
+}
 
-        }
-    };
+proto._parseIndexOf = function(context, array, value)
+{
+  for (var i = 0; i < array.length; ++i)
+  {
+    var s = array[i];
+    if (value == s)
+    {
+      context.newIndex += s.length;
+      return i;
+    }
+  }
+  context.invalid = true;
+  context.newIndex = -1;
+  return -1;
+}
 
-//----------------------------------------------------------------------------
-// Write out an integer padded with leading zeros to a specified width
-// If ensureLength is set, and the number is longer than length, then display only the rightmost length digits.
-//
-// modifies member variable context.dateStr, returns VOID
-//----------------------------------------------------------------------------
-org_apache_myfaces_SimpleDateFormat.prototype._formatNum = function (context, num, length, ensureLength)
+proto._parsePrefixOf = function(context, array, value)
+{
+  for (var i=0; i <array.length; ++i)
+  {
+    var s = array[i];
+    if (value.indexOf(s) == 0)
     {
-        var str = num+"";
+      context.newIndex += s.length;
+      return i;
+    }
+  }
+  context.invalid = true;
+  context.newIndex = -1;
+  return -1;
+}
 
-        while(str.length<length)
-            str="0"+str;
+proto._parseString = function(context, dateStr, dateIndex, strings)
+{
+  var fragment = dateStr.substr(dateIndex);
+  return this._parsePrefixOf(context, strings, fragment);
+}
 
-        // XXX do we have to distinguish left and right 'cutting'
-        //ensureLength - enable cutting only for parameters like the year, the other
-        if (ensureLength && str.length > length) {
-          str = str.substr(str.length - length);
-        }
+proto._parsePostProcess = function(symbols, context)
+{
+  if (context.ambiguousYear)
+  {
+    var date = this._createDateFromContext(context);
+    var threshold = symbols.twoDigitYearStart;
+    if (date.getTime() < threshold.getTime())
+    {
+      context.year += 100;
+    }
+  }
+  
+  if (context.hourAmpm > 0)
+  {
+    if (context.ampm == 1)
+    {
+      context.hour = context.hourAmpm + 12;
+      if (context.hour == 24)
+        context.hour = 0;
+    }
+    else
+    {
+      context.hour = context.hourAmpm;
+    }
+  }
+}
 
-        context.dateStr+=str;
-    };
+proto._formatOps = function(symbols, yearIsWeekYear, firstDayOfWeek, ops, date)
+{
+  var context = new org_apache_myfaces_dateformat_ParserContext(firstDayOfWeek);
 
-    // perhaps add to Array.prototype
-org_apache_myfaces_SimpleDateFormat.prototype._indexOf = function (array, value)
+  context.year = date.getFullYear();
+  context.month = date.getMonth();
+  context.day = date.getDate();
+  context.dayOfWeek = date.getDay();
+  context.hour = date.getHours();
+  context.min = date.getMinutes();
+  context.sec = date.getSeconds();
+  
+  context.ampm = (context.hour < 12) ? 0 : 1;
+  
+  var weekDate = this._getJavaWeekDate(date, firstDayOfWeek);
+  context.weekYear = weekDate.year;
+  context.weekOfWeekYear = weekDate.week;
+  
+  var str = new org_apache_myfaces_dateformat_StringBuffer();
+  for(var i=0; i<ops.length; ++i)
+  {
+    var op = ops[i];
+    var optype = op.substring(0, 2);
+    var opval = op.substring(2);
+
+    if (optype == "f:")
+    {
+      this._formatPattern(symbols, context, opval, yearIsWeekYear, str);
+      if (context.invalid)
+        break;
+    }
+    else if (optype == "l:")
     {
-      for (var i = 0; i < array.length; ++i) {
-        if (array[i] == value) {
-          return i;
-        }
-      }
-      return -1;
-    };
-
-org_apache_myfaces_SimpleDateFormat.prototype._prefixOf = function (array, value)
+      str.append(opval);
+    }
+    else if (optype == "q:")
     {
-      for (var i = 0; i < array.length; ++i) {
-        if (value.indexOf(array[i]) == 0) {
-          return i;
-        }
-      }
-      return -1;
-    };
+      str.append(opval);
+    }
+  }
+
+  if (context.invalid)
+    return null;
+  else
+    return str.toString();  
+}
+
+proto._formatPattern = function(
+  symbols, context, patternSub, yearIsWeekYear, out)
+{
+  var c = patternSub.charAt(0);
+  var patlen = patternSub.length;
 
-org_apache_myfaces_SimpleDateFormat.prototype._parseInt = function(value)
+  if (c=='y')
+  {
+    if (!yearIsWeekYear)
+      this._formatNum(context.year, patlen <= 3 ? 2 : 4, true, out);
+    else
+      this._formatNum(context.weekYear, patlen <= 3 ? 2 : 4, true, out);
+  }
+  else if (c=='x')
+  {
+    this._formatNum(context.weekYear,patlen <= 3 ? 2 : 4, true, out);
+  }
+  else if (c=='M')
+  {
+     if (patlen == 3)
+     {
+       out.append(symbols.shortMonths[context.month]);
+     }
+     else if (patlen >= 4) {
+       out.append(symbols.months[context.month]);
+     }
+     else
+     {
+       this._formatNum(context.month+1,patlen, false, out);
+     }
+  }
+  else if (c=='d')
+  {
+    this._formatNum(context.day,patlen, false, out);
+  }
+  else if (c=='E')
+  {
+      if (patlen <= 3)
+      {
+        out.append(symbols.shortWeekdays[context.dayOfWeek]);
+      }
+      else
+      {
+        out.append(symbols.weekdays[context.dayOfWeek]);
+      }
+  }
+  else if(c=='H')
+  {
+    this._formatNum(context.hour, patlen, false, out);
+  }
+  else if (c=='h')
+  {
+    var hour = context.hour;
+    if (hour == 0) 
     {
-        var sum = 0;
+      hour = 12;
+    }
+    else if (hour > 12)
+    {
+      hour = hour - 12;
+    }
+    this._formatNum(hour, patlen, false, out);
+  }
+  else if (c=='m')
+  {
+    this._formatNum(context.min, patlen, false, out);
+  }
+  else if (c=='s')
+  {
+    this._formatNum(context.sec, patlen, false, out);
+  }
+  else if (c=='a')
+  {
+    out.append(symbols.ampms[context.ampm]);
+  }
+  else if (c=='w')
+  {
+    this._formatNum(context.weekOfWeekYear, patlen, false, out);
+  }
+  else
+  {
+    context.invalid = true;
+  }
+}
 
-        for(var i=0;i<value.length;i++)
-        {
-            var c = value.charAt(i);
+proto._formatNum = function(num, length, ensureLength, out)
+{
+  var str = "" + num;
 
-            if(c<'0'||c>'9')
-            {
-                return -1;
-            }
-            sum = sum*10+(c-'0');
-        }
+  while(str.length<length)
+  {
+    str = "0" + str;
+  }
+
+  if (ensureLength && str.length > length)
+  {
+    str = str.substr(str.length - length);
+  }
 
-        return sum;
-    };
-org_apache_myfaces_SimpleDateFormat.prototype._fullYearFromDate = function(year)
-    {
+  out.append(str);
+}
 
-        var yearStr = year+"";
+proto._appendToArray = function(array, obj)
+{
+  array[array.length] = obj;
+}
 
-        if (yearStr.length < 4)
-        {
-            return year+1900;
-        }
+proto._isLetter = function(c)
+{
+  if ((c>= 'a') && (c<='z')) return true;
+  if ((c>='A') && (c<='Z'))  return true;
+  return false;
+}
 
-        return year;
-    };
+proto._analysePattern = function(pattern)
+{
+  var patternIndex = 0;
+  var patternLen = pattern.length;
+  var lastChar = 0;
+  var patternSub = null;
+  var quoteMode = false;
+
+  var ops = new Array();
+
+  while (patternIndex < patternLen)
+  {
+      var currentChar = pattern.charAt(patternIndex);
+      var nextChar;
+
+      if (patternIndex < patternLen - 1)
+      {
+          nextChar = pattern.charAt(patternIndex + 1);
+      }
+      else
+      {
+          nextChar = 0;
+      }
 
-org_apache_myfaces_SimpleDateFormat.prototype._adjustTwoDigitYear = function(context)
-    {
+      if (currentChar == '\'' && lastChar != '\\')
+      {
+          if (patternSub != null)
+          {
+              this._appendToArray(ops, patternSub.toString());
+              patternSub = null;
+          }
+          quoteMode = !quoteMode;
+      }
+      else if (quoteMode)
+      {
+          if (patternSub == null)
+          {
+              patternSub = new org_apache_myfaces_dateformat_StringBuffer("q:");
+          }
+          patternSub.append(currentChar);
+      }
+      else
+      {
+          if (currentChar == '\\' && lastChar != '\\')
+          {
+              // do nothing
+          }
+          else
+          {
+              if (patternSub == null)
+              {
+                  if (this._isLetter(currentChar))
+                  {
+                      patternSub = new org_apache_myfaces_dateformat_StringBuffer("f:");
+                  }
+                  else
+                  {
+                      patternSub = new org_apache_myfaces_dateformat_StringBuffer("l:");
+                  }
+              }
 
-        if(context.ambigousYear)
-        {
-            var date = this._createDateFromContext(context);
-            var threshold = this.dateFormatSymbols.twoDigitYearStart;
+              patternSub.append(currentChar);
+              if (currentChar != nextChar)
+              {
+                  this._appendToArray(ops, patternSub.toString());
+                  patternSub = null;
+              }
+          }
+      }
 
-            if(date.getTime()<threshold.getTime())
-                context.year += 100;
-        }
-    };
+      patternIndex++;
+      lastChar = currentChar;
+  }
+
+  if (patternSub != null)
+  {
+      this._appendToArray(ops, patternSub.toString());
+  }
 
-org_apache_myfaces_SimpleDateFormat.prototype._append = function(array, obj)
-    {
-        array[array.length] = obj;
-    };
+  return ops;
+}
 
-//----------------------------------------------------------------------------
-// Make static method callable via short name
-//----------------------------------------------------------------------------
-org_apache_myfaces_SimpleDateFormat.prototype._weekNbr=org_apache_myfaces_SimpleDateFormat_weekNbr;
+proto._hasWeekPattern = function(ops)
+{
+  var wwPresent = false;
+  var xxPresent = false;
+  for(var i=0; i<ops.length; ++i)
+  {
+    var s = ops[i];
+    wwPresent = wwPresent || (s.indexOf("f:ww")==0);
+    xxPresent = xxPresent || (s.indexOf("f:xx")==0);
+  }
+  
+  return wwPresent && !xxPresent;
+}
+
+// Constructor for SimpleDateFormatter class
+proto._construct = function(pattern, symbols, firstDayOfWeek)
+{
+  if (symbols == null)
+  {
+    this.symbols = new org_apache_myfaces_dateformat_DateFormatSymbols();
+  }
+  else
+  {
+    this.symbols = symbols;
+  }
+
+  this.ops = this._analysePattern(pattern);
+  this.yearIsWeekYear = this._hasWeekPattern(this.ops);
+  
+  if (firstDayOfWeek != null)
+    this.firstDayOfWeek = firstDayOfWeek;
+  else
+    this.firstDayOfWeek = 1;
+}
+
+proto.setFirstDayOfWeek = function(dow)
+{
+  this.firstDayOfWeek = dow;
+}
+
+proto.parse = function(dateStr)
+{
+  if ((dateStr == null) || (dateStr.length == 0))
+    return null;
+    
+  var context = this._parseOps(
+    this.symbols, this.yearIsWeekYear, this.firstDayOfWeek, this.ops,
+    dateStr); 
+
+  if (context.invalid)
+  {
+    return null;
+  }
+  
+  this._parsePostProcess(this.symbols, context);
+  return this._createDateFromContext(context);
+}
+
+proto.format = function(date)
+{
+  return this._formatOps(
+    this.symbols, this.yearIsWeekYear, this.firstDayOfWeek, this.ops,
+    date);
+}
+
+proto.getWeekDate = function(date)
+{
+  return this._getJavaWeekDate(date, this.firstDayOfWeek);
+}
 
+} // end of static init block for SimpleDateFormatter class
\ No newline at end of file