Source: src/main/javascript/oracle/oj/ojvalidation/IntlDateTimeConverter.js

Oracle® JavaScript Extension Toolkit (JET)
1.1.2

E65298-01

/**
 * Copyright (c) 2014, Oracle and/or its affiliates.
 * All rights reserved.
 */

/*global OraDateTimeConverter:true*/
/**
 * @export
 * Placeholder here as closure compiler objects to export annotation outside of top level
 */

/**
 * @constructor
 * 
 * @classdesc Constructs an immutable instance and initializes it with the options provided. 
 * <p>
 *  The converter instance uses locale symbols for the locale set on the page (returned by 
 *  {@li.nk oj.Config#getLocale}. Please note that timezone is currently not supported by the 
 *  converter.
 *  </p>
 * There are several ways to initialize the converter.
 * <ul>
 * <li>Using options defined by the ECMA 402 Specification, these would be the properties year, 
 * month, day, hour, minute, second, weekday, era, timeZoneName, hour12</li>
 * <li>Using a custom date and/or time format pattern using the 'pattern' property</li>
 * <li>Using the standard date, datetime and time format lengths defined by Unicode CLDR, these 
 * would include the properties formaType, dateFormat, timeFormat.</li>
 * </ul>
 * 
 * <p>
 * The options when specified take precendence in the following order:<br>
 * 1. pattern.<br>
 * 2. ECMA options.<br>
 * 3. formatType/dateFormat/timeFormat.
 * <p>
 * The converter provides great leniency when parsing a user input value to a date in the following 
 * ways: <br/>
 * <ul>
 * <li>Allows use of any character for separators irrespective of the separator specified in the 
 * associated pattern. E.g., if pattern is set to 'y-M-d', the following values are all valid - 
 * 2013-11-16, 2013/11-16 and 2013aaa11xxx16.</li>
 * <li>Allows specifying 4 digit year in any position in relation to day and month. E.g., 11-2013-16 
 * or 16-11-2013</li>
 * <li>Supports auto-correction of value, when month and day positions are swapped as long as the 
 * date is > 12 when working with the Gregorian calendar. E.g., if the pattern is 'y-M-d', 
 * 2013-16-11 will be auto-corrected to 2013-11-16. However if both date and month are less or equal 
 * to 12, no assumptions are made about the day or month and the value parsed against the exact pattern.</li>
 * <li>Supports auto-correction of value, for the short and long types of weekday and month names. 
 * So they can are used anywhere in the value. E.g., if the expected pattern is E, MMM, d, y, all 
 * these values are acceptable - Tue, Nov 26 2013 or Nov, Tue 2013 26 or 2013 Tue 26 Nov. <br/>
 * NOTE: Lenient parsing of narrow era, weekday or month name is not supported because of ambiguity in 
 * choosing the right value. So we expect for narrow era, weekday or month option that values be 
 * provided either in their short or long forms. E.g., Sat, March 02, 2013.
 * </li>
 * <li>Specifying the weekday is optional. E.g., if the expected pattern is E, MMM, d, y; then 
 * entering Nov 26, 2013, is automatically turned to Tuesday Nov 26, 2013. But entering an invalid 
 * weekday, i.e., if the weekday does not match the date, an exception is thrown.</li>
 * <li>Leniency rules apply equally no matter which option is used - pattern, ECMA options or formatType</li>
 * </ul>
 * 
 * @property {Object=} options - an object literal used to provide an optional information to 
 * initialize the converter.<p>
 * @property {string=} options.year - allowed values are "2-digit", "numeric". When no options are 
 * set the default value of "numeric" is used.
 * @property {string=} options.month - specifies how the month is formatted. Allowed values are 
 * "2-digit", "numeric", "narrow", "short", "long". The last 3 values behave in the same way as for 
 * weekday, indicating the length of the string used. When no options are set the default value of 
 * "numeric" is used.
 * @property {string=} options.day - specifies how the day is formatted. Allowed values are "2-digit",
 *  "numeric". When no options are set the default value of "numeric" is used.
 * @property {string=} options.hour - specifies how the hour is formatted. Allowed values are 
 * "2-digit" or "numeric". The hour is displayed using the 12 or 24 hour clock, depending on the 
 * locale. See 'hour12' for details.
 * @property {string=} options.minute - specifies how the minute is formatted. Allowed values are 
 * "2-digit", "numeric".
 * @property {string=} options.second - specifies whether the second should be displayed as "2-digit" 
 * or "numeric".
 * @property {string=} options.weekday - specifies how the day of the week is formatted. If absent, it 
 * is not included in the date formatting. Allowed values are "narrow", "short", "long" indicating the 
 * length of the string used.
 * @property {string=} options.era - specifies how the era is included in the formatted date. If 
 * absent, it is not included in the date formatting. Allowed values are "narrow", "short", "long".
 * @property {string=} options.timeZoneName - allowed values are "short", "long".
 * @property {boolean=} options.hour12 - specifies what time notation is used for formatting the time. 
 * A true value uses the 12-hour clock and false uses the 24-hour clock (often called military time 
 * in the US). This property is undefined if the hour property is not used when formatting the date.
 * 
 * @property {string=} options.pattern - a localized string pattern, where the the characters used in 
 * pattern conform to Unicode CLDR for date time formats. This will override all other options 
 * when present. <br/>
 * NOTE: 'pattern' is provided for backwards compatibility with existing apps that may want the 
 * convenience of specifying an explicit format mask. Setting a 'pattern' will override the default 
 * locale specific format.
 * 
 * @property {string=} options.formatType - determines the 'standard' date and/or time format lengths 
 * to use. Allowed values: "date", "time", "datetime". See 'dateFormat' and 'timeFormat' options. 
 * When set a value must be specified.
 * @property {string=} options.dateFormat - specifies the standard date format length to use when 
 * formatType is set to "date" or "datetime". Allowed values are : "short" (default), "medium", "long", 
 * "full". 
 * @property {string=} options.timeFormat - specifies the standard time format length to use when 
 * 'formatType' is set to "time" or "datetime". Allowed values: "short" (default), "medium", "long", 
 * "full". 
 * 
 * @example <caption>Create a date time converter using no options. This uses the default value for 
 * year, month, day properties</caption>
 * var converterFactory = oj.Validation.converterFactory("datetime");
 * converter = converterFactory.createConverter();
 * var resolved = converter.resolvedOpions();
 * // logs "day=numeric, month=numeric, year=numeric"
 * console.log("day=" + resolved.day + ", month=" + resolved.month + ", year=" + resolved.year);
 * <br/>
 * 
 * @example <caption>Create a date time converter using the ECMA options to represent date</caption>
 * var options = { year:'2-digit', month: '2-digit', day: '2-digit'};
 * var converterFactory = oj.Validation.converterFactory("datetime");
 * converter = converterFactory.createConverter(options);<br/>
 * 
 * @example <caption>Create a date time converter using the 'pattern' option</caption>
 * var options = {pattern: 'MM-dd-yyyy'}; 
 * var converterFactory = oj.Validation.converterFactory("datetime");
 * converter = converterFactory.createConverter(options);<br/>
 * 
 * @example <caption>Create a date time converter using the standard format length</caption>
 * var options = {formatType: 'date', dateFormat: 'medium'}; 
 * var converterFactory = oj.Validation.converterFactory("datetime");
 * converter = converterFactory.createConverter(options);<br/>
 * 
 * @export
 * @augments oj.DateTimeConverter 
 * @name oj.IntlDateTimeConverter
 * @since 0.6
 */
