Time.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706
  1. /* *
  2. *
  3. * (c) 2010-2019 Torstein Honsi
  4. *
  5. * License: www.highcharts.com/license
  6. *
  7. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  8. *
  9. * */
  10. 'use strict';
  11. import Highcharts from './Globals.js';
  12. import U from './Utilities.js';
  13. var defined = U.defined, extend = U.extend, isObject = U.isObject, objectEach = U.objectEach, pad = U.pad, pick = U.pick, splat = U.splat;
  14. /**
  15. * Normalized interval.
  16. *
  17. * @interface Highcharts.TimeNormalizedObject
  18. */ /**
  19. * The count.
  20. *
  21. * @name Highcharts.TimeNormalizedObject#count
  22. * @type {number}
  23. */ /**
  24. * The interval in axis values (ms).
  25. *
  26. * @name Highcharts.TimeNormalizedObject#unitRange
  27. * @type {number}
  28. */
  29. /**
  30. * Function of an additional date format specifier.
  31. *
  32. * @callback Highcharts.TimeFormatCallbackFunction
  33. *
  34. * @param {number} timestamp
  35. * The time to format.
  36. *
  37. * @return {string}
  38. * The formatted portion of the date.
  39. */
  40. /**
  41. * Additonal time tick information.
  42. *
  43. * @interface Highcharts.TimeTicksInfoObject
  44. * @augments Highcharts.TimeNormalizedObject
  45. */ /**
  46. * @name Highcharts.TimeTicksInfoObject#higherRanks
  47. * @type {Array<string>}
  48. */ /**
  49. * @name Highcharts.TimeTicksInfoObject#totalRange
  50. * @type {number}
  51. */
  52. /**
  53. * Time ticks.
  54. *
  55. * @interface Highcharts.AxisTickPositionsArray
  56. */ /**
  57. * @name Highcharts.AxisTickPositionsArray#info
  58. * @type {Highcharts.TimeTicksInfoObject}
  59. */
  60. var H = Highcharts, merge = H.merge, timeUnits = H.timeUnits, win = H.win;
  61. /* eslint-disable no-invalid-this, valid-jsdoc */
  62. /**
  63. * The Time class. Time settings are applied in general for each page using
  64. * `Highcharts.setOptions`, or individually for each Chart item through the
  65. * [time](https://api.highcharts.com/highcharts/time) options set.
  66. *
  67. * The Time object is available from {@link Highcharts.Chart#time},
  68. * which refers to `Highcharts.time` if no individual time settings are
  69. * applied.
  70. *
  71. * @example
  72. * // Apply time settings globally
  73. * Highcharts.setOptions({
  74. * time: {
  75. * timezone: 'Europe/London'
  76. * }
  77. * });
  78. *
  79. * // Apply time settings by instance
  80. * var chart = Highcharts.chart('container', {
  81. * time: {
  82. * timezone: 'America/New_York'
  83. * },
  84. * series: [{
  85. * data: [1, 4, 3, 5]
  86. * }]
  87. * });
  88. *
  89. * // Use the Time object
  90. * console.log(
  91. * 'Current time in New York',
  92. * chart.time.dateFormat('%Y-%m-%d %H:%M:%S', Date.now())
  93. * );
  94. *
  95. * @class
  96. * @name Highcharts.Time
  97. *
  98. * @param {Highcharts.TimeOptions} options
  99. * Time options as defined in [chart.options.time](/highcharts/time).
  100. *
  101. * @since 6.0.5
  102. */
  103. Highcharts.Time = function (options) {
  104. this.update(options, false);
  105. };
  106. Highcharts.Time.prototype = {
  107. /**
  108. * Time options that can apply globally or to individual charts. These
  109. * settings affect how `datetime` axes are laid out, how tooltips are
  110. * formatted, how series
  111. * [pointIntervalUnit](#plotOptions.series.pointIntervalUnit) works and how
  112. * the Highstock range selector handles time.
  113. *
  114. * The common use case is that all charts in the same Highcharts object
  115. * share the same time settings, in which case the global settings are set
  116. * using `setOptions`.
  117. *
  118. * ```js
  119. * // Apply time settings globally
  120. * Highcharts.setOptions({
  121. * time: {
  122. * timezone: 'Europe/London'
  123. * }
  124. * });
  125. * // Apply time settings by instance
  126. * var chart = Highcharts.chart('container', {
  127. * time: {
  128. * timezone: 'America/New_York'
  129. * },
  130. * series: [{
  131. * data: [1, 4, 3, 5]
  132. * }]
  133. * });
  134. *
  135. * // Use the Time object
  136. * console.log(
  137. * 'Current time in New York',
  138. * chart.time.dateFormat('%Y-%m-%d %H:%M:%S', Date.now())
  139. * );
  140. * ```
  141. *
  142. * Since v6.0.5, the time options were moved from the `global` obect to the
  143. * `time` object, and time options can be set on each individual chart.
  144. *
  145. * @sample {highcharts|highstock}
  146. * highcharts/time/timezone/
  147. * Set the timezone globally
  148. * @sample {highcharts}
  149. * highcharts/time/individual/
  150. * Set the timezone per chart instance
  151. * @sample {highstock}
  152. * stock/time/individual/
  153. * Set the timezone per chart instance
  154. *
  155. * @since 6.0.5
  156. * @optionparent time
  157. */
  158. defaultOptions: {
  159. /**
  160. * A custom `Date` class for advanced date handling. For example,
  161. * [JDate](https://github.com/tahajahangir/jdate) can be hooked in to
  162. * handle Jalali dates.
  163. *
  164. * @type {*}
  165. * @since 4.0.4
  166. * @product highcharts highstock gantt
  167. */
  168. Date: void 0,
  169. /**
  170. * A callback to return the time zone offset for a given datetime. It
  171. * takes the timestamp in terms of milliseconds since January 1 1970,
  172. * and returns the timezone offset in minutes. This provides a hook
  173. * for drawing time based charts in specific time zones using their
  174. * local DST crossover dates, with the help of external libraries.
  175. *
  176. * @see [global.timezoneOffset](#global.timezoneOffset)
  177. *
  178. * @sample {highcharts|highstock} highcharts/time/gettimezoneoffset/
  179. * Use moment.js to draw Oslo time regardless of browser locale
  180. *
  181. * @since 4.1.0
  182. * @product highcharts highstock gantt
  183. */
  184. getTimezoneOffset: void 0,
  185. /**
  186. * Requires [moment.js](http://momentjs.com/). If the timezone option
  187. * is specified, it creates a default
  188. * [getTimezoneOffset](#time.getTimezoneOffset) function that looks
  189. * up the specified timezone in moment.js. If moment.js is not included,
  190. * this throws a Highcharts error in the console, but does not crash the
  191. * chart.
  192. *
  193. * @see [getTimezoneOffset](#time.getTimezoneOffset)
  194. *
  195. * @sample {highcharts|highstock} highcharts/time/timezone/
  196. * Europe/Oslo
  197. *
  198. * @type {string}
  199. * @since 5.0.7
  200. * @product highcharts highstock gantt
  201. */
  202. timezone: void 0,
  203. /**
  204. * The timezone offset in minutes. Positive values are west, negative
  205. * values are east of UTC, as in the ECMAScript
  206. * [getTimezoneOffset](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTimezoneOffset)
  207. * method. Use this to display UTC based data in a predefined time zone.
  208. *
  209. * @see [time.getTimezoneOffset](#time.getTimezoneOffset)
  210. *
  211. * @sample {highcharts|highstock} highcharts/time/timezoneoffset/
  212. * Timezone offset
  213. *
  214. * @since 3.0.8
  215. * @product highcharts highstock gantt
  216. */
  217. timezoneOffset: 0,
  218. /**
  219. * Whether to use UTC time for axis scaling, tickmark placement and
  220. * time display in `Highcharts.dateFormat`. Advantages of using UTC
  221. * is that the time displays equally regardless of the user agent's
  222. * time zone settings. Local time can be used when the data is loaded
  223. * in real time or when correct Daylight Saving Time transitions are
  224. * required.
  225. *
  226. * @sample {highcharts} highcharts/time/useutc-true/
  227. * True by default
  228. * @sample {highcharts} highcharts/time/useutc-false/
  229. * False
  230. */
  231. useUTC: true
  232. },
  233. /**
  234. * Update the Time object with current options. It is called internally on
  235. * initializing Highcharts, after running `Highcharts.setOptions` and on
  236. * `Chart.update`.
  237. *
  238. * @private
  239. * @function Highcharts.Time#update
  240. *
  241. * @param {Highcharts.TimeOptions} options
  242. *
  243. * @return {void}
  244. */
  245. update: function (options) {
  246. var useUTC = pick(options && options.useUTC, true), time = this;
  247. this.options = options = merge(true, this.options || {}, options);
  248. // Allow using a different Date class
  249. this.Date = options.Date || win.Date || Date;
  250. this.useUTC = useUTC;
  251. this.timezoneOffset = (useUTC && options.timezoneOffset);
  252. /**
  253. * Get the time zone offset based on the current timezone information as
  254. * set in the global options.
  255. *
  256. * @function Highcharts.Time#getTimezoneOffset
  257. *
  258. * @param {number} timestamp
  259. * The JavaScript timestamp to inspect.
  260. *
  261. * @return {number}
  262. * The timezone offset in minutes compared to UTC.
  263. */
  264. this.getTimezoneOffset = this.timezoneOffsetFunction();
  265. /*
  266. * The time object has options allowing for variable time zones, meaning
  267. * the axis ticks or series data needs to consider this.
  268. */
  269. this.variableTimezone = !!(!useUTC ||
  270. options.getTimezoneOffset ||
  271. options.timezone);
  272. // UTC time with timezone handling
  273. if (this.variableTimezone || this.timezoneOffset) {
  274. this.get = function (unit, date) {
  275. var realMs = date.getTime(), ms = realMs - time.getTimezoneOffset(date), ret;
  276. date.setTime(ms); // Temporary adjust to timezone
  277. ret = date['getUTC' + unit]();
  278. date.setTime(realMs); // Reset
  279. return ret;
  280. };
  281. this.set = function (unit, date, value) {
  282. var ms, offset, newOffset;
  283. // For lower order time units, just set it directly using local
  284. // time
  285. if (unit === 'Milliseconds' ||
  286. unit === 'Seconds' ||
  287. // If we're dealting with minutes, we only need to
  288. // consider timezone if we're in Indian time zones with
  289. // half-hour offsets (#8768).
  290. (unit === 'Minutes' &&
  291. date.getTimezoneOffset() % 60 === 0)) {
  292. date['set' + unit](value);
  293. // Higher order time units need to take the time zone into
  294. // account
  295. }
  296. else {
  297. // Adjust by timezone
  298. offset = time.getTimezoneOffset(date);
  299. ms = date.getTime() - offset;
  300. date.setTime(ms);
  301. date['setUTC' + unit](value);
  302. newOffset = time.getTimezoneOffset(date);
  303. ms = date.getTime() + newOffset;
  304. date.setTime(ms);
  305. }
  306. };
  307. // UTC time with no timezone handling
  308. }
  309. else if (useUTC) {
  310. this.get = function (unit, date) {
  311. return date['getUTC' + unit]();
  312. };
  313. this.set = function (unit, date, value) {
  314. return date['setUTC' + unit](value);
  315. };
  316. // Local time
  317. }
  318. else {
  319. this.get = function (unit, date) {
  320. return date['get' + unit]();
  321. };
  322. this.set = function (unit, date, value) {
  323. return date['set' + unit](value);
  324. };
  325. }
  326. },
  327. /**
  328. * Make a time and returns milliseconds. Interprets the inputs as UTC time,
  329. * local time or a specific timezone time depending on the current time
  330. * settings.
  331. *
  332. * @function Highcharts.Time#makeTime
  333. *
  334. * @param {number} year
  335. * The year
  336. *
  337. * @param {number} month
  338. * The month. Zero-based, so January is 0.
  339. *
  340. * @param {number} [date=1]
  341. * The day of the month
  342. *
  343. * @param {number} [hours=0]
  344. * The hour of the day, 0-23.
  345. *
  346. * @param {number} [minutes=0]
  347. * The minutes
  348. *
  349. * @param {number} [seconds=0]
  350. * The seconds
  351. *
  352. * @return {number}
  353. * The time in milliseconds since January 1st 1970.
  354. */
  355. makeTime: function (year, month, date, hours, minutes, seconds) {
  356. var d, offset, newOffset;
  357. if (this.useUTC) {
  358. d = this.Date.UTC.apply(0, arguments);
  359. offset = this.getTimezoneOffset(d);
  360. d += offset;
  361. newOffset = this.getTimezoneOffset(d);
  362. if (offset !== newOffset) {
  363. d += newOffset - offset;
  364. // A special case for transitioning from summer time to winter time.
  365. // When the clock is set back, the same time is repeated twice, i.e.
  366. // 02:30 am is repeated since the clock is set back from 3 am to
  367. // 2 am. We need to make the same time as local Date does.
  368. }
  369. else if (offset - 36e5 === this.getTimezoneOffset(d - 36e5) &&
  370. !H.isSafari) {
  371. d -= 36e5;
  372. }
  373. }
  374. else {
  375. d = new this.Date(year, month, pick(date, 1), pick(hours, 0), pick(minutes, 0), pick(seconds, 0)).getTime();
  376. }
  377. return d;
  378. },
  379. /**
  380. * Sets the getTimezoneOffset function. If the `timezone` option is set, a
  381. * default getTimezoneOffset function with that timezone is returned. If
  382. * a `getTimezoneOffset` option is defined, it is returned. If neither are
  383. * specified, the function using the `timezoneOffset` option or 0 offset is
  384. * returned.
  385. *
  386. * @private
  387. * @function Highcharts.Time#timezoneOffsetFunction
  388. *
  389. * @return {Function}
  390. * A getTimezoneOffset function
  391. */
  392. timezoneOffsetFunction: function () {
  393. var time = this, options = this.options, moment = win.moment;
  394. if (!this.useUTC) {
  395. return function (timestamp) {
  396. return new Date(timestamp).getTimezoneOffset() * 60000;
  397. };
  398. }
  399. if (options.timezone) {
  400. if (!moment) {
  401. // getTimezoneOffset-function stays undefined because it depends
  402. // on Moment.js
  403. H.error(25);
  404. }
  405. else {
  406. return function (timestamp) {
  407. return -moment.tz(timestamp, options.timezone).utcOffset() * 60000;
  408. };
  409. }
  410. }
  411. // If not timezone is set, look for the getTimezoneOffset callback
  412. if (this.useUTC && options.getTimezoneOffset) {
  413. return function (timestamp) {
  414. return options.getTimezoneOffset(timestamp) * 60000;
  415. };
  416. }
  417. // Last, use the `timezoneOffset` option if set
  418. return function () {
  419. return (time.timezoneOffset || 0) * 60000;
  420. };
  421. },
  422. /**
  423. * Formats a JavaScript date timestamp (milliseconds since Jan 1st 1970)
  424. * into a human readable date string. The available format keys are listed
  425. * below. Additional formats can be given in the
  426. * {@link Highcharts.dateFormats} hook.
  427. *
  428. * Supported format keys:
  429. * - `%a`: Short weekday, like 'Mon'
  430. * - `%A`: Long weekday, like 'Monday'
  431. * - `%d`: Two digit day of the month, 01 to 31
  432. * - `%e`: Day of the month, 1 through 31
  433. * - `%w`: Day of the week, 0 through 6
  434. * - `%b`: Short month, like 'Jan'
  435. * - `%B`: Long month, like 'January'
  436. * - `%m`: Two digit month number, 01 through 12
  437. * - `%y`: Two digits year, like 09 for 2009
  438. * - `%Y`: Four digits year, like 2009
  439. * - `%H`: Two digits hours in 24h format, 00 through 23
  440. * - `%k`: Hours in 24h format, 0 through 23
  441. * - `%I`: Two digits hours in 12h format, 00 through 11
  442. * - `%l`: Hours in 12h format, 1 through 12
  443. * - `%M`: Two digits minutes, 00 through 59
  444. * - `%p`: Upper case AM or PM
  445. * - `%P`: Lower case AM or PM
  446. * - `%S`: Two digits seconds, 00 through 59
  447. * - `%L`: Milliseconds (naming from Ruby)
  448. *
  449. * @example
  450. * const time = new Highcharts.Time();
  451. * const s = time.dateFormat('%Y-%m-%d %H:%M:%S', Date.UTC(2020, 0, 1));
  452. * console.log(s); // => 2020-01-01 00:00:00
  453. *
  454. * @function Highcharts.Time#dateFormat
  455. *
  456. * @param {string} [format]
  457. * The desired format where various time representations are
  458. * prefixed with %.
  459. *
  460. * @param {number} timestamp
  461. * The JavaScript timestamp.
  462. *
  463. * @param {boolean} [capitalize=false]
  464. * Upper case first letter in the return.
  465. *
  466. * @return {string}
  467. * The formatted date.
  468. */
  469. dateFormat: function (format, timestamp, capitalize) {
  470. if (!defined(timestamp) || isNaN(timestamp)) {
  471. return H.defaultOptions.lang.invalidDate || '';
  472. }
  473. format = pick(format, '%Y-%m-%d %H:%M:%S');
  474. var time = this, date = new this.Date(timestamp),
  475. // get the basic time values
  476. hours = this.get('Hours', date), day = this.get('Day', date), dayOfMonth = this.get('Date', date), month = this.get('Month', date), fullYear = this.get('FullYear', date), lang = H.defaultOptions.lang, langWeekdays = lang.weekdays, shortWeekdays = lang.shortWeekdays,
  477. // List all format keys. Custom formats can be added from the
  478. // outside.
  479. replacements = extend({
  480. // Day
  481. // Short weekday, like 'Mon'
  482. a: shortWeekdays ?
  483. shortWeekdays[day] :
  484. langWeekdays[day].substr(0, 3),
  485. // Long weekday, like 'Monday'
  486. A: langWeekdays[day],
  487. // Two digit day of the month, 01 to 31
  488. d: pad(dayOfMonth),
  489. // Day of the month, 1 through 31
  490. e: pad(dayOfMonth, 2, ' '),
  491. // Day of the week, 0 through 6
  492. w: day,
  493. // Week (none implemented)
  494. // 'W': weekNumber(),
  495. // Month
  496. // Short month, like 'Jan'
  497. b: lang.shortMonths[month],
  498. // Long month, like 'January'
  499. B: lang.months[month],
  500. // Two digit month number, 01 through 12
  501. m: pad(month + 1),
  502. // Month number, 1 through 12 (#8150)
  503. o: month + 1,
  504. // Year
  505. // Two digits year, like 09 for 2009
  506. y: fullYear.toString().substr(2, 2),
  507. // Four digits year, like 2009
  508. Y: fullYear,
  509. // Time
  510. // Two digits hours in 24h format, 00 through 23
  511. H: pad(hours),
  512. // Hours in 24h format, 0 through 23
  513. k: hours,
  514. // Two digits hours in 12h format, 00 through 11
  515. I: pad((hours % 12) || 12),
  516. // Hours in 12h format, 1 through 12
  517. l: (hours % 12) || 12,
  518. // Two digits minutes, 00 through 59
  519. M: pad(time.get('Minutes', date)),
  520. // Upper case AM or PM
  521. p: hours < 12 ? 'AM' : 'PM',
  522. // Lower case AM or PM
  523. P: hours < 12 ? 'am' : 'pm',
  524. // Two digits seconds, 00 through 59
  525. S: pad(date.getSeconds()),
  526. // Milliseconds (naming from Ruby)
  527. L: pad(Math.floor(timestamp % 1000), 3)
  528. }, H.dateFormats);
  529. // Do the replaces
  530. objectEach(replacements, function (val, key) {
  531. // Regex would do it in one line, but this is faster
  532. while (format.indexOf('%' + key) !== -1) {
  533. format = format.replace('%' + key, typeof val === 'function' ? val.call(time, timestamp) : val);
  534. }
  535. });
  536. // Optionally capitalize the string and return
  537. return capitalize ?
  538. (format.substr(0, 1).toUpperCase() +
  539. format.substr(1)) :
  540. format;
  541. },
  542. /**
  543. * Resolve legacy formats of dateTimeLabelFormats (strings and arrays) into
  544. * an object.
  545. * @private
  546. * @param {string|Array<T>|Highcharts.Dictionary<T>} f - General format description
  547. * @return {Highcharts.Dictionary<T>} - The object definition
  548. */
  549. resolveDTLFormat: function (f) {
  550. if (!isObject(f, true)) { // check for string or array
  551. f = splat(f);
  552. return {
  553. main: f[0],
  554. from: f[1],
  555. to: f[2]
  556. };
  557. }
  558. return f;
  559. },
  560. /**
  561. * Return an array with time positions distributed on round time values
  562. * right and right after min and max. Used in datetime axes as well as for
  563. * grouping data on a datetime axis.
  564. *
  565. * @function Highcharts.Time#getTimeTicks
  566. *
  567. * @param {Highcharts.TimeNormalizedObject} normalizedInterval
  568. * The interval in axis values (ms) and the count
  569. *
  570. * @param {number} [min]
  571. * The minimum in axis values
  572. *
  573. * @param {number} [max]
  574. * The maximum in axis values
  575. *
  576. * @param {number} [startOfWeek=1]
  577. *
  578. * @return {Highcharts.AxisTickPositionsArray}
  579. */
  580. getTimeTicks: function (normalizedInterval, min, max, startOfWeek) {
  581. var time = this, Date = time.Date, tickPositions = [], i, higherRanks = {}, minYear, // used in months and years as a basis for Date.UTC()
  582. // When crossing DST, use the max. Resolves #6278.
  583. minDate = new Date(min), interval = normalizedInterval.unitRange, count = normalizedInterval.count || 1, variableDayLength, minDay;
  584. startOfWeek = pick(startOfWeek, 1);
  585. if (defined(min)) { // #1300
  586. time.set('Milliseconds', minDate, interval >= timeUnits.second ?
  587. 0 : // #3935
  588. count * Math.floor(time.get('Milliseconds', minDate) / count)); // #3652, #3654
  589. if (interval >= timeUnits.second) { // second
  590. time.set('Seconds', minDate, interval >= timeUnits.minute ?
  591. 0 : // #3935
  592. count * Math.floor(time.get('Seconds', minDate) / count));
  593. }
  594. if (interval >= timeUnits.minute) { // minute
  595. time.set('Minutes', minDate, interval >= timeUnits.hour ?
  596. 0 :
  597. count * Math.floor(time.get('Minutes', minDate) / count));
  598. }
  599. if (interval >= timeUnits.hour) { // hour
  600. time.set('Hours', minDate, interval >= timeUnits.day ?
  601. 0 :
  602. count * Math.floor(time.get('Hours', minDate) / count));
  603. }
  604. if (interval >= timeUnits.day) { // day
  605. time.set('Date', minDate, interval >= timeUnits.month ?
  606. 1 :
  607. Math.max(1, count * Math.floor(time.get('Date', minDate) / count)));
  608. }
  609. if (interval >= timeUnits.month) { // month
  610. time.set('Month', minDate, interval >= timeUnits.year ? 0 :
  611. count * Math.floor(time.get('Month', minDate) / count));
  612. minYear = time.get('FullYear', minDate);
  613. }
  614. if (interval >= timeUnits.year) { // year
  615. minYear -= minYear % count;
  616. time.set('FullYear', minDate, minYear);
  617. }
  618. // week is a special case that runs outside the hierarchy
  619. if (interval === timeUnits.week) {
  620. // get start of current week, independent of count
  621. minDay = time.get('Day', minDate);
  622. time.set('Date', minDate, (time.get('Date', minDate) -
  623. minDay + startOfWeek +
  624. // We don't want to skip days that are before
  625. // startOfWeek (#7051)
  626. (minDay < startOfWeek ? -7 : 0)));
  627. }
  628. // Get basics for variable time spans
  629. minYear = time.get('FullYear', minDate);
  630. var minMonth = time.get('Month', minDate), minDateDate = time.get('Date', minDate), minHours = time.get('Hours', minDate);
  631. // Redefine min to the floored/rounded minimum time (#7432)
  632. min = minDate.getTime();
  633. // Handle local timezone offset
  634. if (time.variableTimezone) {
  635. // Detect whether we need to take the DST crossover into
  636. // consideration. If we're crossing over DST, the day length may
  637. // be 23h or 25h and we need to compute the exact clock time for
  638. // each tick instead of just adding hours. This comes at a cost,
  639. // so first we find out if it is needed (#4951).
  640. variableDayLength = (
  641. // Long range, assume we're crossing over.
  642. max - min > 4 * timeUnits.month ||
  643. // Short range, check if min and max are in different time
  644. // zones.
  645. time.getTimezoneOffset(min) !==
  646. time.getTimezoneOffset(max));
  647. }
  648. // Iterate and add tick positions at appropriate values
  649. var t = minDate.getTime();
  650. i = 1;
  651. while (t < max) {
  652. tickPositions.push(t);
  653. // if the interval is years, use Date.UTC to increase years
  654. if (interval === timeUnits.year) {
  655. t = time.makeTime(minYear + i * count, 0);
  656. // if the interval is months, use Date.UTC to increase months
  657. }
  658. else if (interval === timeUnits.month) {
  659. t = time.makeTime(minYear, minMonth + i * count);
  660. // if we're using global time, the interval is not fixed as it
  661. // jumps one hour at the DST crossover
  662. }
  663. else if (variableDayLength &&
  664. (interval === timeUnits.day || interval === timeUnits.week)) {
  665. t = time.makeTime(minYear, minMonth, minDateDate +
  666. i * count * (interval === timeUnits.day ? 1 : 7));
  667. }
  668. else if (variableDayLength &&
  669. interval === timeUnits.hour &&
  670. count > 1) {
  671. // make sure higher ranks are preserved across DST (#6797,
  672. // #7621)
  673. t = time.makeTime(minYear, minMonth, minDateDate, minHours + i * count);
  674. // else, the interval is fixed and we use simple addition
  675. }
  676. else {
  677. t += interval * count;
  678. }
  679. i++;
  680. }
  681. // push the last time
  682. tickPositions.push(t);
  683. // Handle higher ranks. Mark new days if the time is on midnight
  684. // (#950, #1649, #1760, #3349). Use a reasonable dropout threshold
  685. // to prevent looping over dense data grouping (#6156).
  686. if (interval <= timeUnits.hour && tickPositions.length < 10000) {
  687. tickPositions.forEach(function (t) {
  688. if (
  689. // Speed optimization, no need to run dateFormat unless
  690. // we're on a full or half hour
  691. t % 1800000 === 0 &&
  692. // Check for local or global midnight
  693. time.dateFormat('%H%M%S%L', t) === '000000000') {
  694. higherRanks[t] = 'day';
  695. }
  696. });
  697. }
  698. }
  699. // record information on the chosen unit - for dynamic label formatter
  700. tickPositions.info = extend(normalizedInterval, {
  701. higherRanks: higherRanks,
  702. totalRange: interval * count
  703. });
  704. return tickPositions;
  705. }
  706. }; // end of Time