@cubejs-client/core 1.3.14 → 1.3.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/dist/{cubejs-client-core.js → cubejs-client-core.cjs.js} +1016 -411
  2. package/dist/cubejs-client-core.cjs.js.map +1 -0
  3. package/dist/cubejs-client-core.umd.js +2901 -12088
  4. package/dist/cubejs-client-core.umd.js.map +1 -1
  5. package/dist/src/HttpTransport.d.ts +54 -0
  6. package/dist/src/HttpTransport.d.ts.map +1 -0
  7. package/dist/src/HttpTransport.js +55 -0
  8. package/dist/src/Meta.d.ts +62 -0
  9. package/dist/src/Meta.d.ts.map +1 -0
  10. package/dist/src/Meta.js +150 -0
  11. package/dist/src/ProgressResult.d.ts +8 -0
  12. package/dist/src/ProgressResult.d.ts.map +1 -0
  13. package/dist/src/ProgressResult.js +11 -0
  14. package/dist/src/RequestError.d.ts +6 -0
  15. package/dist/src/RequestError.d.ts.map +1 -0
  16. package/dist/src/RequestError.js +7 -0
  17. package/dist/src/ResultSet.d.ts +430 -0
  18. package/dist/src/ResultSet.d.ts.map +1 -0
  19. package/dist/src/ResultSet.js +952 -0
  20. package/dist/src/SqlQuery.d.ts +17 -0
  21. package/dist/src/SqlQuery.d.ts.map +1 -0
  22. package/dist/src/SqlQuery.js +11 -0
  23. package/dist/src/index.d.ts +194 -0
  24. package/dist/src/index.d.ts.map +1 -0
  25. package/dist/src/index.js +411 -0
  26. package/dist/src/index.umd.d.ts +3 -0
  27. package/dist/src/index.umd.d.ts.map +1 -0
  28. package/dist/src/index.umd.js +6 -0
  29. package/dist/src/time.d.ts +70 -0
  30. package/dist/src/time.d.ts.map +1 -0
  31. package/dist/src/time.js +249 -0
  32. package/dist/src/types.d.ts +424 -0
  33. package/dist/src/types.d.ts.map +1 -0
  34. package/dist/src/types.js +1 -0
  35. package/dist/src/utils.d.ts +19 -0
  36. package/dist/src/utils.d.ts.map +1 -0
  37. package/dist/src/utils.js +294 -0
  38. package/dist/test/CubeApi.test.d.ts +7 -0
  39. package/dist/test/CubeApi.test.d.ts.map +1 -0
  40. package/dist/test/CubeApi.test.js +279 -0
  41. package/dist/test/HttpTransport.test.d.ts +2 -0
  42. package/dist/test/HttpTransport.test.d.ts.map +1 -0
  43. package/dist/test/HttpTransport.test.js +244 -0
  44. package/dist/test/ResultSet.test.d.ts +7 -0
  45. package/dist/test/ResultSet.test.d.ts.map +1 -0
  46. package/dist/test/ResultSet.test.js +1725 -0
  47. package/dist/test/compare-date-range.test.d.ts +2 -0
  48. package/dist/test/compare-date-range.test.d.ts.map +1 -0
  49. package/dist/test/compare-date-range.test.js +742 -0
  50. package/dist/test/data-blending.test.d.ts +2 -0
  51. package/dist/test/data-blending.test.d.ts.map +1 -0
  52. package/dist/test/data-blending.test.js +423 -0
  53. package/dist/test/default-heuristics.test.d.ts +2 -0
  54. package/dist/test/default-heuristics.test.d.ts.map +1 -0
  55. package/dist/test/default-heuristics.test.js +108 -0
  56. package/dist/test/drill-down.test.d.ts +2 -0
  57. package/dist/test/drill-down.test.d.ts.map +1 -0
  58. package/dist/test/drill-down.test.js +373 -0
  59. package/dist/test/fixtures/datablending/load-responses.json +261 -0
  60. package/dist/test/granularity.test.d.ts +2 -0
  61. package/dist/test/granularity.test.d.ts.map +1 -0
  62. package/dist/test/granularity.test.js +218 -0
  63. package/dist/test/helpers.d.ts +283 -0
  64. package/dist/test/helpers.d.ts.map +1 -0
  65. package/dist/test/helpers.js +974 -0
  66. package/dist/test/index.test.d.ts +7 -0
  67. package/dist/test/index.test.d.ts.map +1 -0
  68. package/dist/test/index.test.js +370 -0
  69. package/dist/test/table.test.d.ts +2 -0
  70. package/dist/test/table.test.d.ts.map +1 -0
  71. package/dist/test/table.test.js +757 -0
  72. package/dist/test/utils.test.d.ts +2 -0
  73. package/dist/test/utils.test.d.ts.map +1 -0
  74. package/dist/test/utils.test.js +32 -0
  75. package/package.json +26 -21
  76. package/dist/cubejs-client-core.esm.js +0 -1639
  77. package/dist/cubejs-client-core.esm.js.map +0 -1
  78. package/dist/cubejs-client-core.js.map +0 -1
  79. package/index.d.ts +0 -1338
  80. package/src/HttpTransport.js +0 -60
  81. package/src/HttpTransport.test.js +0 -117
  82. package/src/Meta.js +0 -142
  83. package/src/ProgressResult.js +0 -13
  84. package/src/RequestError.js +0 -7
  85. package/src/ResultSet.js +0 -746
  86. package/src/SqlQuery.js +0 -13
  87. package/src/index.js +0 -398
  88. package/src/index.test.js +0 -454
  89. package/src/index.umd.js +0 -8
  90. package/src/tests/ResultSet.test.js +0 -1655
  91. package/src/tests/compare-date-range.test.js +0 -753
  92. package/src/tests/data-blending.test.js +0 -432
  93. package/src/tests/default-heuristics.test.js +0 -118
  94. package/src/tests/drill-down.test.js +0 -402
  95. package/src/tests/fixtures/datablending/load-responses.json +0 -261
  96. package/src/tests/granularity.test.js +0 -225
  97. package/src/tests/table.test.js +0 -791
  98. package/src/tests/utils.test.js +0 -35
  99. package/src/time.js +0 -296
  100. package/src/utils.js +0 -368