oj.IntlDateTimeConverter = function(options)
{
  this.Init(options);
};

// Subclass from oj.Object 
oj.Object.createSubclass(oj.IntlDateTimeConverter, oj.DateTimeConverter, "oj.IntlDateTimeConverter");
oj.IntlDateTimeConverter._DEFAULT_DATE = new Date(1998, 10, 29, 15, 45, 31);

/**
 * Initializes the date time converter instance with the set options.
 * @param {Object=} options an object literal used to provide an optional information to initialize 
 * the converter.<p>
 * 
 * @export
 */
oj.IntlDateTimeConverter.prototype.Init = function(options) 
{
  oj.IntlDateTimeConverter.superclass.Init.call(this, options);
};


// Returns the wrapped date time converter implementation object.
oj.IntlDateTimeConverter.prototype._getWrapped = function ()
{
  if (!this._wrapped)
  {
    this._wrapped = OraDateTimeConverter.getInstance();
  }
  
  return this._wrapped;
};

/**
 * Formats the local isoString value using the options provided and returns a string value. 
 * <p>
 * Note: Application code that was previosuly passinga JavaScript Date object to this method, can 
 * now use the utility function oj.IntlConverterUtils.dateToLocalIso(), to get the proper isoString 
 * value.
 * 
 * @param {string} value to be formatted for display which should be a local isoString
 * @return {string|Object} the localized and formatted value suitable for display
 * 
 * @throws {Error} a ConverterError both when formatting fails, and if the options provided during 
 * initialization cannot be resolved correctly. Also if the iso string value contains time zone 
 * information, like the UTC designator (Z) or timezone offsets, an error is thrown.
 * 
 * @example <caption>To convert Javascript Date to an iso string before passing to <code class="prettyprint">format</code></caption>
 * var date = new Date();
 * var formatted = converter.format(oj.IntlConverterUtils.dateToLocalIso(date));
 * 
 * @see oj.IntlConverterUtils.dateToLocalIso
 * @memberOf oj.IntlDateTimeConverter
 * @export
 */
