globalize.date.js 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887
  1. /**
  2. * Globalize v1.0.1
  3. *
  4. * http://github.com/jquery/globalize
  5. *
  6. * Copyright jQuery Foundation and other contributors
  7. * Released under the MIT license
  8. * http://jquery.org/license
  9. *
  10. * Date: 2016-01-20T16:57Z
  11. */
  12. /*!
  13. * Globalize v1.0.1 2016-01-20T16:57Z Released under the MIT license
  14. * http://git.io/TrdQbw
  15. */
  16. (function( root, factory ) {
  17. // UMD returnExports
  18. if ( typeof define === "function" && define.amd ) {
  19. // AMD
  20. define([
  21. "cldr",
  22. "../globalize",
  23. "./number",
  24. "cldr/event",
  25. "cldr/supplemental"
  26. ], factory );
  27. } else if ( typeof exports === "object" ) {
  28. // Node, CommonJS
  29. module.exports = factory( require( "cldrjs" ), require( "globalize" ) );
  30. } else {
  31. // Extend global
  32. factory( root.Cldr, root.Globalize );
  33. }
  34. }(this, function( Cldr, Globalize ) {
  35. var createError = Globalize._createError,
  36. createErrorUnsupportedFeature = Globalize._createErrorUnsupportedFeature,
  37. formatMessage = Globalize._formatMessage,
  38. numberSymbol = Globalize._numberSymbol,
  39. regexpEscape = Globalize._regexpEscape,
  40. stringPad = Globalize._stringPad,
  41. validateCldr = Globalize._validateCldr,
  42. validateDefaultLocale = Globalize._validateDefaultLocale,
  43. validateParameterPresence = Globalize._validateParameterPresence,
  44. validateParameterType = Globalize._validateParameterType,
  45. validateParameterTypePlainObject = Globalize._validateParameterTypePlainObject,
  46. validateParameterTypeString = Globalize._validateParameterTypeString;
  47. var validateParameterTypeDate = function( value, name ) {
  48. validateParameterType( value, name, value === undefined || value instanceof Date, "Date" );
  49. };
  50. var createErrorInvalidParameterValue = function( name, value ) {
  51. return createError( "E_INVALID_PAR_VALUE", "Invalid `{name}` value ({value}).", {
  52. name: name,
  53. value: value
  54. });
  55. };
  56. /**
  57. * expandPattern( options, cldr )
  58. *
  59. * @options [Object] if String, it's considered a skeleton. Object accepts:
  60. * - skeleton: [String] lookup availableFormat;
  61. * - date: [String] ( "full" | "long" | "medium" | "short" );
  62. * - time: [String] ( "full" | "long" | "medium" | "short" );
  63. * - datetime: [String] ( "full" | "long" | "medium" | "short" );
  64. * - raw: [String] For more info see datetime/format.js.
  65. *
  66. * @cldr [Cldr instance].
  67. *
  68. * Return the corresponding pattern.
  69. * Eg for "en":
  70. * - "GyMMMd" returns "MMM d, y G";
  71. * - { skeleton: "GyMMMd" } returns "MMM d, y G";
  72. * - { date: "full" } returns "EEEE, MMMM d, y";
  73. * - { time: "full" } returns "h:mm:ss a zzzz";
  74. * - { datetime: "full" } returns "EEEE, MMMM d, y 'at' h:mm:ss a zzzz";
  75. * - { raw: "dd/mm" } returns "dd/mm";
  76. */
  77. var dateExpandPattern = function( options, cldr ) {
  78. var dateSkeleton, result, skeleton, timeSkeleton, type;
  79. function combineDateTime( type, datePattern, timePattern ) {
  80. return formatMessage(
  81. cldr.main([
  82. "dates/calendars/gregorian/dateTimeFormats",
  83. type
  84. ]),
  85. [ timePattern, datePattern ]
  86. );
  87. }
  88. switch ( true ) {
  89. case "skeleton" in options:
  90. skeleton = options.skeleton;
  91. result = cldr.main([
  92. "dates/calendars/gregorian/dateTimeFormats/availableFormats",
  93. skeleton
  94. ]);
  95. if ( !result ) {
  96. timeSkeleton = skeleton.split( /[^hHKkmsSAzZOvVXx]/ ).slice( -1 )[ 0 ];
  97. dateSkeleton = skeleton.split( /[^GyYuUrQqMLlwWdDFgEec]/ )[ 0 ];
  98. if ( /(MMMM|LLLL).*[Ec]/.test( dateSkeleton ) ) {
  99. type = "full";
  100. } else if ( /MMMM/g.test( dateSkeleton ) ) {
  101. type = "long";
  102. } else if ( /MMM/g.test( dateSkeleton ) || /LLL/g.test( dateSkeleton ) ) {
  103. type = "medium";
  104. } else {
  105. type = "short";
  106. }
  107. result = combineDateTime( type,
  108. cldr.main([
  109. "dates/calendars/gregorian/dateTimeFormats/availableFormats",
  110. dateSkeleton
  111. ]),
  112. cldr.main([
  113. "dates/calendars/gregorian/dateTimeFormats/availableFormats",
  114. timeSkeleton
  115. ])
  116. );
  117. }
  118. break;
  119. case "date" in options:
  120. case "time" in options:
  121. result = cldr.main([
  122. "dates/calendars/gregorian",
  123. "date" in options ? "dateFormats" : "timeFormats",
  124. ( options.date || options.time )
  125. ]);
  126. break;
  127. case "datetime" in options:
  128. result = combineDateTime( options.datetime,
  129. cldr.main([ "dates/calendars/gregorian/dateFormats", options.datetime ]),
  130. cldr.main([ "dates/calendars/gregorian/timeFormats", options.datetime ])
  131. );
  132. break;
  133. case "raw" in options:
  134. result = options.raw;
  135. break;
  136. default:
  137. throw createErrorInvalidParameterValue({
  138. name: "options",
  139. value: options
  140. });
  141. }
  142. return result;
  143. };
  144. /**
  145. * dayOfWeek( date, firstDay )
  146. *
  147. * @date
  148. *
  149. * @firstDay the result of `dateFirstDayOfWeek( cldr )`
  150. *
  151. * Return the day of the week normalized by the territory's firstDay [0-6].
  152. * Eg for "mon":
  153. * - return 0 if territory is GB, or BR, or DE, or FR (week starts on "mon");
  154. * - return 1 if territory is US (week starts on "sun");
  155. * - return 2 if territory is EG (week starts on "sat");
  156. */
  157. var dateDayOfWeek = function( date, firstDay ) {
  158. return ( date.getDay() - firstDay + 7 ) % 7;
  159. };
  160. /**
  161. * distanceInDays( from, to )
  162. *
  163. * Return the distance in days between from and to Dates.
  164. */
  165. var dateDistanceInDays = function( from, to ) {
  166. var inDays = 864e5;
  167. return ( to.getTime() - from.getTime() ) / inDays;
  168. };
  169. /**
  170. * startOf changes the input to the beginning of the given unit.
  171. *
  172. * For example, starting at the start of a day, resets hours, minutes
  173. * seconds and milliseconds to 0. Starting at the month does the same, but
  174. * also sets the date to 1.
  175. *
  176. * Returns the modified date
  177. */
  178. var dateStartOf = function( date, unit ) {
  179. date = new Date( date.getTime() );
  180. switch ( unit ) {
  181. case "year":
  182. date.setMonth( 0 );
  183. /* falls through */
  184. case "month":
  185. date.setDate( 1 );
  186. /* falls through */
  187. case "day":
  188. date.setHours( 0 );
  189. /* falls through */
  190. case "hour":
  191. date.setMinutes( 0 );
  192. /* falls through */
  193. case "minute":
  194. date.setSeconds( 0 );
  195. /* falls through */
  196. case "second":
  197. date.setMilliseconds( 0 );
  198. }
  199. return date;
  200. };
  201. /**
  202. * dayOfYear
  203. *
  204. * Return the distance in days of the date to the begin of the year [0-d].
  205. */
  206. var dateDayOfYear = function( date ) {
  207. return Math.floor( dateDistanceInDays( dateStartOf( date, "year" ), date ) );
  208. };
  209. var dateWeekDays = [ "sun", "mon", "tue", "wed", "thu", "fri", "sat" ];
  210. /**
  211. * firstDayOfWeek
  212. */
  213. var dateFirstDayOfWeek = function( cldr ) {
  214. return dateWeekDays.indexOf( cldr.supplemental.weekData.firstDay() );
  215. };
  216. /**
  217. * millisecondsInDay
  218. */
  219. var dateMillisecondsInDay = function( date ) {
  220. // TODO Handle daylight savings discontinuities
  221. return date - dateStartOf( date, "day" );
  222. };
  223. var datePatternRe = ( /([a-z])\1*|'([^']|'')+'|''|./ig );
  224. /**
  225. * hourFormat( date, format, timeSeparator, formatNumber )
  226. *
  227. * Return date's timezone offset according to the format passed.
  228. * Eg for format when timezone offset is 180:
  229. * - "+H;-H": -3
  230. * - "+HHmm;-HHmm": -0300
  231. * - "+HH:mm;-HH:mm": -03:00
  232. */
  233. var dateTimezoneHourFormat = function( date, format, timeSeparator, formatNumber ) {
  234. var absOffset,
  235. offset = date.getTimezoneOffset();
  236. absOffset = Math.abs( offset );
  237. formatNumber = formatNumber || {
  238. 1: function( value ) {
  239. return stringPad( value, 1 );
  240. },
  241. 2: function( value ) {
  242. return stringPad( value, 2 );
  243. }
  244. };
  245. return format
  246. // Pick the correct sign side (+ or -).
  247. .split( ";" )[ offset > 0 ? 1 : 0 ]
  248. // Localize time separator
  249. .replace( ":", timeSeparator )
  250. // Update hours offset.
  251. .replace( /HH?/, function( match ) {
  252. return formatNumber[ match.length ]( Math.floor( absOffset / 60 ) );
  253. })
  254. // Update minutes offset and return.
  255. .replace( /mm/, function() {
  256. return formatNumber[ 2 ]( absOffset % 60 );
  257. });
  258. };
  259. /**
  260. * format( date, properties )
  261. *
  262. * @date [Date instance].
  263. *
  264. * @properties
  265. *
  266. * TODO Support other calendar types.
  267. *
  268. * Disclosure: this function borrows excerpts of dojo/date/locale.
  269. */
  270. var dateFormat = function( date, numberFormatters, properties ) {
  271. var timeSeparator = properties.timeSeparator;
  272. return properties.pattern.replace( datePatternRe, function( current ) {
  273. var ret,
  274. chr = current.charAt( 0 ),
  275. length = current.length;
  276. if ( chr === "j" ) {
  277. // Locale preferred hHKk.
  278. // http://www.unicode.org/reports/tr35/tr35-dates.html#Time_Data
  279. chr = properties.preferredTime;
  280. }
  281. if ( chr === "Z" ) {
  282. // Z..ZZZ: same as "xxxx".
  283. if ( length < 4 ) {
  284. chr = "x";
  285. length = 4;
  286. // ZZZZ: same as "OOOO".
  287. } else if ( length < 5 ) {
  288. chr = "O";
  289. length = 4;
  290. // ZZZZZ: same as "XXXXX"
  291. } else {
  292. chr = "X";
  293. length = 5;
  294. }
  295. }
  296. switch ( chr ) {
  297. // Era
  298. case "G":
  299. ret = properties.eras[ date.getFullYear() < 0 ? 0 : 1 ];
  300. break;
  301. // Year
  302. case "y":
  303. // Plain year.
  304. // The length specifies the padding, but for two letters it also specifies the
  305. // maximum length.
  306. ret = date.getFullYear();
  307. if ( length === 2 ) {
  308. ret = String( ret );
  309. ret = +ret.substr( ret.length - 2 );
  310. }
  311. break;
  312. case "Y":
  313. // Year in "Week of Year"
  314. // The length specifies the padding, but for two letters it also specifies the
  315. // maximum length.
  316. // yearInWeekofYear = date + DaysInAWeek - (dayOfWeek - firstDay) - minDays
  317. ret = new Date( date.getTime() );
  318. ret.setDate(
  319. ret.getDate() + 7 -
  320. dateDayOfWeek( date, properties.firstDay ) -
  321. properties.firstDay -
  322. properties.minDays
  323. );
  324. ret = ret.getFullYear();
  325. if ( length === 2 ) {
  326. ret = String( ret );
  327. ret = +ret.substr( ret.length - 2 );
  328. }
  329. break;
  330. // Quarter
  331. case "Q":
  332. case "q":
  333. ret = Math.ceil( ( date.getMonth() + 1 ) / 3 );
  334. if ( length > 2 ) {
  335. ret = properties.quarters[ chr ][ length ][ ret ];
  336. }
  337. break;
  338. // Month
  339. case "M":
  340. case "L":
  341. ret = date.getMonth() + 1;
  342. if ( length > 2 ) {
  343. ret = properties.months[ chr ][ length ][ ret ];
  344. }
  345. break;
  346. // Week
  347. case "w":
  348. // Week of Year.
  349. // woy = ceil( ( doy + dow of 1/1 ) / 7 ) - minDaysStuff ? 1 : 0.
  350. // TODO should pad on ww? Not documented, but I guess so.
  351. ret = dateDayOfWeek( dateStartOf( date, "year" ), properties.firstDay );
  352. ret = Math.ceil( ( dateDayOfYear( date ) + ret ) / 7 ) -
  353. ( 7 - ret >= properties.minDays ? 0 : 1 );
  354. break;
  355. case "W":
  356. // Week of Month.
  357. // wom = ceil( ( dom + dow of `1/month` ) / 7 ) - minDaysStuff ? 1 : 0.
  358. ret = dateDayOfWeek( dateStartOf( date, "month" ), properties.firstDay );
  359. ret = Math.ceil( ( date.getDate() + ret ) / 7 ) -
  360. ( 7 - ret >= properties.minDays ? 0 : 1 );
  361. break;
  362. // Day
  363. case "d":
  364. ret = date.getDate();
  365. break;
  366. case "D":
  367. ret = dateDayOfYear( date ) + 1;
  368. break;
  369. case "F":
  370. // Day of Week in month. eg. 2nd Wed in July.
  371. ret = Math.floor( date.getDate() / 7 ) + 1;
  372. break;
  373. // Week day
  374. case "e":
  375. case "c":
  376. if ( length <= 2 ) {
  377. // Range is [1-7] (deduced by example provided on documentation)
  378. // TODO Should pad with zeros (not specified in the docs)?
  379. ret = dateDayOfWeek( date, properties.firstDay ) + 1;
  380. break;
  381. }
  382. /* falls through */
  383. case "E":
  384. ret = dateWeekDays[ date.getDay() ];
  385. ret = properties.days[ chr ][ length ][ ret ];
  386. break;
  387. // Period (AM or PM)
  388. case "a":
  389. ret = properties.dayPeriods[ date.getHours() < 12 ? "am" : "pm" ];
  390. break;
  391. // Hour
  392. case "h": // 1-12
  393. ret = ( date.getHours() % 12 ) || 12;
  394. break;
  395. case "H": // 0-23
  396. ret = date.getHours();
  397. break;
  398. case "K": // 0-11
  399. ret = date.getHours() % 12;
  400. break;
  401. case "k": // 1-24
  402. ret = date.getHours() || 24;
  403. break;
  404. // Minute
  405. case "m":
  406. ret = date.getMinutes();
  407. break;
  408. // Second
  409. case "s":
  410. ret = date.getSeconds();
  411. break;
  412. case "S":
  413. ret = Math.round( date.getMilliseconds() * Math.pow( 10, length - 3 ) );
  414. break;
  415. case "A":
  416. ret = Math.round( dateMillisecondsInDay( date ) * Math.pow( 10, length - 3 ) );
  417. break;
  418. // Zone
  419. case "z":
  420. case "O":
  421. // O: "{gmtFormat}+H;{gmtFormat}-H" or "{gmtZeroFormat}", eg. "GMT-8" or "GMT".
  422. // OOOO: "{gmtFormat}{hourFormat}" or "{gmtZeroFormat}", eg. "GMT-08:00" or "GMT".
  423. if ( date.getTimezoneOffset() === 0 ) {
  424. ret = properties.gmtZeroFormat;
  425. } else {
  426. ret = dateTimezoneHourFormat(
  427. date,
  428. length < 4 ? "+H;-H" : properties.tzLongHourFormat,
  429. timeSeparator,
  430. numberFormatters
  431. );
  432. ret = properties.gmtFormat.replace( /\{0\}/, ret );
  433. }
  434. break;
  435. case "X":
  436. // Same as x*, except it uses "Z" for zero offset.
  437. if ( date.getTimezoneOffset() === 0 ) {
  438. ret = "Z";
  439. break;
  440. }
  441. /* falls through */
  442. case "x":
  443. // x: hourFormat("+HH;-HH")
  444. // xx or xxxx: hourFormat("+HHmm;-HHmm")
  445. // xxx or xxxxx: hourFormat("+HH:mm;-HH:mm")
  446. ret = length === 1 ? "+HH;-HH" : ( length % 2 ? "+HH:mm;-HH:mm" : "+HHmm;-HHmm" );
  447. ret = dateTimezoneHourFormat( date, ret, ":" );
  448. break;
  449. // timeSeparator
  450. case ":":
  451. ret = timeSeparator;
  452. break;
  453. // ' literals.
  454. case "'":
  455. current = current.replace( /''/, "'" );
  456. if ( length > 2 ) {
  457. current = current.slice( 1, -1 );
  458. }
  459. ret = current;
  460. break;
  461. // Anything else is considered a literal, including [ ,:/.@#], chinese, japonese, and
  462. // arabic characters.
  463. default:
  464. ret = current;
  465. }
  466. if ( typeof ret === "number" ) {
  467. ret = numberFormatters[ length ]( ret );
  468. }
  469. return ret;
  470. });
  471. };
  472. /**
  473. * properties( pattern, cldr )
  474. *
  475. * @pattern [String] raw pattern.
  476. * ref: http://www.unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
  477. *
  478. * @cldr [Cldr instance].
  479. *
  480. * Return the properties given the pattern and cldr.
  481. *
  482. * TODO Support other calendar types.
  483. */
  484. var dateFormatProperties = function( pattern, cldr ) {
  485. var properties = {
  486. numberFormatters: {},
  487. pattern: pattern,
  488. timeSeparator: numberSymbol( "timeSeparator", cldr )
  489. },
  490. widths = [ "abbreviated", "wide", "narrow" ];
  491. function setNumberFormatterPattern( pad ) {
  492. properties.numberFormatters[ pad ] = stringPad( "", pad );
  493. }
  494. pattern.replace( datePatternRe, function( current ) {
  495. var formatNumber,
  496. chr = current.charAt( 0 ),
  497. length = current.length;
  498. if ( chr === "j" ) {
  499. // Locale preferred hHKk.
  500. // http://www.unicode.org/reports/tr35/tr35-dates.html#Time_Data
  501. properties.preferredTime = chr = cldr.supplemental.timeData.preferred();
  502. }
  503. // ZZZZ: same as "OOOO".
  504. if ( chr === "Z" && length === 4 ) {
  505. chr = "O";
  506. length = 4;
  507. }
  508. switch ( chr ) {
  509. // Era
  510. case "G":
  511. properties.eras = cldr.main([
  512. "dates/calendars/gregorian/eras",
  513. length <= 3 ? "eraAbbr" : ( length === 4 ? "eraNames" : "eraNarrow" )
  514. ]);
  515. break;
  516. // Year
  517. case "y":
  518. // Plain year.
  519. formatNumber = true;
  520. break;
  521. case "Y":
  522. // Year in "Week of Year"
  523. properties.firstDay = dateFirstDayOfWeek( cldr );
  524. properties.minDays = cldr.supplemental.weekData.minDays();
  525. formatNumber = true;
  526. break;
  527. case "u": // Extended year. Need to be implemented.
  528. case "U": // Cyclic year name. Need to be implemented.
  529. throw createErrorUnsupportedFeature({
  530. feature: "year pattern `" + chr + "`"
  531. });
  532. // Quarter
  533. case "Q":
  534. case "q":
  535. if ( length > 2 ) {
  536. if ( !properties.quarters ) {
  537. properties.quarters = {};
  538. }
  539. if ( !properties.quarters[ chr ] ) {
  540. properties.quarters[ chr ] = {};
  541. }
  542. properties.quarters[ chr ][ length ] = cldr.main([
  543. "dates/calendars/gregorian/quarters",
  544. chr === "Q" ? "format" : "stand-alone",
  545. widths[ length - 3 ]
  546. ]);
  547. } else {
  548. formatNumber = true;
  549. }
  550. break;
  551. // Month
  552. case "M":
  553. case "L":
  554. if ( length > 2 ) {
  555. if ( !properties.months ) {
  556. properties.months = {};
  557. }
  558. if ( !properties.months[ chr ] ) {
  559. properties.months[ chr ] = {};
  560. }
  561. properties.months[ chr ][ length ] = cldr.main([
  562. "dates/calendars/gregorian/months",
  563. chr === "M" ? "format" : "stand-alone",
  564. widths[ length - 3 ]
  565. ]);
  566. } else {
  567. formatNumber = true;
  568. }
  569. break;
  570. // Week - Week of Year (w) or Week of Month (W).
  571. case "w":
  572. case "W":
  573. properties.firstDay = dateFirstDayOfWeek( cldr );
  574. properties.minDays = cldr.supplemental.weekData.minDays();
  575. formatNumber = true;
  576. break;
  577. // Day
  578. case "d":
  579. case "D":
  580. case "F":
  581. formatNumber = true;
  582. break;
  583. case "g":
  584. // Modified Julian day. Need to be implemented.
  585. throw createErrorUnsupportedFeature({
  586. feature: "Julian day pattern `g`"
  587. });
  588. // Week day
  589. case "e":
  590. case "c":
  591. if ( length <= 2 ) {
  592. properties.firstDay = dateFirstDayOfWeek( cldr );
  593. formatNumber = true;
  594. break;
  595. }
  596. /* falls through */
  597. case "E":
  598. if ( !properties.days ) {
  599. properties.days = {};
  600. }
  601. if ( !properties.days[ chr ] ) {
  602. properties.days[ chr ] = {};
  603. }
  604. if ( length === 6 ) {
  605. // If short day names are not explicitly specified, abbreviated day names are
  606. // used instead.
  607. // http://www.unicode.org/reports/tr35/tr35-dates.html#months_days_quarters_eras
  608. // http://unicode.org/cldr/trac/ticket/6790
  609. properties.days[ chr ][ length ] = cldr.main([
  610. "dates/calendars/gregorian/days",
  611. chr === "c" ? "stand-alone" : "format",
  612. "short"
  613. ]) || cldr.main([
  614. "dates/calendars/gregorian/days",
  615. chr === "c" ? "stand-alone" : "format",
  616. "abbreviated"
  617. ]);
  618. } else {
  619. properties.days[ chr ][ length ] = cldr.main([
  620. "dates/calendars/gregorian/days",
  621. chr === "c" ? "stand-alone" : "format",
  622. widths[ length < 3 ? 0 : length - 3 ]
  623. ]);
  624. }
  625. break;
  626. // Period (AM or PM)
  627. case "a":
  628. properties.dayPeriods = cldr.main(
  629. "dates/calendars/gregorian/dayPeriods/format/wide"
  630. );
  631. break;
  632. // Hour
  633. case "h": // 1-12
  634. case "H": // 0-23
  635. case "K": // 0-11
  636. case "k": // 1-24
  637. // Minute
  638. case "m":
  639. // Second
  640. case "s":
  641. case "S":
  642. case "A":
  643. formatNumber = true;
  644. break;
  645. // Zone
  646. case "z":
  647. case "O":
  648. // O: "{gmtFormat}+H;{gmtFormat}-H" or "{gmtZeroFormat}", eg. "GMT-8" or "GMT".
  649. // OOOO: "{gmtFormat}{hourFormat}" or "{gmtZeroFormat}", eg. "GMT-08:00" or "GMT".
  650. properties.gmtFormat = cldr.main( "dates/timeZoneNames/gmtFormat" );
  651. properties.gmtZeroFormat = cldr.main( "dates/timeZoneNames/gmtZeroFormat" );
  652. properties.tzLongHourFormat = cldr.main( "dates/timeZoneNames/hourFormat" );
  653. /* falls through */
  654. case "Z":
  655. case "X":
  656. case "x":
  657. setNumberFormatterPattern( 1 );
  658. setNumberFormatterPattern( 2 );
  659. break;
  660. case "v":
  661. case "V":
  662. throw createErrorUnsupportedFeature({
  663. feature: "timezone pattern `" + chr + "`"
  664. });
  665. }
  666. if ( formatNumber ) {
  667. setNumberFormatterPattern( length );
  668. }
  669. });
  670. return properties;
  671. };
  672. /**
  673. * isLeapYear( year )
  674. *
  675. * @year [Number]
  676. *
  677. * Returns an indication whether the specified year is a leap year.
  678. */
  679. var dateIsLeapYear = function( year ) {
  680. return new Date( year, 1, 29 ).getMonth() === 1;
  681. };
  682. /**
  683. * lastDayOfMonth( date )
  684. *
  685. * @date [Date]
  686. *
  687. * Return the last day of the given date's month
  688. */
  689. var dateLastDayOfMonth = function( date ) {
  690. return new Date( date.getFullYear(), date.getMonth() + 1, 0 ).getDate();
  691. };
  692. /**
  693. * Differently from native date.setDate(), this function returns a date whose
  694. * day remains inside the month boundaries. For example:
  695. *
  696. * setDate( FebDate, 31 ): a "Feb 28" date.
  697. * setDate( SepDate, 31 ): a "Sep 30" date.
  698. */
  699. var dateSetDate = function( date, day ) {
  700. var lastDay = new Date( date.getFullYear(), date.getMonth() + 1, 0 ).getDate();
  701. date.setDate( day < 1 ? 1 : day < lastDay ? day : lastDay );
  702. };
  703. /**
  704. * Differently from native date.setMonth(), this function adjusts date if
  705. * needed, so final month is always the one set.
  706. *
  707. * setMonth( Jan31Date, 1 ): a "Feb 28" date.
  708. * setDate( Jan31Date, 8 ): a "Sep 30" date.
  709. */
  710. var dateSetMonth = function( date, month ) {
  711. var originalDate = date.getDate();
  712. date.setDate( 1 );
  713. date.setMonth( month );
  714. dateSetDate( date, originalDate );
  715. };
  716. var outOfRange = function( value, low, high ) {
  717. return value < low || value > high;
  718. };
  719. /**
  720. * parse( value, tokens, properties )
  721. *
  722. * @value [String] string date.
  723. *
  724. * @tokens [Object] tokens returned by date/tokenizer.
  725. *
  726. * @properties [Object] output returned by date/tokenizer-properties.
  727. *
  728. * ref: http://www.unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
  729. */
  730. var dateParse = function( value, tokens, properties ) {
  731. var amPm, day, daysOfYear, era, hour, hour12, timezoneOffset, valid,
  732. YEAR = 0,
  733. MONTH = 1,
  734. DAY = 2,
  735. HOUR = 3,
  736. MINUTE = 4,
  737. SECOND = 5,
  738. MILLISECONDS = 6,
  739. date = new Date(),
  740. truncateAt = [],
  741. units = [ "year", "month", "day", "hour", "minute", "second", "milliseconds" ];
  742. if ( !tokens.length ) {
  743. return null;
  744. }
  745. valid = tokens.every(function( token ) {
  746. var century, chr, value, length;
  747. if ( token.type === "literal" ) {
  748. // continue
  749. return true;
  750. }
  751. chr = token.type.charAt( 0 );
  752. length = token.type.length;
  753. if ( chr === "j" ) {
  754. // Locale preferred hHKk.
  755. // http://www.unicode.org/reports/tr35/tr35-dates.html#Time_Data
  756. chr = properties.preferredTimeData;
  757. }
  758. switch ( chr ) {
  759. // Era
  760. case "G":
  761. truncateAt.push( YEAR );
  762. era = +token.value;
  763. break;
  764. // Year
  765. case "y":
  766. value = token.value;
  767. if ( length === 2 ) {
  768. if ( outOfRange( value, 0, 99 ) ) {
  769. return false;
  770. }
  771. // mimic dojo/date/locale: choose century to apply, according to a sliding
  772. // window of 80 years before and 20 years after present year.
  773. century = Math.floor( date.getFullYear() / 100 ) * 100;
  774. value += century;
  775. if ( value > date.getFullYear() + 20 ) {
  776. value -= 100;
  777. }
  778. }
  779. date.setFullYear( value );
  780. truncateAt.push( YEAR );
  781. break;
  782. case "Y": // Year in "Week of Year"
  783. throw createErrorUnsupportedFeature({
  784. feature: "year pattern `" + chr + "`"
  785. });
  786. // Quarter (skip)
  787. case "Q":
  788. case "q":
  789. break;
  790. // Month
  791. case "M":
  792. case "L":
  793. if ( length <= 2 ) {
  794. value = token.value;
  795. } else {
  796. value = +token.value;
  797. }
  798. if ( outOfRange( value, 1, 12 ) ) {
  799. return false;
  800. }
  801. dateSetMonth( date, value - 1 );
  802. truncateAt.push( MONTH );
  803. break;
  804. // Week (skip)
  805. case "w": // Week of Year.
  806. case "W": // Week of Month.
  807. break;
  808. // Day
  809. case "d":
  810. day = token.value;
  811. truncateAt.push( DAY );
  812. break;
  813. case "D":
  814. daysOfYear = token.value;
  815. truncateAt.push( DAY );
  816. break;
  817. case "F":
  818. // Day of Week in month. eg. 2nd Wed in July.
  819. // Skip
  820. break;
  821. // Week day
  822. case "e":
  823. case "c":
  824. case "E":
  825. // Skip.
  826. // value = arrayIndexOf( dateWeekDays, token.value );
  827. break;
  828. // Period (AM or PM)
  829. case "a":
  830. amPm = token.value;
  831. break;
  832. // Hour
  833. case "h": // 1-12
  834. value = token.value;
  835. if ( outOfRange( value, 1, 12 ) ) {
  836. return false;
  837. }
  838. hour = hour12 = true;
  839. date.setHours( value === 12 ? 0 : value );
  840. truncateAt.push( HOUR );
  841. break;
  842. case "K": // 0-11
  843. value = token.value;
  844. if ( outOfRange( value, 0, 11 ) ) {
  845. return false;
  846. }
  847. hour = hour12 = true;
  848. date.setHours( value );
  849. truncateAt.push( HOUR );
  850. break;
  851. case "k": // 1-24
  852. value = token.value;
  853. if ( outOfRange( value, 1, 24 ) ) {
  854. return false;
  855. }
  856. hour = true;
  857. date.setHours( value === 24 ? 0 : value );
  858. truncateAt.push( HOUR );
  859. break;
  860. case "H": // 0-23
  861. value = token.value;
  862. if ( outOfRange( value, 0, 23 ) ) {
  863. return false;
  864. }
  865. hour = true;
  866. date.setHours( value );
  867. truncateAt.push( HOUR );
  868. break;
  869. // Minute
  870. case "m":
  871. value = token.value;
  872. if ( outOfRange( value, 0, 59 ) ) {
  873. return false;
  874. }
  875. date.setMinutes( value );
  876. truncateAt.push( MINUTE );
  877. break;
  878. // Second
  879. case "s":
  880. value = token.value;
  881. if ( outOfRange( value, 0, 59 ) ) {
  882. return false;
  883. }
  884. date.setSeconds( value );
  885. truncateAt.push( SECOND );
  886. break;
  887. case "A":
  888. date.setHours( 0 );
  889. date.setMinutes( 0 );
  890. date.setSeconds( 0 );
  891. /* falls through */
  892. case "S":
  893. value = Math.round( token.value * Math.pow( 10, 3 - length ) );
  894. date.setMilliseconds( value );
  895. truncateAt.push( MILLISECONDS );
  896. break;
  897. // Zone
  898. case "Z":
  899. case "z":
  900. case "O":
  901. case "X":
  902. case "x":
  903. timezoneOffset = token.value - date.getTimezoneOffset();
  904. break;
  905. }
  906. return true;
  907. });
  908. if ( !valid ) {
  909. return null;
  910. }
  911. // 12-hour format needs AM or PM, 24-hour format doesn't, ie. return null
  912. // if amPm && !hour12 || !amPm && hour12.
  913. if ( hour && !( !amPm ^ hour12 ) ) {
  914. return null;
  915. }
  916. if ( era === 0 ) {
  917. // 1 BC = year 0
  918. date.setFullYear( date.getFullYear() * -1 + 1 );
  919. }
  920. if ( day !== undefined ) {
  921. if ( outOfRange( day, 1, dateLastDayOfMonth( date ) ) ) {
  922. return null;
  923. }
  924. date.setDate( day );
  925. } else if ( daysOfYear !== undefined ) {
  926. if ( outOfRange( daysOfYear, 1, dateIsLeapYear( date.getFullYear() ) ? 366 : 365 ) ) {
  927. return null;
  928. }
  929. date.setMonth( 0 );
  930. date.setDate( daysOfYear );
  931. }
  932. if ( hour12 && amPm === "pm" ) {
  933. date.setHours( date.getHours() + 12 );
  934. }
  935. if ( timezoneOffset ) {
  936. date.setMinutes( date.getMinutes() + timezoneOffset );
  937. }
  938. // Truncate date at the most precise unit defined. Eg.
  939. // If value is "12/31", and pattern is "MM/dd":
  940. // => new Date( <current Year>, 12, 31, 0, 0, 0, 0 );
  941. truncateAt = Math.max.apply( null, truncateAt );
  942. date = dateStartOf( date, units[ truncateAt ] );
  943. return date;
  944. };
  945. /**
  946. * parseProperties( cldr )
  947. *
  948. * @cldr [Cldr instance].
  949. *
  950. * Return parser properties.
  951. */
  952. var dateParseProperties = function( cldr ) {
  953. return {
  954. preferredTimeData: cldr.supplemental.timeData.preferred()
  955. };
  956. };
  957. /**
  958. * Generated by:
  959. *
  960. * regenerate().add( require( "unicode-7.0.0/categories/N/symbols" ) ).toString();
  961. *
  962. * https://github.com/mathiasbynens/regenerate
  963. * https://github.com/mathiasbynens/unicode-7.0.0
  964. */
  965. var regexpN = /[0-9\xB2\xB3\xB9\xBC-\xBE\u0660-\u0669\u06F0-\u06F9\u07C0-\u07C9\u0966-\u096F\u09E6-\u09EF\u09F4-\u09F9\u0A66-\u0A6F\u0AE6-\u0AEF\u0B66-\u0B6F\u0B72-\u0B77\u0BE6-\u0BF2\u0C66-\u0C6F\u0C78-\u0C7E\u0CE6-\u0CEF\u0D66-\u0D75\u0DE6-\u0DEF\u0E50-\u0E59\u0ED0-\u0ED9\u0F20-\u0F33\u1040-\u1049\u1090-\u1099\u1369-\u137C\u16EE-\u16F0\u17E0-\u17E9\u17F0-\u17F9\u1810-\u1819\u1946-\u194F\u19D0-\u19DA\u1A80-\u1A89\u1A90-\u1A99\u1B50-\u1B59\u1BB0-\u1BB9\u1C40-\u1C49\u1C50-\u1C59\u2070\u2074-\u2079\u2080-\u2089\u2150-\u2182\u2185-\u2189\u2460-\u249B\u24EA-\u24FF\u2776-\u2793\u2CFD\u3007\u3021-\u3029\u3038-\u303A\u3192-\u3195\u3220-\u3229\u3248-\u324F\u3251-\u325F\u3280-\u3289\u32B1-\u32BF\uA620-\uA629\uA6E6-\uA6EF\uA830-\uA835\uA8D0-\uA8D9\uA900-\uA909\uA9D0-\uA9D9\uA9F0-\uA9F9\uAA50-\uAA59\uABF0-\uABF9\uFF10-\uFF19]|\uD800[\uDD07-\uDD33\uDD40-\uDD78\uDD8A\uDD8B\uDEE1-\uDEFB\uDF20-\uDF23\uDF41\uDF4A\uDFD1-\uDFD5]|\uD801[\uDCA0-\uDCA9]|\uD802[\uDC58-\uDC5F\uDC79-\uDC7F\uDCA7-\uDCAF\uDD16-\uDD1B\uDE40-\uDE47\uDE7D\uDE7E\uDE9D-\uDE9F\uDEEB-\uDEEF\uDF58-\uDF5F\uDF78-\uDF7F\uDFA9-\uDFAF]|\uD803[\uDE60-\uDE7E]|\uD804[\uDC52-\uDC6F\uDCF0-\uDCF9\uDD36-\uDD3F\uDDD0-\uDDD9\uDDE1-\uDDF4\uDEF0-\uDEF9]|\uD805[\uDCD0-\uDCD9\uDE50-\uDE59\uDEC0-\uDEC9]|\uD806[\uDCE0-\uDCF2]|\uD809[\uDC00-\uDC6E]|\uD81A[\uDE60-\uDE69\uDF50-\uDF59\uDF5B-\uDF61]|\uD834[\uDF60-\uDF71]|\uD835[\uDFCE-\uDFFF]|\uD83A[\uDCC7-\uDCCF]|\uD83C[\uDD00-\uDD0C]/;
  966. /**
  967. * tokenizer( value, pattern, properties )
  968. *
  969. * @value [String] string date.
  970. *
  971. * @properties [Object] output returned by date/tokenizer-properties.
  972. *
  973. * Returns an Array of tokens, eg. value "5 o'clock PM", pattern "h 'o''clock' a":
  974. * [{
  975. * type: "h",
  976. * lexeme: "5"
  977. * }, {
  978. * type: "literal",
  979. * lexeme: " "
  980. * }, {
  981. * type: "literal",
  982. * lexeme: "o'clock"
  983. * }, {
  984. * type: "literal",
  985. * lexeme: " "
  986. * }, {
  987. * type: "a",
  988. * lexeme: "PM",
  989. * value: "pm"
  990. * }]
  991. *
  992. * OBS: lexeme's are always String and may return invalid ranges depending of the token type.
  993. * Eg. "99" for month number.
  994. *
  995. * Return an empty Array when not successfully parsed.
  996. */
  997. var dateTokenizer = function( value, numberParser, properties ) {
  998. var valid,
  999. timeSeparator = properties.timeSeparator,
  1000. tokens = [],
  1001. widths = [ "abbreviated", "wide", "narrow" ];
  1002. valid = properties.pattern.match( datePatternRe ).every(function( current ) {
  1003. var chr, length, numeric, tokenRe,
  1004. token = {};
  1005. function hourFormatParse( tokenRe, numberParser ) {
  1006. var aux = value.match( tokenRe );
  1007. numberParser = numberParser || function( value ) {
  1008. return +value;
  1009. };
  1010. if ( !aux ) {
  1011. return false;
  1012. }
  1013. // hourFormat containing H only, e.g., `+H;-H`
  1014. if ( aux.length < 8 ) {
  1015. token.value =
  1016. ( aux[ 1 ] ? -numberParser( aux[ 1 ] ) : numberParser( aux[ 4 ] ) ) * 60;
  1017. // hourFormat containing H and m, e.g., `+HHmm;-HHmm`
  1018. } else {
  1019. token.value =
  1020. ( aux[ 1 ] ? -numberParser( aux[ 1 ] ) : numberParser( aux[ 7 ] ) ) * 60 +
  1021. ( aux[ 1 ] ? -numberParser( aux[ 4 ] ) : numberParser( aux[ 10 ] ) );
  1022. }
  1023. return true;
  1024. }
  1025. // Transform:
  1026. // - "+H;-H" -> /\+(\d\d?)|-(\d\d?)/
  1027. // - "+HH;-HH" -> /\+(\d\d)|-(\d\d)/
  1028. // - "+HHmm;-HHmm" -> /\+(\d\d)(\d\d)|-(\d\d)(\d\d)/
  1029. // - "+HH:mm;-HH:mm" -> /\+(\d\d):(\d\d)|-(\d\d):(\d\d)/
  1030. //
  1031. // If gmtFormat is GMT{0}, the regexp must fill {0} in each side, e.g.:
  1032. // - "+H;-H" -> /GMT\+(\d\d?)|GMT-(\d\d?)/
  1033. function hourFormatRe( hourFormat, gmtFormat, timeSeparator ) {
  1034. var re;
  1035. if ( !gmtFormat ) {
  1036. gmtFormat = "{0}";
  1037. }
  1038. re = hourFormat
  1039. .replace( "+", "\\+" )
  1040. // Unicode equivalent to (\\d\\d)
  1041. .replace( /HH|mm/g, "((" + regexpN.source + ")(" + regexpN.source + "))" )
  1042. // Unicode equivalent to (\\d\\d?)
  1043. .replace( /H|m/g, "((" + regexpN.source + ")(" + regexpN.source + ")?)" );
  1044. if ( timeSeparator ) {
  1045. re = re.replace( /:/g, timeSeparator );
  1046. }
  1047. re = re.split( ";" ).map(function( part ) {
  1048. return gmtFormat.replace( "{0}", part );
  1049. }).join( "|" );
  1050. return new RegExp( re );
  1051. }
  1052. function oneDigitIfLengthOne() {
  1053. if ( length === 1 ) {
  1054. // Unicode equivalent to /\d/
  1055. numeric = true;
  1056. return tokenRe = regexpN;
  1057. }
  1058. }
  1059. function oneOrTwoDigitsIfLengthOne() {
  1060. if ( length === 1 ) {
  1061. // Unicode equivalent to /\d\d?/
  1062. numeric = true;
  1063. return tokenRe = new RegExp( "(" + regexpN.source + ")(" + regexpN.source + ")?" );
  1064. }
  1065. }
  1066. function twoDigitsIfLengthTwo() {
  1067. if ( length === 2 ) {
  1068. // Unicode equivalent to /\d\d/
  1069. numeric = true;
  1070. return tokenRe = new RegExp( "(" + regexpN.source + ")(" + regexpN.source + ")" );
  1071. }
  1072. }
  1073. // Brute-force test every locale entry in an attempt to match the given value.
  1074. // Return the first found one (and set token accordingly), or null.
  1075. function lookup( path ) {
  1076. var i, re,
  1077. data = properties[ path.join( "/" ) ];
  1078. for ( i in data ) {
  1079. re = new RegExp( "^" + data[ i ] );
  1080. if ( re.test( value ) ) {
  1081. token.value = i;
  1082. return tokenRe = new RegExp( data[ i ] );
  1083. }
  1084. }
  1085. return null;
  1086. }
  1087. token.type = current;
  1088. chr = current.charAt( 0 ),
  1089. length = current.length;
  1090. if ( chr === "Z" ) {
  1091. // Z..ZZZ: same as "xxxx".
  1092. if ( length < 4 ) {
  1093. chr = "x";
  1094. length = 4;
  1095. // ZZZZ: same as "OOOO".
  1096. } else if ( length < 5 ) {
  1097. chr = "O";
  1098. length = 4;
  1099. // ZZZZZ: same as "XXXXX"
  1100. } else {
  1101. chr = "X";
  1102. length = 5;
  1103. }
  1104. }
  1105. switch ( chr ) {
  1106. // Era
  1107. case "G":
  1108. lookup([
  1109. "gregorian/eras",
  1110. length <= 3 ? "eraAbbr" : ( length === 4 ? "eraNames" : "eraNarrow" )
  1111. ]);
  1112. break;
  1113. // Year
  1114. case "y":
  1115. case "Y":
  1116. numeric = true;
  1117. // number l=1:+, l=2:{2}, l=3:{3,}, l=4:{4,}, ...
  1118. if ( length === 1 ) {
  1119. // Unicode equivalent to /\d+/.
  1120. tokenRe = new RegExp( "(" + regexpN.source + ")+" );
  1121. } else if ( length === 2 ) {
  1122. // Unicode equivalent to /\d\d/
  1123. tokenRe = new RegExp( "(" + regexpN.source + ")(" + regexpN.source + ")" );
  1124. } else {
  1125. // Unicode equivalent to /\d{length,}/
  1126. tokenRe = new RegExp( "(" + regexpN.source + "){" + length + ",}" );
  1127. }
  1128. break;
  1129. // Quarter
  1130. case "Q":
  1131. case "q":
  1132. // number l=1:{1}, l=2:{2}.
  1133. // lookup l=3...
  1134. oneDigitIfLengthOne() || twoDigitsIfLengthTwo() || lookup([
  1135. "gregorian/quarters",
  1136. chr === "Q" ? "format" : "stand-alone",
  1137. widths[ length - 3 ]
  1138. ]);
  1139. break;
  1140. // Month
  1141. case "M":
  1142. case "L":
  1143. // number l=1:{1,2}, l=2:{2}.
  1144. // lookup l=3...
  1145. oneOrTwoDigitsIfLengthOne() || twoDigitsIfLengthTwo() || lookup([
  1146. "gregorian/months",
  1147. chr === "M" ? "format" : "stand-alone",
  1148. widths[ length - 3 ]
  1149. ]);
  1150. break;
  1151. // Day
  1152. case "D":
  1153. // number {l,3}.
  1154. if ( length <= 3 ) {
  1155. // Unicode equivalent to /\d{length,3}/
  1156. numeric = true;
  1157. tokenRe = new RegExp( "(" + regexpN.source + "){" + length + ",3}" );
  1158. }
  1159. break;
  1160. case "W":
  1161. case "F":
  1162. // number l=1:{1}.
  1163. oneDigitIfLengthOne();
  1164. break;
  1165. // Week day
  1166. case "e":
  1167. case "c":
  1168. // number l=1:{1}, l=2:{2}.
  1169. // lookup for length >=3.
  1170. if ( length <= 2 ) {
  1171. oneDigitIfLengthOne() || twoDigitsIfLengthTwo();
  1172. break;
  1173. }
  1174. /* falls through */
  1175. case "E":
  1176. if ( length === 6 ) {
  1177. // Note: if short day names are not explicitly specified, abbreviated day
  1178. // names are used instead http://www.unicode.org/reports/tr35/tr35-dates.html#months_days_quarters_eras
  1179. lookup([
  1180. "gregorian/days",
  1181. [ chr === "c" ? "stand-alone" : "format" ],
  1182. "short"
  1183. ]) || lookup([
  1184. "gregorian/days",
  1185. [ chr === "c" ? "stand-alone" : "format" ],
  1186. "abbreviated"
  1187. ]);
  1188. } else {
  1189. lookup([
  1190. "gregorian/days",
  1191. [ chr === "c" ? "stand-alone" : "format" ],
  1192. widths[ length < 3 ? 0 : length - 3 ]
  1193. ]);
  1194. }
  1195. break;
  1196. // Period (AM or PM)
  1197. case "a":
  1198. lookup([
  1199. "gregorian/dayPeriods/format/wide"
  1200. ]);
  1201. break;
  1202. // Week, Day, Hour, Minute, or Second
  1203. case "w":
  1204. case "d":
  1205. case "h":
  1206. case "H":
  1207. case "K":
  1208. case "k":
  1209. case "j":
  1210. case "m":
  1211. case "s":
  1212. // number l1:{1,2}, l2:{2}.
  1213. oneOrTwoDigitsIfLengthOne() || twoDigitsIfLengthTwo();
  1214. break;
  1215. case "S":
  1216. // number {l}.
  1217. // Unicode equivalent to /\d{length}/
  1218. numeric = true;
  1219. tokenRe = new RegExp( "(" + regexpN.source + "){" + length + "}" );
  1220. break;
  1221. case "A":
  1222. // number {l+5}.
  1223. // Unicode equivalent to /\d{length+5}/
  1224. numeric = true;
  1225. tokenRe = new RegExp( "(" + regexpN.source + "){" + ( length + 5 ) + "}" );
  1226. break;
  1227. // Zone
  1228. case "z":
  1229. case "O":
  1230. // O: "{gmtFormat}+H;{gmtFormat}-H" or "{gmtZeroFormat}", eg. "GMT-8" or "GMT".
  1231. // OOOO: "{gmtFormat}{hourFormat}" or "{gmtZeroFormat}", eg. "GMT-08:00" or "GMT".
  1232. if ( value === properties[ "timeZoneNames/gmtZeroFormat" ] ) {
  1233. token.value = 0;
  1234. tokenRe = new RegExp( properties[ "timeZoneNames/gmtZeroFormat" ] );
  1235. } else {
  1236. tokenRe = hourFormatRe(
  1237. length < 4 ? "+H;-H" : properties[ "timeZoneNames/hourFormat" ],
  1238. properties[ "timeZoneNames/gmtFormat" ],
  1239. timeSeparator
  1240. );
  1241. if ( !hourFormatParse( tokenRe, numberParser ) ) {
  1242. return null;
  1243. }
  1244. }
  1245. break;
  1246. case "X":
  1247. // Same as x*, except it uses "Z" for zero offset.
  1248. if ( value === "Z" ) {
  1249. token.value = 0;
  1250. tokenRe = /Z/;
  1251. break;
  1252. }
  1253. /* falls through */
  1254. case "x":
  1255. // x: hourFormat("+HH;-HH")
  1256. // xx or xxxx: hourFormat("+HHmm;-HHmm")
  1257. // xxx or xxxxx: hourFormat("+HH:mm;-HH:mm")
  1258. tokenRe = hourFormatRe(
  1259. length === 1 ? "+HH;-HH" : ( length % 2 ? "+HH:mm;-HH:mm" : "+HHmm;-HHmm" )
  1260. );
  1261. if ( !hourFormatParse( tokenRe ) ) {
  1262. return null;
  1263. }
  1264. break;
  1265. case "'":
  1266. token.type = "literal";
  1267. current = current.replace( /''/, "'" );
  1268. if ( length > 2 ) {
  1269. current = current.slice( 1, -1 );
  1270. }
  1271. tokenRe = new RegExp( regexpEscape( current ) );
  1272. break;
  1273. default:
  1274. token.type = "literal";
  1275. tokenRe = /./;
  1276. }
  1277. if ( !tokenRe ) {
  1278. return false;
  1279. }
  1280. // Get lexeme and consume it.
  1281. value = value.replace( new RegExp( "^" + tokenRe.source ), function( lexeme ) {
  1282. token.lexeme = lexeme;
  1283. if ( numeric ) {
  1284. token.value = numberParser( lexeme );
  1285. }
  1286. return "";
  1287. });
  1288. if ( !token.lexeme ) {
  1289. return false;
  1290. }
  1291. tokens.push( token );
  1292. return true;
  1293. });
  1294. if ( value !== "" ) {
  1295. valid = false;
  1296. }
  1297. return valid ? tokens : [];
  1298. };
  1299. /**
  1300. * tokenizerProperties( pattern, cldr )
  1301. *
  1302. * @pattern [String] raw pattern.
  1303. *
  1304. * @cldr [Cldr instance].
  1305. *
  1306. * Return Object with data that will be used by tokenizer.
  1307. */
  1308. var dateTokenizerProperties = function( pattern, cldr ) {
  1309. var properties = {
  1310. pattern: pattern,
  1311. timeSeparator: numberSymbol( "timeSeparator", cldr )
  1312. },
  1313. widths = [ "abbreviated", "wide", "narrow" ];
  1314. function populateProperties( path, value ) {
  1315. // The `dates` and `calendars` trim's purpose is to reduce properties' key size only.
  1316. properties[ path.replace( /^.*\/dates\//, "" ).replace( /calendars\//, "" ) ] = value;
  1317. }
  1318. cldr.on( "get", populateProperties );
  1319. pattern.match( datePatternRe ).forEach(function( current ) {
  1320. var chr, length;
  1321. chr = current.charAt( 0 ),
  1322. length = current.length;
  1323. if ( chr === "Z" && length < 5 ) {
  1324. chr = "O";
  1325. length = 4;
  1326. }
  1327. switch ( chr ) {
  1328. // Era
  1329. case "G":
  1330. cldr.main([
  1331. "dates/calendars/gregorian/eras",
  1332. length <= 3 ? "eraAbbr" : ( length === 4 ? "eraNames" : "eraNarrow" )
  1333. ]);
  1334. break;
  1335. // Year
  1336. case "u": // Extended year. Need to be implemented.
  1337. case "U": // Cyclic year name. Need to be implemented.
  1338. throw createErrorUnsupportedFeature({
  1339. feature: "year pattern `" + chr + "`"
  1340. });
  1341. // Quarter
  1342. case "Q":
  1343. case "q":
  1344. if ( length > 2 ) {
  1345. cldr.main([
  1346. "dates/calendars/gregorian/quarters",
  1347. chr === "Q" ? "format" : "stand-alone",
  1348. widths[ length - 3 ]
  1349. ]);
  1350. }
  1351. break;
  1352. // Month
  1353. case "M":
  1354. case "L":
  1355. // number l=1:{1,2}, l=2:{2}.
  1356. // lookup l=3...
  1357. if ( length > 2 ) {
  1358. cldr.main([
  1359. "dates/calendars/gregorian/months",
  1360. chr === "M" ? "format" : "stand-alone",
  1361. widths[ length - 3 ]
  1362. ]);
  1363. }
  1364. break;
  1365. // Day
  1366. case "g":
  1367. // Modified Julian day. Need to be implemented.
  1368. throw createErrorUnsupportedFeature({
  1369. feature: "Julian day pattern `g`"
  1370. });
  1371. // Week day
  1372. case "e":
  1373. case "c":
  1374. // lookup for length >=3.
  1375. if ( length <= 2 ) {
  1376. break;
  1377. }
  1378. /* falls through */
  1379. case "E":
  1380. if ( length === 6 ) {
  1381. // Note: if short day names are not explicitly specified, abbreviated day
  1382. // names are used instead http://www.unicode.org/reports/tr35/tr35-dates.html#months_days_quarters_eras
  1383. cldr.main([
  1384. "dates/calendars/gregorian/days",
  1385. [ chr === "c" ? "stand-alone" : "format" ],
  1386. "short"
  1387. ]) || cldr.main([
  1388. "dates/calendars/gregorian/days",
  1389. [ chr === "c" ? "stand-alone" : "format" ],
  1390. "abbreviated"
  1391. ]);
  1392. } else {
  1393. cldr.main([
  1394. "dates/calendars/gregorian/days",
  1395. [ chr === "c" ? "stand-alone" : "format" ],
  1396. widths[ length < 3 ? 0 : length - 3 ]
  1397. ]);
  1398. }
  1399. break;
  1400. // Period (AM or PM)
  1401. case "a":
  1402. cldr.main([
  1403. "dates/calendars/gregorian/dayPeriods/format/wide"
  1404. ]);
  1405. break;
  1406. // Zone
  1407. case "z":
  1408. case "O":
  1409. cldr.main( "dates/timeZoneNames/gmtFormat" );
  1410. cldr.main( "dates/timeZoneNames/gmtZeroFormat" );
  1411. cldr.main( "dates/timeZoneNames/hourFormat" );
  1412. break;
  1413. case "v":
  1414. case "V":
  1415. throw createErrorUnsupportedFeature({
  1416. feature: "timezone pattern `" + chr + "`"
  1417. });
  1418. }
  1419. });
  1420. cldr.off( "get", populateProperties );
  1421. return properties;
  1422. };
  1423. function validateRequiredCldr( path, value ) {
  1424. validateCldr( path, value, {
  1425. skip: [
  1426. /dates\/calendars\/gregorian\/dateTimeFormats\/availableFormats/,
  1427. /dates\/calendars\/gregorian\/days\/.*\/short/,
  1428. /supplemental\/timeData\/(?!001)/,
  1429. /supplemental\/weekData\/(?!001)/
  1430. ]
  1431. });
  1432. }
  1433. /**
  1434. * .dateFormatter( options )
  1435. *
  1436. * @options [Object] see date/expand_pattern for more info.
  1437. *
  1438. * Return a date formatter function (of the form below) according to the given options and the
  1439. * default/instance locale.
  1440. *
  1441. * fn( value )
  1442. *
  1443. * @value [Date]
  1444. *
  1445. * Return a function that formats a date according to the given `format` and the default/instance
  1446. * locale.
  1447. */
  1448. Globalize.dateFormatter =
  1449. Globalize.prototype.dateFormatter = function( options ) {
  1450. var cldr, numberFormatters, pad, pattern, properties;
  1451. validateParameterTypePlainObject( options, "options" );
  1452. cldr = this.cldr;
  1453. options = options || { skeleton: "yMd" };
  1454. validateDefaultLocale( cldr );
  1455. cldr.on( "get", validateRequiredCldr );
  1456. pattern = dateExpandPattern( options, cldr );
  1457. properties = dateFormatProperties( pattern, cldr );
  1458. cldr.off( "get", validateRequiredCldr );
  1459. // Create needed number formatters.
  1460. numberFormatters = properties.numberFormatters;
  1461. delete properties.numberFormatters;
  1462. for ( pad in numberFormatters ) {
  1463. numberFormatters[ pad ] = this.numberFormatter({
  1464. raw: numberFormatters[ pad ]
  1465. });
  1466. }
  1467. return function( value ) {
  1468. validateParameterPresence( value, "value" );
  1469. validateParameterTypeDate( value, "value" );
  1470. return dateFormat( value, numberFormatters, properties );
  1471. };
  1472. };
  1473. /**
  1474. * .dateParser( options )
  1475. *
  1476. * @options [Object] see date/expand_pattern for more info.
  1477. *
  1478. * Return a function that parses a string date according to the given `formats` and the
  1479. * default/instance locale.
  1480. */
  1481. Globalize.dateParser =
  1482. Globalize.prototype.dateParser = function( options ) {
  1483. var cldr, numberParser, parseProperties, pattern, tokenizerProperties;
  1484. validateParameterTypePlainObject( options, "options" );
  1485. cldr = this.cldr;
  1486. options = options || { skeleton: "yMd" };
  1487. validateDefaultLocale( cldr );
  1488. cldr.on( "get", validateRequiredCldr );
  1489. pattern = dateExpandPattern( options, cldr );
  1490. tokenizerProperties = dateTokenizerProperties( pattern, cldr );
  1491. parseProperties = dateParseProperties( cldr );
  1492. cldr.off( "get", validateRequiredCldr );
  1493. numberParser = this.numberParser({ raw: "0" });
  1494. return function( value ) {
  1495. var tokens;
  1496. validateParameterPresence( value, "value" );
  1497. validateParameterTypeString( value, "value" );
  1498. tokens = dateTokenizer( value, numberParser, tokenizerProperties );
  1499. return dateParse( value, tokens, parseProperties ) || null;
  1500. };
  1501. };
  1502. /**
  1503. * .formatDate( value, options )
  1504. *
  1505. * @value [Date]
  1506. *
  1507. * @options [Object] see date/expand_pattern for more info.
  1508. *
  1509. * Formats a date or number according to the given options string and the default/instance locale.
  1510. */
  1511. Globalize.formatDate =
  1512. Globalize.prototype.formatDate = function( value, options ) {
  1513. validateParameterPresence( value, "value" );
  1514. validateParameterTypeDate( value, "value" );
  1515. return this.dateFormatter( options )( value );
  1516. };
  1517. /**
  1518. * .parseDate( value, options )
  1519. *
  1520. * @value [String]
  1521. *
  1522. * @options [Object] see date/expand_pattern for more info.
  1523. *
  1524. * Return a Date instance or null.
  1525. */
  1526. Globalize.parseDate =
  1527. Globalize.prototype.parseDate = function( value, options ) {
  1528. validateParameterPresence( value, "value" );
  1529. validateParameterTypeString( value, "value" );
  1530. return this.dateParser( options )( value );
  1531. };
  1532. return Globalize;
  1533. }));