/**

SC.DateFormatter is used with String.fmt to format dates with a format
string. For example:

    "My date: %{date:yyyy} %{date:MM} %{date:dd}".fmt(myDate, SC.DateFormatter)

You can use almost any of the Unicode Technical Standard #35 (draft 10)
formatting strings.  See:

  http://unicode.org/reports/tr35/tr35-10.html#Date_Format_Patterns

Note that you can only put one (yyyy, MM, dd) per token, but you can put as
many tokens as you'd like.

*/ SC.DateFormatter = function(date, f) {

if (!date) {
  throw new Error("No date passed to date formatter.");
} else if (!date.getFullYear) {
  throw new Error("Object passed to date formatter was not a date!");
}

// f is expected to be a character, potentially repeated.
// What we do is figure out what letter, and how many times it is repeated.
// we also sanity-check.
if (!f) {
  throw new Error("No formatting string passed to date formatter. Date: " + date);
}

var len = f.length, first = f[0], idx;
for (idx = 1; idx < len; idx++) {
  if (f[idx] !== first) {
    throw new Error("Invalid format string for a date; all characters must be the same: " + f + "; date: " + date);
  }
}

var formatter = SC.DateFormatter[first];
if (!formatter) {
  throw new Error("No formatter `" + first + "` exists for date: " + date);
}

return formatter(date, len);

};

// // Era // SC.DateFormatter.G = function(date, count) {

var era = "SC.Date.Era.";
era += date.getFullYear() >= 0 ? "AD" : "BC";

if (count <= 3) {
  // Abbreviated era (AD, BC, etc.)
  return (era + ".Abbreviated").loc();
} else if (count === 4) {
  return (era + ".Full").loc();
} else if (count === 5) {
  return (era + ".Letter").loc();
} else {
  throw new Error("Invalid era format: expected at most 5 digits; found " + count + ".");
}

};

// // Year // SC.DateFormatter.y = function(date, count) {

// this is expected to be the year not accounting for AD/BC.
// JavaScript stores it as a negative for BC years, so we need to
// do a Math.abs()
var year = Math.abs(date.getFullYear()).toString();
while (year.length < count) { year = '0' + year; }
year = year.substr(year.length - count);
return year;

};

// We only support gregorian calendars, so YYYY would mean the same as yyyy SC.DateFormatter.Y = function(date, count) {

return SC.DateFormatter.y(date, count);

};

// u just doesn't do Math.abs SC.DateFormatter.u = function(date, count) {

var lt0 = date.getFullYear() < 0;
var year = Math.abs(date.getFullYear()).toString();

while (year.length < count) { year = '0' + year; }
year = year.substr(year.length - count);

return (lt0 ? "-" : "") + year;

};

// // Quarter // // I am not overly sure what “standAlone” is, but I think it may be for those // cases where the month is “standalone” and therefore should be capitalized, // and such… SC.DateFormatter.Q = function(date, count, isStandAlone) {

var month = date.getMonth(),
    quarter = Math.floor(month / 3) + 1,
    quarterName = "SC.Date.Quarter." + (isStandAlone ? "StandAlone." : "") + 
      "Q" + quarter;

if (count === 1) {
  return "" + quarter;
} else if (count === 2) {
  return "0" + quarter;
} else if (count === 3) {
  return (quarterName + ".Abbreviated").loc();
} else if (count == 4) {
  return (quarterName + ".Full").loc();
} else {
  throw new Error("Unrecognized number of characters for quarter: " + count);
}

};

SC.DateFormatter.q = function(date, count) {

return SC.DateFormatter.Q(date, count, YES);

};

// // Month //

// It is a bit easier to translate an english month name to another language // than to translate a number like 0, 1, 2, etc.–especially because you have // no idea, as a translator: are we beginning at 0, or at 1? SC.DateFormatter.ENGLISH_MONTH_NAMES = [

"January", "February", "March", 
"April", "May", "June",
"July", "August", "September",
"October", "November", "December"

];

SC.DateFormatter.M = function(date, count, isStandAlone) {

var month = date.getMonth(),
    monthString = "" + (month + 1),
    monthName = "SC.Date.Month." + (isStandAlone ? "StandAlone." : "") + 
      SC.DateFormatter.ENGLISH_MONTH_NAMES[month];

if (count === 1) {
  return monthString;
} else if (count === 2) {
  if (monthString.length < 2) monthString = "0" + monthString;
  return monthString;
} else if (count === 3) {
  return (monthName + ".Abbreviated").loc();
} else if (count === 4) {
  return (monthName + ".Full").loc();
} else if (count === 5) {
  return (monthName + ".Letter").loc();
} else {
  throw new Error("The number of Ms or Ls must be from 1 to 5. Supplied: " + count);
}

};

SC.DateFormatter.L = function(date, count) {

return SC.DateFormatter.M(date, count, YES);

};

// OMITTED: l; used only with Chinese calendar. SC.DateFormatter.l = function() { throw new Error(“`l` date formatter not implemented.”); };