oj.IntlDateTimeConverter.prototype.format = function (value) 
{
  var localeElements = oj.LocaleData.__getBundle(), locale = oj.Config.getLocale(), 
          resolvedOptions = this.resolvedOptions(), converterError;
  
  // undefined, null and empty string values all return null. If value is NaN then return "".
  // TODO: Should we automatically parse() the integer value representing the number of milliseconds 
  // since 1 January 1970 00:00:00 UTC (Unix Epoch)?
  if (value == null || 
      (typeof value === "string" && (oj.StringUtils.trim("" + value)).length === 0)) 
  {
    return oj.IntlConverterUtils.__getNullFormattedValue();
  }
  
  try
  {
    return this._getWrapped().format(value, localeElements, resolvedOptions, locale);
  }
  catch (e)
  {
    converterError = this._processConverterError(e, value);
    throw converterError;
  }
};


/**
 * Formats an ISOString as a relative date, using the relativeOptions. 
 * <p>
 * Note: Application code that was previosuly passing a JavaScript Date object to this method can 
 * now use the utility function oj.IntlConverterUtils.dateToLocalIso to get the proper isoString 
 * value.
 * 
 * @param {string} value - value to be formatted. This value is compared with the current date 
 * on the client to arrive at the relative formatted value.
 * @param {Object} relativeOptions - an Object literal containing the following properties. The 
 * default options are ignored during relative formatting - <br>
 * <ul>
 * <li><b>formatUsing</b>: Specifies the relative formatting convention to use for (calendar or) 
 * the date field type. allowed values: "displayName". <br>Setting value to 'displayName' uses the 
 * relative display name for the instance of the dateField, and one or two past and future instances.
 * </li>
 * <li><b>dateField</b>: allowed values are: "day", "week", "month", "year"</li>
 * </ul>
 * @return {string|null} relative date. null if the value falls out side the supported relative range.
 * @throws {Object} an instance of {@link oj.ConverterError}
 * 
 * @example <caption>To convert Javascript Date to an iso string before passing to <code class="prettyprint">formatRelative</code></caption>
 * var formatted = converter.formatRelative(oj.IntlConverterUtils.dateToLocalIso(new Date()));
 * 
 * @see oj.IntlConverterUtils.dateToLocalIso
 * 
 * @memberOf oj.IntlDateTimeConverter
 * @export
 */
