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; + } + } +}