1 /** 2 SC.DateFormatter is used with String.fmt to format dates with a format 3 string. For example: 4 5 "My date: %{date:yyyy} %{date:MM} %{date:dd}".fmt(myDate, SC.DateFormatter) 6 7 You can use almost any of the Unicode Technical Standard #35 (draft 10) 8 formatting strings. See: 9 10 http://unicode.org/reports/tr35/tr35-10.html#Date_Format_Patterns 11 12 Note that you can only put one (yyyy, MM, dd) per token, but you can put as 13 many tokens as you'd like. 14 15 */ 16 SC.DateFormatter = function(date, f) { 17 if (!date) { 18 throw new Error("No date passed to date formatter."); 19 } else if (!date.getFullYear) { 20 throw new Error("Object passed to date formatter was not a date!"); 21 } 22 23 // f is expected to be a character, potentially repeated. 24 // What we do is figure out what letter, and how many times it is repeated. 25 // we also sanity-check. 26 if (!f) { 27 throw new Error("No formatting string passed to date formatter. Date: " + date); 28 } 29 30 var len = f.length, first = f[0], idx; 31 for (idx = 1; idx < len; idx++) { 32 if (f[idx] !== first) { 33 throw new Error("Invalid format string for a date; all characters must be the same: " + f + "; date: " + date); 34 } 35 } 36 37 var formatter = SC.DateFormatter[first]; 38 if (!formatter) { 39 throw new Error("No formatter `" + first + "` exists for date: " + date); 40 } 41 42 return formatter(date, len); 43 }; 44 45 // 46 // Era 47 // 48 SC.DateFormatter.G = function(date, count) { 49 var era = "SC.Date.Era."; 50 era += date.getFullYear() >= 0 ? "AD" : "BC"; 51 52 if (count <= 3) { 53 // Abbreviated era (AD, BC, etc.) 54 return (era + ".Abbreviated").loc(); 55 } else if (count === 4) { 56 return (era + ".Full").loc(); 57 } else if (count === 5) { 58 return (era + ".Letter").loc(); 59 } else { 60 throw new Error("Invalid era format: expected at most 5 digits; found " + count + "."); 61 } 62 }; 63 64 // 65 // Year 66 // 67 SC.DateFormatter.y = function(date, count) { 68 // this is expected to be the year not accounting for AD/BC. 69 // JavaScript stores it as a negative for BC years, so we need to 70 // do a Math.abs() 71 var year = Math.abs(date.getFullYear()).toString(); 72 while (year.length < count) { year = '0' + year; } 73 year = year.substr(year.length - count); 74 return year; 75 }; 76 77 // We only support gregorian calendars, so YYYY would mean the same as yyyy 78 SC.DateFormatter.Y = function(date, count) { 79 return SC.DateFormatter.y(date, count); 80 }; 81 82 // u just doesn't do Math.abs 83 SC.DateFormatter.u = function(date, count) { 84 var lt0 = date.getFullYear() < 0; 85 var year = Math.abs(date.getFullYear()).toString(); 86 87 while (year.length < count) { year = '0' + year; } 88 year = year.substr(year.length - count); 89 90 return (lt0 ? "-" : "") + year; 91 }; 92 93 // 94 // Quarter 95 // 96 // I am not overly sure what "standAlone" is, but I think it may be for those 97 // cases where the month is "standalone" and therefore should be capitalized, 98 // and such... 99 SC.DateFormatter.Q = function(date, count, isStandAlone) { 100 var month = date.getMonth(), 101 quarter = Math.floor(month / 3) + 1, 102 quarterName = "SC.Date.Quarter." + (isStandAlone ? "StandAlone." : "") + 103 "Q" + quarter; 104 105 if (count === 1) { 106 return "" + quarter; 107 } else if (count === 2) { 108 return "0" + quarter; 109 } else if (count === 3) { 110 return (quarterName + ".Abbreviated").loc(); 111 } else if (count == 4) { 112 return (quarterName + ".Full").loc(); 113 } else { 114 throw new Error("Unrecognized number of characters for quarter: " + count); 115 } 116 }; 117 118 SC.DateFormatter.q = function(date, count) { 119 return SC.DateFormatter.Q(date, count, YES); 120 }; 121 122 // 123 // Month 124 // 125 126 // It is a bit easier to translate an english month name to another language 127 // than to translate a number like 0, 1, 2, etc.--especially because you have 128 // no idea, as a translator: are we beginning at 0, or at 1? 129 SC.DateFormatter.ENGLISH_MONTH_NAMES = [ 130 "January", "February", "March", 131 "April", "May", "June", 132 "July", "August", "September", 133 "October", "November", "December" 134 ]; 135 136 SC.DateFormatter.M = function(date, count, isStandAlone) { 137 138 var month = date.getMonth(), 139 monthString = "" + (month + 1), 140 monthName = "SC.Date.Month." + (isStandAlone ? "StandAlone." : "") + 141 SC.DateFormatter.ENGLISH_MONTH_NAMES[month]; 142 143 if (count === 1) { 144 return monthString; 145 } else if (count === 2) { 146 if (monthString.length < 2) monthString = "0" + monthString; 147 return monthString; 148 } else if (count === 3) { 149 return (monthName + ".Abbreviated").loc(); 150 } else if (count === 4) { 151 return (monthName + ".Full").loc(); 152 } else if (count === 5) { 153 return (monthName + ".Letter").loc(); 154 } else { 155 throw new Error("The number of Ms or Ls must be from 1 to 5. Supplied: " + count); 156 } 157 }; 158 159 SC.DateFormatter.L = function(date, count) { 160 return SC.DateFormatter.M(date, count, YES); 161 }; 162 163 // OMITTED: l; used only with Chinese calendar. 164 SC.DateFormatter.l = function() { throw new Error("`l` date formatter not implemented."); }; 165 166 // 167 // Omitted for now: Week Number 168 // 169 SC.DateFormatter.w = function(date, count) { 170 throw new Error("Week number currently not supported for date formatting."); 171 }; 172 173 SC.DateFormatter.W = function(date, count) { 174 throw new Error("Week number currently not supported for date formatting."); 175 }; 176 177 // 178 // Day 179 // 180 SC.DateFormatter.d = function(date, count) { 181 // day of month 182 var dayString = "" + date.getDate(); 183 if (count > dayString.length) dayString = "0" + dayString; 184 return dayString; 185 }; 186 187 SC.DateFormatter.D = function(date, count) { 188 // day of year 189 var firstDayOfYear = new Date(date.getFullYear(), 0, 1), 190 timestamp = firstDayOfYear.getTime(), 191 diff = date.getTime() - timestamp, 192 days = Math.floor(diff / (24 * 60 * 60 * 1000)) + 1; 193 194 days = "" + days; 195 while (days.length < count) days = "0" + days; 196 return days; 197 }; 198 199 SC.DateFormatter.F = function(date, count) { 200 // day of week in month; for instance, 2 if it is the second wednesday in the month. 201 throw new Error("Day of week in month (F) is not supported in date formatting"); 202 }; 203 204 SC.DateFormatter.g = function(date, count) { 205 // Julian day, modified: should be based on local timezone, 206 // and, more than that, local timezone midnight (not noon) 207 throw new Error("Julian day not supported in date formatting."); 208 }; 209 210 // 211 // Week Day 212 // 213 214 // See discussion of using a mapping to english names for months... 215 SC.DateFormatter.ENGLISH_DAY_NAMES = [ 216 // JavaScript starts week on Sunday 217 "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" 218 ]; 219 220 221 SC.DateFormatter.E = function(date, count) { 222 // E is like e, except that it doesn't ever create a day-of-week number, 223 // and therefore, if the count is less than three, it should be coerced 224 // up to three, because it means the same thing (short day) 225 if (count < 3) count = 3; 226 return SC.DateFormatter.e(date, count); 227 }; 228 229 SC.DateFormatter.e = function(date, count, isStandAlone) { 230 var day = date.getDay(), 231 dayString = "" + (day + 1), 232 dayName = "SC.Date.Day." + (isStandAlone ? "StandAlone." : "") + 233 SC.DateFormatter.ENGLISH_DAY_NAMES[day]; 234 235 if (count === 1) { 236 return dayString; 237 } else if (count === 2) { 238 dayString = "0" + dayString; 239 return dayString; 240 } else if (count === 3) { 241 return (dayName + ".Abbreviated").loc(); 242 } else if (count === 4) { 243 return (dayName + ".Full").loc(); 244 } else if (count === 5) { 245 return (dayName + ".Letter").loc(); 246 } else { 247 throw new Error("Unrecognized number of `e`s, `c`s, or `E`s in date format string."); 248 } 249 }; 250 251 SC.DateFormatter.c = function(date, count) { 252 return SC.DateFormatter.e(date, count, YES); 253 }; 254 255 256 257 // 258 // Period 259 // 260 SC.DateFormatter.a = function(date, count) { 261 if (count !== 1) { 262 throw new Error("`a` can only be included in a date format string once."); 263 } 264 265 var name = "SC.Date.Period." + (date.getHours() > 11 ? "PM" : "AM"); 266 return name.loc(); 267 }; 268 269 // 270 // Hour 271 // 272 // upper-case: 0-based. WEIRD PART: upper-case H and lower-case k are 273 // 24 hour. WTF? This makes NO SENSE AT ALL! 274 SC.DateFormatter._h = function(date, count, is24, base) { 275 var hour = date.getHours(); 276 if (!is24) hour = hour % 12; 277 if (base) { 278 if (!is24) { 279 // if hour is 0, then we need to make it 12. 280 if (hour === 0) hour = 12; 281 } else { 282 // if hour is 0, hour needs to be 24 283 if (hour === 0) hour = 24; 284 } 285 } 286 287 var hourStr = "" + hour; 288 if (hourStr.length < count) hourStr = "0" + hourStr; 289 290 return hourStr; 291 }; 292 293 SC.DateFormatter.h = function(date, count) { 294 return SC.DateFormatter._h(date, count, NO, 1); 295 }; 296 297 SC.DateFormatter.H = function(date, count) { 298 return SC.DateFormatter._h(date, count, YES, 0); 299 }; 300 301 SC.DateFormatter.K = function(date, count) { 302 return SC.DateFormatter._h(date, count, NO, 0); 303 }; 304 305 SC.DateFormatter.k = function(date, count) { 306 return SC.DateFormatter._h(date, count, YES, 1); 307 }; 308 309 // 310 // Minute 311 // 312 SC.DateFormatter.m = function(date, count) { 313 var str = "" + date.getMinutes(); 314 if (str.length < count) str = "0" + str; 315 return str; 316 }; 317 318 SC.DateFormatter.s = function(date, count) { 319 var str = "" + date.getSeconds(); 320 if (str.length < count) str = "0" + str; 321 return str; 322 }; 323 324 SC.DateFormatter.S = function(date, count) { 325 var fraction = date.getMilliseconds() / 1000.0, 326 mult = Math.pow(10, count); 327 328 fraction = Math.round(fraction * mult); 329 fraction = "" + fraction; 330 331 while(fraction.length < count) fraction="0" + fraction; 332 return fraction; 333 }; 334 335 SC.DateFormatter.A = function(date, count) { 336 // ms in day... 337 var timestamp = new Date(date.getFullYear(), date.getMonth(), date.getDay()).getTime(); 338 var res = date.getTime() - timestamp; 339 res = "" + res; 340 while (res.length < count) res = "0" + res; 341 return res; 342 }; 343 344 SC.DateFormatter.z = 345 SC.DateFormatter.Z = 346 SC.DateFormatter.v = 347 SC.DateFormatter.V = function(date, count) { 348 throw new Error("Timezone not supported in date format strings."); 349 }; 350 351 352