oj.IntlDateTimeConverter.prototype.formatRelative = function(value, relativeOptions) 
{
  var localeElements = oj.LocaleData.__getBundle(), locale = oj.Config.getLocale(), converterError;
  try
  {
    return this._getWrapped().formatRelative(value, localeElements, relativeOptions, locale);
  }
  catch (e)
  {
    converterError = this._processConverterError(e, value);
    throw converterError;
  }
};

/**
 * Retrieves a hint String describing the format the value is expected to be in. THe pattern used is 
 * provided through resolvedOptions, except when an actual pattern is set in the options. Otherwise 
 * hint is empty.
 * 
 * @return {String} a hint describing the format the value is expected to be in.
 * @memberOf oj.IntlDateTimeConverter
 * @export
 */
oj.IntlDateTimeConverter.prototype.getHint = function ()
{
  var resolvedOptions = this.resolvedOptions(), patternFromOptions = 
          resolvedOptions["patternFromOptions"] || this.getOptions()['pattern'];
  
  // TODO: converter hints are often shown in placeholder and is a user-friendly readable pattern. 
  // Until this feature is implemented use the CLDR pattern converted to lowercase
  return patternFromOptions ? patternFromOptions.toLowerCase() : "";
};

// Returns the hint value.
oj.IntlDateTimeConverter.prototype._getHintValue = function()
{
  var value = "";
  try
  {
    // example date 
    value =  this.format(oj.IntlConverterUtils.dateToLocalIso(oj.IntlDateTimeConverter._DEFAULT_DATE));
  }
  catch (e)
  {
    if (e instanceof oj.ConverterError)
    {
      // Something went wrong and we don't have a way to retrieve a valid value.
      // TODO: Log an error
      value = "";
    }
  }
  finally
  {
    return value;
  }
};


/**
 * Returns the options called with converter initialization.
 * @return {Object} an object of options.
 * @export
 * @memberOf oj.IntlDateTimeConverter
 * 
 */
oj.IntlDateTimeConverter.prototype.getOptions = function () 
{
  return oj.IntlDateTimeConverter.superclass.getOptions.call(this);
};

/**
 * Returns an object literal with locale and formatting options computed during initialization of 
 * the object. If options was not provided at the time of initialization, the properties will be 
 * derived from the locale defaults.
 * @return {Object} an object of resolved options. Properties whose corresponding internal 
 * properties are not present are not assigned. More properties may be included as needed.
 * <ul>
 * <li><b>locale</b> - a String value with the language tag of the locale whose localization is used 
 * for formatting.</li>
 * <li><b>numberingSystem</b>: a String value of the numbering system used. E.g. latn</li>
 * <li><b>era</b>: a String value. One of allowed values - "narrow", "short", "long"</li>
 * <li><b>year</b>: a String value. One of allowed values - "2-digit", "numeric"</li>
 * <li><b>month</b>: a String value. One of allowed values - "2-digit", "numeric", "narrow", "short"
 * , "long"</li>
 * <li><b>weekday</b>: a String value. One of the allowed values - "narrow", "short", "long"</li>
 * <li><b>day</b>: a String value. One of allowed values - "2-digit", "numeric"</li>
 * <li><b>hour</b>: String value. One of allowed values - "2-digit", "numeric"</li>
 * <li><b>minute</b>: a String value. One of allowed values - "2-digit", "numeric"</li>
 * <li><b>second</b>: a String value. One of allowed values - "2-digit", "numeric"</li>
 * <li><b>hour12</b>: a Boolean value indicating whether 12-hour format (true) or 24-hour format 
 * (false) should be used. It is only relevant when hour is also present.</li>
 * <li><b>timeZoneName</b>: String value. One of allowed values - "short", "long".</li>
 * </ul>
 * 
 * @throws a oj.ConverterError when the options that the converter was initialized with are invalid. 
 * @memberOf oj.IntlDateTimeConverter
 * @export
 */
