diff --git a/src/com/adobe/serialization/json/JSONDecoder.as b/src/com/adobe/serialization/json/JSONDecoder.as
index 09fbe88..7a5c282 100644
--- a/src/com/adobe/serialization/json/JSONDecoder.as
+++ b/src/com/adobe/serialization/json/JSONDecoder.as
@@ -32,11 +32,12 @@
package com.adobe.serialization.json
{
+ import com.adobe.utils.DateUtil;
public class JSONDecoder
- {
-
- /**
+ {
+
+ /**
* Flag indicating if the parser should be strict about the format
* of the JSON string it is attempting to decode.
*/
@@ -52,7 +53,7 @@ package com.adobe.serialization.json
private var token:JSONToken;
/**
- * Constructs a new JSONDecoder to parse a JSON string
+ * Constructs a new JSONDecoder to parse a JSON string
* into a native object.
*
* @param s The JSON string to be converted
@@ -64,7 +65,7 @@ package com.adobe.serialization.json
* @tiptext
*/
public function JSONDecoder( s:String, strict:Boolean )
- {
+ {
this.strict = strict;
tokenizer = new JSONTokenizer( s, strict );
@@ -97,39 +98,15 @@ package com.adobe.serialization.json
* Returns the next token from the tokenzier reading
* the JSON string
*/
- private final function nextToken():JSONToken
+ private function nextToken():JSONToken
{
return token = tokenizer.getNextToken();
}
- /**
- * Returns the next token from the tokenizer reading
- * the JSON string and verifies that the token is valid.
- */
- private final function nextValidToken():JSONToken
- {
- token = tokenizer.getNextToken();
- checkValidToken();
-
- return token;
- }
-
- /**
- * Verifies that the token is valid.
- */
- private final function checkValidToken():void
- {
- // Catch errors when the input stream ends abruptly
- if ( token == null )
- {
- tokenizer.parseError( "Unexpected end of input" );
- }
- }
-
/**
* Attempt to parse an array.
*/
- private final function parseArray():Array
+ private function parseArray():Array
{
// create an array internally that we're going to attempt
// to parse from the tokenizer
@@ -137,7 +114,7 @@ package com.adobe.serialization.json
// grab the next token from the tokenizer to move
// past the opening [
- nextValidToken();
+ nextToken();
// check to see if we have an empty array
if ( token.type == JSONTokenType.RIGHT_BRACKET )
@@ -150,12 +127,12 @@ package com.adobe.serialization.json
else if ( !strict && token.type == JSONTokenType.COMMA )
{
// move past the comma
- nextValidToken();
+ nextToken();
// check to see if we're reached the end of the array
if ( token.type == JSONTokenType.RIGHT_BRACKET )
{
- return a;
+ return a;
}
else
{
@@ -169,9 +146,9 @@ package com.adobe.serialization.json
{
// read in the value and add it to the array
a.push( parseValue() );
-
+
// after the value there should be a ] or a ,
- nextValidToken();
+ nextToken();
if ( token.type == JSONTokenType.RIGHT_BRACKET )
{
@@ -187,8 +164,6 @@ package com.adobe.serialization.json
// if the decoder is not in strict mode
if ( !strict )
{
- checkValidToken();
-
// Reached ",]" as the end of the array, so return it
if ( token.type == JSONTokenType.RIGHT_BRACKET )
{
@@ -201,25 +176,24 @@ package com.adobe.serialization.json
tokenizer.parseError( "Expecting ] or , but found " + token.value );
}
}
-
- return null;
+ return null;
}
/**
* Attempt to parse an object.
*/
- private final function parseObject():Object
+ private function parseObject():Object
{
// create the object internally that we're going to
// attempt to parse from the tokenizer
var o:Object = new Object();
-
+
// store the string part of an object member so
// that we can assign it a value in the object
var key:String
// grab the next token from the tokenizer
- nextValidToken();
+ nextToken();
// check to see if we have an empty object
if ( token.type == JSONTokenType.RIGHT_BRACE )
@@ -232,7 +206,7 @@ package com.adobe.serialization.json
else if ( !strict && token.type == JSONTokenType.COMMA )
{
// move past the comma
- nextValidToken();
+ nextToken();
// check to see if we're reached the end of the object
if ( token.type == JSONTokenType.RIGHT_BRACE )
@@ -255,23 +229,23 @@ package com.adobe.serialization.json
key = String( token.value );
// move past the string to see what's next
- nextValidToken();
+ nextToken();
// after the string there should be a :
if ( token.type == JSONTokenType.COLON )
- {
+ {
// move past the : and read/assign a value for the key
nextToken();
- o[ key ] = parseValue();
+ o[key] = parseValue();
// move past the value to see what's next
- nextValidToken();
+ nextToken();
// after the value there's either a } or a ,
if ( token.type == JSONTokenType.RIGHT_BRACE )
{
// we're done reading the object, so return it
- return o;
+ return o;
}
else if ( token.type == JSONTokenType.COMMA )
{
@@ -282,8 +256,6 @@ package com.adobe.serialization.json
// if the decoder is not in strict mode
if ( !strict )
{
- checkValidToken();
-
// Reached ",}" as the end of the object, so return it
if ( token.type == JSONTokenType.RIGHT_BRACE )
{
@@ -302,35 +274,45 @@ package com.adobe.serialization.json
}
}
else
- {
+ {
tokenizer.parseError( "Expecting string but found " + token.value );
}
}
- return null;
+ return null;
}
/**
* Attempt to parse a value
*/
- private final function parseValue():Object
+ private function parseValue():Object
{
- checkValidToken();
-
+ // Catch errors when the input stream ends abruptly
+ if ( token == null )
+ {
+ tokenizer.parseError( "Unexpected end of input" );
+ }
+
switch ( token.type )
{
case JSONTokenType.LEFT_BRACE:
return parseObject();
-
+
case JSONTokenType.LEFT_BRACKET:
return parseArray();
-
+
case JSONTokenType.STRING:
+ if (!isDate(token))
+ return token.value;
+
+ convertToDate(token);
+ return token.value;
+
case JSONTokenType.NUMBER:
case JSONTokenType.TRUE:
case JSONTokenType.FALSE:
case JSONTokenType.NULL:
return token.value;
-
+
case JSONTokenType.NAN:
if ( !strict )
{
@@ -340,13 +322,43 @@ package com.adobe.serialization.json
{
tokenizer.parseError( "Unexpected " + token.value );
}
-
+
default:
tokenizer.parseError( "Unexpected " + token.value );
-
+
}
- return null;
+ return null;
+ }
+
+ /**
+ * Determine if the given token contains a a Date in serialized form
+ * @return
+ */
+ private function isDate(token:JSONToken):Boolean
+ {
+ if (JSONTokenType.DATE == token.type)
+ return true;
+
+ if (DateUtil.isMicrosoftJSONDate(String(token.value)))
+ return true;
+
+ return false;
+ }
+
+ /**
+ * Convert the given token's type/value to Date
+ * @param token
+ */
+ private function convertToDate(token:JSONToken):void
+ {
+ if (JSONTokenType.DATE == token.type)
+ return;
+
+ var result:Date = DateUtil.parseMicrosoftJSONDate(String(token.value));
+
+ token.value = result;
+ token.type = JSONTokenType.DATE;
}
}
}
diff --git a/src/com/adobe/serialization/json/JSONTokenType.as b/src/com/adobe/serialization/json/JSONTokenType.as
index 8002b32..59d6a86 100644
--- a/src/com/adobe/serialization/json/JSONTokenType.as
+++ b/src/com/adobe/serialization/json/JSONTokenType.as
@@ -1,69 +1,71 @@
-/*
- Copyright (c) 2008, Adobe Systems Incorporated
- All rights reserved.
-
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are
- met:
-
- * Redistributions of source code must retain the above copyright notice,
- this list of conditions and the following disclaimer.
-
- * Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
-
- * Neither the name of Adobe Systems Incorporated nor the names of its
- contributors may be used to endorse or promote products derived from
- this software without specific prior written permission.
-
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
- IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
- THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-*/
-
-package com.adobe.serialization.json
-{
-
- /**
- * Class containing constant values for the different types
- * of tokens in a JSON encoded string.
- */
- public final class JSONTokenType
- {
- public static const UNKNOWN:int = -1;
-
- public static const COMMA:int = 0;
-
- public static const LEFT_BRACE:int = 1;
-
- public static const RIGHT_BRACE:int = 2;
-
- public static const LEFT_BRACKET:int = 3;
-
- public static const RIGHT_BRACKET:int = 4;
-
- public static const COLON:int = 6;
-
- public static const TRUE:int = 7;
-
- public static const FALSE:int = 8;
-
- public static const NULL:int = 9;
-
- public static const STRING:int = 10;
-
- public static const NUMBER:int = 11;
-
- public static const NAN:int = 12;
-
- }
+/*
+ Copyright (c) 2008, Adobe Systems Incorporated
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ * Neither the name of Adobe Systems Incorporated nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+package com.adobe.serialization.json {
+
+ /**
+ * Class containing constant values for the different types
+ * of tokens in a JSON encoded string.
+ */
+ public class JSONTokenType {
+
+ public static const UNKNOWN:int = -1;
+
+ public static const COMMA:int = 0;
+
+ public static const LEFT_BRACE:int = 1;
+
+ public static const RIGHT_BRACE:int = 2;
+
+ public static const LEFT_BRACKET:int = 3;
+
+ public static const RIGHT_BRACKET:int = 4;
+
+ public static const COLON:int = 6;
+
+ public static const TRUE:int = 7;
+
+ public static const FALSE:int = 8;
+
+ public static const NULL:int = 9;
+
+ public static const STRING:int = 10;
+
+ public static const NUMBER:int = 11;
+
+ public static const NAN:int = 12;
+
+ public static const DATE:int = 13;
+
+ }
+
}
\ No newline at end of file
diff --git a/src/com/adobe/serialization/json/JSONTokenizer.as b/src/com/adobe/serialization/json/JSONTokenizer.as
index 0072f6f..40d11c9 100644
--- a/src/com/adobe/serialization/json/JSONTokenizer.as
+++ b/src/com/adobe/serialization/json/JSONTokenizer.as
@@ -1,708 +1,708 @@
-/*
- Copyright (c) 2008, Adobe Systems Incorporated
- All rights reserved.
-
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are
- met:
-
- * Redistributions of source code must retain the above copyright notice,
- this list of conditions and the following disclaimer.
-
- * Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
-
- * Neither the name of Adobe Systems Incorporated nor the names of its
- contributors may be used to endorse or promote products derived from
- this software without specific prior written permission.
-
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
- IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
- THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-*/
-
-package com.adobe.serialization.json
-{
-
- public class JSONTokenizer
- {
-
- /**
- * Flag indicating if the tokenizer should only recognize
- * standard JSON tokens. Setting to false
allows
- * tokens such as NaN and allows numbers to be formatted as
- * hex, etc.
- */
- private var strict:Boolean;
-
- /** The object that will get parsed from the JSON string */
- private var obj:Object;
-
- /** The JSON string to be parsed */
- private var jsonString:String;
-
- /** The current parsing location in the JSON string */
- private var loc:int;
-
- /** The current character in the JSON string during parsing */
- private var ch:String;
-
- /**
- * The regular expression used to make sure the string does not
- * contain invalid control characters.
- */
- private const controlCharsRegExp:RegExp = /[\x00-\x1F]/;
-
- /**
- * Constructs a new JSONDecoder to parse a JSON string
- * into a native object.
- *
- * @param s The JSON string to be converted
- * into a native object
- */
- public function JSONTokenizer( s:String, strict:Boolean )
- {
- jsonString = s;
- this.strict = strict;
- loc = 0;
-
- // prime the pump by getting the first character
- nextChar();
- }
-
- /**
- * Gets the next token in the input sting and advances
- * the character to the next character after the token
- */
- public function getNextToken():JSONToken
- {
- var token:JSONToken = null;
-
- // skip any whitespace / comments since the last
- // token was read
- skipIgnored();
-
- // examine the new character and see what we have...
- switch ( ch )
- {
- case '{':
- token = JSONToken.create( JSONTokenType.LEFT_BRACE, ch );
- nextChar();
- break
-
- case '}':
- token = JSONToken.create( JSONTokenType.RIGHT_BRACE, ch );
- nextChar();
- break
-
- case '[':
- token = JSONToken.create( JSONTokenType.LEFT_BRACKET, ch );
- nextChar();
- break
-
- case ']':
- token = JSONToken.create( JSONTokenType.RIGHT_BRACKET, ch );
- nextChar();
- break
-
- case ',':
- token = JSONToken.create( JSONTokenType.COMMA, ch );
- nextChar();
- break
-
- case ':':
- token = JSONToken.create( JSONTokenType.COLON, ch );
- nextChar();
- break;
-
- case 't': // attempt to read true
- var possibleTrue:String = "t" + nextChar() + nextChar() + nextChar();
-
- if ( possibleTrue == "true" )
- {
- token = JSONToken.create( JSONTokenType.TRUE, true );
- nextChar();
- }
- else
- {
- parseError( "Expecting 'true' but found " + possibleTrue );
- }
-
- break;
-
- case 'f': // attempt to read false
- var possibleFalse:String = "f" + nextChar() + nextChar() + nextChar() + nextChar();
-
- if ( possibleFalse == "false" )
- {
- token = JSONToken.create( JSONTokenType.FALSE, false );
- nextChar();
- }
- else
- {
- parseError( "Expecting 'false' but found " + possibleFalse );
- }
-
- break;
-
- case 'n': // attempt to read null
- var possibleNull:String = "n" + nextChar() + nextChar() + nextChar();
-
- if ( possibleNull == "null" )
- {
- token = JSONToken.create( JSONTokenType.NULL, null );
- nextChar();
- }
- else
- {
- parseError( "Expecting 'null' but found " + possibleNull );
- }
-
- break;
-
- case 'N': // attempt to read NaN
- var possibleNaN:String = "N" + nextChar() + nextChar();
-
- if ( possibleNaN == "NaN" )
- {
- token = JSONToken.create( JSONTokenType.NAN, NaN );
- nextChar();
- }
- else
- {
- parseError( "Expecting 'NaN' but found " + possibleNaN );
- }
-
- break;
-
- case '"': // the start of a string
- token = readString();
- break;
-
- default:
- // see if we can read a number
- if ( isDigit( ch ) || ch == '-' )
- {
- token = readNumber();
- }
- else if ( ch == '' )
- {
- // check for reading past the end of the string
- token = null;
- }
- else
- {
- // not sure what was in the input string - it's not
- // anything we expected
- parseError( "Unexpected " + ch + " encountered" );
- }
- }
-
- return token;
- }
-
- /**
- * Attempts to read a string from the input string. Places
- * the character location at the first character after the
- * string. It is assumed that ch is " before this method is called.
- *
- * @return the JSONToken with the string value if a string could
- * be read. Throws an error otherwise.
- */
- private final function readString():JSONToken
- {
- // Rather than examine the string character-by-character, it's
- // faster to use indexOf to try to and find the closing quote character
- // and then replace escape sequences after the fact.
-
- // Start at the current input stream position
- var quoteIndex:int = loc;
- do
- {
- // Find the next quote in the input stream
- quoteIndex = jsonString.indexOf( "\"", quoteIndex );
-
- if ( quoteIndex >= 0 )
- {
- // We found the next double quote character in the string, but we need
- // to make sure it is not part of an escape sequence.
-
- // Keep looping backwards while the previous character is a backslash
- var backspaceCount:int = 0;
- var backspaceIndex:int = quoteIndex - 1;
- while ( jsonString.charAt( backspaceIndex ) == "\\" )
- {
- backspaceCount++;
- backspaceIndex--;
- }
-
- // If we have an even number of backslashes, that means this is the ending quote
- if ( ( backspaceCount & 1 ) == 0 )
- {
- break;
- }
-
- // At this point, the quote was determined to be part of an escape sequence
- // so we need to move past the quote index to look for the next one
- quoteIndex++;
- }
- else // There are no more quotes in the string and we haven't found the end yet
- {
- parseError( "Unterminated string literal" );
- }
- } while ( true );
-
- // Unescape the string
- // the token for the string we'll try to read
- var token:JSONToken = JSONToken.create(
- JSONTokenType.STRING,
- // Attach resulting string to the token to return it
- unescapeString( jsonString.substr( loc, quoteIndex - loc ) ) );
-
- // Move past the closing quote in the input string. This updates the next
- // character in the input stream to be the character one after the closing quote
- loc = quoteIndex + 1;
- nextChar();
-
- return token;
- }
-
- /**
- * Convert all JavaScript escape characters into normal characters
- *
- * @param input The input string to convert
- * @return Original string with escape characters replaced by real characters
- */
- public function unescapeString( input:String ):String
- {
- // Issue #104 - If the string contains any unescaped control characters, this
- // is an error in strict mode
- if ( strict && controlCharsRegExp.test( input ) )
- {
- parseError( "String contains unescaped control character (0x00-0x1F)" );
- }
-
- var result:String = "";
- var backslashIndex:int = 0;
- var nextSubstringStartPosition:int = 0;
- var len:int = input.length;
- do
- {
- // Find the next backslash in the input
- backslashIndex = input.indexOf( '\\', nextSubstringStartPosition );
-
- if ( backslashIndex >= 0 )
- {
- result += input.substr( nextSubstringStartPosition, backslashIndex - nextSubstringStartPosition );
-
- // Move past the backslash and next character (all escape sequences are
- // two characters, except for \u, which will advance this further)
- nextSubstringStartPosition = backslashIndex + 2;
-
- // Check the next character so we know what to escape
- var escapedChar:String = input.charAt( backslashIndex + 1 );
- switch ( escapedChar )
- {
- // Try to list the most common expected cases first to improve performance
-
- case '"':
- result += escapedChar;
- break; // quotation mark
- case '\\':
- result += escapedChar;
- break; // reverse solidus
- case 'n':
- result += '\n';
- break; // newline
- case 'r':
- result += '\r';
- break; // carriage return
- case 't':
- result += '\t';
- break; // horizontal tab
-
- // Convert a unicode escape sequence to it's character value
- case 'u':
-
- // Save the characters as a string we'll convert to an int
- var hexValue:String = "";
-
- var unicodeEndPosition:int = nextSubstringStartPosition + 4;
-
- // Make sure there are enough characters in the string leftover
- if ( unicodeEndPosition > len )
- {
- parseError( "Unexpected end of input. Expecting 4 hex digits after \\u." );
- }
-
- // Try to find 4 hex characters
- for ( var i:int = nextSubstringStartPosition; i < unicodeEndPosition; i++ )
- {
- // get the next character and determine
- // if it's a valid hex digit or not
- var possibleHexChar:String = input.charAt( i );
- if ( !isHexDigit( possibleHexChar ) )
- {
- parseError( "Excepted a hex digit, but found: " + possibleHexChar );
- }
-
- // Valid hex digit, add it to the value
- hexValue += possibleHexChar;
- }
-
- // Convert hexValue to an integer, and use that
- // integer value to create a character to add
- // to our string.
- result += String.fromCharCode( parseInt( hexValue, 16 ) );
-
- // Move past the 4 hex digits that we just read
- nextSubstringStartPosition = unicodeEndPosition;
- break;
-
- case 'f':
- result += '\f';
- break; // form feed
- case '/':
- result += '/';
- break; // solidus
- case 'b':
- result += '\b';
- break; // bell
- default:
- result += '\\' + escapedChar; // Couldn't unescape the sequence, so just pass it through
- }
- }
- else
- {
- // No more backslashes to replace, append the rest of the string
- result += input.substr( nextSubstringStartPosition );
- break;
- }
-
- } while ( nextSubstringStartPosition < len );
-
- return result;
- }
-
- /**
- * Attempts to read a number from the input string. Places
- * the character location at the first character after the
- * number.
- *
- * @return The JSONToken with the number value if a number could
- * be read. Throws an error otherwise.
- */
- private final function readNumber():JSONToken
- {
- // the string to accumulate the number characters
- // into that we'll convert to a number at the end
- var input:String = "";
-
- // check for a negative number
- if ( ch == '-' )
- {
- input += '-';
- nextChar();
- }
-
- // the number must start with a digit
- if ( !isDigit( ch ) )
- {
- parseError( "Expecting a digit" );
- }
-
- // 0 can only be the first digit if it
- // is followed by a decimal point
- if ( ch == '0' )
- {
- input += ch;
- nextChar();
-
- // make sure no other digits come after 0
- if ( isDigit( ch ) )
- {
- parseError( "A digit cannot immediately follow 0" );
- }
- // unless we have 0x which starts a hex number, but this
- // doesn't match JSON spec so check for not strict mode.
- else if ( !strict && ch == 'x' )
- {
- // include the x in the input
- input += ch;
- nextChar();
-
- // need at least one hex digit after 0x to
- // be valid
- if ( isHexDigit( ch ) )
- {
- input += ch;
- nextChar();
- }
- else
- {
- parseError( "Number in hex format require at least one hex digit after \"0x\"" );
- }
-
- // consume all of the hex values
- while ( isHexDigit( ch ) )
- {
- input += ch;
- nextChar();
- }
- }
- }
- else
- {
- // read numbers while we can
- while ( isDigit( ch ) )
- {
- input += ch;
- nextChar();
- }
- }
-
- // check for a decimal value
- if ( ch == '.' )
- {
- input += '.';
- nextChar();
-
- // after the decimal there has to be a digit
- if ( !isDigit( ch ) )
- {
- parseError( "Expecting a digit" );
- }
-
- // read more numbers to get the decimal value
- while ( isDigit( ch ) )
- {
- input += ch;
- nextChar();
- }
- }
-
- // check for scientific notation
- if ( ch == 'e' || ch == 'E' )
- {
- input += "e"
- nextChar();
- // check for sign
- if ( ch == '+' || ch == '-' )
- {
- input += ch;
- nextChar();
- }
-
- // require at least one number for the exponent
- // in this case
- if ( !isDigit( ch ) )
- {
- parseError( "Scientific notation number needs exponent value" );
- }
-
- // read in the exponent
- while ( isDigit( ch ) )
- {
- input += ch;
- nextChar();
- }
- }
-
- // convert the string to a number value
- var num:Number = Number( input );
-
- if ( isFinite( num ) && !isNaN( num ) )
- {
- // the token for the number that we've read
- return JSONToken.create( JSONTokenType.NUMBER, num );
- }
- else
- {
- parseError( "Number " + num + " is not valid!" );
- }
-
- return null;
- }
-
- /**
- * Reads the next character in the input
- * string and advances the character location.
- *
- * @return The next character in the input string, or
- * null if we've read past the end.
- */
- private final function nextChar():String
- {
- return ch = jsonString.charAt( loc++ );
- }
-
- /**
- * Advances the character location past any
- * sort of white space and comments
- */
- private final function skipIgnored():void
- {
- var originalLoc:int;
-
- // keep trying to skip whitespace and comments as long
- // as we keep advancing past the original location
- do
- {
- originalLoc = loc;
- skipWhite();
- skipComments();
- } while ( originalLoc != loc );
- }
-
- /**
- * Skips comments in the input string, either
- * single-line or multi-line. Advances the character
- * to the first position after the end of the comment.
- */
- private function skipComments():void
- {
- if ( ch == '/' )
- {
- // Advance past the first / to find out what type of comment
- nextChar();
- switch ( ch )
- {
- case '/': // single-line comment, read through end of line
-
- // Loop over the characters until we find
- // a newline or until there's no more characters left
- do
- {
- nextChar();
- } while ( ch != '\n' && ch != '' )
-
- // move past the \n
- nextChar();
-
- break;
-
- case '*': // multi-line comment, read until closing */
-
- // move past the opening *
- nextChar();
-
- // try to find a trailing */
- while ( true )
- {
- if ( ch == '*' )
- {
- // check to see if we have a closing /
- nextChar();
- if ( ch == '/' )
- {
- // move past the end of the closing */
- nextChar();
- break;
- }
- }
- else
- {
- // move along, looking if the next character is a *
- nextChar();
- }
-
- // when we're here we've read past the end of
- // the string without finding a closing */, so error
- if ( ch == '' )
- {
- parseError( "Multi-line comment not closed" );
- }
- }
-
- break;
-
- // Can't match a comment after a /, so it's a parsing error
- default:
- parseError( "Unexpected " + ch + " encountered (expecting '/' or '*' )" );
- }
- }
-
- }
-
-
- /**
- * Skip any whitespace in the input string and advances
- * the character to the first character after any possible
- * whitespace.
- */
- private final function skipWhite():void
- {
- // As long as there are spaces in the input
- // stream, advance the current location pointer
- // past them
- while ( isWhiteSpace( ch ) )
- {
- nextChar();
- }
-
- }
-
- /**
- * Determines if a character is whitespace or not.
- *
- * @return True if the character passed in is a whitespace
- * character
- */
- private final function isWhiteSpace( ch:String ):Boolean
- {
- // Check for the whitespace defined in the spec
- if ( ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' )
- {
- return true;
- }
- // If we're not in strict mode, we also accept non-breaking space
- else if ( !strict && ch.charCodeAt( 0 ) == 160 )
- {
- return true;
- }
-
- return false;
- }
-
- /**
- * Determines if a character is a digit [0-9].
- *
- * @return True if the character passed in is a digit
- */
- private final function isDigit( ch:String ):Boolean
- {
- return ( ch >= '0' && ch <= '9' );
- }
-
- /**
- * Determines if a character is a hex digit [0-9A-Fa-f].
- *
- * @return True if the character passed in is a hex digit
- */
- private final function isHexDigit( ch:String ):Boolean
- {
- return ( isDigit( ch ) || ( ch >= 'A' && ch <= 'F' ) || ( ch >= 'a' && ch <= 'f' ) );
- }
-
- /**
- * Raises a parsing error with a specified message, tacking
- * on the error location and the original string.
- *
- * @param message The message indicating why the error occurred
- */
- public final function parseError( message:String ):void
- {
- throw new JSONParseError( message, loc, jsonString );
- }
- }
-
-}
+/*
+ Copyright (c) 2008, Adobe Systems Incorporated
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ * Neither the name of Adobe Systems Incorporated nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+package com.adobe.serialization.json {
+ import com.adobe.utils.DateUtil;
+
+ public class JSONTokenizer {
+
+ /**
+ * Flag indicating if the tokenizer should only recognize
+ * standard JSON tokens. Setting to false
allows
+ * tokens such as NaN and allows numbers to be formatted as
+ * hex, etc.
+ */
+ private var strict:Boolean;
+
+ /** The object that will get parsed from the JSON string */
+ private var obj:Object;
+
+ /** The JSON string to be parsed */
+ private var jsonString:String;
+
+ /** The current parsing location in the JSON string */
+ private var loc:int;
+
+ /** The current character in the JSON string during parsing */
+ private var ch:String;
+
+ /**
+ * The regular expression used to make sure the string does not
+ * contain invalid control characters.
+ */
+ private var controlCharsRegExp:RegExp = /[\x00-\x1F]/;
+
+ /**
+ * Constructs a new JSONDecoder to parse a JSON string
+ * into a native object.
+ *
+ * @param s The JSON string to be converted
+ * into a native object
+ */
+ public function JSONTokenizer( s:String, strict:Boolean )
+ {
+ jsonString = s;
+ this.strict = strict;
+ loc = 0;
+
+ // prime the pump by getting the first character
+ nextChar();
+ }
+
+ /**
+ * Gets the next token in the input sting and advances
+ * the character to the next character after the token
+ */
+ public function getNextToken():JSONToken
+ {
+ var token:JSONToken = new JSONToken();
+
+ // skip any whitespace / comments since the last
+ // token was read
+ skipIgnored();
+
+ // examine the new character and see what we have...
+ switch ( ch )
+ {
+ case '{':
+ token.type = JSONTokenType.LEFT_BRACE;
+ token.value = '{';
+ nextChar();
+ break
+
+ case '}':
+ token.type = JSONTokenType.RIGHT_BRACE;
+ token.value = '}';
+ nextChar();
+ break
+
+ case '[':
+ token.type = JSONTokenType.LEFT_BRACKET;
+ token.value = '[';
+ nextChar();
+ break
+
+ case ']':
+ token.type = JSONTokenType.RIGHT_BRACKET;
+ token.value = ']';
+ nextChar();
+ break
+
+ case ',':
+ token.type = JSONTokenType.COMMA;
+ token.value = ',';
+ nextChar();
+ break
+
+ case ':':
+ token.type = JSONTokenType.COLON;
+ token.value = ':';
+ nextChar();
+ break;
+
+ case 't': // attempt to read true
+ var possibleTrue:String = "t" + nextChar() + nextChar() + nextChar();
+
+ if ( possibleTrue == "true" )
+ {
+ token.type = JSONTokenType.TRUE;
+ token.value = true;
+ nextChar();
+ }
+ else
+ {
+ parseError( "Expecting 'true' but found " + possibleTrue );
+ }
+
+ break;
+
+ case 'f': // attempt to read false
+ var possibleFalse:String = "f" + nextChar() + nextChar() + nextChar() + nextChar();
+
+ if ( possibleFalse == "false" )
+ {
+ token.type = JSONTokenType.FALSE;
+ token.value = false;
+ nextChar();
+ }
+ else
+ {
+ parseError( "Expecting 'false' but found " + possibleFalse );
+ }
+
+ break;
+
+ case 'n': // attempt to read null
+ var possibleNull:String = "n" + nextChar() + nextChar() + nextChar();
+
+ if ( possibleNull == "null" )
+ {
+ token.type = JSONTokenType.NULL;
+ token.value = null;
+ nextChar();
+ }
+ else
+ {
+ parseError( "Expecting 'null' but found " + possibleNull );
+ }
+
+ break;
+
+ case 'N': // attempt to read NaN
+ var possibleNaN:String = "N" + nextChar() + nextChar();
+
+ if ( possibleNaN == "NaN" )
+ {
+ token.type = JSONTokenType.NAN;
+ token.value = NaN;
+ nextChar();
+ }
+ else
+ {
+ parseError( "Expecting 'NaN' but found " + possibleNaN );
+ }
+
+ break;
+
+ case '"': // the start of a string
+ token = readString();
+ //if (isDate(token))
+ //convertToDate(token);
+
+ break;
+
+ default:
+ // see if we can read a number
+ if ( isDigit( ch ) || ch == '-' )
+ {
+ token = readNumber();
+ }
+ else if ( ch == '' )
+ {
+ // check for reading past the end of the string
+ return null;
+ }
+ else
+ {
+ // not sure what was in the input string - it's not
+ // anything we expected
+ parseError( "Unexpected " + ch + " encountered" );
+ }
+ }
+
+ return token;
+ }
+
+
+
+ /**
+ * Attempts to read a string from the input string. Places
+ * the character location at the first character after the
+ * string. It is assumed that ch is " before this method is called.
+ *
+ * @return the JSONToken with the string value if a string could
+ * be read. Throws an error otherwise.
+ */
+ private function readString():JSONToken
+ {
+ // Rather than examine the string character-by-character, it's
+ // faster to use indexOf to try to and find the closing quote character
+ // and then replace escape sequences after the fact.
+
+ // Start at the current input stream position
+ var quoteIndex:int = loc;
+ do
+ {
+ // Find the next quote in the input stream
+ quoteIndex = jsonString.indexOf( "\"", quoteIndex );
+
+ if ( quoteIndex >= 0 )
+ {
+ // We found the next double quote character in the string, but we need
+ // to make sure it is not part of an escape sequence.
+
+ // Keep looping backwards while the previous character is a backslash
+ var backspaceCount:int = 0;
+ var backspaceIndex:int = quoteIndex - 1;
+ while ( jsonString.charAt( backspaceIndex ) == "\\" )
+ {
+ backspaceCount++;
+ backspaceIndex--;
+ }
+
+ // If we have an even number of backslashes, that means this is the ending quote
+ if ( backspaceCount % 2 == 0 )
+ {
+ break;
+ }
+
+ // At this point, the quote was determined to be part of an escape sequence
+ // so we need to move past the quote index to look for the next one
+ quoteIndex++;
+ }
+ else // There are no more quotes in the string and we haven't found the end yet
+ {
+ parseError( "Unterminated string literal" );
+ }
+ } while ( true );
+
+ // Unescape the string
+ // the token for the string we'll try to read
+ var token:JSONToken = new JSONToken();
+ token.type = JSONTokenType.STRING;
+ // Attach resulting string to the token to return it
+ token.value = unescapeString( jsonString.substr( loc, quoteIndex - loc ) );
+
+ // Move past the closing quote in the input string. This updates the next
+ // character in the input stream to be the character one after the closing quote
+ loc = quoteIndex + 1;
+ nextChar();
+
+ return token;
+ }
+
+ /**
+ * Convert all JavaScript escape characters into normal characters
+ *
+ * @param input The input string to convert
+ * @return Original string with escape characters replaced by real characters
+ */
+ public function unescapeString( input:String ):String
+ {
+ // Issue #104 - If the string contains any unescaped control characters, this
+ // is an error in strict mode
+ if ( strict && controlCharsRegExp.test( input ) )
+ {
+ parseError( "String contains unescaped control character (0x00-0x1F)" );
+ }
+
+ var result:String = "";
+ var backslashIndex:int = 0;
+ var nextSubstringStartPosition:int = 0;
+ var len:int = input.length;
+ do
+ {
+ // Find the next backslash in the input
+ backslashIndex = input.indexOf( '\\', nextSubstringStartPosition );
+
+ if ( backslashIndex >= 0 )
+ {
+ result += input.substr( nextSubstringStartPosition, backslashIndex - nextSubstringStartPosition );
+
+ // Move past the backslash and next character (all escape sequences are
+ // two characters, except for \u, which will advance this further)
+ nextSubstringStartPosition = backslashIndex + 2;
+
+ // Check the next character so we know what to escape
+ var afterBackslashIndex:int = backslashIndex + 1;
+ var escapedChar:String = input.charAt( afterBackslashIndex );
+ switch ( escapedChar )
+ {
+ // Try to list the most common expected cases first to improve performance
+
+ case '"': result += '"'; break; // quotation mark
+ case '\\': result += '\\'; break; // reverse solidus
+ case 'n': result += '\n'; break; // newline
+ case 'r': result += '\r'; break; // carriage return
+ case 't': result += '\t'; break; // horizontal tab
+
+ // Convert a unicode escape sequence to it's character value
+ case 'u':
+
+ // Save the characters as a string we'll convert to an int
+ var hexValue:String = "";
+
+ // Make sure there are enough characters in the string leftover
+ if ( nextSubstringStartPosition + 4 > len )
+ {
+ parseError( "Unexpected end of input. Expecting 4 hex digits after \\u." );
+ }
+
+ // Try to find 4 hex characters
+ for ( var i:int = nextSubstringStartPosition; i < nextSubstringStartPosition + 4; i++ )
+ {
+ // get the next character and determine
+ // if it's a valid hex digit or not
+ var possibleHexChar:String = input.charAt( i );
+ if ( !isHexDigit( possibleHexChar ) )
+ {
+ parseError( "Excepted a hex digit, but found: " + possibleHexChar );
+ }
+
+ // Valid hex digit, add it to the value
+ hexValue += possibleHexChar;
+ }
+
+ // Convert hexValue to an integer, and use that
+ // integer value to create a character to add
+ // to our string.
+ result += String.fromCharCode( parseInt( hexValue, 16 ) );
+ // Move past the 4 hex digits that we just read
+ nextSubstringStartPosition += 4;
+ break;
+
+ case 'f': result += '\f'; break; // form feed
+ case '/': result += '/'; break; // solidus
+ case 'b': result += '\b'; break; // bell
+ default: result += '\\' + escapedChar; // Couldn't unescape the sequence, so just pass it through
+ }
+ }
+ else
+ {
+ // No more backslashes to replace, append the rest of the string
+ result += input.substr( nextSubstringStartPosition );
+ break;
+ }
+
+ } while ( nextSubstringStartPosition < len );
+
+ return result;
+ }
+
+ /**
+ * Attempts to read a number from the input string. Places
+ * the character location at the first character after the
+ * number.
+ *
+ * @return The JSONToken with the number value if a number could
+ * be read. Throws an error otherwise.
+ */
+ private function readNumber():JSONToken
+ {
+ // the string to accumulate the number characters
+ // into that we'll convert to a number at the end
+ var input:String = "";
+
+ // check for a negative number
+ if ( ch == '-' )
+ {
+ input += '-';
+ nextChar();
+ }
+
+ // the number must start with a digit
+ if ( !isDigit( ch ) )
+ {
+ parseError( "Expecting a digit" );
+ }
+
+ // 0 can only be the first digit if it
+ // is followed by a decimal point
+ if ( ch == '0' )
+ {
+ input += ch;
+ nextChar();
+
+ // make sure no other digits come after 0
+ if ( isDigit( ch ) )
+ {
+ parseError( "A digit cannot immediately follow 0" );
+ }
+ // unless we have 0x which starts a hex number, but this
+ // doesn't match JSON spec so check for not strict mode.
+ else if ( !strict && ch == 'x' )
+ {
+ // include the x in the input
+ input += ch;
+ nextChar();
+
+ // need at least one hex digit after 0x to
+ // be valid
+ if ( isHexDigit( ch ) )
+ {
+ input += ch;
+ nextChar();
+ }
+ else
+ {
+ parseError( "Number in hex format require at least one hex digit after \"0x\"" );
+ }
+
+ // consume all of the hex values
+ while ( isHexDigit( ch ) )
+ {
+ input += ch;
+ nextChar();
+ }
+ }
+ }
+ else
+ {
+ // read numbers while we can
+ while ( isDigit( ch ) )
+ {
+ input += ch;
+ nextChar();
+ }
+ }
+
+ // check for a decimal value
+ if ( ch == '.' )
+ {
+ input += '.';
+ nextChar();
+
+ // after the decimal there has to be a digit
+ if ( !isDigit( ch ) )
+ {
+ parseError( "Expecting a digit" );
+ }
+
+ // read more numbers to get the decimal value
+ while ( isDigit( ch ) )
+ {
+ input += ch;
+ nextChar();
+ }
+ }
+
+ // check for scientific notation
+ if ( ch == 'e' || ch == 'E' )
+ {
+ input += "e"
+ nextChar();
+ // check for sign
+ if ( ch == '+' || ch == '-' )
+ {
+ input += ch;
+ nextChar();
+ }
+
+ // require at least one number for the exponent
+ // in this case
+ if ( !isDigit( ch ) )
+ {
+ parseError( "Scientific notation number needs exponent value" );
+ }
+
+ // read in the exponent
+ while ( isDigit( ch ) )
+ {
+ input += ch;
+ nextChar();
+ }
+ }
+
+ // convert the string to a number value
+ var num:Number = Number( input );
+
+ if ( isFinite( num ) && !isNaN( num ) )
+ {
+ // the token for the number that we've read
+ var token:JSONToken = new JSONToken();
+ token.type = JSONTokenType.NUMBER;
+ token.value = num;
+ return token;
+ }
+ else
+ {
+ parseError( "Number " + num + " is not valid!" );
+ }
+
+ return null;
+ }
+
+ /**
+ * Reads the next character in the input
+ * string and advances the character location.
+ *
+ * @return The next character in the input string, or
+ * null if we've read past the end.
+ */
+ private function nextChar():String
+ {
+ return ch = jsonString.charAt( loc++ );
+ }
+
+ /**
+ * Advances the character location past any
+ * sort of white space and comments
+ */
+ private function skipIgnored():void
+ {
+ var originalLoc:int;
+
+ // keep trying to skip whitespace and comments as long
+ // as we keep advancing past the original location
+ do
+ {
+ originalLoc = loc;
+ skipWhite();
+ skipComments();
+ }
+ while ( originalLoc != loc );
+ }
+
+ /**
+ * Skips comments in the input string, either
+ * single-line or multi-line. Advances the character
+ * to the first position after the end of the comment.
+ */
+ private function skipComments():void
+ {
+ if ( ch == '/' )
+ {
+ // Advance past the first / to find out what type of comment
+ nextChar();
+ switch ( ch )
+ {
+ case '/': // single-line comment, read through end of line
+
+ // Loop over the characters until we find
+ // a newline or until there's no more characters left
+ do
+ {
+ nextChar();
+ }
+ while ( ch != '\n' && ch != '' )
+
+ // move past the \n
+ nextChar();
+
+ break;
+
+ case '*': // multi-line comment, read until closing */
+
+ // move past the opening *
+ nextChar();
+
+ // try to find a trailing */
+ while ( true )
+ {
+ if ( ch == '*' )
+ {
+ // check to see if we have a closing /
+ nextChar();
+ if ( ch == '/')
+ {
+ // move past the end of the closing */
+ nextChar();
+ break;
+ }
+ }
+ else
+ {
+ // move along, looking if the next character is a *
+ nextChar();
+ }
+
+ // when we're here we've read past the end of
+ // the string without finding a closing */, so error
+ if ( ch == '' )
+ {
+ parseError( "Multi-line comment not closed" );
+ }
+ }
+
+ break;
+
+ // Can't match a comment after a /, so it's a parsing error
+ default:
+ parseError( "Unexpected " + ch + " encountered (expecting '/' or '*' )" );
+ }
+ }
+
+ }
+
+
+ /**
+ * Skip any whitespace in the input string and advances
+ * the character to the first character after any possible
+ * whitespace.
+ */
+ private function skipWhite():void
+ {
+ // As long as there are spaces in the input
+ // stream, advance the current location pointer
+ // past them
+ while ( isWhiteSpace( ch ) )
+ {
+ nextChar();
+ }
+
+ }
+
+ /**
+ * Determines if a character is whitespace or not.
+ *
+ * @return True if the character passed in is a whitespace
+ * character
+ */
+ private function isWhiteSpace( ch:String ):Boolean
+ {
+ // Check for the whitespace defined in the spec
+ if ( ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' )
+ {
+ return true;
+ }
+ // If we're not in strict mode, we also accept non-breaking space
+ else if ( !strict && ch.charCodeAt( 0 ) == 160 )
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Determines if a character is a digit [0-9].
+ *
+ * @return True if the character passed in is a digit
+ */
+ private function isDigit( ch:String ):Boolean
+ {
+ return ( ch >= '0' && ch <= '9' );
+ }
+
+ /**
+ * Determines if a character is a hex digit [0-9A-Fa-f].
+ *
+ * @return True if the character passed in is a hex digit
+ */
+ private function isHexDigit( ch:String ):Boolean
+ {
+ return ( isDigit( ch ) || ( ch >= 'A' && ch <= 'F' ) || ( ch >= 'a' && ch <= 'f' ) );
+ }
+
+ /**
+ * Raises a parsing error with a specified message, tacking
+ * on the error location and the original string.
+ *
+ * @param message The message indicating why the error occurred
+ */
+ public function parseError( message:String ):void
+ {
+ throw new JSONParseError( message, loc, jsonString );
+ }
+ }
+
+}
diff --git a/src/com/adobe/utils/DateUtil.as b/src/com/adobe/utils/DateUtil.as
index a741df0..a4a83dc 100644
--- a/src/com/adobe/utils/DateUtil.as
+++ b/src/com/adobe/utils/DateUtil.as
@@ -1,701 +1,755 @@
-/*
- Copyright (c) 2008, Adobe Systems Incorporated
- All rights reserved.
-
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are
- met:
-
- * Redistributions of source code must retain the above copyright notice,
- this list of conditions and the following disclaimer.
-
- * Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
-
- * Neither the name of Adobe Systems Incorporated nor the names of its
- contributors may be used to endorse or promote products derived from
- this software without specific prior written permission.
-
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
- IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
- THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-*/
-
-package com.adobe.utils
-{
- import mx.formatters.DateBase;
-
- /**
- * Class that contains static utility methods for manipulating and working
- * with Dates.
- *
- * @langversion ActionScript 3.0
- * @playerversion Flash 9.0
- * @tiptext
- */
- public class DateUtil
- {
-
- /**
- * Returns the English Short Month name (3 letters) for the Month that
- * the Date represents.
- *
- * @param d The Date instance whose month will be used to retrieve the
- * short month name.
- *
- * @return An English 3 Letter Month abbreviation.
- *
- * @langversion ActionScript 3.0
- * @playerversion Flash 9.0
- * @tiptext
- *
- * @see SHORT_MONTH
- */
- public static function getShortMonthName(d:Date):String
- {
- return DateBase.monthNamesShort[d.getMonth()];
- }
-
- /**
- * Returns the index of the month that the short month name string
- * represents.
- *
- * @param m The 3 letter abbreviation representing a short month name.
- *
- * @param Optional parameter indicating whether the search should be case
- * sensitive
- *
- * @return A int that represents that month represented by the specifed
- * short name.
- *
- * @langversion ActionScript 3.0
- * @playerversion Flash 9.0
- * @tiptext
- *
- * @see SHORT_MONTH
- */
- public static function getShortMonthIndex(m:String):int
- {
- return DateBase.monthNamesShort.indexOf(m);
- }
-
- /**
- * Returns the English full Month name for the Month that
- * the Date represents.
- *
- * @param d The Date instance whose month will be used to retrieve the
- * full month name.
- *
- * @return An English full month name.
- *
- * @langversion ActionScript 3.0
- * @playerversion Flash 9.0
- * @tiptext
- *
- * @see FULL_MONTH
- */
- public static function getFullMonthName(d:Date):String
- {
- return DateBase.monthNamesLong[d.getMonth()];
- }
-
- /**
- * Returns the index of the month that the full month name string
- * represents.
- *
- * @param m A full month name.
- *
- * @return A int that represents that month represented by the specifed
- * full month name.
- *
- * @langversion ActionScript 3.0
- * @playerversion Flash 9.0
- * @tiptext
- *
- * @see FULL_MONTH
- */
- public static function getFullMonthIndex(m:String):int
- {
- return DateBase.monthNamesLong.indexOf(m);
- }
-
- /**
- * Returns the English Short Day name (3 letters) for the day that
- * the Date represents.
- *
- * @param d The Date instance whose day will be used to retrieve the
- * short day name.
- *
- * @return An English 3 Letter day abbreviation.
- *
- * @langversion ActionScript 3.0
- * @playerversion Flash 9.0
- * @tiptext
- *
- * @see SHORT_DAY
- */
- public static function getShortDayName(d:Date):String
- {
- return DateBase.dayNamesShort[d.getDay()];
- }
-
- /**
- * Returns the index of the day that the short day name string
- * represents.
- *
- * @param m A short day name.
- *
- * @return A int that represents that short day represented by the specifed
- * full month name.
- *
- * @langversion ActionScript 3.0
- * @playerversion Flash 9.0
- * @tiptext
- *
- * @see SHORT_DAY
- */
- public static function getShortDayIndex(d:String):int
- {
- return DateBase.dayNamesShort.indexOf(d);
- }
-
- /**
- * Returns the English full day name for the day that
- * the Date represents.
- *
- * @param d The Date instance whose day will be used to retrieve the
- * full day name.
- *
- * @return An English full day name.
- *
- * @langversion ActionScript 3.0
- * @playerversion Flash 9.0
- * @tiptext
- *
- * @see FULL_DAY
- */
- public static function getFullDayName(d:Date):String
- {
- return DateBase.dayNamesLong[d.getDay()];
- }
-
- /**
- * Returns the index of the day that the full day name string
- * represents.
- *
- * @param m A full day name.
- *
- * @return A int that represents that full day represented by the specifed
- * full month name.
- *
- * @langversion ActionScript 3.0
- * @playerversion Flash 9.0
- * @tiptext
- *
- * @see FULL_DAY
- */
- public static function getFullDayIndex(d:String):int
- {
- return DateBase.dayNamesLong.indexOf(d);
- }
-
- /**
- * Returns a two digit representation of the year represented by the
- * specified date.
- *
- * @param d The Date instance whose year will be used to generate a two
- * digit string representation of the year.
- *
- * @return A string that contains a 2 digit representation of the year.
- * Single digits will be padded with 0.
- *
- * @langversion ActionScript 3.0
- * @playerversion Flash 9.0
- * @tiptext
- */
- public static function getShortYear(d:Date):String
- {
- var dStr:String = String(d.getFullYear());
-
- if(dStr.length < 3)
- {
- return dStr;
- }
-
- return (dStr.substr(dStr.length - 2));
- }
-
- /**
- * Compares two dates and returns an integer depending on their relationship.
- *
- * Returns -1 if d1 is greater than d2.
- * Returns 1 if d2 is greater than d1.
- * Returns 0 if both dates are equal.
- *
- * @param d1 The date that will be compared to the second date.
- * @param d2 The date that will be compared to the first date.
- *
- * @return An int indicating how the two dates compare.
- *
- * @langversion ActionScript 3.0
- * @playerversion Flash 9.0
- * @tiptext
- */
- public static function compareDates(d1:Date, d2:Date):int
- {
- var d1ms:Number = d1.getTime();
- var d2ms:Number = d2.getTime();
-
- if(d1ms > d2ms)
- {
- return -1;
- }
- else if(d1ms < d2ms)
- {
- return 1;
- }
- else
- {
- return 0;
- }
- }
-
- /**
- * Returns a short hour (0 - 12) represented by the specified date.
- *
- * If the hour is less than 12 (0 - 11 AM) then the hour will be returned.
- *
- * If the hour is greater than 12 (12 - 23 PM) then the hour minus 12
- * will be returned.
- *
- * @param d1 The Date from which to generate the short hour
- *
- * @return An int between 0 and 13 ( 1 - 12 ) representing the short hour.
- *
- * @langversion ActionScript 3.0
- * @playerversion Flash 9.0
- * @tiptext
- */
- public static function getShortHour(d:Date):int
- {
- var h:int = d.hours;
-
- if(h == 0 || h == 12)
- {
- return 12;
- }
- else if(h > 12)
- {
- return h - 12;
- }
- else
- {
- return h;
- }
- }
-
- /**
- * Returns a string indicating whether the date represents a time in the
- * ante meridiem (AM) or post meridiem (PM).
- *
- * If the hour is less than 12 then "AM" will be returned.
- *
- * If the hour is greater than 12 then "PM" will be returned.
- *
- * @param d1 The Date from which to generate the 12 hour clock indicator.
- *
- * @return A String ("AM" or "PM") indicating which half of the day the
- * hour represents.
- *
- * @langversion ActionScript 3.0
- * @playerversion Flash 9.0
- * @tiptext
- */
- public static function getAMPM(d:Date):String
- {
- return (d.hours > 11)? "PM" : "AM";
- }
-
- /**
- * Parses dates that conform to RFC822 into Date objects. This method also
- * supports four-digit years (not supported in RFC822), but two-digit years
- * (referring to the 20th century) are fine, too.
- *
- * This function is useful for parsing RSS .91, .92, and 2.0 dates.
- *
- * @param str
- *
- * @returns
- *
- * @langversion ActionScript 3.0
- * @playerversion Flash 9.0
- * @tiptext
- *
- * @see http://asg.web.cmu.edu/rfc/rfc822.html
- */
- public static function parseRFC822(str:String):Date
- {
- var finalDate:Date;
- try
- {
- var dateParts:Array = str.split(" ");
- var day:String = null;
-
- if (dateParts[0].search(/\d/) == -1)
- {
- day = dateParts.shift().replace(/\W/, "");
- }
-
- var date:Number = Number(dateParts.shift());
- var month:Number = Number(DateUtil.getShortMonthIndex(dateParts.shift()));
- var year:Number = Number(dateParts.shift());
- var timeParts:Array = dateParts.shift().split(":");
- var hour:Number = int(timeParts.shift());
- var minute:Number = int(timeParts.shift());
- var second:Number = (timeParts.length > 0) ? int(timeParts.shift()): 0;
-
- var milliseconds:Number = Date.UTC(year, month, date, hour, minute, second, 0);
-
- var timezone:String = dateParts.shift();
- var offset:Number = 0;
-
- if (timezone.search(/\d/) == -1)
- {
- switch(timezone)
- {
- case "UT":
- offset = 0;
- break;
- case "UTC":
- offset = 0;
- break;
- case "GMT":
- offset = 0;
- break;
- case "EST":
- offset = (-5 * 3600000);
- break;
- case "EDT":
- offset = (-4 * 3600000);
- break;
- case "CST":
- offset = (-6 * 3600000);
- break;
- case "CDT":
- offset = (-5 * 3600000);
- break;
- case "MST":
- offset = (-7 * 3600000);
- break;
- case "MDT":
- offset = (-6 * 3600000);
- break;
- case "PST":
- offset = (-8 * 3600000);
- break;
- case "PDT":
- offset = (-7 * 3600000);
- break;
- case "Z":
- offset = 0;
- break;
- case "A":
- offset = (-1 * 3600000);
- break;
- case "M":
- offset = (-12 * 3600000);
- break;
- case "N":
- offset = (1 * 3600000);
- break;
- case "Y":
- offset = (12 * 3600000);
- break;
- default:
- offset = 0;
- }
- }
- else
- {
- var multiplier:Number = 1;
- var oHours:Number = 0;
- var oMinutes:Number = 0;
- if (timezone.length != 4)
- {
- if (timezone.charAt(0) == "-")
- {
- multiplier = -1;
- }
- timezone = timezone.substr(1, 4);
- }
- oHours = Number(timezone.substr(0, 2));
- oMinutes = Number(timezone.substr(2, 2));
- offset = (((oHours * 3600000) + (oMinutes * 60000)) * multiplier);
- }
-
- finalDate = new Date(milliseconds - offset);
-
- if (finalDate.toString() == "Invalid Date")
- {
- throw new Error("This date does not conform to RFC822.");
- }
- }
- catch (e:Error)
- {
- var eStr:String = "Unable to parse the string [" +str+ "] into a date. ";
- eStr += "The internal error was: " + e.toString();
- throw new Error(eStr);
- }
- return finalDate;
- }
-
- /**
- * Returns a date string formatted according to RFC822.
- *
- * @param d
- *
- * @returns
- *
- * @langversion ActionScript 3.0
- * @playerversion Flash 9.0
- * @tiptext
- *
- * @see http://asg.web.cmu.edu/rfc/rfc822.html
- */
- public static function toRFC822(d:Date):String
- {
- var date:Number = d.getUTCDate();
- var hours:Number = d.getUTCHours();
- var minutes:Number = d.getUTCMinutes();
- var seconds:Number = d.getUTCSeconds();
- var sb:String = new String();
- sb += DateBase.dayNamesShort[d.getUTCDay()];
- sb += ", ";
-
- if (date < 10)
- {
- sb += "0";
- }
- sb += date;
- sb += " ";
- //sb += DateUtil.SHORT_MONTH[d.getUTCMonth()];
- sb += DateBase.monthNamesShort[d.getUTCMonth()];
- sb += " ";
- sb += d.getUTCFullYear();
- sb += " ";
- if (hours < 10)
- {
- sb += "0";
- }
- sb += hours;
- sb += ":";
- if (minutes < 10)
- {
- sb += "0";
- }
- sb += minutes;
- sb += ":";
- if (seconds < 10)
- {
- sb += "0";
- }
- sb += seconds;
- sb += " GMT";
- return sb;
- }
-
- /**
- * Parses dates that conform to the W3C Date-time Format into Date objects.
- *
- * This function is useful for parsing RSS 1.0 and Atom 1.0 dates.
- *
- * @param str
- *
- * @returns
- *
- * @langversion ActionScript 3.0
- * @playerversion Flash 9.0
- * @tiptext
- *
- * @see http://www.w3.org/TR/NOTE-datetime
- */
- public static function parseW3CDTF(str:String):Date
- {
- var finalDate:Date;
- try
- {
- var dateStr:String = str.substring(0, str.indexOf("T"));
- var timeStr:String = str.substring(str.indexOf("T")+1, str.length);
- var dateArr:Array = dateStr.split("-");
- var year:Number = Number(dateArr.shift());
- var month:Number = Number(dateArr.shift());
- var date:Number = Number(dateArr.shift());
-
- var multiplier:Number;
- var offsetHours:Number;
- var offsetMinutes:Number;
- var offsetStr:String;
-
- if (timeStr.indexOf("Z") != -1)
- {
- multiplier = 1;
- offsetHours = 0;
- offsetMinutes = 0;
- timeStr = timeStr.replace("Z", "");
- }
- else if (timeStr.indexOf("+") != -1)
- {
- multiplier = 1;
- offsetStr = timeStr.substring(timeStr.indexOf("+")+1, timeStr.length);
- offsetHours = Number(offsetStr.substring(0, offsetStr.indexOf(":")));
- offsetMinutes = Number(offsetStr.substring(offsetStr.indexOf(":")+1, offsetStr.length));
- timeStr = timeStr.substring(0, timeStr.indexOf("+"));
- }
- else // offset is -
- {
- multiplier = -1;
- offsetStr = timeStr.substring(timeStr.indexOf("-")+1, timeStr.length);
- offsetHours = Number(offsetStr.substring(0, offsetStr.indexOf(":")));
- offsetMinutes = Number(offsetStr.substring(offsetStr.indexOf(":")+1, offsetStr.length));
- timeStr = timeStr.substring(0, timeStr.indexOf("-"));
- }
- var timeArr:Array = timeStr.split(":");
- var hour:Number = Number(timeArr.shift());
- var minutes:Number = Number(timeArr.shift());
- var secondsArr:Array = (timeArr.length > 0) ? String(timeArr.shift()).split(".") : null;
- var seconds:Number = (secondsArr != null && secondsArr.length > 0) ? Number(secondsArr.shift()) : 0;
- //var milliseconds:Number = (secondsArr != null && secondsArr.length > 0) ? Number(secondsArr.shift()) : 0;
-
- var milliseconds:Number = (secondsArr != null && secondsArr.length > 0) ? 1000*parseFloat("0." + secondsArr.shift()) : 0;
- var utc:Number = Date.UTC(year, month-1, date, hour, minutes, seconds, milliseconds);
- var offset:Number = (((offsetHours * 3600000) + (offsetMinutes * 60000)) * multiplier);
- finalDate = new Date(utc - offset);
-
- if (finalDate.toString() == "Invalid Date")
- {
- throw new Error("This date does not conform to W3CDTF.");
- }
- }
- catch (e:Error)
- {
- var eStr:String = "Unable to parse the string [" +str+ "] into a date. ";
- eStr += "The internal error was: " + e.toString();
- throw new Error(eStr);
- }
- return finalDate;
- }
-
- /**
- * Returns a date string formatted according to W3CDTF.
- *
- * @param d
- * @param includeMilliseconds Determines whether to include the
- * milliseconds value (if any) in the formatted string.
- *
- * @returns
- *
- * @langversion ActionScript 3.0
- * @playerversion Flash 9.0
- * @tiptext
- *
- * @see http://www.w3.org/TR/NOTE-datetime
- */
- public static function toW3CDTF(d:Date,includeMilliseconds:Boolean=false):String
- {
- var date:Number = d.getUTCDate();
- var month:Number = d.getUTCMonth();
- var hours:Number = d.getUTCHours();
- var minutes:Number = d.getUTCMinutes();
- var seconds:Number = d.getUTCSeconds();
- var milliseconds:Number = d.getUTCMilliseconds();
- var sb:String = new String();
-
- sb += d.getUTCFullYear();
- sb += "-";
-
- //thanks to "dom" who sent in a fix for the line below
- if (month + 1 < 10)
- {
- sb += "0";
- }
- sb += month + 1;
- sb += "-";
- if (date < 10)
- {
- sb += "0";
- }
- sb += date;
- sb += "T";
- if (hours < 10)
- {
- sb += "0";
- }
- sb += hours;
- sb += ":";
- if (minutes < 10)
- {
- sb += "0";
- }
- sb += minutes;
- sb += ":";
- if (seconds < 10)
- {
- sb += "0";
- }
- sb += seconds;
- if (includeMilliseconds && milliseconds > 0)
- {
- sb += ".";
- sb += milliseconds;
- }
- sb += "-00:00";
- return sb;
- }
-
- /**
- * Converts a date into just after midnight.
- */
- public static function makeMorning(d:Date):Date
- {
- var d:Date = new Date(d.time);
- d.hours = 0;
- d.minutes = 0;
- d.seconds = 0;
- d.milliseconds = 0;
- return d;
- }
-
- /**
- * Converts a date into just befor midnight.
- */
- public static function makeNight(d:Date):Date
- {
- var d:Date = new Date(d.time);
- d.hours = 23;
- d.minutes = 59;
- d.seconds = 59;
- d.milliseconds = 999;
- return d;
- }
-
- /**
- * Sort of converts a date into UTC.
- */
- public static function getUTCDate(d:Date):Date
- {
- var nd:Date = new Date();
- var offset:Number = d.getTimezoneOffset() * 60 * 1000;
- nd.setTime(d.getTime() + offset);
- return nd;
- }
- }
-}
+/*
+ Copyright (c) 2008, Adobe Systems Incorporated
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ * Neither the name of Adobe Systems Incorporated nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+package com.adobe.utils
+{
+ import mx.formatters.DateBase;
+
+ /**
+ * Class that contains static utility methods for manipulating and working
+ * with Dates.
+ *
+ * @langversion ActionScript 3.0
+ * @playerversion Flash 9.0
+ * @tiptext
+ */
+ public class DateUtil
+ {
+
+ /**
+ * Returns the English Short Month name (3 letters) for the Month that
+ * the Date represents.
+ *
+ * @param d The Date instance whose month will be used to retrieve the
+ * short month name.
+ *
+ * @return An English 3 Letter Month abbreviation.
+ *
+ * @langversion ActionScript 3.0
+ * @playerversion Flash 9.0
+ * @tiptext
+ *
+ * @see SHORT_MONTH
+ */
+ public static function getShortMonthName(d:Date):String
+ {
+ return DateBase.monthNamesShort[d.getMonth()];
+ }
+
+ /**
+ * Returns the index of the month that the short month name string
+ * represents.
+ *
+ * @param m The 3 letter abbreviation representing a short month name.
+ *
+ * @param Optional parameter indicating whether the search should be case
+ * sensitive
+ *
+ * @return A int that represents that month represented by the specifed
+ * short name.
+ *
+ * @langversion ActionScript 3.0
+ * @playerversion Flash 9.0
+ * @tiptext
+ *
+ * @see SHORT_MONTH
+ */
+ public static function getShortMonthIndex(m:String):int
+ {
+ return DateBase.monthNamesShort.indexOf(m);
+ }
+
+ /**
+ * Returns the English full Month name for the Month that
+ * the Date represents.
+ *
+ * @param d The Date instance whose month will be used to retrieve the
+ * full month name.
+ *
+ * @return An English full month name.
+ *
+ * @langversion ActionScript 3.0
+ * @playerversion Flash 9.0
+ * @tiptext
+ *
+ * @see FULL_MONTH
+ */
+ public static function getFullMonthName(d:Date):String
+ {
+ return DateBase.monthNamesLong[d.getMonth()];
+ }
+
+ /**
+ * Returns the index of the month that the full month name string
+ * represents.
+ *
+ * @param m A full month name.
+ *
+ * @return A int that represents that month represented by the specifed
+ * full month name.
+ *
+ * @langversion ActionScript 3.0
+ * @playerversion Flash 9.0
+ * @tiptext
+ *
+ * @see FULL_MONTH
+ */
+ public static function getFullMonthIndex(m:String):int
+ {
+ return DateBase.monthNamesLong.indexOf(m);
+ }
+
+ /**
+ * Returns the English Short Day name (3 letters) for the day that
+ * the Date represents.
+ *
+ * @param d The Date instance whose day will be used to retrieve the
+ * short day name.
+ *
+ * @return An English 3 Letter day abbreviation.
+ *
+ * @langversion ActionScript 3.0
+ * @playerversion Flash 9.0
+ * @tiptext
+ *
+ * @see SHORT_DAY
+ */
+ public static function getShortDayName(d:Date):String
+ {
+ return DateBase.dayNamesShort[d.getDay()];
+ }
+
+ /**
+ * Returns the index of the day that the short day name string
+ * represents.
+ *
+ * @param m A short day name.
+ *
+ * @return A int that represents that short day represented by the specifed
+ * full month name.
+ *
+ * @langversion ActionScript 3.0
+ * @playerversion Flash 9.0
+ * @tiptext
+ *
+ * @see SHORT_DAY
+ */
+ public static function getShortDayIndex(d:String):int
+ {
+ return DateBase.dayNamesShort.indexOf(d);
+ }
+
+ /**
+ * Returns the English full day name for the day that
+ * the Date represents.
+ *
+ * @param d The Date instance whose day will be used to retrieve the
+ * full day name.
+ *
+ * @return An English full day name.
+ *
+ * @langversion ActionScript 3.0
+ * @playerversion Flash 9.0
+ * @tiptext
+ *
+ * @see FULL_DAY
+ */
+ public static function getFullDayName(d:Date):String
+ {
+ return DateBase.dayNamesLong[d.getDay()];
+ }
+
+ /**
+ * Returns the index of the day that the full day name string
+ * represents.
+ *
+ * @param m A full day name.
+ *
+ * @return A int that represents that full day represented by the specifed
+ * full month name.
+ *
+ * @langversion ActionScript 3.0
+ * @playerversion Flash 9.0
+ * @tiptext
+ *
+ * @see FULL_DAY
+ */
+ public static function getFullDayIndex(d:String):int
+ {
+ return DateBase.dayNamesLong.indexOf(d);
+ }
+
+ /**
+ * Returns a two digit representation of the year represented by the
+ * specified date.
+ *
+ * @param d The Date instance whose year will be used to generate a two
+ * digit string representation of the year.
+ *
+ * @return A string that contains a 2 digit representation of the year.
+ * Single digits will be padded with 0.
+ *
+ * @langversion ActionScript 3.0
+ * @playerversion Flash 9.0
+ * @tiptext
+ */
+ public static function getShortYear(d:Date):String
+ {
+ var dStr:String = String(d.getFullYear());
+
+ if(dStr.length < 3)
+ {
+ return dStr;
+ }
+
+ return (dStr.substr(dStr.length - 2));
+ }
+
+ /**
+ * Compares two dates and returns an integer depending on their relationship.
+ *
+ * Returns -1 if d1 is greater than d2.
+ * Returns 1 if d2 is greater than d1.
+ * Returns 0 if both dates are equal.
+ *
+ * @param d1 The date that will be compared to the second date.
+ * @param d2 The date that will be compared to the first date.
+ *
+ * @return An int indicating how the two dates compare.
+ *
+ * @langversion ActionScript 3.0
+ * @playerversion Flash 9.0
+ * @tiptext
+ */
+ public static function compareDates(d1:Date, d2:Date):int
+ {
+ var d1ms:Number = d1.getTime();
+ var d2ms:Number = d2.getTime();
+
+ if(d1ms > d2ms)
+ {
+ return -1;
+ }
+ else if(d1ms < d2ms)
+ {
+ return 1;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+
+ /**
+ * Returns a short hour (0 - 12) represented by the specified date.
+ *
+ * If the hour is less than 12 (0 - 11 AM) then the hour will be returned.
+ *
+ * If the hour is greater than 12 (12 - 23 PM) then the hour minus 12
+ * will be returned.
+ *
+ * @param d1 The Date from which to generate the short hour
+ *
+ * @return An int between 0 and 13 ( 1 - 12 ) representing the short hour.
+ *
+ * @langversion ActionScript 3.0
+ * @playerversion Flash 9.0
+ * @tiptext
+ */
+ public static function getShortHour(d:Date):int
+ {
+ var h:int = d.hours;
+
+ if(h == 0 || h == 12)
+ {
+ return 12;
+ }
+ else if(h > 12)
+ {
+ return h - 12;
+ }
+ else
+ {
+ return h;
+ }
+ }
+
+ /**
+ * Returns a string indicating whether the date represents a time in the
+ * ante meridiem (AM) or post meridiem (PM).
+ *
+ * If the hour is less than 12 then "AM" will be returned.
+ *
+ * If the hour is greater than 12 then "PM" will be returned.
+ *
+ * @param d1 The Date from which to generate the 12 hour clock indicator.
+ *
+ * @return A String ("AM" or "PM") indicating which half of the day the
+ * hour represents.
+ *
+ * @langversion ActionScript 3.0
+ * @playerversion Flash 9.0
+ * @tiptext
+ */
+ public static function getAMPM(d:Date):String
+ {
+ return (d.hours > 11)? "PM" : "AM";
+ }
+
+ /**
+ * Parses dates that conform to RFC822 into Date objects. This method also
+ * supports four-digit years (not supported in RFC822), but two-digit years
+ * (referring to the 20th century) are fine, too.
+ *
+ * This function is useful for parsing RSS .91, .92, and 2.0 dates.
+ *
+ * @param str
+ *
+ * @returns
+ *
+ * @langversion ActionScript 3.0
+ * @playerversion Flash 9.0
+ * @tiptext
+ *
+ * @see http://asg.web.cmu.edu/rfc/rfc822.html
+ */
+ public static function parseRFC822(str:String):Date
+ {
+ var finalDate:Date;
+ try
+ {
+ var dateParts:Array = str.split(" ");
+ var day:String = null;
+
+ if (dateParts[0].search(/\d/) == -1)
+ {
+ day = dateParts.shift().replace(/\W/, "");
+ }
+
+ var date:Number = Number(dateParts.shift());
+ var month:Number = Number(DateUtil.getShortMonthIndex(dateParts.shift()));
+ var year:Number = Number(dateParts.shift());
+ var timeParts:Array = dateParts.shift().split(":");
+ var hour:Number = int(timeParts.shift());
+ var minute:Number = int(timeParts.shift());
+ var second:Number = (timeParts.length > 0) ? int(timeParts.shift()): 0;
+
+ var milliseconds:Number = Date.UTC(year, month, date, hour, minute, second, 0);
+
+ var timezone:String = dateParts.shift();
+ var offset:Number = 0;
+
+ if (timezone.search(/\d/) == -1)
+ {
+ switch(timezone)
+ {
+ case "UT":
+ offset = 0;
+ break;
+ case "UTC":
+ offset = 0;
+ break;
+ case "GMT":
+ offset = 0;
+ break;
+ case "EST":
+ offset = (-5 * 3600000);
+ break;
+ case "EDT":
+ offset = (-4 * 3600000);
+ break;
+ case "CST":
+ offset = (-6 * 3600000);
+ break;
+ case "CDT":
+ offset = (-5 * 3600000);
+ break;
+ case "MST":
+ offset = (-7 * 3600000);
+ break;
+ case "MDT":
+ offset = (-6 * 3600000);
+ break;
+ case "PST":
+ offset = (-8 * 3600000);
+ break;
+ case "PDT":
+ offset = (-7 * 3600000);
+ break;
+ case "Z":
+ offset = 0;
+ break;
+ case "A":
+ offset = (-1 * 3600000);
+ break;
+ case "M":
+ offset = (-12 * 3600000);
+ break;
+ case "N":
+ offset = (1 * 3600000);
+ break;
+ case "Y":
+ offset = (12 * 3600000);
+ break;
+ default:
+ offset = 0;
+ }
+ }
+ else
+ {
+ var multiplier:Number = 1;
+ var oHours:Number = 0;
+ var oMinutes:Number = 0;
+ if (timezone.length != 4)
+ {
+ if (timezone.charAt(0) == "-")
+ {
+ multiplier = -1;
+ }
+ timezone = timezone.substr(1, 4);
+ }
+ oHours = Number(timezone.substr(0, 2));
+ oMinutes = Number(timezone.substr(2, 2));
+ offset = (((oHours * 3600000) + (oMinutes * 60000)) * multiplier);
+ }
+
+ finalDate = new Date(milliseconds - offset);
+
+ if (finalDate.toString() == "Invalid Date")
+ {
+ throw new Error("This date does not conform to RFC822.");
+ }
+ }
+ catch (e:Error)
+ {
+ var eStr:String = "Unable to parse the string [" +str+ "] into a date. ";
+ eStr += "The internal error was: " + e.toString();
+ throw new Error(eStr);
+ }
+ return finalDate;
+ }
+
+ /**
+ * Returns a date string formatted according to RFC822.
+ *
+ * @param d
+ *
+ * @returns
+ *
+ * @langversion ActionScript 3.0
+ * @playerversion Flash 9.0
+ * @tiptext
+ *
+ * @see http://asg.web.cmu.edu/rfc/rfc822.html
+ */
+ public static function toRFC822(d:Date):String
+ {
+ var date:Number = d.getUTCDate();
+ var hours:Number = d.getUTCHours();
+ var minutes:Number = d.getUTCMinutes();
+ var seconds:Number = d.getUTCSeconds();
+ var sb:String = new String();
+ sb += DateBase.dayNamesShort[d.getUTCDay()];
+ sb += ", ";
+
+ if (date < 10)
+ {
+ sb += "0";
+ }
+ sb += date;
+ sb += " ";
+ //sb += DateUtil.SHORT_MONTH[d.getUTCMonth()];
+ sb += DateBase.monthNamesShort[d.getUTCMonth()];
+ sb += " ";
+ sb += d.getUTCFullYear();
+ sb += " ";
+ if (hours < 10)
+ {
+ sb += "0";
+ }
+ sb += hours;
+ sb += ":";
+ if (minutes < 10)
+ {
+ sb += "0";
+ }
+ sb += minutes;
+ sb += ":";
+ if (seconds < 10)
+ {
+ sb += "0";
+ }
+ sb += seconds;
+ sb += " GMT";
+ return sb;
+ }
+
+ /**
+ * Parses dates that conform to the W3C Date-time Format into Date objects.
+ *
+ * This function is useful for parsing RSS 1.0 and Atom 1.0 dates.
+ *
+ * @param str
+ *
+ * @returns
+ *
+ * @langversion ActionScript 3.0
+ * @playerversion Flash 9.0
+ * @tiptext
+ *
+ * @see http://www.w3.org/TR/NOTE-datetime
+ */
+ public static function parseW3CDTF(str:String):Date
+ {
+ var finalDate:Date;
+ try
+ {
+ var dateStr:String = str.substring(0, str.indexOf("T"));
+ var timeStr:String = str.substring(str.indexOf("T")+1, str.length);
+ var dateArr:Array = dateStr.split("-");
+ var year:Number = Number(dateArr.shift());
+ var month:Number = Number(dateArr.shift());
+ var date:Number = Number(dateArr.shift());
+
+ var multiplier:Number;
+ var offsetHours:Number;
+ var offsetMinutes:Number;
+ var offsetStr:String;
+
+ if (timeStr.indexOf("Z") != -1)
+ {
+ multiplier = 1;
+ offsetHours = 0;
+ offsetMinutes = 0;
+ timeStr = timeStr.replace("Z", "");
+ }
+ else if (timeStr.indexOf("+") != -1)
+ {
+ multiplier = 1;
+ offsetStr = timeStr.substring(timeStr.indexOf("+")+1, timeStr.length);
+ offsetHours = Number(offsetStr.substring(0, offsetStr.indexOf(":")));
+ offsetMinutes = Number(offsetStr.substring(offsetStr.indexOf(":")+1, offsetStr.length));
+ timeStr = timeStr.substring(0, timeStr.indexOf("+"));
+ }
+ else // offset is -
+ {
+ multiplier = -1;
+ offsetStr = timeStr.substring(timeStr.indexOf("-")+1, timeStr.length);
+ offsetHours = Number(offsetStr.substring(0, offsetStr.indexOf(":")));
+ offsetMinutes = Number(offsetStr.substring(offsetStr.indexOf(":")+1, offsetStr.length));
+ timeStr = timeStr.substring(0, timeStr.indexOf("-"));
+ }
+ var timeArr:Array = timeStr.split(":");
+ var hour:Number = Number(timeArr.shift());
+ var minutes:Number = Number(timeArr.shift());
+ var secondsArr:Array = (timeArr.length > 0) ? String(timeArr.shift()).split(".") : null;
+ var seconds:Number = (secondsArr != null && secondsArr.length > 0) ? Number(secondsArr.shift()) : 0;
+ //var milliseconds:Number = (secondsArr != null && secondsArr.length > 0) ? Number(secondsArr.shift()) : 0;
+
+ var milliseconds:Number = (secondsArr != null && secondsArr.length > 0) ? 1000*parseFloat("0." + secondsArr.shift()) : 0;
+ var utc:Number = Date.UTC(year, month-1, date, hour, minutes, seconds, milliseconds);
+ var offset:Number = (((offsetHours * 3600000) + (offsetMinutes * 60000)) * multiplier);
+ finalDate = new Date(utc - offset);
+
+ if (finalDate.toString() == "Invalid Date")
+ {
+ throw new Error("This date does not conform to W3CDTF.");
+ }
+ }
+ catch (e:Error)
+ {
+ var eStr:String = "Unable to parse the string [" +str+ "] into a date. ";
+ eStr += "The internal error was: " + e.toString();
+ throw new Error(eStr);
+ }
+ return finalDate;
+ }
+
+ /**
+ * Returns a date string formatted according to W3CDTF.
+ *
+ * @param d
+ * @param includeMilliseconds Determines whether to include the
+ * milliseconds value (if any) in the formatted string.
+ *
+ * @returns
+ *
+ * @langversion ActionScript 3.0
+ * @playerversion Flash 9.0
+ * @tiptext
+ *
+ * @see http://www.w3.org/TR/NOTE-datetime
+ */
+ public static function toW3CDTF(d:Date,includeMilliseconds:Boolean=false):String
+ {
+ var date:Number = d.getUTCDate();
+ var month:Number = d.getUTCMonth();
+ var hours:Number = d.getUTCHours();
+ var minutes:Number = d.getUTCMinutes();
+ var seconds:Number = d.getUTCSeconds();
+ var milliseconds:Number = d.getUTCMilliseconds();
+ var sb:String = new String();
+
+ sb += d.getUTCFullYear();
+ sb += "-";
+
+ //thanks to "dom" who sent in a fix for the line below
+ if (month + 1 < 10)
+ {
+ sb += "0";
+ }
+ sb += month + 1;
+ sb += "-";
+ if (date < 10)
+ {
+ sb += "0";
+ }
+ sb += date;
+ sb += "T";
+ if (hours < 10)
+ {
+ sb += "0";
+ }
+ sb += hours;
+ sb += ":";
+ if (minutes < 10)
+ {
+ sb += "0";
+ }
+ sb += minutes;
+ sb += ":";
+ if (seconds < 10)
+ {
+ sb += "0";
+ }
+ sb += seconds;
+ if (includeMilliseconds && milliseconds > 0)
+ {
+ sb += ".";
+ sb += milliseconds;
+ }
+ sb += "-00:00";
+ return sb;
+ }
+
+ /**
+ * RegExp for matching a date that has been serialized to JSON in the Microsoft format - e.g. "/Date(ticks[+-]offset)/"
+ */
+ private static const MicrosoftJSONDateRegEx:RegExp = /^\/Date\((\d*)([-+](\d+))?\)\/$/;
+
+ /**
+ * Determine if the given string contains a date that has been serialized to JSON in the Microsoft format - e.g. "/Date(ticks-offset)/"
+ *
+ * See for more detail: http://msdn.microsoft.com/en-us/library/bb299886.aspx#intro_to_json_sidebarb
+ *
+ * @param str
+ * @return
+ */
+ public static function isMicrosoftJSONDate(str:String):Boolean
+ {
+ if (!str)
+ return false;
+
+ var datePattern:RegExp = DateUtil.MicrosoftJSONDateRegEx;
+
+ return datePattern.test(str);
+ }
+
+ /**
+ * Parse a string containing a date in the Microsoft format to an ActionScript Date. This will throw an exception
+ * if the string does not represent an ActionScript Date
+ *
+ * @param str
+ * @return
+ */
+ public static function parseMicrosoftJSONDate(str:String):Date
+ {
+ if (!str)
+ throw new Error("Received null/empty");
+
+ var datePattern:RegExp = DateUtil.MicrosoftJSONDateRegEx;
+
+ var matches:Array = datePattern.exec(str); //str.match(datePattern);
+
+ if (!matches || 0 == matches.length)
+ throw new Error("String does not represent a Date");
+
+ var ticks:Number = parseFloat(matches[1]); //int is too small
+ var date:Date = new Date(ticks);
+
+ //If we only have two matches, then there's no offset information, so return immediately
+ if (2 >= matches.length)
+ return date;
+
+ var offset:String = matches[2];
+
+ return date;
+ }
+
+ /**
+ * Converts a date into just after midnight.
+ */
+ public static function makeMorning(d:Date):Date
+ {
+ var d:Date = new Date(d.time);
+ d.hours = 0;
+ d.minutes = 0;
+ d.seconds = 0;
+ d.milliseconds = 0;
+ return d;
+ }
+
+ /**
+ * Converts a date into just befor midnight.
+ */
+ public static function makeNight(d:Date):Date
+ {
+ var d:Date = new Date(d.time);
+ d.hours = 23;
+ d.minutes = 59;
+ d.seconds = 59;
+ d.milliseconds = 999;
+ return d;
+ }
+
+ /**
+ * Sort of converts a date into UTC.
+ */
+ public static function getUTCDate(d:Date):Date
+ {
+ var nd:Date = new Date();
+ var offset:Number = d.getTimezoneOffset() * 60 * 1000;
+ nd.setTime(d.getTime() + offset);
+ return nd;
+ }
+ }
+}