data-94e8d392.js 60 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655
  1. /*!
  2. * (C) Ionic http://ionicframework.com - MIT License
  3. */
  4. 'use strict';
  5. const index = require('./index-cc858e97.js');
  6. /**
  7. * Returns true if the selected day is equal to the reference day
  8. */
  9. const isSameDay = (baseParts, compareParts) => {
  10. return (baseParts.month === compareParts.month && baseParts.day === compareParts.day && baseParts.year === compareParts.year);
  11. };
  12. /**
  13. * Returns true is the selected day is before the reference day.
  14. */
  15. const isBefore = (baseParts, compareParts) => {
  16. return !!(baseParts.year < compareParts.year ||
  17. (baseParts.year === compareParts.year && baseParts.month < compareParts.month) ||
  18. (baseParts.year === compareParts.year &&
  19. baseParts.month === compareParts.month &&
  20. baseParts.day !== null &&
  21. baseParts.day < compareParts.day));
  22. };
  23. /**
  24. * Returns true is the selected day is after the reference day.
  25. */
  26. const isAfter = (baseParts, compareParts) => {
  27. return !!(baseParts.year > compareParts.year ||
  28. (baseParts.year === compareParts.year && baseParts.month > compareParts.month) ||
  29. (baseParts.year === compareParts.year &&
  30. baseParts.month === compareParts.month &&
  31. baseParts.day !== null &&
  32. baseParts.day > compareParts.day));
  33. };
  34. const warnIfValueOutOfBounds = (value, min, max) => {
  35. const valueArray = Array.isArray(value) ? value : [value];
  36. for (const val of valueArray) {
  37. if ((min !== undefined && isBefore(val, min)) || (max !== undefined && isAfter(val, max))) {
  38. index.printIonWarning('[ion-datetime] - The value provided to ion-datetime is out of bounds.\n\n' +
  39. `Min: ${JSON.stringify(min)}\n` +
  40. `Max: ${JSON.stringify(max)}\n` +
  41. `Value: ${JSON.stringify(value)}`);
  42. break;
  43. }
  44. }
  45. };
  46. /**
  47. * Determines if given year is a
  48. * leap year. Returns `true` if year
  49. * is a leap year. Returns `false`
  50. * otherwise.
  51. */
  52. const isLeapYear = (year) => {
  53. return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
  54. };
  55. /**
  56. * Determines the hour cycle for a user.
  57. * If the hour cycle is explicitly defined, just use that.
  58. * Otherwise, we try to derive it from either the specified
  59. * locale extension tags or from Intl.DateTimeFormat directly.
  60. */
  61. const getHourCycle = (locale, hourCycle) => {
  62. /**
  63. * If developer has explicitly enabled 24-hour time
  64. * then return early and do not look at the system default.
  65. */
  66. if (hourCycle !== undefined) {
  67. return hourCycle;
  68. }
  69. /**
  70. * If hourCycle was not specified, check the locale
  71. * that is set on the user's device. We first check the
  72. * Intl.DateTimeFormat hourCycle option as developers can encode this
  73. * option into the locale string. Example: `en-US-u-hc-h23`
  74. */
  75. const formatted = new Intl.DateTimeFormat(locale, { hour: 'numeric' });
  76. const options = formatted.resolvedOptions();
  77. if (options.hourCycle !== undefined) {
  78. return options.hourCycle;
  79. }
  80. /**
  81. * If hourCycle is not specified (either through lack
  82. * of browser support or locale information) then fall
  83. * back to this slower hourCycle check.
  84. */
  85. const date = new Date('5/18/2021 00:00');
  86. const parts = formatted.formatToParts(date);
  87. const hour = parts.find((p) => p.type === 'hour');
  88. if (!hour) {
  89. throw new Error('Hour value not found from DateTimeFormat');
  90. }
  91. /**
  92. * Midnight for h11 starts at 0:00am
  93. * Midnight for h12 starts at 12:00am
  94. * Midnight for h23 starts at 00:00
  95. * Midnight for h24 starts at 24:00
  96. */
  97. switch (hour.value) {
  98. case '0':
  99. return 'h11';
  100. case '12':
  101. return 'h12';
  102. case '00':
  103. return 'h23';
  104. case '24':
  105. return 'h24';
  106. default:
  107. throw new Error(`Invalid hour cycle "${hourCycle}"`);
  108. }
  109. };
  110. /**
  111. * Determine if the hour cycle uses a 24-hour format.
  112. * Returns true for h23 and h24. Returns false otherwise.
  113. * If you don't know the hourCycle, use getHourCycle above
  114. * and pass the result into this function.
  115. */
  116. const is24Hour = (hourCycle) => {
  117. return hourCycle === 'h23' || hourCycle === 'h24';
  118. };
  119. /**
  120. * Given a date object, returns the number
  121. * of days in that month.
  122. * Month value begin at 1, not 0.
  123. * i.e. January = month 1.
  124. */
  125. const getNumDaysInMonth = (month, year) => {
  126. return month === 4 || month === 6 || month === 9 || month === 11
  127. ? 30
  128. : month === 2
  129. ? isLeapYear(year)
  130. ? 29
  131. : 28
  132. : 31;
  133. };
  134. /**
  135. * Certain locales display month then year while
  136. * others display year then month.
  137. * We can use Intl.DateTimeFormat to determine
  138. * the ordering for each locale.
  139. * The formatOptions param can be used to customize
  140. * which pieces of a date to compare against the month
  141. * with. For example, some locales render dd/mm/yyyy
  142. * while others render mm/dd/yyyy. This function can be
  143. * used for variations of the same "month first" check.
  144. */
  145. const isMonthFirstLocale = (locale, formatOptions = {
  146. month: 'numeric',
  147. year: 'numeric',
  148. }) => {
  149. /**
  150. * By setting month and year we guarantee that only
  151. * month, year, and literal (slashes '/', for example)
  152. * values are included in the formatToParts results.
  153. *
  154. * The ordering of the parts will be determined by
  155. * the locale. So if the month is the first value,
  156. * then we know month should be shown first. If the
  157. * year is the first value, then we know year should be shown first.
  158. *
  159. * This ordering can be controlled by customizing the locale property.
  160. */
  161. const parts = new Intl.DateTimeFormat(locale, formatOptions).formatToParts(new Date());
  162. return parts[0].type === 'month';
  163. };
  164. /**
  165. * Determines if the given locale formats the day period (am/pm) to the
  166. * left or right of the hour.
  167. * @param locale The locale to check.
  168. * @returns `true` if the locale formats the day period to the left of the hour.
  169. */
  170. const isLocaleDayPeriodRTL = (locale) => {
  171. const parts = new Intl.DateTimeFormat(locale, { hour: 'numeric' }).formatToParts(new Date());
  172. return parts[0].type === 'dayPeriod';
  173. };
  174. const ISO_8601_REGEXP =
  175. // eslint-disable-next-line no-useless-escape
  176. /^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/;
  177. // eslint-disable-next-line no-useless-escape
  178. const TIME_REGEXP = /^((\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/;
  179. /**
  180. * Use to convert a string of comma separated numbers or
  181. * an array of numbers, and clean up any user input
  182. */
  183. const convertToArrayOfNumbers = (input) => {
  184. if (input === undefined) {
  185. return;
  186. }
  187. let processedInput = input;
  188. if (typeof input === 'string') {
  189. // convert the string to an array of strings
  190. // auto remove any whitespace and [] characters
  191. processedInput = input.replace(/\[|\]|\s/g, '').split(',');
  192. }
  193. let values;
  194. if (Array.isArray(processedInput)) {
  195. // ensure each value is an actual number in the returned array
  196. values = processedInput.map((num) => parseInt(num, 10)).filter(isFinite);
  197. }
  198. else {
  199. values = [processedInput];
  200. }
  201. return values;
  202. };
  203. /**
  204. * Extracts date information
  205. * from a .calendar-day element
  206. * into DatetimeParts.
  207. */
  208. const getPartsFromCalendarDay = (el) => {
  209. return {
  210. month: parseInt(el.getAttribute('data-month'), 10),
  211. day: parseInt(el.getAttribute('data-day'), 10),
  212. year: parseInt(el.getAttribute('data-year'), 10),
  213. dayOfWeek: parseInt(el.getAttribute('data-day-of-week'), 10),
  214. };
  215. };
  216. function parseDate(val) {
  217. if (Array.isArray(val)) {
  218. const parsedArray = [];
  219. for (const valStr of val) {
  220. const parsedVal = parseDate(valStr);
  221. /**
  222. * If any of the values weren't parsed correctly, consider
  223. * the entire batch incorrect. This simplifies the type
  224. * signatures by having "undefined" be a general error case
  225. * instead of returning (Datetime | undefined)[], which is
  226. * harder for TS to perform type narrowing on.
  227. */
  228. if (!parsedVal) {
  229. return undefined;
  230. }
  231. parsedArray.push(parsedVal);
  232. }
  233. return parsedArray;
  234. }
  235. // manually parse IS0 cuz Date.parse cannot be trusted
  236. // ISO 8601 format: 1994-12-15T13:47:20Z
  237. let parse = null;
  238. if (val != null && val !== '') {
  239. // try parsing for just time first, HH:MM
  240. parse = TIME_REGEXP.exec(val);
  241. if (parse) {
  242. // adjust the array so it fits nicely with the datetime parse
  243. parse.unshift(undefined, undefined);
  244. parse[2] = parse[3] = undefined;
  245. }
  246. else {
  247. // try parsing for full ISO datetime
  248. parse = ISO_8601_REGEXP.exec(val);
  249. }
  250. }
  251. if (parse === null) {
  252. // wasn't able to parse the ISO datetime
  253. index.printIonWarning(`[ion-datetime] - Unable to parse date string: ${val}. Please provide a valid ISO 8601 datetime string.`);
  254. return undefined;
  255. }
  256. // ensure all the parse values exist with at least 0
  257. for (let i = 1; i < 8; i++) {
  258. parse[i] = parse[i] !== undefined ? parseInt(parse[i], 10) : undefined;
  259. }
  260. // can also get second and millisecond from parse[6] and parse[7] if needed
  261. return {
  262. year: parse[1],
  263. month: parse[2],
  264. day: parse[3],
  265. hour: parse[4],
  266. minute: parse[5],
  267. ampm: parse[4] < 12 ? 'am' : 'pm',
  268. };
  269. }
  270. const clampDate = (dateParts, minParts, maxParts) => {
  271. if (minParts && isBefore(dateParts, minParts)) {
  272. return minParts;
  273. }
  274. else if (maxParts && isAfter(dateParts, maxParts)) {
  275. return maxParts;
  276. }
  277. return dateParts;
  278. };
  279. /**
  280. * Parses an hour and returns if the value is in the morning (am) or afternoon (pm).
  281. * @param hour The hour to format, should be 0-23
  282. * @returns `pm` if the hour is greater than or equal to 12, `am` if less than 12.
  283. */
  284. const parseAmPm = (hour) => {
  285. return hour >= 12 ? 'pm' : 'am';
  286. };
  287. /**
  288. * Takes a max date string and creates a DatetimeParts
  289. * object, filling in any missing information.
  290. * For example, max="2012" would fill in the missing
  291. * month, day, hour, and minute information.
  292. */
  293. const parseMaxParts = (max, todayParts) => {
  294. const result = parseDate(max);
  295. /**
  296. * If min was not a valid date then return undefined.
  297. */
  298. if (result === undefined) {
  299. return;
  300. }
  301. const { month, day, year, hour, minute } = result;
  302. /**
  303. * When passing in `max` or `min`, developers
  304. * can pass in any ISO-8601 string. This means
  305. * that not all of the date/time fields are defined.
  306. * For example, passing max="2012" is valid even though
  307. * there is no month, day, hour, or minute data.
  308. * However, all of this data is required when clamping the date
  309. * so that the correct initial value can be selected. As a result,
  310. * we need to fill in any omitted data with the min or max values.
  311. */
  312. const yearValue = year !== null && year !== void 0 ? year : todayParts.year;
  313. const monthValue = month !== null && month !== void 0 ? month : 12;
  314. return {
  315. month: monthValue,
  316. day: day !== null && day !== void 0 ? day : getNumDaysInMonth(monthValue, yearValue),
  317. /**
  318. * Passing in "HH:mm" is a valid ISO-8601
  319. * string, so we just default to the current year
  320. * in this case.
  321. */
  322. year: yearValue,
  323. hour: hour !== null && hour !== void 0 ? hour : 23,
  324. minute: minute !== null && minute !== void 0 ? minute : 59,
  325. };
  326. };
  327. /**
  328. * Takes a min date string and creates a DatetimeParts
  329. * object, filling in any missing information.
  330. * For example, min="2012" would fill in the missing
  331. * month, day, hour, and minute information.
  332. */
  333. const parseMinParts = (min, todayParts) => {
  334. const result = parseDate(min);
  335. /**
  336. * If min was not a valid date then return undefined.
  337. */
  338. if (result === undefined) {
  339. return;
  340. }
  341. const { month, day, year, hour, minute } = result;
  342. /**
  343. * When passing in `max` or `min`, developers
  344. * can pass in any ISO-8601 string. This means
  345. * that not all of the date/time fields are defined.
  346. * For example, passing max="2012" is valid even though
  347. * there is no month, day, hour, or minute data.
  348. * However, all of this data is required when clamping the date
  349. * so that the correct initial value can be selected. As a result,
  350. * we need to fill in any omitted data with the min or max values.
  351. */
  352. return {
  353. month: month !== null && month !== void 0 ? month : 1,
  354. day: day !== null && day !== void 0 ? day : 1,
  355. /**
  356. * Passing in "HH:mm" is a valid ISO-8601
  357. * string, so we just default to the current year
  358. * in this case.
  359. */
  360. year: year !== null && year !== void 0 ? year : todayParts.year,
  361. hour: hour !== null && hour !== void 0 ? hour : 0,
  362. minute: minute !== null && minute !== void 0 ? minute : 0,
  363. };
  364. };
  365. const twoDigit = (val) => {
  366. return ('0' + (val !== undefined ? Math.abs(val) : '0')).slice(-2);
  367. };
  368. const fourDigit = (val) => {
  369. return ('000' + (val !== undefined ? Math.abs(val) : '0')).slice(-4);
  370. };
  371. function convertDataToISO(data) {
  372. if (Array.isArray(data)) {
  373. return data.map((parts) => convertDataToISO(parts));
  374. }
  375. // https://www.w3.org/TR/NOTE-datetime
  376. let rtn = '';
  377. if (data.year !== undefined) {
  378. // YYYY
  379. rtn = fourDigit(data.year);
  380. if (data.month !== undefined) {
  381. // YYYY-MM
  382. rtn += '-' + twoDigit(data.month);
  383. if (data.day !== undefined) {
  384. // YYYY-MM-DD
  385. rtn += '-' + twoDigit(data.day);
  386. if (data.hour !== undefined) {
  387. // YYYY-MM-DDTHH:mm:SS
  388. rtn += `T${twoDigit(data.hour)}:${twoDigit(data.minute)}:00`;
  389. }
  390. }
  391. }
  392. }
  393. else if (data.hour !== undefined) {
  394. // HH:mm
  395. rtn = twoDigit(data.hour) + ':' + twoDigit(data.minute);
  396. }
  397. return rtn;
  398. }
  399. /**
  400. * Converts an 12 hour value to 24 hours.
  401. */
  402. const convert12HourTo24Hour = (hour, ampm) => {
  403. if (ampm === undefined) {
  404. return hour;
  405. }
  406. /**
  407. * If AM and 12am
  408. * then return 00:00.
  409. * Otherwise just return
  410. * the hour since it is
  411. * already in 24 hour format.
  412. */
  413. if (ampm === 'am') {
  414. if (hour === 12) {
  415. return 0;
  416. }
  417. return hour;
  418. }
  419. /**
  420. * If PM and 12pm
  421. * just return 12:00
  422. * since it is already
  423. * in 24 hour format.
  424. * Otherwise add 12 hours
  425. * to the time.
  426. */
  427. if (hour === 12) {
  428. return 12;
  429. }
  430. return hour + 12;
  431. };
  432. const getStartOfWeek = (refParts) => {
  433. const { dayOfWeek } = refParts;
  434. if (dayOfWeek === null || dayOfWeek === undefined) {
  435. throw new Error('No day of week provided');
  436. }
  437. return subtractDays(refParts, dayOfWeek);
  438. };
  439. const getEndOfWeek = (refParts) => {
  440. const { dayOfWeek } = refParts;
  441. if (dayOfWeek === null || dayOfWeek === undefined) {
  442. throw new Error('No day of week provided');
  443. }
  444. return addDays(refParts, 6 - dayOfWeek);
  445. };
  446. const getNextDay = (refParts) => {
  447. return addDays(refParts, 1);
  448. };
  449. const getPreviousDay = (refParts) => {
  450. return subtractDays(refParts, 1);
  451. };
  452. const getPreviousWeek = (refParts) => {
  453. return subtractDays(refParts, 7);
  454. };
  455. const getNextWeek = (refParts) => {
  456. return addDays(refParts, 7);
  457. };
  458. /**
  459. * Given datetime parts, subtract
  460. * numDays from the date.
  461. * Returns a new DatetimeParts object
  462. * Currently can only go backward at most 1 month.
  463. */
  464. const subtractDays = (refParts, numDays) => {
  465. const { month, day, year } = refParts;
  466. if (day === null) {
  467. throw new Error('No day provided');
  468. }
  469. const workingParts = {
  470. month,
  471. day,
  472. year,
  473. };
  474. workingParts.day = day - numDays;
  475. /**
  476. * If wrapping to previous month
  477. * update days and decrement month
  478. */
  479. if (workingParts.day < 1) {
  480. workingParts.month -= 1;
  481. }
  482. /**
  483. * If moving to previous year, reset
  484. * month to December and decrement year
  485. */
  486. if (workingParts.month < 1) {
  487. workingParts.month = 12;
  488. workingParts.year -= 1;
  489. }
  490. /**
  491. * Determine how many days are in the current
  492. * month
  493. */
  494. if (workingParts.day < 1) {
  495. const daysInMonth = getNumDaysInMonth(workingParts.month, workingParts.year);
  496. /**
  497. * Take num days in month and add the
  498. * number of underflow days. This number will
  499. * be negative.
  500. * Example: 1 week before Jan 2, 2021 is
  501. * December 26, 2021 so:
  502. * 2 - 7 = -5
  503. * 31 + (-5) = 26
  504. */
  505. workingParts.day = daysInMonth + workingParts.day;
  506. }
  507. return workingParts;
  508. };
  509. /**
  510. * Given datetime parts, add
  511. * numDays to the date.
  512. * Returns a new DatetimeParts object
  513. * Currently can only go forward at most 1 month.
  514. */
  515. const addDays = (refParts, numDays) => {
  516. const { month, day, year } = refParts;
  517. if (day === null) {
  518. throw new Error('No day provided');
  519. }
  520. const workingParts = {
  521. month,
  522. day,
  523. year,
  524. };
  525. const daysInMonth = getNumDaysInMonth(month, year);
  526. workingParts.day = day + numDays;
  527. /**
  528. * If wrapping to next month
  529. * update days and increment month
  530. */
  531. if (workingParts.day > daysInMonth) {
  532. workingParts.day -= daysInMonth;
  533. workingParts.month += 1;
  534. }
  535. /**
  536. * If moving to next year, reset
  537. * month to January and increment year
  538. */
  539. if (workingParts.month > 12) {
  540. workingParts.month = 1;
  541. workingParts.year += 1;
  542. }
  543. return workingParts;
  544. };
  545. /**
  546. * Given DatetimeParts, generate the previous month.
  547. */
  548. const getPreviousMonth = (refParts) => {
  549. /**
  550. * If current month is January, wrap backwards
  551. * to December of the previous year.
  552. */
  553. const month = refParts.month === 1 ? 12 : refParts.month - 1;
  554. const year = refParts.month === 1 ? refParts.year - 1 : refParts.year;
  555. const numDaysInMonth = getNumDaysInMonth(month, year);
  556. const day = numDaysInMonth < refParts.day ? numDaysInMonth : refParts.day;
  557. return { month, year, day };
  558. };
  559. /**
  560. * Given DatetimeParts, generate the next month.
  561. */
  562. const getNextMonth = (refParts) => {
  563. /**
  564. * If current month is December, wrap forwards
  565. * to January of the next year.
  566. */
  567. const month = refParts.month === 12 ? 1 : refParts.month + 1;
  568. const year = refParts.month === 12 ? refParts.year + 1 : refParts.year;
  569. const numDaysInMonth = getNumDaysInMonth(month, year);
  570. const day = numDaysInMonth < refParts.day ? numDaysInMonth : refParts.day;
  571. return { month, year, day };
  572. };
  573. const changeYear = (refParts, yearDelta) => {
  574. const month = refParts.month;
  575. const year = refParts.year + yearDelta;
  576. const numDaysInMonth = getNumDaysInMonth(month, year);
  577. const day = numDaysInMonth < refParts.day ? numDaysInMonth : refParts.day;
  578. return { month, year, day };
  579. };
  580. /**
  581. * Given DatetimeParts, generate the previous year.
  582. */
  583. const getPreviousYear = (refParts) => {
  584. return changeYear(refParts, -1);
  585. };
  586. /**
  587. * Given DatetimeParts, generate the next year.
  588. */
  589. const getNextYear = (refParts) => {
  590. return changeYear(refParts, 1);
  591. };
  592. /**
  593. * If PM, then internal value should
  594. * be converted to 24-hr time.
  595. * Does not apply when public
  596. * values are already 24-hr time.
  597. */
  598. const getInternalHourValue = (hour, use24Hour, ampm) => {
  599. if (use24Hour) {
  600. return hour;
  601. }
  602. return convert12HourTo24Hour(hour, ampm);
  603. };
  604. /**
  605. * Unless otherwise stated, all month values are
  606. * 1 indexed instead of the typical 0 index in JS Date.
  607. * Example:
  608. * January = Month 0 when using JS Date
  609. * January = Month 1 when using this datetime util
  610. */
  611. /**
  612. * Given the current datetime parts and a new AM/PM value
  613. * calculate what the hour should be in 24-hour time format.
  614. * Used when toggling the AM/PM segment since we store our hours
  615. * in 24-hour time format internally.
  616. */
  617. const calculateHourFromAMPM = (currentParts, newAMPM) => {
  618. const { ampm: currentAMPM, hour } = currentParts;
  619. let newHour = hour;
  620. /**
  621. * If going from AM --> PM, need to update the
  622. *
  623. */
  624. if (currentAMPM === 'am' && newAMPM === 'pm') {
  625. newHour = convert12HourTo24Hour(newHour, 'pm');
  626. /**
  627. * If going from PM --> AM
  628. */
  629. }
  630. else if (currentAMPM === 'pm' && newAMPM === 'am') {
  631. newHour = Math.abs(newHour - 12);
  632. }
  633. return newHour;
  634. };
  635. /**
  636. * Updates parts to ensure that month and day
  637. * values are valid. For days that do not exist,
  638. * or are outside the min/max bounds, the closest
  639. * valid day is used.
  640. */
  641. const validateParts = (parts, minParts, maxParts) => {
  642. const { month, day, year } = parts;
  643. const partsCopy = clampDate(Object.assign({}, parts), minParts, maxParts);
  644. const numDays = getNumDaysInMonth(month, year);
  645. /**
  646. * If the max number of days
  647. * is greater than the day we want
  648. * to set, update the DatetimeParts
  649. * day field to be the max days.
  650. */
  651. if (day !== null && numDays < day) {
  652. partsCopy.day = numDays;
  653. }
  654. /**
  655. * If value is same day as min day,
  656. * make sure the time value is in bounds.
  657. */
  658. if (minParts !== undefined && isSameDay(partsCopy, minParts)) {
  659. /**
  660. * If the hour is out of bounds,
  661. * update both the hour and minute.
  662. * This is done so that the new time
  663. * is closest to what the user selected.
  664. */
  665. if (partsCopy.hour !== undefined && minParts.hour !== undefined) {
  666. if (partsCopy.hour < minParts.hour) {
  667. partsCopy.hour = minParts.hour;
  668. partsCopy.minute = minParts.minute;
  669. /**
  670. * If only the minute is out of bounds,
  671. * set it to the min minute.
  672. */
  673. }
  674. else if (partsCopy.hour === minParts.hour &&
  675. partsCopy.minute !== undefined &&
  676. minParts.minute !== undefined &&
  677. partsCopy.minute < minParts.minute) {
  678. partsCopy.minute = minParts.minute;
  679. }
  680. }
  681. }
  682. /**
  683. * If value is same day as max day,
  684. * make sure the time value is in bounds.
  685. */
  686. if (maxParts !== undefined && isSameDay(parts, maxParts)) {
  687. /**
  688. * If the hour is out of bounds,
  689. * update both the hour and minute.
  690. * This is done so that the new time
  691. * is closest to what the user selected.
  692. */
  693. if (partsCopy.hour !== undefined && maxParts.hour !== undefined) {
  694. if (partsCopy.hour > maxParts.hour) {
  695. partsCopy.hour = maxParts.hour;
  696. partsCopy.minute = maxParts.minute;
  697. /**
  698. * If only the minute is out of bounds,
  699. * set it to the max minute.
  700. */
  701. }
  702. else if (partsCopy.hour === maxParts.hour &&
  703. partsCopy.minute !== undefined &&
  704. maxParts.minute !== undefined &&
  705. partsCopy.minute > maxParts.minute) {
  706. partsCopy.minute = maxParts.minute;
  707. }
  708. }
  709. }
  710. return partsCopy;
  711. };
  712. /**
  713. * Returns the closest date to refParts
  714. * that also meets the constraints of
  715. * the *Values params.
  716. */
  717. const getClosestValidDate = ({ refParts, monthValues, dayValues, yearValues, hourValues, minuteValues, minParts, maxParts, }) => {
  718. const { hour, minute, day, month, year } = refParts;
  719. const copyParts = Object.assign(Object.assign({}, refParts), { dayOfWeek: undefined });
  720. if (yearValues !== undefined) {
  721. // Filters out years that are out of the min/max bounds
  722. const filteredYears = yearValues.filter((year) => {
  723. if (minParts !== undefined && year < minParts.year) {
  724. return false;
  725. }
  726. if (maxParts !== undefined && year > maxParts.year) {
  727. return false;
  728. }
  729. return true;
  730. });
  731. copyParts.year = findClosestValue(year, filteredYears);
  732. }
  733. if (monthValues !== undefined) {
  734. // Filters out months that are out of the min/max bounds
  735. const filteredMonths = monthValues.filter((month) => {
  736. if (minParts !== undefined && copyParts.year === minParts.year && month < minParts.month) {
  737. return false;
  738. }
  739. if (maxParts !== undefined && copyParts.year === maxParts.year && month > maxParts.month) {
  740. return false;
  741. }
  742. return true;
  743. });
  744. copyParts.month = findClosestValue(month, filteredMonths);
  745. }
  746. // Day is nullable but cannot be undefined
  747. if (day !== null && dayValues !== undefined) {
  748. // Filters out days that are out of the min/max bounds
  749. const filteredDays = dayValues.filter((day) => {
  750. if (minParts !== undefined && isBefore(Object.assign(Object.assign({}, copyParts), { day }), minParts)) {
  751. return false;
  752. }
  753. if (maxParts !== undefined && isAfter(Object.assign(Object.assign({}, copyParts), { day }), maxParts)) {
  754. return false;
  755. }
  756. return true;
  757. });
  758. copyParts.day = findClosestValue(day, filteredDays);
  759. }
  760. if (hour !== undefined && hourValues !== undefined) {
  761. // Filters out hours that are out of the min/max bounds
  762. const filteredHours = hourValues.filter((hour) => {
  763. if ((minParts === null || minParts === void 0 ? void 0 : minParts.hour) !== undefined && isSameDay(copyParts, minParts) && hour < minParts.hour) {
  764. return false;
  765. }
  766. if ((maxParts === null || maxParts === void 0 ? void 0 : maxParts.hour) !== undefined && isSameDay(copyParts, maxParts) && hour > maxParts.hour) {
  767. return false;
  768. }
  769. return true;
  770. });
  771. copyParts.hour = findClosestValue(hour, filteredHours);
  772. copyParts.ampm = parseAmPm(copyParts.hour);
  773. }
  774. if (minute !== undefined && minuteValues !== undefined) {
  775. // Filters out minutes that are out of the min/max bounds
  776. const filteredMinutes = minuteValues.filter((minute) => {
  777. if ((minParts === null || minParts === void 0 ? void 0 : minParts.minute) !== undefined &&
  778. isSameDay(copyParts, minParts) &&
  779. copyParts.hour === minParts.hour &&
  780. minute < minParts.minute) {
  781. return false;
  782. }
  783. if ((maxParts === null || maxParts === void 0 ? void 0 : maxParts.minute) !== undefined &&
  784. isSameDay(copyParts, maxParts) &&
  785. copyParts.hour === maxParts.hour &&
  786. minute > maxParts.minute) {
  787. return false;
  788. }
  789. return true;
  790. });
  791. copyParts.minute = findClosestValue(minute, filteredMinutes);
  792. }
  793. return copyParts;
  794. };
  795. /**
  796. * Finds the value in "values" that is
  797. * numerically closest to "reference".
  798. * This function assumes that "values" is
  799. * already sorted in ascending order.
  800. * @param reference The reference number to use
  801. * when finding the closest value
  802. * @param values The allowed values that will be
  803. * searched to find the closest value to "reference"
  804. */
  805. const findClosestValue = (reference, values) => {
  806. let closestValue = values[0];
  807. let rank = Math.abs(closestValue - reference);
  808. for (let i = 1; i < values.length; i++) {
  809. const value = values[i];
  810. /**
  811. * This code prioritizes the first
  812. * closest result. Given two values
  813. * with the same distance from reference,
  814. * this code will prioritize the smaller of
  815. * the two values.
  816. */
  817. const valueRank = Math.abs(value - reference);
  818. if (valueRank < rank) {
  819. closestValue = value;
  820. rank = valueRank;
  821. }
  822. }
  823. return closestValue;
  824. };
  825. const getFormattedDayPeriod = (dayPeriod) => {
  826. if (dayPeriod === undefined) {
  827. return '';
  828. }
  829. return dayPeriod.toUpperCase();
  830. };
  831. /**
  832. * Including time zone options may lead to the rendered text showing a
  833. * different time from what was selected in the Datetime, which could cause
  834. * confusion.
  835. */
  836. const stripTimeZone = (formatOptions) => {
  837. return Object.assign(Object.assign({}, formatOptions), {
  838. /**
  839. * Setting the time zone to UTC ensures that the value shown is always the
  840. * same as what was selected and safeguards against older Safari bugs with
  841. * Intl.DateTimeFormat.
  842. */
  843. timeZone: 'UTC',
  844. /**
  845. * We do not want to display the time zone name
  846. */
  847. timeZoneName: undefined });
  848. };
  849. const getLocalizedTime = (locale, refParts, hourCycle, formatOptions = { hour: 'numeric', minute: 'numeric' }) => {
  850. const timeParts = {
  851. hour: refParts.hour,
  852. minute: refParts.minute,
  853. };
  854. if (timeParts.hour === undefined || timeParts.minute === undefined) {
  855. return 'Invalid Time';
  856. }
  857. return new Intl.DateTimeFormat(locale, Object.assign(Object.assign({}, stripTimeZone(formatOptions)), {
  858. /**
  859. * We use hourCycle here instead of hour12 due to:
  860. * https://bugs.chromium.org/p/chromium/issues/detail?id=1347316&q=hour12&can=2
  861. */
  862. hourCycle })).format(new Date(convertDataToISO(Object.assign({
  863. /**
  864. * JS uses a simplified ISO 8601 format which allows for
  865. * date-only formats and date-time formats, but not
  866. * time-only formats: https://tc39.es/ecma262/#sec-date-time-string-format
  867. * As a result, developers who only pass a time will get
  868. * an "Invalid Date" error. To account for this, we make sure that
  869. * year/day/month values are set when passing to new Date().
  870. * The Intl.DateTimeFormat call above only uses the hour/minute
  871. * values, so passing these date values should have no impact
  872. * on the time output.
  873. */
  874. year: 2023, day: 1, month: 1 }, timeParts)) + 'Z'));
  875. };
  876. /**
  877. * Adds padding to a time value so
  878. * that it is always 2 digits.
  879. */
  880. const addTimePadding = (value) => {
  881. const valueToString = value.toString();
  882. if (valueToString.length > 1) {
  883. return valueToString;
  884. }
  885. return `0${valueToString}`;
  886. };
  887. /**
  888. * Formats 24 hour times so that
  889. * it always has 2 digits. For
  890. * 12 hour times it ensures that
  891. * hour 0 is formatted as '12'.
  892. */
  893. const getFormattedHour = (hour, hourCycle) => {
  894. /**
  895. * Midnight for h11 starts at 0:00am
  896. * Midnight for h12 starts at 12:00am
  897. * Midnight for h23 starts at 00:00
  898. * Midnight for h24 starts at 24:00
  899. */
  900. if (hour === 0) {
  901. switch (hourCycle) {
  902. case 'h11':
  903. return '0';
  904. case 'h12':
  905. return '12';
  906. case 'h23':
  907. return '00';
  908. case 'h24':
  909. return '24';
  910. default:
  911. throw new Error(`Invalid hour cycle "${hourCycle}"`);
  912. }
  913. }
  914. const use24Hour = is24Hour(hourCycle);
  915. /**
  916. * h23 and h24 use 24 hour times.
  917. */
  918. if (use24Hour) {
  919. return addTimePadding(hour);
  920. }
  921. return hour.toString();
  922. };
  923. /**
  924. * Generates an aria-label to be read by screen readers
  925. * given a local, a date, and whether or not that date is
  926. * today's date.
  927. */
  928. const generateDayAriaLabel = (locale, today, refParts) => {
  929. if (refParts.day === null) {
  930. return null;
  931. }
  932. /**
  933. * MM/DD/YYYY will return midnight in the user's timezone.
  934. */
  935. const date = getNormalizedDate(refParts);
  936. const labelString = new Intl.DateTimeFormat(locale, {
  937. weekday: 'long',
  938. month: 'long',
  939. day: 'numeric',
  940. timeZone: 'UTC',
  941. }).format(date);
  942. /**
  943. * If date is today, prepend "Today" so screen readers indicate
  944. * that the date is today.
  945. */
  946. return today ? `Today, ${labelString}` : labelString;
  947. };
  948. /**
  949. * Given a locale and a date object,
  950. * return a formatted string that includes
  951. * the month name and full year.
  952. * Example: May 2021
  953. */
  954. const getMonthAndYear = (locale, refParts) => {
  955. const date = getNormalizedDate(refParts);
  956. return new Intl.DateTimeFormat(locale, { month: 'long', year: 'numeric', timeZone: 'UTC' }).format(date);
  957. };
  958. /**
  959. * Given a locale and a date object,
  960. * return a formatted string that includes
  961. * the numeric day.
  962. * Note: Some languages will add literal characters
  963. * to the end. This function removes those literals.
  964. * Example: 29
  965. */
  966. const getDay = (locale, refParts) => {
  967. return getLocalizedDateTimeParts(locale, refParts, { day: 'numeric' }).find((obj) => obj.type === 'day').value;
  968. };
  969. /**
  970. * Given a locale and a date object,
  971. * return a formatted string that includes
  972. * the numeric year.
  973. * Example: 2022
  974. */
  975. const getYear = (locale, refParts) => {
  976. return getLocalizedDateTime(locale, refParts, { year: 'numeric' });
  977. };
  978. /**
  979. * Given reference parts, return a JS Date object
  980. * with a normalized time.
  981. */
  982. const getNormalizedDate = (refParts) => {
  983. var _a, _b, _c;
  984. const timeString = refParts.hour !== undefined && refParts.minute !== undefined ? ` ${refParts.hour}:${refParts.minute}` : '';
  985. /**
  986. * We use / notation here for the date
  987. * so we do not need to do extra work and pad values with zeroes.
  988. * Values such as YYYY-MM are still valid, so
  989. * we add fallback values so we still get
  990. * a valid date otherwise we will pass in a string
  991. * like "//2023". Some browsers, such as Chrome, will
  992. * account for this and still return a valid date. However,
  993. * this is not a consistent behavior across all browsers.
  994. */
  995. return new Date(`${(_a = refParts.month) !== null && _a !== void 0 ? _a : 1}/${(_b = refParts.day) !== null && _b !== void 0 ? _b : 1}/${(_c = refParts.year) !== null && _c !== void 0 ? _c : 2023}${timeString} GMT+0000`);
  996. };
  997. /**
  998. * Given a locale, DatetimeParts, and options
  999. * format the DatetimeParts according to the options
  1000. * and locale combination. This returns a string. If
  1001. * you want an array of the individual pieces
  1002. * that make up the localized date string, use
  1003. * getLocalizedDateTimeParts.
  1004. */
  1005. const getLocalizedDateTime = (locale, refParts, options) => {
  1006. const date = getNormalizedDate(refParts);
  1007. return getDateTimeFormat(locale, stripTimeZone(options)).format(date);
  1008. };
  1009. /**
  1010. * Given a locale, DatetimeParts, and options
  1011. * format the DatetimeParts according to the options
  1012. * and locale combination. This returns an array of
  1013. * each piece of the date.
  1014. */
  1015. const getLocalizedDateTimeParts = (locale, refParts, options) => {
  1016. const date = getNormalizedDate(refParts);
  1017. return getDateTimeFormat(locale, options).formatToParts(date);
  1018. };
  1019. /**
  1020. * Wrapper function for Intl.DateTimeFormat.
  1021. * Allows developers to apply an allowed format to DatetimeParts.
  1022. * This function also has built in safeguards for older browser bugs
  1023. * with Intl.DateTimeFormat.
  1024. */
  1025. const getDateTimeFormat = (locale, options) => {
  1026. return new Intl.DateTimeFormat(locale, Object.assign(Object.assign({}, options), { timeZone: 'UTC' }));
  1027. };
  1028. /**
  1029. * Gets a localized version of "Today"
  1030. * Falls back to "Today" in English for
  1031. * browsers that do not support RelativeTimeFormat.
  1032. */
  1033. const getTodayLabel = (locale) => {
  1034. if ('RelativeTimeFormat' in Intl) {
  1035. const label = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' }).format(0, 'day');
  1036. return label.charAt(0).toUpperCase() + label.slice(1);
  1037. }
  1038. else {
  1039. return 'Today';
  1040. }
  1041. };
  1042. /**
  1043. * When calling toISOString(), the browser
  1044. * will convert the date to UTC time by either adding
  1045. * or subtracting the time zone offset.
  1046. * To work around this, we need to either add
  1047. * or subtract the time zone offset to the Date
  1048. * object prior to calling toISOString().
  1049. * This allows us to get an ISO string
  1050. * that is in the user's time zone.
  1051. *
  1052. * Example:
  1053. * Time zone offset is 240
  1054. * Meaning: The browser needs to add 240 minutes
  1055. * to the Date object to get UTC time.
  1056. * What Ionic does: We subtract 240 minutes
  1057. * from the Date object. The browser then adds
  1058. * 240 minutes in toISOString(). The result
  1059. * is a time that is in the user's time zone
  1060. * and not UTC.
  1061. *
  1062. * Note: Some timezones include minute adjustments
  1063. * such as 30 or 45 minutes. This is why we use setMinutes
  1064. * instead of setHours.
  1065. * Example: India Standard Time
  1066. * Timezone offset: -330 = -5.5 hours.
  1067. *
  1068. * List of timezones with 30 and 45 minute timezones:
  1069. * https://www.timeanddate.com/time/time-zones-interesting.html
  1070. */
  1071. const removeDateTzOffset = (date) => {
  1072. const tzOffset = date.getTimezoneOffset();
  1073. date.setMinutes(date.getMinutes() - tzOffset);
  1074. return date;
  1075. };
  1076. const DATE_AM = removeDateTzOffset(new Date('2022T01:00'));
  1077. const DATE_PM = removeDateTzOffset(new Date('2022T13:00'));
  1078. /**
  1079. * Formats the locale's string representation of the day period (am/pm) for a given
  1080. * ref parts day period.
  1081. *
  1082. * @param locale The locale to format the day period in.
  1083. * @param value The date string, in ISO format.
  1084. * @returns The localized day period (am/pm) representation of the given value.
  1085. */
  1086. const getLocalizedDayPeriod = (locale, dayPeriod) => {
  1087. const date = dayPeriod === 'am' ? DATE_AM : DATE_PM;
  1088. const localizedDayPeriod = new Intl.DateTimeFormat(locale, {
  1089. hour: 'numeric',
  1090. timeZone: 'UTC',
  1091. })
  1092. .formatToParts(date)
  1093. .find((part) => part.type === 'dayPeriod');
  1094. if (localizedDayPeriod) {
  1095. return localizedDayPeriod.value;
  1096. }
  1097. return getFormattedDayPeriod(dayPeriod);
  1098. };
  1099. /**
  1100. * Formats the datetime's value to a string, for use in the native input.
  1101. *
  1102. * @param value The value to format, either an ISO string or an array thereof.
  1103. */
  1104. const formatValue = (value) => {
  1105. return Array.isArray(value) ? value.join(',') : value;
  1106. };
  1107. /**
  1108. * Returns the current date as
  1109. * an ISO string in the user's
  1110. * time zone.
  1111. */
  1112. const getToday = () => {
  1113. /**
  1114. * ion-datetime intentionally does not
  1115. * parse time zones/do automatic time zone
  1116. * conversion when accepting user input.
  1117. * However when we get today's date string,
  1118. * we want it formatted relative to the user's
  1119. * time zone.
  1120. *
  1121. * When calling toISOString(), the browser
  1122. * will convert the date to UTC time by either adding
  1123. * or subtracting the time zone offset.
  1124. * To work around this, we need to either add
  1125. * or subtract the time zone offset to the Date
  1126. * object prior to calling toISOString().
  1127. * This allows us to get an ISO string
  1128. * that is in the user's time zone.
  1129. */
  1130. return removeDateTzOffset(new Date()).toISOString();
  1131. };
  1132. const minutes = [
  1133. 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
  1134. 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
  1135. ];
  1136. // h11 hour system uses 0-11. Midnight starts at 0:00am.
  1137. const hour11 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
  1138. // h12 hour system uses 0-12. Midnight starts at 12:00am.
  1139. const hour12 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
  1140. // h23 hour system uses 0-23. Midnight starts at 0:00.
  1141. const hour23 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23];
  1142. // h24 hour system uses 1-24. Midnight starts at 24:00.
  1143. const hour24 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 0];
  1144. /**
  1145. * Given a locale and a mode,
  1146. * return an array with formatted days
  1147. * of the week. iOS should display days
  1148. * such as "Mon" or "Tue".
  1149. * MD should display days such as "M"
  1150. * or "T".
  1151. */
  1152. const getDaysOfWeek = (locale, mode, firstDayOfWeek = 0) => {
  1153. /**
  1154. * Nov 1st, 2020 starts on a Sunday.
  1155. * ion-datetime assumes weeks start on Sunday,
  1156. * but is configurable via `firstDayOfWeek`.
  1157. */
  1158. const weekdayFormat = mode === 'ios' ? 'short' : 'narrow';
  1159. const intl = new Intl.DateTimeFormat(locale, { weekday: weekdayFormat });
  1160. const startDate = new Date('11/01/2020');
  1161. const daysOfWeek = [];
  1162. /**
  1163. * For each day of the week,
  1164. * get the day name.
  1165. */
  1166. for (let i = firstDayOfWeek; i < firstDayOfWeek + 7; i++) {
  1167. const currentDate = new Date(startDate);
  1168. currentDate.setDate(currentDate.getDate() + i);
  1169. daysOfWeek.push(intl.format(currentDate));
  1170. }
  1171. return daysOfWeek;
  1172. };
  1173. /**
  1174. * Returns an array containing all of the
  1175. * days in a month for a given year. Values are
  1176. * aligned with a week calendar starting on
  1177. * the firstDayOfWeek value (Sunday by default)
  1178. * using null values.
  1179. */
  1180. const getDaysOfMonth = (month, year, firstDayOfWeek) => {
  1181. const numDays = getNumDaysInMonth(month, year);
  1182. const firstOfMonth = new Date(`${month}/1/${year}`).getDay();
  1183. /**
  1184. * To get the first day of the month aligned on the correct
  1185. * day of the week, we need to determine how many "filler" days
  1186. * to generate. These filler days as empty/disabled buttons
  1187. * that fill the space of the days of the week before the first
  1188. * of the month.
  1189. *
  1190. * There are two cases here:
  1191. *
  1192. * 1. If firstOfMonth = 4, firstDayOfWeek = 0 then the offset
  1193. * is (4 - (0 + 1)) = 3. Since the offset loop goes from 0 to 3 inclusive,
  1194. * this will generate 4 filler days (0, 1, 2, 3), and then day of week 4 will have
  1195. * the first day of the month.
  1196. *
  1197. * 2. If firstOfMonth = 2, firstDayOfWeek = 4 then the offset
  1198. * is (6 - (4 - 2)) = 4. Since the offset loop goes from 0 to 4 inclusive,
  1199. * this will generate 5 filler days (0, 1, 2, 3, 4), and then day of week 5 will have
  1200. * the first day of the month.
  1201. */
  1202. const offset = firstOfMonth >= firstDayOfWeek ? firstOfMonth - (firstDayOfWeek + 1) : 6 - (firstDayOfWeek - firstOfMonth);
  1203. let days = [];
  1204. for (let i = 1; i <= numDays; i++) {
  1205. days.push({ day: i, dayOfWeek: (offset + i) % 7 });
  1206. }
  1207. for (let i = 0; i <= offset; i++) {
  1208. days = [{ day: null, dayOfWeek: null }, ...days];
  1209. }
  1210. return days;
  1211. };
  1212. /**
  1213. * Returns an array of pre-defined hour
  1214. * values based on the provided hourCycle.
  1215. */
  1216. const getHourData = (hourCycle) => {
  1217. switch (hourCycle) {
  1218. case 'h11':
  1219. return hour11;
  1220. case 'h12':
  1221. return hour12;
  1222. case 'h23':
  1223. return hour23;
  1224. case 'h24':
  1225. return hour24;
  1226. default:
  1227. throw new Error(`Invalid hour cycle "${hourCycle}"`);
  1228. }
  1229. };
  1230. /**
  1231. * Given a local, reference datetime parts and option
  1232. * max/min bound datetime parts, calculate the acceptable
  1233. * hour and minute values according to the bounds and locale.
  1234. */
  1235. const generateTime = (locale, refParts, hourCycle = 'h12', minParts, maxParts, hourValues, minuteValues) => {
  1236. const computedHourCycle = getHourCycle(locale, hourCycle);
  1237. const use24Hour = is24Hour(computedHourCycle);
  1238. let processedHours = getHourData(computedHourCycle);
  1239. let processedMinutes = minutes;
  1240. let isAMAllowed = true;
  1241. let isPMAllowed = true;
  1242. if (hourValues) {
  1243. processedHours = processedHours.filter((hour) => hourValues.includes(hour));
  1244. }
  1245. if (minuteValues) {
  1246. processedMinutes = processedMinutes.filter((minute) => minuteValues.includes(minute));
  1247. }
  1248. if (minParts) {
  1249. /**
  1250. * If ref day is the same as the
  1251. * minimum allowed day, filter hour/minute
  1252. * values according to min hour and minute.
  1253. */
  1254. if (isSameDay(refParts, minParts)) {
  1255. /**
  1256. * Users may not always set the hour/minute for
  1257. * min value (i.e. 2021-06-02) so we should allow
  1258. * all hours/minutes in that case.
  1259. */
  1260. if (minParts.hour !== undefined) {
  1261. processedHours = processedHours.filter((hour) => {
  1262. const convertedHour = refParts.ampm === 'pm' ? (hour + 12) % 24 : hour;
  1263. return (use24Hour ? hour : convertedHour) >= minParts.hour;
  1264. });
  1265. isAMAllowed = minParts.hour < 13;
  1266. }
  1267. if (minParts.minute !== undefined) {
  1268. /**
  1269. * The minimum minute range should not be enforced when
  1270. * the hour is greater than the min hour.
  1271. *
  1272. * For example with a minimum range of 09:30, users
  1273. * should be able to select 10:00-10:29 and beyond.
  1274. */
  1275. let isPastMinHour = false;
  1276. if (minParts.hour !== undefined && refParts.hour !== undefined) {
  1277. if (refParts.hour > minParts.hour) {
  1278. isPastMinHour = true;
  1279. }
  1280. }
  1281. processedMinutes = processedMinutes.filter((minute) => {
  1282. if (isPastMinHour) {
  1283. return true;
  1284. }
  1285. return minute >= minParts.minute;
  1286. });
  1287. }
  1288. /**
  1289. * If ref day is before minimum
  1290. * day do not render any hours/minute values
  1291. */
  1292. }
  1293. else if (isBefore(refParts, minParts)) {
  1294. processedHours = [];
  1295. processedMinutes = [];
  1296. isAMAllowed = isPMAllowed = false;
  1297. }
  1298. }
  1299. if (maxParts) {
  1300. /**
  1301. * If ref day is the same as the
  1302. * maximum allowed day, filter hour/minute
  1303. * values according to max hour and minute.
  1304. */
  1305. if (isSameDay(refParts, maxParts)) {
  1306. /**
  1307. * Users may not always set the hour/minute for
  1308. * max value (i.e. 2021-06-02) so we should allow
  1309. * all hours/minutes in that case.
  1310. */
  1311. if (maxParts.hour !== undefined) {
  1312. processedHours = processedHours.filter((hour) => {
  1313. const convertedHour = refParts.ampm === 'pm' ? (hour + 12) % 24 : hour;
  1314. return (use24Hour ? hour : convertedHour) <= maxParts.hour;
  1315. });
  1316. isPMAllowed = maxParts.hour >= 12;
  1317. }
  1318. if (maxParts.minute !== undefined && refParts.hour === maxParts.hour) {
  1319. // The available minutes should only be filtered when the hour is the same as the max hour.
  1320. // For example if the max hour is 10:30 and the current hour is 10:00,
  1321. // users should be able to select 00-30 minutes.
  1322. // If the current hour is 09:00, users should be able to select 00-60 minutes.
  1323. processedMinutes = processedMinutes.filter((minute) => minute <= maxParts.minute);
  1324. }
  1325. /**
  1326. * If ref day is after minimum
  1327. * day do not render any hours/minute values
  1328. */
  1329. }
  1330. else if (isAfter(refParts, maxParts)) {
  1331. processedHours = [];
  1332. processedMinutes = [];
  1333. isAMAllowed = isPMAllowed = false;
  1334. }
  1335. }
  1336. return {
  1337. hours: processedHours,
  1338. minutes: processedMinutes,
  1339. am: isAMAllowed,
  1340. pm: isPMAllowed,
  1341. };
  1342. };
  1343. /**
  1344. * Given DatetimeParts, generate the previous,
  1345. * current, and and next months.
  1346. */
  1347. const generateMonths = (refParts, forcedDate) => {
  1348. const current = { month: refParts.month, year: refParts.year, day: refParts.day };
  1349. /**
  1350. * If we're forcing a month to appear, and it's different from the current month,
  1351. * ensure it appears by replacing the next or previous month as appropriate.
  1352. */
  1353. if (forcedDate !== undefined && (refParts.month !== forcedDate.month || refParts.year !== forcedDate.year)) {
  1354. const forced = { month: forcedDate.month, year: forcedDate.year, day: forcedDate.day };
  1355. const forcedMonthIsBefore = isBefore(forced, current);
  1356. return forcedMonthIsBefore
  1357. ? [forced, current, getNextMonth(refParts)]
  1358. : [getPreviousMonth(refParts), current, forced];
  1359. }
  1360. return [getPreviousMonth(refParts), current, getNextMonth(refParts)];
  1361. };
  1362. const getMonthColumnData = (locale, refParts, minParts, maxParts, monthValues, formatOptions = {
  1363. month: 'long',
  1364. }) => {
  1365. const { year } = refParts;
  1366. const months = [];
  1367. if (monthValues !== undefined) {
  1368. let processedMonths = monthValues;
  1369. if ((maxParts === null || maxParts === void 0 ? void 0 : maxParts.month) !== undefined) {
  1370. processedMonths = processedMonths.filter((month) => month <= maxParts.month);
  1371. }
  1372. if ((minParts === null || minParts === void 0 ? void 0 : minParts.month) !== undefined) {
  1373. processedMonths = processedMonths.filter((month) => month >= minParts.month);
  1374. }
  1375. processedMonths.forEach((processedMonth) => {
  1376. const date = new Date(`${processedMonth}/1/${year} GMT+0000`);
  1377. const monthString = new Intl.DateTimeFormat(locale, Object.assign(Object.assign({}, formatOptions), { timeZone: 'UTC' })).format(date);
  1378. months.push({ text: monthString, value: processedMonth });
  1379. });
  1380. }
  1381. else {
  1382. const maxMonth = maxParts && maxParts.year === year ? maxParts.month : 12;
  1383. const minMonth = minParts && minParts.year === year ? minParts.month : 1;
  1384. for (let i = minMonth; i <= maxMonth; i++) {
  1385. /**
  1386. *
  1387. * There is a bug on iOS 14 where
  1388. * Intl.DateTimeFormat takes into account
  1389. * the local timezone offset when formatting dates.
  1390. *
  1391. * Forcing the timezone to 'UTC' fixes the issue. However,
  1392. * we should keep this workaround as it is safer. In the event
  1393. * this breaks in another browser, we will not be impacted
  1394. * because all dates will be interpreted in UTC.
  1395. *
  1396. * Example:
  1397. * new Intl.DateTimeFormat('en-US', { month: 'long' }).format(new Date('Sat Apr 01 2006 00:00:00 GMT-0400 (EDT)')) // "March"
  1398. * new Intl.DateTimeFormat('en-US', { month: 'long', timeZone: 'UTC' }).format(new Date('Sat Apr 01 2006 00:00:00 GMT-0400 (EDT)')) // "April"
  1399. *
  1400. * In certain timezones, iOS 14 shows the wrong
  1401. * date for .toUTCString(). To combat this, we
  1402. * force all of the timezones to GMT+0000 (UTC).
  1403. *
  1404. * Example:
  1405. * Time Zone: Central European Standard Time
  1406. * new Date('1/1/1992').toUTCString() // "Tue, 31 Dec 1991 23:00:00 GMT"
  1407. * new Date('1/1/1992 GMT+0000').toUTCString() // "Wed, 01 Jan 1992 00:00:00 GMT"
  1408. */
  1409. const date = new Date(`${i}/1/${year} GMT+0000`);
  1410. const monthString = new Intl.DateTimeFormat(locale, Object.assign(Object.assign({}, formatOptions), { timeZone: 'UTC' })).format(date);
  1411. months.push({ text: monthString, value: i });
  1412. }
  1413. }
  1414. return months;
  1415. };
  1416. /**
  1417. * Returns information regarding
  1418. * selectable dates (i.e 1st, 2nd, 3rd, etc)
  1419. * within a reference month.
  1420. * @param locale The locale to format the date with
  1421. * @param refParts The reference month/year to generate dates for
  1422. * @param minParts The minimum bound on the date that can be returned
  1423. * @param maxParts The maximum bound on the date that can be returned
  1424. * @param dayValues The allowed date values
  1425. * @returns Date data to be used in ion-picker-column
  1426. */
  1427. const getDayColumnData = (locale, refParts, minParts, maxParts, dayValues, formatOptions = {
  1428. day: 'numeric',
  1429. }) => {
  1430. const { month, year } = refParts;
  1431. const days = [];
  1432. /**
  1433. * If we have max/min bounds that in the same
  1434. * month/year as the refParts, we should
  1435. * use the define day as the max/min day.
  1436. * Otherwise, fallback to the max/min days in a month.
  1437. */
  1438. const numDaysInMonth = getNumDaysInMonth(month, year);
  1439. const maxDay = (maxParts === null || maxParts === void 0 ? void 0 : maxParts.day) !== null && (maxParts === null || maxParts === void 0 ? void 0 : maxParts.day) !== undefined && maxParts.year === year && maxParts.month === month
  1440. ? maxParts.day
  1441. : numDaysInMonth;
  1442. const minDay = (minParts === null || minParts === void 0 ? void 0 : minParts.day) !== null && (minParts === null || minParts === void 0 ? void 0 : minParts.day) !== undefined && minParts.year === year && minParts.month === month
  1443. ? minParts.day
  1444. : 1;
  1445. if (dayValues !== undefined) {
  1446. let processedDays = dayValues;
  1447. processedDays = processedDays.filter((day) => day >= minDay && day <= maxDay);
  1448. processedDays.forEach((processedDay) => {
  1449. const date = new Date(`${month}/${processedDay}/${year} GMT+0000`);
  1450. const dayString = new Intl.DateTimeFormat(locale, Object.assign(Object.assign({}, formatOptions), { timeZone: 'UTC' })).format(date);
  1451. days.push({ text: dayString, value: processedDay });
  1452. });
  1453. }
  1454. else {
  1455. for (let i = minDay; i <= maxDay; i++) {
  1456. const date = new Date(`${month}/${i}/${year} GMT+0000`);
  1457. const dayString = new Intl.DateTimeFormat(locale, Object.assign(Object.assign({}, formatOptions), { timeZone: 'UTC' })).format(date);
  1458. days.push({ text: dayString, value: i });
  1459. }
  1460. }
  1461. return days;
  1462. };
  1463. const getYearColumnData = (locale, refParts, minParts, maxParts, yearValues) => {
  1464. var _a, _b;
  1465. let processedYears = [];
  1466. if (yearValues !== undefined) {
  1467. processedYears = yearValues;
  1468. if ((maxParts === null || maxParts === void 0 ? void 0 : maxParts.year) !== undefined) {
  1469. processedYears = processedYears.filter((year) => year <= maxParts.year);
  1470. }
  1471. if ((minParts === null || minParts === void 0 ? void 0 : minParts.year) !== undefined) {
  1472. processedYears = processedYears.filter((year) => year >= minParts.year);
  1473. }
  1474. }
  1475. else {
  1476. const { year } = refParts;
  1477. const maxYear = (_a = maxParts === null || maxParts === void 0 ? void 0 : maxParts.year) !== null && _a !== void 0 ? _a : year;
  1478. const minYear = (_b = minParts === null || minParts === void 0 ? void 0 : minParts.year) !== null && _b !== void 0 ? _b : year - 100;
  1479. for (let i = minYear; i <= maxYear; i++) {
  1480. processedYears.push(i);
  1481. }
  1482. }
  1483. return processedYears.map((year) => ({
  1484. text: getYear(locale, { year, month: refParts.month, day: refParts.day }),
  1485. value: year,
  1486. }));
  1487. };
  1488. /**
  1489. * Given a starting date and an upper bound,
  1490. * this functions returns an array of all
  1491. * month objects in that range.
  1492. */
  1493. const getAllMonthsInRange = (currentParts, maxParts) => {
  1494. if (currentParts.month === maxParts.month && currentParts.year === maxParts.year) {
  1495. return [currentParts];
  1496. }
  1497. return [currentParts, ...getAllMonthsInRange(getNextMonth(currentParts), maxParts)];
  1498. };
  1499. /**
  1500. * Creates and returns picker items
  1501. * that represent the days in a month.
  1502. * Example: "Thu, Jun 2"
  1503. */
  1504. const getCombinedDateColumnData = (locale, todayParts, minParts, maxParts, dayValues, monthValues) => {
  1505. let items = [];
  1506. let parts = [];
  1507. /**
  1508. * Get all month objects from the min date
  1509. * to the max date. Note: Do not use getMonthColumnData
  1510. * as that function only generates dates within a
  1511. * single year.
  1512. */
  1513. let months = getAllMonthsInRange(minParts, maxParts);
  1514. /**
  1515. * Filter out any disallowed month values.
  1516. */
  1517. if (monthValues) {
  1518. months = months.filter(({ month }) => monthValues.includes(month));
  1519. }
  1520. /**
  1521. * Get all of the days in the month.
  1522. * From there, generate an array where
  1523. * each item has the month, date, and day
  1524. * of work as the text.
  1525. */
  1526. months.forEach((monthObject) => {
  1527. const referenceMonth = { month: monthObject.month, day: null, year: monthObject.year };
  1528. const monthDays = getDayColumnData(locale, referenceMonth, minParts, maxParts, dayValues, {
  1529. month: 'short',
  1530. day: 'numeric',
  1531. weekday: 'short',
  1532. });
  1533. const dateParts = [];
  1534. const dateColumnItems = [];
  1535. monthDays.forEach((dayObject) => {
  1536. const isToday = isSameDay(Object.assign(Object.assign({}, referenceMonth), { day: dayObject.value }), todayParts);
  1537. /**
  1538. * Today's date should read as "Today" (localized)
  1539. * not the actual date string
  1540. */
  1541. dateColumnItems.push({
  1542. text: isToday ? getTodayLabel(locale) : dayObject.text,
  1543. value: `${referenceMonth.year}-${referenceMonth.month}-${dayObject.value}`,
  1544. });
  1545. /**
  1546. * When selecting a date in the wheel picker
  1547. * we need access to the raw datetime parts data.
  1548. * The picker column only accepts values of
  1549. * type string or number, so we need to return
  1550. * two sets of data: A data set to be passed
  1551. * to the picker column, and a data set to
  1552. * be used to reference the raw data when
  1553. * updating the picker column value.
  1554. */
  1555. dateParts.push({
  1556. month: referenceMonth.month,
  1557. year: referenceMonth.year,
  1558. day: dayObject.value,
  1559. });
  1560. });
  1561. parts = [...parts, ...dateParts];
  1562. items = [...items, ...dateColumnItems];
  1563. });
  1564. return {
  1565. parts,
  1566. items,
  1567. };
  1568. };
  1569. const getTimeColumnsData = (locale, refParts, hourCycle, minParts, maxParts, allowedHourValues, allowedMinuteValues) => {
  1570. const computedHourCycle = getHourCycle(locale, hourCycle);
  1571. const use24Hour = is24Hour(computedHourCycle);
  1572. const { hours, minutes, am, pm } = generateTime(locale, refParts, computedHourCycle, minParts, maxParts, allowedHourValues, allowedMinuteValues);
  1573. const hoursItems = hours.map((hour) => {
  1574. return {
  1575. text: getFormattedHour(hour, computedHourCycle),
  1576. value: getInternalHourValue(hour, use24Hour, refParts.ampm),
  1577. };
  1578. });
  1579. const minutesItems = minutes.map((minute) => {
  1580. return {
  1581. text: addTimePadding(minute),
  1582. value: minute,
  1583. };
  1584. });
  1585. const dayPeriodItems = [];
  1586. if (am && !use24Hour) {
  1587. dayPeriodItems.push({
  1588. text: getLocalizedDayPeriod(locale, 'am'),
  1589. value: 'am',
  1590. });
  1591. }
  1592. if (pm && !use24Hour) {
  1593. dayPeriodItems.push({
  1594. text: getLocalizedDayPeriod(locale, 'pm'),
  1595. value: 'pm',
  1596. });
  1597. }
  1598. return {
  1599. minutesData: minutesItems,
  1600. hoursData: hoursItems,
  1601. dayPeriodData: dayPeriodItems,
  1602. };
  1603. };
  1604. exports.calculateHourFromAMPM = calculateHourFromAMPM;
  1605. exports.clampDate = clampDate;
  1606. exports.convertDataToISO = convertDataToISO;
  1607. exports.convertToArrayOfNumbers = convertToArrayOfNumbers;
  1608. exports.formatValue = formatValue;
  1609. exports.generateDayAriaLabel = generateDayAriaLabel;
  1610. exports.generateMonths = generateMonths;
  1611. exports.getClosestValidDate = getClosestValidDate;
  1612. exports.getCombinedDateColumnData = getCombinedDateColumnData;
  1613. exports.getDay = getDay;
  1614. exports.getDayColumnData = getDayColumnData;
  1615. exports.getDaysOfMonth = getDaysOfMonth;
  1616. exports.getDaysOfWeek = getDaysOfWeek;
  1617. exports.getEndOfWeek = getEndOfWeek;
  1618. exports.getHourCycle = getHourCycle;
  1619. exports.getLocalizedDateTime = getLocalizedDateTime;
  1620. exports.getLocalizedTime = getLocalizedTime;
  1621. exports.getMonthAndYear = getMonthAndYear;
  1622. exports.getMonthColumnData = getMonthColumnData;
  1623. exports.getNextDay = getNextDay;
  1624. exports.getNextMonth = getNextMonth;
  1625. exports.getNextWeek = getNextWeek;
  1626. exports.getNextYear = getNextYear;
  1627. exports.getNumDaysInMonth = getNumDaysInMonth;
  1628. exports.getPartsFromCalendarDay = getPartsFromCalendarDay;
  1629. exports.getPreviousDay = getPreviousDay;
  1630. exports.getPreviousMonth = getPreviousMonth;
  1631. exports.getPreviousWeek = getPreviousWeek;
  1632. exports.getPreviousYear = getPreviousYear;
  1633. exports.getStartOfWeek = getStartOfWeek;
  1634. exports.getTimeColumnsData = getTimeColumnsData;
  1635. exports.getToday = getToday;
  1636. exports.getYearColumnData = getYearColumnData;
  1637. exports.isAfter = isAfter;
  1638. exports.isBefore = isBefore;
  1639. exports.isLocaleDayPeriodRTL = isLocaleDayPeriodRTL;
  1640. exports.isMonthFirstLocale = isMonthFirstLocale;
  1641. exports.isSameDay = isSameDay;
  1642. exports.parseAmPm = parseAmPm;
  1643. exports.parseDate = parseDate;
  1644. exports.parseMaxParts = parseMaxParts;
  1645. exports.parseMinParts = parseMinParts;
  1646. exports.validateParts = validateParts;
  1647. exports.warnIfValueOutOfBounds = warnIfValueOutOfBounds;