oj.IntlDateTimeConverter.prototype.resolvedOptions = function ()
{
  var localeElements, locale = oj.Config.getLocale(), converterError, options = this.getOptions();
  // options are resolved and cached for a locale
  if ((locale !== this._locale) || !this._resolvedOptions)
  {
    localeElements = oj.LocaleData.__getBundle();
    try
    {
      if (!localeElements)
      {
        oj.Logger.error("locale bundle for the current locale %s is unavailable", locale);
        return {};
      }
      // cache if successfully resolved
      this._resolvedOptions = this._getWrapped().resolvedOptions(localeElements, 
                                                                 options, 
                                                                 locale);
      this._locale = locale;
    }
    catch (e)
    {
      converterError = this._processConverterError(e);
      throw converterError;
    }
  }
  
  return this._resolvedOptions;
};

/**
 * Returns true if a 24-hour format is set; false otherwise.
 * @memberOf oj.IntlDateTimeConverter
 * @export
 */
oj.IntlDateTimeConverter.prototype.isHourInDaySet = function()
{
  var ro = this.resolvedOptions(), hour = ro['hour'], hour12 = ro['hour12'];
  if (hour && !hour12)
  {
    // if hour12=false or not set and hour is set to some value
    return true;
  }
  
  return false;
};

/**
 * Returns true if 12-hour is set; false otherwise.
 * @memberOf oj.IntlDateTimeConverter
 * @export
 */
oj.IntlDateTimeConverter.prototype.isHourInAMPMSet = function()
{
  var ro = this.resolvedOptions(), hour = ro['hour'], hour12 = ro['hour12'];  
  if (hour && hour12)
  {
    // if hour12==true and hour is set to some value
    return true;
  }
  
  return false;
  
};

/**
 * Returns true if minutes are shown in the time portion; false otherwise.
 * @memberOf oj.IntlDateTimeConverter
 * @export
 */
oj.IntlDateTimeConverter.prototype.isMinuteSet = function()
{
  return this._isOptionSet('minute');
};

/**
 * Returns true if seconds are shown in the time portion; false otherwise.
 * @memberOf oj.IntlDateTimeConverter
 * @export
 */
oj.IntlDateTimeConverter.prototype.isSecondSet = function()
{
  return this._isOptionSet('second');
};

/**
 * Returns true if milliseconds are shown in the time portion; false otherwise.
 * @memberOf oj.IntlDateTimeConverter
 * @export
 */
oj.IntlDateTimeConverter.prototype.isMilliSecondSet = function()
{
  return this._isOptionSet('millisecond');
};

/**
 * Returns true if year is shown in the date portion; false otherwise.
 * @memberOf oj.IntlDateTimeConverter
 * @export
 */
oj.IntlDateTimeConverter.prototype.isYearSet = function()
{
  return this._isOptionSet('year');
};

/**
 * Returns true if month is shown in the date portion; false otherwise.
 * @memberOf oj.IntlDateTimeConverter
 * @export
 */
oj.IntlDateTimeConverter.prototype.isMonthSet = function()
{
  return this._isOptionSet('month');
};

/**
 * Returns true if day is shown in the date portion; false otherwise.
 * @memberOf oj.IntlDateTimeConverter
 * @export
 */
oj.IntlDateTimeConverter.prototype.isDaySet = function()
{
  return this._isOptionSet('day');
};

/**
 * Returns true if the day name is shown in the date portion; false otherwise.
 * @memberOf oj.IntlDateTimeConverter
 * @export
 */
