dates.tsx 8.7 KB

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