@@ -0,0 +1,70 @@
1
+ import dayjs from 'dayjs';
2
+ export type SqlInterval = string;
3
+ export type ParsedInterval = Record<string, number>;
4
+ export type Granularity = {
5
+ interval: SqlInterval;
6
+ origin?: string;
7
+ offset?: SqlInterval;
8
+ };
9
+ export type DayRange = {
10
+ by: (value: any) => dayjs.Dayjs[];
11
+ snapTo: (value: any) => DayRange;
12
+ start: dayjs.Dayjs;
13
+ end: dayjs.Dayjs;
14
+ };
15
+ export type TimeDimensionPredefinedGranularity = 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year';
16
+ export type TimeDimensionGranularity = TimeDimensionPredefinedGranularity | string;
17
+ export type TGranularityMap = {
18
+ name: TimeDimensionGranularity | undefined;
19
+ title: string;
20
+ };
21
+ export declare const GRANULARITIES: TGranularityMap[];
22
+ export declare const DEFAULT_GRANULARITY = "day";
23
+ export declare const internalDayjs: (...args: any[]) => dayjs.Dayjs;
24
+ export declare const TIME_SERIES: Record<string, (range: DayRange) => string[]>;
25
+ export declare const isPredefinedGranularity: (granularity: TimeDimensionGranularity) => boolean;
26
+ export declare const DateRegex: RegExp;
27
+ export declare const LocalDateRegex: RegExp;
28
+ export declare const dayRange: (from: any, to: any) => DayRange;
29
+ /**
30
+ * Parse PostgreSQL-like interval string into object
31
+ * E.g. '2 years 15 months 100 weeks 99 hours 15 seconds'
32
+ * Negative units are also supported
33
+ * E.g. '-2 months 5 days -10 hours'
34
+ *
35
+ * TODO: It's copy/paste of parseSqlInterval from @cubejs-backend/shared [time.ts]
36
+ * It's not referenced to omit imports of moment.js staff.
37
+ * Probably one day we should choose one implementation and reuse it in other places.
38
+ */
39
+ export declare function parseSqlInterval(intervalStr: SqlInterval): ParsedInterval;
40
+ /**
41
+ * Adds interval to provided date.
42
+ * TODO: It's copy/paste of addInterval from @cubejs-backend/shared [time.ts]
43
+ * but operates with dayjs instead of moment.js
44
+ * @param {dayjs} date
45
+ * @param interval
46
+ * @returns {dayjs}
47
+ */
48
+ export declare function addInterval(date: dayjs.Dayjs, interval: ParsedInterval): dayjs.Dayjs;
49
+ /**
50
+ * Adds interval to provided date.
51
+ * TODO: It's copy/paste of subtractInterval from @cubejs-backend/shared [time.ts]
52
+ * but operates with dayjs instead of moment.js
53
+ * @param {dayjs} date
54
+ * @param interval
55
+ * @returns {dayjs}
56
+ */
57
+ export declare function subtractInterval(date: dayjs.Dayjs, interval: ParsedInterval): dayjs.Dayjs;
58
+ /**
59
+ * Returns the time series points for the custom interval
60
+ * TODO: It's almost a copy/paste of timeSeriesFromCustomInterval from
61
+ * @cubejs-backend/shared [time.ts] but operates with dayjs instead of moment.js
62
+ */
63
+ export declare const timeSeriesFromCustomInterval: (from: string, to: string, granularity: Granularity) => string[];
64
+ /**
65
+ * Returns the lowest time unit for the interval
66
+ */
67
+ export declare const diffTimeUnitForInterval: (interval: string) => string;
68
+ export declare const minGranularityForIntervals: (i1: string, i2: string) => string;
69
+ export declare const granularityFor: (dateStr: string) => string;
70
+ //# sourceMappingURL=time.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time.d.ts","sourceRoot":"","sources":["../../src/time.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAU1B,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC;AAGjC,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAEpD,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,EAAE,WAAW,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,EAAE,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,KAAK,CAAC,KAAK,EAAE,CAAC;IAClC,MAAM,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,QAAQ,CAAC;IACjC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC;IACnB,GAAG,EAAE,KAAK,CAAC,KAAK,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,kCAAkC,GAC5C,QAAQ,GACN,QAAQ,GACR,MAAM,GACN,KAAK,GACL,MAAM,GACN,OAAO,GACP,SAAS,GACT,MAAM,CAAC;AAEX,MAAM,MAAM,wBAAwB,GAAG,kCAAkC,GAAG,MAAM,CAAC;AAEnF,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,wBAAwB,GAAG,SAAS,CAAC;IAC3C,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,eAAe,EAU1C,CAAC;AAEF,eAAO,MAAM,mBAAmB,QAAQ,CAAC;AAIzC,eAAO,MAAM,aAAa,YAAa,GAAG,EAAE,KAAG,MAAM,KAAuD,CAAC;AAE7G,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,MAAM,EAAE,CAWrE,CAAC;AAEF,eAAO,MAAM,uBAAuB,gBAAiB,wBAAwB,KAAG,OAAqC,CAAC;AAEtH,eAAO,MAAM,SAAS,QAAyB,CAAC;AAChD,eAAO,MAAM,cAAc,QAAkD,CAAC;AAE9E,eAAO,MAAM,QAAQ,SAAU,GAAG,MAAM,GAAG,KAAG,QAiB5C,CAAC;AAEH;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,WAAW,GAAG,cAAc,CAczE;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,EAAE,QAAQ,EAAE,cAAc,GAAG,KAAK,CAAC,KAAK,CAQpF;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,EAAE,QAAQ,EAAE,cAAc,GAAG,KAAK,CAAC,KAAK,CAQzF;AA4CD;;;;GAIG;AACH,eAAO,MAAM,4BAA4B,SAAU,MAAM,MAAM,MAAM,eAAe,WAAW,KAAG,MAAM,EAkBvG,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,uBAAuB,aAAc,MAAM,KAAG,MAkB1D,CAAC;AAIF,eAAO,MAAM,0BAA0B,OAAQ,MAAM,MAAM,MAAM,KAAG,MAWnE,CAAC;AAEF,eAAO,MAAM,cAAc,YAAa,MAAM,KAAG,MA4DhD,CAAC"}
@@ -0,0 +1,249 @@
1
+ import dayjs from 'dayjs';
2
+ import quarterOfYear from 'dayjs/plugin/quarterOfYear';
3
+ import duration from 'dayjs/plugin/duration';
4
+ import isoWeek from 'dayjs/plugin/isoWeek';
5
+ import en from 'dayjs/locale/en';
6
+ dayjs.extend(quarterOfYear);
7
+ dayjs.extend(duration);
8
+ dayjs.extend(isoWeek);
9
+ export const GRANULARITIES = [
10
+ { name: undefined, title: 'w/o grouping' },
11
+ { name: 'second', title: 'Second' },
12
+ { name: 'minute', title: 'Minute' },
13
+ { name: 'hour', title: 'Hour' },
14
+ { name: 'day', title: 'Day' },
15
+ { name: 'week', title: 'Week' },
16
+ { name: 'month', title: 'Month' },
17
+ { name: 'quarter', title: 'Quarter' },
18
+ { name: 'year', title: 'Year' },
19
+ ];
20
+ export const DEFAULT_GRANULARITY = 'day';
21
+ // When granularity is week, weekStart Value must be 1. However, since the client can change it globally
22
+ // (https://day.js.org/docs/en/i18n/changing-locale) So the function below has been added.
23
+ export const internalDayjs = (...args) => dayjs(...args).locale({ ...en, weekStart: 1 });
24
+ export const TIME_SERIES = {
25
+ day: (range) => range.by('d').map(d => d.format('YYYY-MM-DDT00:00:00.000')),
26
+ month: (range) => range.snapTo('month').by('M').map(d => d.format('YYYY-MM-01T00:00:00.000')),
27
+ year: (range) => range.snapTo('year').by('y').map(d => d.format('YYYY-01-01T00:00:00.000')),
28
+ hour: (range) => range.by('h').map(d => d.format('YYYY-MM-DDTHH:00:00.000')),
29
+ minute: (range) => range.by('m').map(d => d.format('YYYY-MM-DDTHH:mm:00.000')),
30
+ second: (range) => range.by('s').map(d => d.format('YYYY-MM-DDTHH:mm:ss.000')),
31
+ week: (range) => range.snapTo('week').by('w').map(d => d.startOf('week').format('YYYY-MM-DDT00:00:00.000')),
32
+ quarter: (range) => range.snapTo('quarter').by('quarter').map(d => d.startOf('quarter').format('YYYY-MM-DDT00:00:00.000')),
33
+ };
34
+ export const isPredefinedGranularity = (granularity) => !!TIME_SERIES[granularity];
35
+ export const DateRegex = /^\d\d\d\d-\d\d-\d\d$/;
36
+ export const LocalDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z?$/;
37
+ export const dayRange = (from, to) => ({
38
+ by: (value) => {
39
+ const results = [];
40
+ let start = internalDayjs(from);
41
+ const end = internalDayjs(to);
42
+ while (start.isBefore(end) || start.isSame(end)) {
43
+ results.push(start);
44
+ start = start.add(1, value);
45
+ }
46
+ return results;
47
+ },
48
+ snapTo: (value) => dayRange(internalDayjs(from).startOf(value), internalDayjs(to).endOf(value)),
49
+ start: internalDayjs(from),
50
+ end: internalDayjs(to),
51
+ });
52
+ /**
53
+ * Parse PostgreSQL-like interval string into object
54
+ * E.g. '2 years 15 months 100 weeks 99 hours 15 seconds'
55
+ * Negative units are also supported
56
+ * E.g. '-2 months 5 days -10 hours'
57
+ *
58
+ * TODO: It's copy/paste of parseSqlInterval from @cubejs-backend/shared [time.ts]
59
+ * It's not referenced to omit imports of moment.js staff.
60
+ * Probably one day we should choose one implementation and reuse it in other places.
61
+ */
62
+ export function parseSqlInterval(intervalStr) {
63
+ const interval = {};
64
+ const parts = intervalStr.split(/\s+/);
65
+ for (let i = 0; i < parts.length; i += 2) {
66
+ const value = parseInt(parts[i], 10);
67
+ const unit = parts[i + 1];
68
+ // Remove ending 's' (e.g., 'days' -> 'day')
69
+ const singularUnit = unit.endsWith('s') ? unit.slice(0, -1) : unit;
70
+ interval[singularUnit] = value;
71
+ }
72
+ return interval;
73
+ }
74
+ /**
75
+ * Adds interval to provided date.
76
+ * TODO: It's copy/paste of addInterval from @cubejs-backend/shared [time.ts]
77
+ * but operates with dayjs instead of moment.js
78
+ * @param {dayjs} date
79
+ * @param interval
80
+ * @returns {dayjs}
81
+ */
82
+ export function addInterval(date, interval) {
83
+ let res = date.clone();
84
+ Object.entries(interval).forEach(([key, value]) => {
85
+ res = res.add(value, key);
86
+ });
87
+ return res;
88
+ }
89
+ /**
90
+ * Adds interval to provided date.
91
+ * TODO: It's copy/paste of subtractInterval from @cubejs-backend/shared [time.ts]
92
+ * but operates with dayjs instead of moment.js
93
+ * @param {dayjs} date
94
+ * @param interval
95
+ * @returns {dayjs}
96
+ */
97
+ export function subtractInterval(date, interval) {
98
+ let res = date.clone();
99
+ Object.entries(interval).forEach(([key, value]) => {
100
+ res = res.subtract(value, key);
101
+ });
102
+ return res;
103
+ }
104
+ /**
105
+ * Returns the closest date prior to date parameter aligned with the origin point
106
+ * TODO: It's copy/paste of alignToOrigin from @cubejs-backend/shared [time.ts]
107
+ * but operates with dayjs instead of moment.js
108
+ */
109
+ function alignToOrigin(startDate, interval, origin) {
110
+ let alignedDate = startDate.clone();
111
+ let intervalOp;
112
+ let isIntervalNegative = false;
113
+ let offsetDate = addInterval(origin, interval);
114
+ // The easiest way to check the interval sign
115
+ if (offsetDate.isBefore(origin)) {
116
+ isIntervalNegative = true;
117
+ }
118
+ offsetDate = origin.clone();
119
+ if (startDate.isBefore(origin)) {
120
+ intervalOp = isIntervalNegative ? addInterval : subtractInterval;
121
+ while (offsetDate.isAfter(startDate)) {
122
+ offsetDate = intervalOp(offsetDate, interval);
123
+ }
124
+ alignedDate = offsetDate;
125
+ }
126
+ else {
127
+ intervalOp = isIntervalNegative ? subtractInterval : addInterval;
128
+ while (offsetDate.isBefore(startDate)) {
129
+ alignedDate = offsetDate.clone();
130
+ offsetDate = intervalOp(offsetDate, interval);
131
+ }
132
+ if (offsetDate.isSame(startDate)) {
133
+ alignedDate = offsetDate;
134
+ }
135
+ }
136
+ return alignedDate;
137
+ }
138
+ /**
139
+ * Returns the time series points for the custom interval
140
+ * TODO: It's almost a copy/paste of timeSeriesFromCustomInterval from
141
+ * @cubejs-backend/shared [time.ts] but operates with dayjs instead of moment.js
142
+ */
143
+ export const timeSeriesFromCustomInterval = (from, to, granularity) => {
144
+ const intervalParsed = parseSqlInterval(granularity.interval);
145
+ const start = internalDayjs(from);
146
+ const end = internalDayjs(to);
147
+ let origin = granularity.origin ? internalDayjs(granularity.origin) : internalDayjs().startOf('year');
148
+ if (granularity.offset) {
149
+ origin = addInterval(origin, parseSqlInterval(granularity.offset));
150
+ }
151
+ let alignedStart = alignToOrigin(start, intervalParsed, origin);
152
+ const dates = [];
153
+ while (alignedStart.isBefore(end) || alignedStart.isSame(end)) {
154
+ dates.push(alignedStart.format('YYYY-MM-DDTHH:mm:ss.000'));
155
+ alignedStart = addInterval(alignedStart, intervalParsed);
156
+ }
157
+ return dates;
158
+ };
159
+ /**
160
+ * Returns the lowest time unit for the interval
161
+ */
162
+ export const diffTimeUnitForInterval = (interval) => {
163
+ if (/second/i.test(interval)) {
164
+ return 'second';
165
+ }
166
+ else if (/minute/i.test(interval)) {
167
+ return 'minute';
168
+ }
169
+ else if (/hour/i.test(interval)) {
170
+ return 'hour';
171
+ }
172
+ else if (/day/i.test(interval)) {
173
+ return 'day';
174
+ }
175
+ else if (/week/i.test(interval)) {
176
+ return 'day';
177
+ }
178
+ else if (/month/i.test(interval)) {
179
+ return 'month';
180
+ }
181
+ else if (/quarter/i.test(interval)) {
182
+ return 'month';
183
+ }
184
+ else /* if (/year/i.test(interval)) */ {
185
+ return 'year';
186
+ }
187
+ };
188
+ const granularityOrder = ['year', 'quarter', 'month', 'week', 'day', 'hour', 'minute', 'second'];
189
+ export const minGranularityForIntervals = (i1, i2) => {
190
+ const g1 = diffTimeUnitForInterval(i1);
191
+ const g2 = diffTimeUnitForInterval(i2);
192
+ const g1pos = granularityOrder.indexOf(g1);
193
+ const g2pos = granularityOrder.indexOf(g2);
194
+ if (g1pos > g2pos) {
195
+ return g1;
196
+ }
197
+ return g2;
198
+ };
199
+ export const granularityFor = (dateStr) => {
200
+ const dayjsDate = internalDayjs(dateStr);
201
+ const month = dayjsDate.month();
202
+ const date = dayjsDate.date();
203
+ const hours = dayjsDate.hour();
204
+ const minutes = dayjsDate.minute();
205
+ const seconds = dayjsDate.second();
206
+ const milliseconds = dayjsDate.millisecond();
207
+ const weekDay = dayjsDate.isoWeekday();
208
+ if (month === 0 &&
209
+ date === 1 &&
210
+ hours === 0 &&
211
+ minutes === 0 &&
212
+ seconds === 0 &&
213
+ milliseconds === 0) {
214
+ return 'year';
215
+ }
216
+ else if (date === 1 &&
217
+ hours === 0 &&
218
+ minutes === 0 &&
219
+ seconds === 0 &&
220
+ milliseconds === 0) {
221
+ return 'month';
222
+ }
223
+ else if (weekDay === 1 &&
224
+ hours === 0 &&
225
+ minutes === 0 &&
226
+ seconds === 0 &&
227
+ milliseconds === 0) {
228
+ return 'week';
229
+ }
230
+ else if (hours === 0 &&
231
+ minutes === 0 &&
232
+ seconds === 0 &&
233
+ milliseconds === 0) {
234
+ return 'day';
235
+ }
236
+ else if (minutes === 0 &&
237
+ seconds === 0 &&
238
+ milliseconds === 0) {
239
+ return 'hour';
240
+ }
241
+ else if (seconds === 0 &&
242
+ milliseconds === 0) {
243
+ return 'minute';
244
+ }
245
+ else if (milliseconds === 0) {
246
+ return 'second';
247
+ }
248
+ return 'second'; // TODO return 'millisecond';
249
+ };