oj.IntlDateTimeConverter.prototype.isDayNameSet = function()
{
  return this._isOptionSet('weekday');
};

/**
 * Returns the calculated week for the isoString value
 * 
 * @param {string} value to return the calculated week of
 * @return {number} calculated week.
 * 
 * @memberOf oj.IntlDateTimeConverter
 * @export
 */
oj.IntlDateTimeConverter.prototype.calculateWeek = function(value)
{
  return this._getWrapped().calculateWeek(value, oj.LocaleData.__getBundle(), oj.Config.getLocale());
};

/**
 * Parses the value using the options provided and returns the local date and time as a string 
 * expressed using the ISO-8601 format (http://en.wikipedia.org/wiki/ISO_8601).
 * 
 * <p>
 * For converter options specific to a date, the iso date representation alone is returned. <br/>
 * For time only options, the iso local time representation alone is returned. <br/>
 * For options that include both date and time, the iso date and local time representation is 
 * returned.<br/>
 * </p>
 * 
 * <p>
 * For convenience, if one wishes to retrieve a JavaScript Date object from the local isoString, a 
 * utility function oj.IntlConverterUtils.isoToLocalDate is provided.
 * </p>
 * 
 * @param {String|string} value to parse
 * @return {string|Object} the parsed value as an ISOString.
 * 
 * @throws {Error} a ConverterError both when parsing fails, and if the options provided during 
 * initialization cannot be resolved correctly. Parsing can also fail when the value includes a time
 *  zone. 
 * 
 * @example <caption>Parse date, time and date & time values using <code class="prettyprint">parse</code> method.</caption>
 * <!-- For date-time values  -->
 * var options = {pattern: 'MM/dd/yy hh:mm:ss a'};
 * var conv = oj.Validation.converterFactory('datetime').createConverter(options);
 * cnv.parse('09/11/14 03:02:01 PM'); // '2014-10-20T15:02:01'
 * 
 * <!-- For date values -->
 * var options = {pattern: 'MM/dd/yy'};
 * cnv.parse('09/11/14'); // '2014-10-20'
 * 
 * <!-- For time values -->
 * var options = {pattern: 'hh:mm:ss a'};
 * cnv.parse('03:02:01 PM'); // 'T15:02:01'
 * 
 * @example <caption>Convert from iso string to Javascript Date object</caption>
 * var isoString = '2014-10-20T15:02:01';
 * var date = oj.IntlConverterUtils.isoToLocalDate(converter.parse(isoString));
 * 
 * @see oj.IntlConverterUtils.isoToLocalDate
 *  
 * @export
 */
oj.IntlDateTimeConverter.prototype.parse = function (value) 
{
  var result, parsed;
  // undefined, null and empty string values are ignored and not parsed. 
  if (value == null || value === "") 
  {
    return null;
  }
  
  var localeElements = oj.LocaleData.__getBundle(), locale = oj.Config.getLocale(), 
          resolvedOptions = this.resolvedOptions(), converterError;

  try
  {
    // date converter parses the value and returns an Object with 2 fields - 'value' and 'warning'
    result = this._getWrapped().parse(value, localeElements, resolvedOptions, locale); 
    parsed = result['value'];
    if (parsed)
    {
      // TODO: For now log a warning when we leniently parse a value; later we plan to flash the 
      // field.
      if (result['warning'])
      {
        oj.Logger.warn("The value " + value + " was leniently parsed to represent a date " + 
                (parsed.toString) ? parsed.toString() : parsed);
      }
      
    }
    return parsed;
  }
  catch (e)
  {
    converterError = this._processConverterError(e, value);
    throw converterError;
  }
};

/**
 * Processes the error returned by the converter implementation and throws a oj.ConverterError 
 * instance.
 * @param {Error} e
 * @param {String|string|Date|Object=} value 
 * @throws an instance of oj.ConverterError
 * @private
 */
