1/* * 2 * 3 * (c) 2010-2024 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'; 11import H from './Globals.js'; 12const { pageLang, win } = H; 13import U from './Utilities.js'; 14const { defined, error, extend, isNumber, isObject, isString, merge, objectEach, pad, splat, timeUnits, ucfirst } = U; 15/* * 16 * 17 * Constants 18 * 19 * */ 20// To do: Remove this when we no longer need support for Safari < v14.1 21const hasOldSafariBug = H.isSafari && 22 win.Intl && 23 !win.Intl.DateTimeFormat.prototype.formatRange; 24const isDateTimeFormatOptions = (obj) => obj.main === void 0; 25// We use the Spanish locale for internal weekday handling because it uses 26// unique letters for narrow weekdays 27const spanishWeekdayIndex = (weekday) => ['D', 'L', 'M', 'X', 'J', 'V', 'S'].indexOf(weekday); 28/* * 29 * 30 * Class 31 * 32 * */ 33/* eslint-disable no-invalid-this, valid-jsdoc */ 34/** 35 * The Time class. Time settings are applied in general for each page using 36 * `Highcharts.setOptions`, or individually for each Chart item through the 37 * [time](https://api.highcharts.com/highcharts/time) options set. 38 * 39 * The Time object is available from {@link Highcharts.Chart#time}, which refers 40 * to `Highcharts.time` unless individual time settings are applied for each 41 * chart. 42 * 43 * When configuring time settings for individual chart instances, be aware that 44 * using `Highcharts.dateFormat` or `Highcharts.time.dateFormat` within 45 * formatter callbacks relies on the global time object, which applies the 46 * global language and time zone settings. To ensure charts with local time 47 * settings function correctly, use `chart.time.dateFormat? instead. However, 48 * the recommended best practice is to use `setOptions` to define global time 49 * settings unless specific configurations are needed for each chart. 50 * 51 * @example 52 * // Apply time settings globally 53 * Highcharts.setOptions({ 54 * time: { 55 * timezone: 'Europe/London' 56 * } 57 * }); 58 * 59 * // Apply time settings by instance 60 * const chart = Highcharts.chart('container', { 61 * time: { 62 * timezone: 'America/New_York' 63 * }, 64 * series: [{ 65 * data: [1, 4, 3, 5] 66 * }] 67 * }); 68 * 69 * // Use the Time object of a chart instance 70 * console.log( 71 * 'Current time in New York', 72 * chart.time.dateFormat('%Y-%m-%d %H:%M:%S', Date.now()) 73 * ); 74 * 75 * // Standalone use 76 * const time = new Highcharts.Time({ 77 * timezone: 'America/New_York' 78 * }); 79 * const s = time.dateFormat('%Y-%m-%d %H:%M:%S', Date.UTC(2020, 0, 1)); 80 * console.log(s); // => 2019-12-31 19:00:00 81 * 82 * @since 6.0.5 83 * 84 * @class 85 * @name Highcharts.Time 86 * 87 * @param {Highcharts.TimeOptions} [options] Time options as defined in 88 * [chart.options.time](/highcharts/time). 89 */ 90class Time { 91 /* * 92 * 93 * Constructors 94 * 95 * */ 96 constructor(options) { 97 /* * 98 * 99 * Properties 100 * 101 * */ 102 this.options = { 103 timezone: 'UTC' 104 }; 105 this.variableTimezone = false; 106 this.Date = win.Date; 107 this.update(options); 108 } 109 /* * 110 * 111 * Functions 112 * 113 * */ 114 /** 115 * Update the Time object with current options. It is called internally on 116 * initializing Highcharts, after running `Highcharts.setOptions` and on 117 * `Chart.update`. 118 * 119 * @private 120 * @function Highcharts.Time#update 121 * 122 * @param {Highcharts.TimeOptions} [options] 123 * 124 */ 125 update(options = {}) { 126 this.dTLCache = {}; 127 this.options = options = merge(true, this.options, options); 128 const { timezoneOffset, useUTC } = options; 129 // Allow using a different Date class 130 this.Date = options.Date || win.Date || Date; 131 // Assign the time zone. Handle the legacy, deprecated `useUTC` option. 132 let timezone = options.timezone; 133 if (defined(useUTC)) { 134 timezone = useUTC ? 'UTC' : void 0; 135 } 136 // The Etc/GMT time zones do not support offsets with half-hour 137 // resolutions 138 if (timezoneOffset && timezoneOffset % 60 === 0) {
vendor: 5,986 bytes, lines 139-304
139 timezone = 'Etc/GMT' + ((timezoneOffset > 0 ? '+' : '')) + timezoneOffset / 60; 140 } 141 /* 142 * The time object has options allowing for variable time zones, meaning 143 * the axis ticks or series data needs to consider this. 144 */ 145 this.variableTimezone = timezone !== 'UTC' && 146 timezone?.indexOf('Etc/GMT') !== 0; 147 this.timezone = timezone; 148 // Assign default time formats from locale strings 149 ['months', 'shortMonths', 'weekdays', 'shortWeekdays'].forEach((name) => { 150 const isMonth = /months/i.test(name), isShort = /short/.test(name), options = { timeZone: 'UTC' }; 151 options[isMonth ? 'month' : 'weekday'] = isShort ? 'short' : 'long'; 152 this[name] = (isMonth ? 153 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] : 154 [3, 4, 5, 6, 7, 8, 9]).map((position) => this.dateFormat(options, (isMonth ? 31 : 1) * 24 * 36e5 * position)); 155 }); 156 } 157 /** 158 * Get a date in terms of numbers (year, month, day etc) for further 159 * processing. Takes the current `timezone` setting into account. Inverse of 160 * `makeTime` and the native `Date` constructor and `Date.UTC`. 161 * 162 * The date is returned in array format with the following indices: 163 * 164 * 0: year, 165 * 1: month (zero based), 166 * 2: day, 167 * 3: hours, 168 * 4: minutes, 169 * 5: seconds, 170 * 6: milliseconds, 171 * 7: weekday (Sunday as 0) 172 * 173 * @function Highcharts.Time#toParts 174 * 175 * @param {number|Date} [timestamp] 176 * The timestamp in milliseconds since January 1st 1970. 177 * A Date object is also accepted. 178 * 179 * @return {Array<number>} The date parts in array format. 180 */ 181 toParts(timestamp) { 182 const [weekday, dayOfMonth, month, year, hours, minutes, seconds] = this.dateTimeFormat({ 183 weekday: 'narrow', 184 day: 'numeric', 185 month: 'numeric', 186 year: 'numeric', 187 hour: 'numeric', 188 minute: 'numeric', 189 second: 'numeric' 190 }, timestamp, 'es') 191 .split(/(?:, |\/|:)/g); 192 return [ 193 year, 194 +month - 1, 195 dayOfMonth, 196 hours, 197 minutes, 198 seconds, 199 // Milliseconds 200 Math.floor(Number(timestamp) || 0) % 1000, 201 // Weekday index 202 spanishWeekdayIndex(weekday) 203 ].map(Number); 204 } 205 /** 206 * Shorthand to get a cached `Intl.DateTimeFormat` instance. 207 */ 208 dateTimeFormat(options, timestamp, locale = this.options.locale || pageLang) { 209 const cacheKey = JSON.stringify(options) + locale; 210 if (isString(options)) { 211 options = this.str2dtf(options); 212 } 213 let dTL = this.dTLCache[cacheKey]; 214 if (!dTL) { 215 options.timeZone ?? (options.timeZone = this.timezone); 216 try { 217 dTL = new Intl.DateTimeFormat(locale, options); 218 } 219 catch (e) { 220 if (/Invalid time zone/i.test(e.message)) { 221 error(34); 222 options.timeZone = 'UTC'; 223 dTL = new Intl.DateTimeFormat(locale, options); 224 } 225 else { 226 error(e.message, false); 227 } 228 } 229 } 230 this.dTLCache[cacheKey] = dTL; 231 return dTL?.format(timestamp) || ''; 232 } 233 /** 234 * Take a locale-aware string format and return a full DateTimeFormat in 235 * object form. 236 */ 237 str2dtf(s, dtf = {}) { 238 const mapping = { 239 L: { fractionalSecondDigits: 3 }, 240 S: { second: '2-digit' }, 241 M: { minute: 'numeric' }, 242 H: { hour: '2-digit' }, 243 k: { hour: 'numeric' }, 244 E: { weekday: 'narrow' }, 245 a: { weekday: 'short' }, 246 A: { weekday: 'long' }, 247 d: { day: '2-digit' }, 248 e: { day: 'numeric' }, 249 b: { month: 'short' }, 250 B: { month: 'long' }, 251 m: { month: '2-digit' }, 252 o: { month: 'numeric' }, 253 y: { year: '2-digit' }, 254 Y: { year: 'numeric' } 255 }; 256 Object.keys(mapping).forEach((key) => { 257 if (s.indexOf(key) !== -1) { 258 extend(dtf, mapping[key]); 259 } 260 }); 261 return dtf; 262 } 263 /** 264 * Make a time and returns milliseconds. Similar to `Date.UTC`, but takes 265 * the current `timezone` setting into account. 266 * 267 * @function Highcharts.Time#makeTime 268 * 269 * @param {number} year 270 * The year 271 * 272 * @param {number} month 273 * The month. Zero-based, so January is 0. 274 * 275 * @param {number} [date=1] 276 * The day of the month 277 * 278 * @param {number} [hours=0] 279 * The hour of the day, 0-23. 280 * 281 * @param {number} [minutes=0] 282 * The minutes 283 * 284 * @param {number} [seconds=0] 285 * The seconds 286 * 287 * @return {number} 288 * The time in milliseconds since January 1st 1970. 289 */ 290 makeTime(year, month, date = 1, hours = 0, minutes, seconds, milliseconds) { 291 // eslint-disable-next-line new-cap 292 let d = this.Date.UTC(year, month, date, hours, minutes || 0, seconds || 0, milliseconds || 0); 293 if (this.timezone !== 'UTC') { 294 const offset = this.getTimezoneOffset(d); 295 d += offset; 296 // Adjustments close to DST transitions 297 if ( 298 // Optimize for speed by limiting the number of calls to 299 // `getTimezoneOffset`. According to 300 // https://en.wikipedia.org/wiki/Daylight_saving_time_by_country, 301 // DST change may only occur in these months. 302 [2, 3, 8, 9, 10, 11].indexOf(month) !== -1 && 303 // DST transitions occur only in the night-time 304 (hours < 5 || hours >
vendor: 4,215 bytes, lines 304-399
304 20)) { 305 const newOffset = this.getTimezoneOffset(d); 306 if (offset !== newOffset) { 307 d += newOffset - offset; 308 // A special case for transitioning from summer time to winter 309 // time. When the clock is set back, the same time is repeated 310 // twice, i.e. 02:30 am is repeated since the clock is set back 311 // from 3 am to 2 am. We need to make the same time as local 312 // Date does. 313 } 314 else if (offset - 36e5 === this.getTimezoneOffset(d - 36e5) && 315 !hasOldSafariBug) { 316 d -= 36e5; 317 } 318 } 319 } 320 return d; 321 } 322 /** 323 * Parse a datetime string. Unless the string contains time zone 324 * information, apply the current `timezone` from options. If the argument 325 * is a number, return it. 326 * 327 * @function Highcharts.Time#parse 328 * @param {string|number|undefined} s The datetime string to parse 329 * @return {number|undefined} Parsed JavaScript timestamp 330 */ 331 parse(s) { 332 if (!isString(s)) { 333 return s ?? void 0; 334 } 335 s = s 336 // Firefox fails on YYYY/MM/DD 337 .replace(/\//g, '-') 338 // Replace some non-standard notations 339 .replace(/(GMT|UTC)/, ''); 340 // Extend shorthand hour timezone offset like +02 341 // .replace(/([+-][0-9]{2})$/, '$1:00'); 342 // Check if the string has time zone information 343 const hasTimezone = s.indexOf('Z') > -1 || 344 /([+-][0-9]{2}):?[0-9]{2}$/.test(s), isYYYYMMDD = /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/.test(s); 345 if (!hasTimezone && !isYYYYMMDD) { 346 s += 'Z'; 347 } 348 const ts = Date.parse(s); 349 if (isNumber(ts)) { 350 // Unless the string contains time zone information, convert from 351 // the local time result of `Date.parse` via UTC into the current 352 // timezone of the time object. 353 return ts + ((!hasTimezone || isYYYYMMDD) ? 354 this.getTimezoneOffset(ts) : 355 0); 356 } 357 } 358 /** 359 * Get the time zone offset based on the current timezone information as 360 * set in the global options. 361 * 362 * @function Highcharts.Time#getTimezoneOffset 363 * 364 * @param {number} timestamp 365 * The JavaScript timestamp to inspect. 366 * 367 * @return {number} 368 * The timezone offset in minutes compared to UTC. 369 */ 370 getTimezoneOffset(timestamp) { 371 if (this.timezone !== 'UTC') { 372 // eslint-disable-next-line @typescript-eslint/no-unused-vars 373 const [date, gmt, hours, colon, minutes = 0] = this.dateTimeFormat({ timeZoneName: 'shortOffset' }, timestamp, 'en') 374 .split(/(GMT|:)/) 375 .map(Number), offset = -(hours + minutes / 60) * 60 * 60000; 376 // Possible future NaNs stop here 377 if (isNumber(offset)) { 378 return offset; 379 } 380 } 381 return 0; 382 } 383 /** 384 * Formats a JavaScript date timestamp (milliseconds since January 1 1970) 385 * into a human readable date string. 386 * 387 * The `format` parameter accepts two types of values: 388 * - An object containing settings that are passed directly on to 389 * [Intl.DateTimeFormat.prototype.format](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/format). 390 * - A format string containing either individual or locale-aware format 391 * keys. **Individual keys**, for example `%Y-%m-%d`, are listed below. 392 * **Locale-aware keys** are grouped by square brackets, for example 393 * `%[Ymd]`. The order of keys within the square bracket doesn't affect 394 * the output, which is determined by the locale. See example below. 395 * Internally, the locale-aware format keys are just a shorthand for the 396 * full object formats, but are particularly practical in 397 * [templating](https://www.highcharts.com/docs/chart-concepts/templating) 398 * where full object definitions are not an option. 399 *
400 * The available string format keys are listed below. Additional formats can 401 * be given in the {@link Highcharts.dateFormats} hook. 402 * 403 * Supported format keys: 404 * | Key | Description | Notes on locale-aware format | 405 * -------|----------------------------------------------|-------| 406 * | `%A` | Long weekday, like 'Monday' | | 407 * | `%a` | Short weekday, like 'Mon' | | 408 * | `%E` | Narrow weekday, single character | | 409 * | `%d` | Two digit day of the month, 01 to 31 | | 410 * | `%e` | Day of the month, 1 through 31 | | 411 * | `%w` | Day of the week, 0 through 6 | N/A | 412 * | `%b` | Short month, like 'Jan' | | 413 * | `%B` | Long month, like 'January' | | 414 * | `%m` | Two digit month number, 01 through 12 | | 415 * | `%o` | Month number, 1 through 12 | | 416 * | `%y` | Two digits year, like 24 for 2024 | | 417 * | `%Y` | Four digits year, like 2024 | | 418 * | `%H` | Two digits hours in 24h format, 00 through 23 | Depending on the locale, 12h format may be instered. | 419 * | `%k` | Hours in 24h format, 0 through 23 | Depending on the locale, 12h format may be instered. | 420 * | `%I` | Two digits hours in 12h format, 00 through 11 | N/A. The locale determines the hour format. | 421 * | `%l` | Hours in 12h format, 1 through 12 | N/A. The locale determines the hour format. | 422 * | `%M` | Two digits minutes, 00 through 59 | | 423 * | `%p` | Upper case AM or PM | N/A. The locale determines whether to add AM and PM. | 424 * | `%P` | Lower case AM or PM | N/A. The locale determines whether to add AM and PM. | 425 * | `%S` | Two digits seconds, 00 through 59 | | 426 * | `%L` | Milliseconds (naming from Ruby) | | 427 * 428 * @example 429 * // Object format, US English 430 * const time1 = new Highcharts.Time({ locale: 'en-US' }); 431 * console.log( 432 * time1.dateFormat({ 433 * day: 'numeric', 434 * month: 'short', 435 * year: 'numeric', 436 * hour: 'numeric', 437 * minute: 'numeric' 438 * }, Date.UTC(2024, 11, 31)) 439 * ); // => Dec 31, 2024, 12:00 AM 440 * 441 * // Object format, British English 442 * const time2 = new Highcharts.Time({ locale: 'en-GB' }); 443 * console.log( 444 * time2.dateFormat({ 445 * day: 'numeric', 446 * month: 'short', 447 * year: 'numeric', 448 * hour: 'numeric', 449 * minute: 'numeric' 450 * }, Date.UTC(2024, 11, 31)) 451 * ); // => 31 Dec 2024, 00:00 452 * 453 * // Individual key string replacement 454 * const time3 = new Highcharts.Time(); 455 * console.log( 456 * time3.dateFormat('%Y-%m-%d %H:%M:%S', Date.UTC(2024, 11, 31)) 457 * ); // => 2024-12-31 00:00:00 458 * 459 * // Locale-aware keys, US English 460 * const time4 = new Highcharts.Time({ locale: 'en-US' }); 461 * console.log( 462 * time4.dateFormat('%[YebHM]', Date.UTC(2024, 11, 31)) 463 * ); // => Dec 31, 2024, 12:00 AM 464 * 465 * // Locale-aware keys, British English 466 * const time5 = new Highcharts.Time({ locale: 'en-GB' }); 467 * console.log( 468 * time5.dateFormat('%[YebHM]', Date.UTC(2024, 11, 31)) 469 * ); // => 31 Dec 2024, 00:00 470 * 471 * // Mixed locale-aware and individual keys 472 * console.log( 473 * time5.dateFormat('%[Yeb], %H:%M', Date.UTC(2024, 11, 31)) 474 * ); // => 31 Dec 2024, 00:00 475 * 476 * @function Highcharts.Time#dateFormat 477 * 478 * @param {string|Highcharts.DateTimeFormatOptions} format 479 * The desired string format where various time representations are 480 * prefixed with %, or an object representing the locale-aware format 481 * options. 482 * 483 * @param {number} [timestamp] 484 * The JavaScript timestamp. 485 * 486 * @param {boolean} [upperCaseFirst=false] 487 * Upper case first letter in the return. 488 * 489 * @return {string} 490 * The formatted date. 491 */ 492 dateFormat(format, timestamp, upperCaseFirst) { 493 const lang = H.defaultOptions?.lang; 494 if (!defined(timestamp) || isNaN(timestamp)) {
vendor: 5,177 bytes, lines 495-612
495 return lang?.invalidDate || ''; 496 } 497 format = format ?? '%Y-%m-%d %H:%M:%S'; 498 // First, identify and replace locale-aware formats like %[Ymd] 499 if (isString(format)) { 500 const localeAwareRegex = /%\[([a-zA-Z]+)\]/g; 501 let match; 502 while ((match = localeAwareRegex.exec(format))) { 503 format = format.replace(match[0], this.dateTimeFormat(match[1], timestamp)); 504 } 505 } 506 // Then, replace static formats like %Y, %m, %d etc. 507 if (isString(format) && format.indexOf('%') !== -1) { 508 const time = this, [fullYear, month, dayOfMonth, hours, minutes, seconds, milliseconds, weekday] = this.toParts(timestamp), langWeekdays = lang?.weekdays || this.weekdays, shortWeekdays = lang?.shortWeekdays || this.shortWeekdays, months = lang?.months || this.months, shortMonths = lang?.shortMonths || this.shortMonths, 509 // List all format keys. Custom formats can be added from the 510 // outside. 511 replacements = extend({ 512 // Day 513 // Short weekday, like 'Mon' 514 a: shortWeekdays ? 515 shortWeekdays[weekday] : 516 langWeekdays[weekday].substr(0, 3), 517 // Long weekday, like 'Monday' 518 A: langWeekdays[weekday], 519 // Two digit day of the month, 01 to 31 520 d: pad(dayOfMonth), 521 // Day of the month, 1 through 31 522 e: pad(dayOfMonth, 2, ' '), 523 // Day of the week, 0 through 6 524 w: weekday, 525 // Week (none implemented) 526 // 'W': weekNumber(), 527 // Month 528 // Short month, like 'Jan' 529 b: shortMonths[month], 530 // Long month, like 'January' 531 B: months[month], 532 // Two digit month number, 01 through 12 533 m: pad(month + 1), 534 // Month number, 1 through 12 (#8150) 535 o: month + 1, 536 // Year 537 // Two digits year, like 09 for 2009 538 y: fullYear.toString().substr(2, 2), 539 // Four digits year, like 2009 540 Y: fullYear, 541 // Time 542 // Two digits hours in 24h format, 00 through 23 543 H: pad(hours), 544 // Hours in 24h format, 0 through 23 545 k: hours, 546 // Two digits hours in 12h format, 00 through 11 547 I: pad((hours % 12) || 12), 548 // Hours in 12h format, 1 through 12 549 l: (hours % 12) || 12, 550 // Two digits minutes, 00 through 59 551 M: pad(minutes), 552 // Upper case AM or PM 553 p: hours < 12 ? 'AM' : 'PM', 554 // Lower case AM or PM 555 P: hours < 12 ? 'am' : 'pm', 556 // Two digits seconds, 00 through 59 557 S: pad(seconds), 558 // Milliseconds (naming from Ruby) 559 L: pad(milliseconds, 3) 560 }, H.dateFormats); 561 // Do the replaces 562 objectEach(replacements, function (val, key) { 563 if (isString(format)) { 564 // Regex would do it in one line, but this is faster 565 while (format.indexOf('%' + key) !== -1) { 566 format = format.replace('%' + key, typeof val === 'function' ? 567 val.call(time, timestamp) : 568 val); 569 } 570 } 571 }); 572 } 573 else if (isObject(format)) { 574 const tzHours = (this.getTimezoneOffset(timestamp) || 0) / 575 (60000 * 60), timeZone = this.timezone || ('Etc/GMT' + (tzHours >= 0 ? '+' : '') + tzHours), { prefix = '', suffix = '' } = format; 576 format = prefix + this.dateTimeFormat(extend({ timeZone }, format), timestamp) + suffix; 577 } 578 // Optionally sentence-case the string and return 579 return upperCaseFirst ? ucfirst(format) : format; 580 } 581 /** 582 * Resolve legacy formats of dateTimeLabelFormats (strings and arrays) into 583 * an object. 584 * @private 585 * @param {string|Array<T>|Highcharts.Dictionary<T>} f 586 * General format description 587 * @return {Highcharts.Dictionary<T>} 588 * The object definition 589 */ 590 resolveDTLFormat(f) { 591 if (!isObject(f, true)) { // Check for string or array 592 f = splat(f); 593 return { 594 main: f[0], 595 from: f[1], 596 to: f[2] 597 }; 598 } 599 // Type-check DateTimeFormatOptions against DateTimeLabelFormatObject 600 if (isObject(f, true) && isDateTimeFormatOptions(f)) { 601 return { main: f }; 602 } 603 return f; 604 } 605 /** 606 * Return an array with time positions distributed on round time values 607 * right and right after min and max. Used in datetime axes as well as for 608 * grouping data on a datetime axis. 609 * 610 * @function Highcharts.Time#getTimeTicks 611 * 612 * @param {Highcharts.TimeNormalizedObject}
612 normalizedInterval 613 * The interval in axis values (ms) and the count 614 * 615 * @param {number} [min] 616 * The minimum in axis values 617 * 618 * @param {number} [max] 619 * The maximum in axis values 620 * 621 * @param {number} [startOfWeek=1] 622 * 623 * @return {Highcharts.AxisTickPositionsArray} 624 * Time positions 625 */ 626 getTimeTicks(normalizedInterval, min, max, startOfWeek) { 627 const time = this, tickPositions = [], higherRanks = {}, { count = 1, unitRange } = normalizedInterval; 628 let [year, month, dayOfMonth, hours, minutes, seconds] = time.toParts(min), milliseconds = (min || 0) % 1000, variableDayLength; 629 startOfWeek ?? (startOfWeek = 1); 630 if (defined(min)) { // #1300 631 milliseconds = unitRange >= timeUnits.second ? 632 0 : // #3935 633 count * Math.floor(milliseconds / count); 634 if (unitRange >= timeUnits.second) { // Second 635 seconds = unitRange >= timeUnits.minute ? 636 0 : // #3935 637 count * Math.floor(seconds / count); 638 } 639 if (unitRange >= timeUnits.minute) { // Minute 640 minutes = unitRange >= timeUnits.hour ? 641 0 : 642 count * Math.floor(minutes / count); 643 } 644 if (unitRange >= timeUnits.hour) { // Hour 645 hours = unitRange >= timeUnits.day ? 646 0 : 647 count * Math.floor(hours / count); 648 } 649 if (unitRange >= timeUnits.day) { // Day 650 dayOfMonth = unitRange >= timeUnits.month ? 651 1 : 652 Math.max(1, count * Math.floor(dayOfMonth / count)); 653 } 654 if (unitRange >= timeUnits.month) { // Month 655 month = unitRange >= timeUnits.year ? 0 : 656 count * Math.floor(month / count); 657 } 658 if (unitRange >= timeUnits.year) { // Year 659 year -= year % count; 660 } 661 // Week is a special case that runs outside the hierarchy 662 if (unitRange === timeUnits.week) { 663 if (count) { 664 min = time.makeTime(year, month, dayOfMonth, hours, minutes, seconds, milliseconds); 665 } 666 // Get start of current week, independent of count 667 const weekday = this.dateTimeFormat({ 668 timeZone: this.timezone, 669 weekday: 'narrow' 670 }, min, 'es'), weekdayNo = spanishWeekdayIndex(weekday); 671 dayOfMonth += -weekdayNo + startOfWeek + 672 // We don't want to skip days that are before 673 // startOfWeek (#7051) 674 (weekdayNo < startOfWeek ? -7 : 0); 675 } 676 min = time.makeTime(year, month, dayOfMonth, hours, minutes, seconds, milliseconds); 677 // Handle local timezone offset 678 if (time.variableTimezone && defined(max)) { 679 // Detect whether we need to take the DST crossover into 680 // consideration. If we're crossing over DST, the day length may 681 // be 23h or 25h and we need to compute the exact clock time for 682 // each tick instead of just adding hours. This comes at a cost, 683 // so first we find out if it is needed (#4951). 684 variableDayLength = ( 685 // Long range, assume we're crossing over. 686 max - min > 4 * timeUnits.month || 687 // Short range, check if min and max are in different time 688 // zones. 689 time.getTimezoneOffset(min) !== 690 time.getTimezoneOffset(max)); 691 } 692 // Iterate and add tick positions at appropriate values 693 let t = min, i = 1; 694 while (t < max) { 695 tickPositions.push(t); 696 // Increase the years 697 if (unitRange === timeUnits.year) { 698 t = time.makeTime(year + i * count, 0); 699 // Increase the months 700 } 701 else if (unitRange === timeUnits.month) { 702 t = time.makeTime(year, month + i * count); 703 // If we're using local time, the interval is not fixed as it 704 // jumps one hour at the DST crossover 705 } 706 else if (variableDayLength && (unitRange === timeUnits.day || 707 unitRange === timeUnits.week)) { 708 t = time.makeTime(year, month, dayOfMonth + 709 i * count * (unitRange === timeUnits.day ? 1 : 7)); 710 } 711 else if (variableDayLength && 712 unitRange === timeUnits.hour && 713 count > 1) { 714 // Make sure higher ranks are preserved across DST (#6797, 715 // #7621) 716 t = time.makeTime(year, month, dayOfMonth, hours + i * count); 717 // Else, the interval is fixed and we use simple addition 718 } 719 else { 720 t += unitRange * count; 721 } 722 i++; 723 } 724 // Push the last time 725 tickPositions.push(t); 726 // Handle higher ranks. Mark new days if the time is on midnight 727 // (#950, #1649, #1760, #3349). Use a reasonable dropout threshold 728 // to prevent looping over dense data grouping (#6156). 729 if (unitRange <= timeUnits.hour && tickPositions.length < 10000) { 730 tickPositions.forEach((t) => { 731 if ( 732 // Speed optimization, no need to run dateFormat unless 733 // we're on a full or half hour 734 t % 1800000 === 0 && 735 // Check for local or global midnight 736 time.dateFormat('%H%M%S%L', t) === '000000000') { 737 higherRanks[t] = 'day'; 738 } 739 }); 740 } 741 } 742 // Record information on the chosen unit - for dynamic label formatter 743 tickPositions.info = extend(normalizedInterval, { 744 higherRanks, 745 totalRange: unitRange * count 746 }); 747 return tickPositions; 748 } 749 /** 750 * Get the optimal date format for a point, based on a range. 751 * 752 * @private 753 * @function Highcharts.Time#getDateFormat 754 * 755 * @param {number} range 756 * The time range 757 * 758 * @param {number} timestamp 759 * The timestamp of the date 760 * 761 * @param {number} startOfWeek 762 * An integer representing the first day of the week, where 0 is 763 * Sunday. 764 * 765 * @param {Highcharts.Dictionary<string>} dateTimeLabelFormats 766 * A map of time units to formats. 767 * 768 * @return {string} 769 * The optimal date format for a point. 770 */ 771 getDateFormat(range, timestamp, startOfWeek, dateTimeLabelFormats) { 772 const dateStr = this.dateFormat('%m-%d %H:%M:%S.%L', timestamp), blank = '01-01 00:00:00.000', strpos = { 773 millisecond: 15, 774 second: 12, 775 minute: 9, 776 hour: 6, 777 day: 3 778 }; 779 let n = 'millisecond', 780 // For sub-millisecond data, #4223 781 lastN = n; 782 for (n in timeUnits) { // eslint-disable-line guard-for-in 783 // If the range is exactly one week and we're looking at a 784 // Sunday/Monday, go for the week format 785 if (range === timeUnits.week && 786 +this.dateFormat('%w', timestamp) === startOfWeek && 787 dateStr.substr(6) === blank.substr(6)) { 788 n = 'week'; 789 break; 790 } 791 // The first format that is too great for the range 792 if (timeUnits[n] > range) { 793 n = lastN; 794 break; 795 } 796 // If the point is placed every day at 23:59, we need to show 797 // the minutes as well. #2637. 798 if (strpos[n] && 799 dateStr.substr(strpos[n]) !== blank.substr(strpos[n])) { 800 break; 801 } 802 // Weeks are outside the hierarchy, only apply them on 803 // Mondays/Sundays like in the first condition 804 if (n !== 'week') { 805 lastN = n; 806 } 807 } 808 return this.resolveDTLFormat(dateTimeLabelFormats[n]).main; 809 } 810} 811/* * 812 * 813 * Default export 814 * 815 * */ 816export default Time; 817/* * 818 * 819 * API Declarations 820 * 821 * */ 822/** 823 * Normalized interval. 824 * 825 * @interface Highcharts.TimeNormalizedObject 826 */ /** 827* The count. 828* 829* @name Highcharts.TimeNormalizedObject#count 830* @type {number|undefined} 831*/ /** 832* The interval in axis values (ms). 833* 834* @name Highcharts.TimeNormalizedObject#unitRange 835* @type {number} 836*/ 837/**
838 * Function of an additional date format specifier. 839 * 840 * @callback Highcharts.TimeFormatCallbackFunction 841 * 842 * @param {number} timestamp 843 * The time to format. 844 * 845 * @return {string} 846 * The formatted portion of the date. 847 */ 848/** 849 * Time ticks. 850 * 851 * @interface Highcharts.AxisTickPositionsArray 852 * @extends global.Array<number> 853 */ /** 854* @name Highcharts.AxisTickPositionsArray#info 855* @type {Highcharts.TimeTicksInfoObject|undefined} 856*/ 857/** 858 * A callback to return the time zone offset for a given datetime. It 859 * takes the timestamp in terms of milliseconds since January 1 1970, 860 * and returns the timezone offset in minutes. This provides a hook 861 * for drawing time based charts in specific time zones using their 862 * local DST crossover dates, with the help of external libraries. 863 * 864 * @callback Highcharts.TimezoneOffsetCallbackFunction 865 * 866 * @param {number} timestamp 867 * Timestamp in terms of milliseconds since January 1 1970. 868 * 869 * @return {number} 870 * Timezone offset in minutes. 871 */ 872/** 873 * Options for formatting dates and times using the [Intl.DateTimeFormat]( 874 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat 875 * ) API, and extended with some custom options for Highcharts. 876 * 877 * @interface Highcharts.DateTimeFormatOptions 878 */ /** 879* The locale matching algorithm to use. 880* 881* @name Highcharts.DateTimeFormatOptions#localeMatcher 882* @type {string|undefined} 883*/ /** 884* The time zone to use. The default is the browser's default time zone. 885* 886* @name Highcharts.DateTimeFormatOptions#timeZone 887* @type {string|undefined} 888*/ /** 889* Whether to use 12-hour time (as opposed to 24-hour time). 890* 891* @name Highcharts.DateTimeFormatOptions#hour12 892* @type {'auto'|'always'|'never'|undefined} 893*/ /** 894* The format matching algorithm to use. 895* 896* @name Highcharts.DateTimeFormatOptions#formatMatcher 897* @type {string|undefined} 898*/ /** 899* The representation of the weekday. 900* 901* @name Highcharts.DateTimeFormatOptions#weekday 902* @type {'narrow'|'short'|'long'|undefined} 903*/ /** 904* The representation of the era. 905* 906* @name Highcharts.DateTimeFormatOptions#era 907* @type {'narrow'|'short'|'long'|undefined} 908*/ /** 909* The representation of the year. 910* 911* @name Highcharts.DateTimeFormatOptions#year 912* @type {'numeric'|'2-digit'|undefined} 913*/ /** 914* The representation of the month. 915* "narrow", "short", "long". 916* 917* @name Highcharts.DateTimeFormatOptions#month 918* @type {'numeric'|'2-digit'|'narrow'|'short'|'long'|undefined} 919*/ /** 920* The representation of the day. 921* 922* @name Highcharts.DateTimeFormatOptions#day 923* @type {'numeric'|'2-digit'|undefined} 924*/ /** 925* The representation of the hour. 926* 927* @name Highcharts.DateTimeFormatOptions#hour 928* @type {'numeric'|'2-digit'|undefined} 929*/ /** 930* The representation of the minute. 931* 932* @name Highcharts.DateTimeFormatOptions#minute 933* @type {'numeric'|'2-digit'|undefined} 934*/ /** 935* The representation of the second. 936* 937* @name Highcharts.DateTimeFormatOptions#second 938* @type {'numeric'|'2-digit'|undefined} 939*/ /** 940* The number of fractional digits to use. 3 means milliseconds. 941* 942* @name Highcharts.DateTimeFormatOptions#fractionalSecondDigits 943* @type {number|undefined} 944*/ /** 945* The representation of the time zone name. 946* 947* @name Highcharts.DateTimeFormatOptions#timeZoneName 948* @type {'short'|'long'|undefined} 949*/ /** 950* A prefix for the time string. Custom Highcharts option. 951* 952* @name Highcharts.DateTimeFormatOptions#prefix 953* @type {'string'|undefined} 954*/ /** 955* A suffix for the time string. Custom Highcharts option. 956* 957* @name Highcharts.DateTimeFormatOptions#suffix 958* @type {'string'|undefined} 959*/ 960''; // Keeps doclets above in JS file
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.