// // Omitted for now: Week Number // SC.DateFormatter.w = function(date, count) {

throw new Error("Week number currently not supported for date formatting.");

};

SC.DateFormatter.W = function(date, count) {

throw new Error("Week number currently not supported for date formatting.");

};

// // Day // SC.DateFormatter.d = function(date, count) {

// day of month
var dayString = "" + date.getDate();
if (count > dayString.length) dayString = "0" + dayString;
return dayString;

};

SC.DateFormatter.D = function(date, count) {

// day of year
var firstDayOfYear = new Date(date.getFullYear(), 0, 1),
    timestamp = firstDayOfYear.getTime(),
    diff = date.getTime() - timestamp,
    days = Math.floor(diff / (24 * 60 * 60 * 1000)) + 1;

days = "" + days;
while (days.length < count) days = "0" + days;
return days;

};

SC.DateFormatter.F = function(date, count) {

// day of week in month; for instance, 2 if it is the second wednesday in the month.
throw new Error("Day of week in month (F) is not supported in date formatting");

};

SC.DateFormatter.g = function(date, count) {

// Julian day, modified: should be based on local timezone,
// and, more than that, local timezone midnight (not noon)
throw new Error("Julian day not supported in date formatting.");

};

// // Week Day //

// See discussion of using a mapping to english names for months… SC.DateFormatter.ENGLISH_DAY_NAMES = [

// JavaScript starts week on Sunday
"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"

];

SC.DateFormatter.E = function(date, count) {

// E is like e, except that it doesn't ever create a day-of-week number,
// and therefore, if the count is less than three, it should be coerced
// up to three, because it means the same thing (short day)
if (count < 3) count = 3;
return SC.DateFormatter.e(date, count);

};

SC.DateFormatter.e = function(date, count, isStandAlone) {

var day = date.getDay(),
    dayString = "" + (day + 1),
    dayName = "SC.Date.Day." + (isStandAlone ? "StandAlone." : "") +
      SC.DateFormatter.ENGLISH_DAY_NAMES[day];

if (count === 1) {
  return dayString;
} else if (count === 2) {
  dayString = "0" + dayString;
  return dayString;
} else if (count === 3) {
  return (dayName + ".Abbreviated").loc();
} else if (count === 4) {
  return (dayName + ".Full").loc();
} else if (count === 5) {
  return (dayName + ".Letter").loc();
} else {
  throw new Error("Unrecognized number of `e`s, `c`s, or `E`s in date format string.");
}

};

SC.DateFormatter.c = function(date, count) {

return SC.DateFormatter.e(date, count, YES);

};

// // Period // SC.DateFormatter.a = function(date, count) {

if (count !== 1) {
  throw new Error("`a` can only be included in a date format string once.");
}

var name = "SC.Date.Period." + (date.getHours() > 11 ? "PM" : "AM");
return name.loc();

};

// // Hour // // upper-case: 0-based. WEIRD PART: upper-case H and lower-case k are // 24 hour. WTF? This makes NO SENSE AT ALL! SC.DateFormatter._h = function(date, count, is24, base) {

var hour = date.getHours();
if (!is24) hour = hour % 12;
if (base) {
  if (!is24) {
    // if hour is 0, then we need to make it 12.
    if (hour === 0) hour = 12;
  } else {
    // if hour is 0, hour needs to be 24
    if (hour === 0) hour = 24;
  }
}

var hourStr = "" + hour;
if (hourStr.length < count) hourStr = "0" + hourStr;

return hourStr;

};

SC.DateFormatter.h = function(date, count) {

return SC.DateFormatter._h(date, count, NO, 1);

};

SC.DateFormatter.H = function(date, count) {

return SC.DateFormatter._h(date, count, YES, 0);

};

SC.DateFormatter.K = function(date, count) {

return SC.DateFormatter._h(date, count, NO, 0);

};

SC.DateFormatter.k = function(date, count) {

return SC.DateFormatter._h(date, count, YES, 1);

};

// // Minute // SC.DateFormatter.m = function(date, count) {

var str = "" + date.getMinutes();
if (str.length < count) str = "0" + str;
return str;

};

SC.DateFormatter.s = function(date, count) {

var str = "" + date.getSeconds();
if (str.length < count) str = "0" + str;
return str;

};

SC.DateFormatter.S = function(date, count) {

var fraction = date.getMilliseconds() / 1000.0,
    mult = Math.pow(10, count);

fraction = Math.round(fraction * mult);
fraction = "" + fraction;

while(fraction.length < count) fraction="0" + fraction;
return fraction;

};

SC.DateFormatter.A = function(date, count) {

// ms in day...
var timestamp = new Date(date.getFullYear(), date.getMonth(), date.getDay()).getTime();
var res = date.getTime() - timestamp;
res = "" + res;
while (res.length < count) res = "0" + res;
return res;

};

SC.DateFormatter.z = SC.DateFormatter.Z = SC.DateFormatter.v = SC.DateFormatter.V = function(date, count) {

throw new Error("Timezone not supported in date format strings.");

};