oj.IntlDateTimeConverter.prototype._processConverterError = function (e, value)
{
  var errorInfo = e['errorInfo'], summary, detail, errorCode, parameterMap, converterError, 
          propName, resourceKey;
  if (errorInfo)
  {
    errorCode = errorInfo['errorCode'];
    parameterMap = errorInfo['parameterMap'];
    oj.Assert.assertObject(parameterMap);
    propName = parameterMap['propertyName'];
    
    if (e instanceof TypeError)
    {
      if (errorCode === "optionTypesMismatch" || errorCode === "optionTypeInvalid")
      {
        converterError = oj.IntlConverterUtils.__getConverterOptionError(errorCode, parameterMap);
      }
    }
    else if (e instanceof RangeError)
    {
      if (errorCode === "optionOutOfRange")
      {
        converterError = oj.IntlConverterUtils.__getConverterOptionError(errorCode, parameterMap);
      }
      else if (errorCode === 'datetimeOutOfRange') // TODO: NLS should use lower case time
      {
        // The '{value}' is out of range. Enter a value between '{minValue}' and '{maxValue}' for 
        // '{propertyName}'.
        summary = oj.Translations.getTranslatedString("oj-converter.datetime.datetimeOutOfRange.summary", 
          {'propertyName': propName,
           'value': parameterMap['value']});
        detail = oj.Translations.getTranslatedString("oj-converter.datetime.datetimeOutOfRange.detail",
          {'minValue': parameterMap['minValue'],
           'maxValue': parameterMap['maxValue']});
         
        converterError = new oj.ConverterError(summary, detail);
      }
    }
    else if (e instanceof SyntaxError)
    {
      if (errorCode === "optionValueInvalid")
      {
        converterError = oj.IntlConverterUtils.__getConverterOptionError(errorCode, parameterMap);
      }
    }
    else if (e instanceof Error)
    {
      if (errorCode === "dateFormatMismatch")
      {
        // The '{value}' does not match the expected date format '{format}'.
        resourceKey = "oj-converter.datetime.dateFormatMismatch.summary";
      }
      else if (errorCode === "timeFormatMismatch")
      {
        // The {value} does not match the expected time format {format}.
        resourceKey = "oj-converter.datetime.timeFormatMismatch.summary";
      }
      else if (errorCode === "datetimeFormatMismatch")
      {
        resourceKey = "oj-converter.datetime.datetimeFormatMismatch.summary";
      }
      else if (errorCode === "dateToWeekdayMismatch")
      {
        summary = oj.Translations.getTranslatedString("oj-converter.datetime.dateToWeekdayMismatch.summary", 
          {'date': parameterMap['date'], 'weekday': parameterMap['weekday']});
        detail = oj.Translations.getTranslatedString("oj-converter.datetime.dateToWeekdayMismatch.detail");
        converterError = new oj.ConverterError(summary, detail);
      }
      
      if (resourceKey)
      {
        summary = oj.Translations.getTranslatedString(resourceKey, 
          {'value': value || parameterMap['value'],
           'format': parameterMap['format']});
        
        detail = oj.Translations.getTranslatedString("oj-converter.hint.detail",
          {'exampleValue': this._getHintValue()}); 
          
        converterError = new oj.ConverterError(summary, detail);
      }
    }
  }
  
  if (!converterError)
  {
    // An error we are unfamiliar with. Get the message and set as detail
    summary = e.message; // TODO: What should the summary be when it's missing??
    detail = e.message;
    converterError = new oj.ConverterError(summary, detail);
  }
  
  return converterError;
};

/**
 * Checks to see if an option is present in the resolved options.
 * @param {string} optionName
 * @returns {boolean} true if optionName is present.
 * @private
 */
oj.IntlDateTimeConverter.prototype._isOptionSet = function (optionName)
{
  var ro = this.resolvedOptions(), hasOption = ro[optionName] ? true : false;
  return hasOption;
};