Time.js 29 KB

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