dates.tsx 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. import moment from 'moment';
  2. import {parseStatsPeriod} from 'sentry/components/organizations/pageFilters/parse';
  3. import ConfigStore from 'sentry/stores/configStore';
  4. import {DateString} from 'sentry/types';
  5. // TODO(billy): Move to TimeRangeSelector specific utils
  6. export const DEFAULT_DAY_START_TIME = '00:00:00';
  7. export const DEFAULT_DAY_END_TIME = '23:59:59';
  8. const DATE_FORMAT_NO_TIMEZONE = 'YYYY/MM/DD HH:mm:ss';
  9. function getParser(local: boolean = false): typeof moment | typeof moment.utc {
  10. return local ? moment : moment.utc;
  11. }
  12. /**
  13. * Checks if string is valid time. Only accepts 24 hour format.
  14. *
  15. * Chrome's time input will (at least for US locale), allow you to input 12
  16. * hour format with AM/PM but the raw value is in 24 hour.
  17. *
  18. * Safari does not do any validation so you could get a value of > 24 hours
  19. */
  20. export function isValidTime(str: string): boolean {
  21. return moment(str, 'HH:mm', true).isValid();
  22. }
  23. /**
  24. * Given a date object, format in datetime in UTC
  25. * given: Tue Oct 09 2018 00:00:00 GMT-0700 (Pacific Daylight Time)
  26. * returns: "2018-10-09T07:00:00.000"
  27. */
  28. export function getUtcDateString(dateObj: moment.MomentInput): string {
  29. return moment.utc(dateObj).format(moment.HTML5_FMT.DATETIME_LOCAL_SECONDS);
  30. }
  31. export function getFormattedDate(
  32. dateObj: moment.MomentInput,
  33. format: string,
  34. {local}: {local?: boolean} = {}
  35. ): string {
  36. return getParser(local)(dateObj).format(format);
  37. }
  38. /**
  39. * Returns user timezone from their account preferences
  40. */
  41. export function getUserTimezone(): string {
  42. return ConfigStore.get('user')?.options?.timezone;
  43. }
  44. /**
  45. * Given a UTC date, return a Date object in local time
  46. */
  47. export function getUtcToLocalDateObject(date: moment.MomentInput): Date {
  48. return moment.utc(date).local().toDate();
  49. }
  50. /**
  51. * Sets time (hours + minutes) of the current date object
  52. *
  53. * @param timeStr Time in 24hr format (HH:mm)
  54. */
  55. export function setDateToTime(
  56. dateObj: string | Date,
  57. timeStr: string,
  58. {local}: {local?: boolean} = {}
  59. ): Date {
  60. const [hours, minutes, seconds] = timeStr.split(':').map(t => parseInt(t, 10));
  61. const date = new Date(+dateObj);
  62. if (local) {
  63. date.setHours(hours, minutes);
  64. } else {
  65. date.setUTCHours(hours, minutes);
  66. }
  67. if (typeof seconds !== 'undefined') {
  68. date.setSeconds(seconds);
  69. }
  70. return date;
  71. }
  72. /**
  73. * Given a UTC timestamp, return a system date object with the same date
  74. * e.g. given: system is -0700 (PST),
  75. * 1/1/2001 @ 22:00 UTC, return: 1/1/2001 @ 22:00 -0700 (PST)
  76. */
  77. export function getUtcToSystem(dateObj: moment.MomentInput): Date {
  78. // This is required because if your system timezone !== user configured timezone
  79. // then there will be a mismatch of dates with `react-date-picker`
  80. //
  81. // We purposely strip the timezone when formatting from the utc timezone
  82. return new Date(moment.utc(dateObj).format(DATE_FORMAT_NO_TIMEZONE));
  83. }
  84. /**
  85. * Given a timestamp, format to user preference timezone, and strip timezone to
  86. * return a system date object with the same date
  87. *
  88. * e.g. given: system is -0700 (PST) and user preference is -0400 (EST),
  89. * 1/1/2001 @ 22:00 UTC --> 1/1/2001 @ 18:00 -0400 (EST) -->
  90. * return: 1/1/2001 @ 18:00 -0700 (PST)
  91. */
  92. export function getLocalToSystem(dateObj: moment.MomentInput): Date {
  93. // This is required because if your system timezone !== user configured timezone
  94. // then there will be a mismatch of dates with `react-date-picker`
  95. //
  96. // We purposely strip the timezone when formatting from the utc timezone
  97. return new Date(moment(dateObj).format(DATE_FORMAT_NO_TIMEZONE));
  98. }
  99. /**
  100. * Get the beginning of day (e.g. midnight)
  101. */
  102. export function getStartOfDay(date: moment.MomentInput): Date {
  103. return moment(date)
  104. .startOf('day')
  105. .startOf('hour')
  106. .startOf('minute')
  107. .startOf('second')
  108. .local()
  109. .toDate();
  110. }
  111. /**
  112. * Get tomorrow at midnight so that default endtime is inclusive of today
  113. */
  114. export function getEndOfDay(date: moment.MomentInput): Date {
  115. return moment(date)
  116. .add(1, 'day')
  117. .startOf('hour')
  118. .startOf('minute')
  119. .startOf('second')
  120. .subtract(1, 'second')
  121. .local()
  122. .toDate();
  123. }
  124. export function getPeriodAgo(
  125. period: moment.unitOfTime.DurationConstructor,
  126. unit: number
  127. ): moment.Moment {
  128. return moment().local().subtract(unit, period);
  129. }
  130. /**
  131. * Get the start of the day (midnight) for a period ago
  132. *
  133. * e.g. 2 weeks ago at midnight
  134. */
  135. export function getStartOfPeriodAgo(
  136. period: moment.unitOfTime.DurationConstructor,
  137. unit: number
  138. ): Date {
  139. return getStartOfDay(getPeriodAgo(period, unit));
  140. }
  141. /**
  142. * Convert an interval string into a number of seconds.
  143. * This allows us to create end timestamps from starting ones
  144. * enabling us to find events in narrow windows.
  145. *
  146. * @param {String} interval The interval to convert.
  147. * @return {Integer}
  148. */
  149. export function intervalToMilliseconds(interval: string): number {
  150. const pattern = /^(\d+)(d|h|m)$/;
  151. const matches = pattern.exec(interval);
  152. if (!matches) {
  153. return 0;
  154. }
  155. const [, value, unit] = matches;
  156. const multipliers = {
  157. d: 60 * 60 * 24,
  158. h: 60 * 60,
  159. m: 60,
  160. };
  161. return parseInt(value, 10) * multipliers[unit] * 1000;
  162. }
  163. /**
  164. * This parses our period shorthand strings (e.g. <int><unit>)
  165. * and converts it into hours
  166. */
  167. export function parsePeriodToHours(str: string): number {
  168. const result = parseStatsPeriod(str);
  169. if (!result) {
  170. return -1;
  171. }
  172. const {period, periodLength} = result;
  173. const periodNumber = parseInt(period, 10);
  174. switch (periodLength) {
  175. case 's':
  176. return periodNumber / (60 * 60);
  177. case 'm':
  178. return periodNumber / 60;
  179. case 'h':
  180. return periodNumber;
  181. case 'd':
  182. return periodNumber * 24;
  183. case 'w':
  184. return periodNumber * 24 * 7;
  185. default:
  186. return -1;
  187. }
  188. }
  189. export function statsPeriodToDays(
  190. statsPeriod?: string | null,
  191. start?: DateString,
  192. end?: DateString
  193. ) {
  194. if (statsPeriod && statsPeriod.endsWith('d')) {
  195. return parseInt(statsPeriod.slice(0, -1), 10);
  196. }
  197. if (statsPeriod && statsPeriod.endsWith('h')) {
  198. return parseInt(statsPeriod.slice(0, -1), 10) / 24;
  199. }
  200. if (start && end) {
  201. return (new Date(end).getTime() - new Date(start).getTime()) / (24 * 60 * 60 * 1000);
  202. }
  203. return 0;
  204. }
  205. /**
  206. * Does the user prefer a 24 hour clock?
  207. */
  208. export function shouldUse24Hours() {
  209. return ConfigStore.get('user')?.options?.clock24Hours;
  210. }
  211. /**
  212. * Get a common date format
  213. */
  214. export function getDateFormat({year}: {year?: boolean}) {
  215. // "Jan 1, 2022" or "Jan 1"
  216. return year ? 'MMM D, YYYY' : 'MMM D';
  217. }
  218. /**
  219. * Get a common time format
  220. */
  221. export function getTimeFormat({
  222. clock24Hours,
  223. seconds,
  224. timeZone,
  225. }: {
  226. clock24Hours?: boolean;
  227. seconds?: boolean;
  228. timeZone?: boolean;
  229. } = {}) {
  230. let format = '';
  231. if (clock24Hours ?? shouldUse24Hours()) {
  232. format = seconds ? 'HH:mm:ss' : 'HH:mm';
  233. } else {
  234. format = seconds ? 'LTS' : 'LT';
  235. }
  236. if (timeZone) {
  237. format += ' z';
  238. }
  239. return format;
  240. }
  241. interface FormatProps {
  242. /**
  243. * Should show a 24hour clock? If not set the users preference will be used
  244. */
  245. clock24Hours?: boolean;
  246. /**
  247. * If true, will only return the date part, e.g. "Jan 1".
  248. */
  249. dateOnly?: boolean;
  250. /**
  251. * Whether to show the seconds.
  252. */
  253. seconds?: boolean;
  254. /**
  255. * If true, will only return the time part, e.g. "2:50 PM"
  256. */
  257. timeOnly?: boolean;
  258. /**
  259. * Whether to show the time zone.
  260. */
  261. timeZone?: boolean;
  262. /**
  263. * Whether to show the year. If not specified, the returned date string will
  264. * not contain the year _if_ the date is not in the current calendar year.
  265. * For example: "Feb 1" (2022), "Jan 1" (2022), "Dec 31, 2021".
  266. */
  267. year?: boolean;
  268. }
  269. export function getFormat({
  270. dateOnly,
  271. timeOnly,
  272. year,
  273. seconds,
  274. timeZone,
  275. clock24Hours,
  276. }: FormatProps = {}) {
  277. if (dateOnly) {
  278. return getDateFormat({year});
  279. }
  280. if (timeOnly) {
  281. return getTimeFormat({clock24Hours, seconds, timeZone});
  282. }
  283. const dateFormat = getDateFormat({year});
  284. const timeFormat = getTimeFormat({
  285. clock24Hours,
  286. seconds,
  287. timeZone,
  288. });
  289. // If the year is shown, then there's already a comma in dateFormat ("Jan 1, 2020"),
  290. // so we don't need to add another comma between the date and time
  291. return year ? `${dateFormat} ${timeFormat}` : `${dateFormat}, ${timeFormat}`;
  292. }
  293. export function getInternalDate(date: string | Date, utc?: boolean | null) {
  294. if (utc) {
  295. return getUtcToSystem(date);
  296. }
  297. return new Date(
  298. moment.tz(moment.utc(date), getUserTimezone()).format('YYYY/MM/DD HH:mm:ss')
  299. );
  300. }
  301. /**
  302. * Strips timezone from local date, creates a new moment date object with timezone
  303. * returns the moment as a Date object
  304. */
  305. export function getDateWithTimezoneInUtc(date?: Date, utc?: boolean | null) {
  306. return moment
  307. .tz(
  308. moment(date).local().format('YYYY-MM-DD HH:mm:ss'),
  309. utc ? 'UTC' : getUserTimezone()
  310. )
  311. .utc()
  312. .toDate();
  313. }