• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

FontoXML / fontoxpath / 3627994155

pending completion
3627994155

push

github

Martin Middel
Implement adding and subtracting durations from datetimes

4874 of 5672 branches covered (85.93%)

Branch coverage included in aggregate %.

2 of 20 new or added lines in 1 file covered. (10.0%)

10459 of 11113 relevant lines covered (94.12%)

32568.1 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.67
/src/expressions/dataTypes/valueTypes/DateTime.ts
1
import { ValueType, valueTypeToString } from '../Value';
2
import AbstractDuration from './AbstractDuration';
3
import DayTimeDuration from './DayTimeDuration';
3✔
4

5
function parseMatch(match: string | undefined): number | null {
6
        return match ? parseInt(match, 10) : null;
3,160✔
7
}
8

9
function convertYearToString(year: number): string {
10
        let yearString = year + '';
21✔
11
        const isNegative = yearString.startsWith('-');
21✔
12
        if (isNegative) {
21!
13
                yearString = yearString.substring(1);
×
14
        }
15
        return (isNegative ? '-' : '') + yearString.padStart(4, '0');
21!
16
}
17

18
function convertToTwoCharString(value: number): string {
19
        const valueString = value + '';
105✔
20
        return valueString.padStart(2, '0');
105✔
21
}
22

23
function convertSecondsToString(seconds: number): string {
24
        let secondsString = seconds + '';
13✔
25
        if (secondsString.split('.')[0].length === 1) {
13✔
26
                secondsString = secondsString.padStart(secondsString.length + 1, '0');
2✔
27
        }
28
        return secondsString;
13✔
29
}
30

31
function isUTC(timezone: DayTimeDuration): boolean {
32
        return timezone.getHours() === 0 && timezone.getMinutes() === 0;
32✔
33
}
34

35
function timezoneToString(timezone: DayTimeDuration): string {
36
        if (isUTC(timezone)) {
32✔
37
                return 'Z';
15✔
38
        }
39

40
        return (
17✔
41
                (timezone.isPositive() ? '+' : '-') +
17!
42
                convertToTwoCharString(Math.abs(timezone.getHours())) +
43
                ':' +
44
                convertToTwoCharString(Math.abs(timezone.getMinutes()))
45
        );
46
}
47

48
class DateTime {
49
        public secondFraction: number;
50
        public type: ValueType;
51
        protected _days: number;
52
        protected _hours: number;
53
        protected _minutes: number;
54
        protected _months: number;
55
        protected _seconds: number;
56
        protected _timezone: DayTimeDuration;
57
        protected _years: number;
58
        constructor(
59
                years: number,
60
                months: number,
61
                days: number,
62
                hours: number,
63
                minutes: number,
64
                seconds: number,
65
                secondFraction: number,
66
                timezone: DayTimeDuration,
67
                type: ValueType = ValueType.XSDATETIME
7✔
68
        ) {
69
                this._years = years;
687✔
70
                this._months = months;
687✔
71
                this._days = days + (hours === 24 ? 1 : 0);
687✔
72
                this._hours = hours === 24 ? 0 : hours;
687✔
73
                this._minutes = minutes;
687✔
74
                this._seconds = seconds;
687✔
75
                this.secondFraction = secondFraction;
687✔
76
                this._timezone = timezone;
687✔
77

78
                this.type = type;
687✔
79
        }
80

81
        // dateTime    | (-)yyyy-mm-ddThh:mm:ss.ss(Z|[+-]hh:mm)
82
        // time        |               hh:mm:ss.ss(Z|[+-]hh:mm)
83
        // date        | (-)yyyy-mm-dd            (Z|[+-]hh:mm)
84
        // gYearMonth  | (-)yyyy-mm               (Z|[+-]hh:mm)
85
        // gYear       | (-)yyyy                  (Z|[+-]hh:mm)
86
        // gMonthDay   |       --mm-dd            (Z|[+-]hh:mm)
87
        // gDay        |         ---dd            (Z|[+-]hh:mm)
88
        // gMonth      |       --mm               (Z|[+-]hh:mm)
89
        public static fromString(dateString: string): DateTime {
90
                const regex =
91
                        /^(?:(-?\d{4,}))?(?:--?(\d\d))?(?:-{1,3}(\d\d))?(T)?(?:(\d\d):(\d\d):(\d\d))?(\.\d+)?(Z|(?:[+-]\d\d:\d\d))?$/;
632✔
92
                const match = regex.exec(dateString);
632✔
93

94
                const years = match[1] ? parseInt(match[1], 10) : null;
632✔
95
                const months = parseMatch(match[2]);
632✔
96
                const days = parseMatch(match[3]);
632✔
97
                const t = match[4];
632✔
98
                const hours = parseMatch(match[5]);
632✔
99
                const minutes = parseMatch(match[6]);
632✔
100
                const seconds = parseMatch(match[7]);
632✔
101
                const secondFraction = match[8] ? parseFloat(match[8]) : 0;
632✔
102
                const timezone = match[9] ? DayTimeDuration.fromTimezoneString(match[9]) : null;
632✔
103

104
                if (years && (years < -271821 || years > 273860)) {
632!
105
                        // These are the JavaScript bounds for date (https://tc39.github.io/ecma262/#sec-time-values-and-time-range)
106
                        throw new Error('FODT0001: Datetime year is out of bounds');
×
107
                }
108

109
                if (t) {
632✔
110
                        // There is a T separating the date and time components -> dateTime
111
                        return new DateTime(
264✔
112
                                years,
113
                                months,
114
                                days,
115
                                hours,
116
                                minutes,
117
                                seconds,
118
                                secondFraction,
119
                                timezone,
120
                                ValueType.XSDATETIME
121
                        );
122
                }
123

124
                if (hours !== null && minutes !== null && seconds !== null) {
368✔
125
                        // There is no T separator, but there is a time component -> time
126
                        return new DateTime(
74✔
127
                                1972,
128
                                12,
129
                                31,
130
                                hours,
131
                                minutes,
132
                                seconds,
133
                                secondFraction,
134
                                timezone,
135
                                ValueType.XSTIME
136
                        );
137
                }
138

139
                if (years !== null && months !== null && days !== null) {
294✔
140
                        // There is no T separator, but there is a complete date component -> date
141
                        return new DateTime(years, months, days, 0, 0, 0, 0, timezone, ValueType.XSDATE);
83✔
142
                }
143

144
                if (years !== null && months !== null) {
211✔
145
                        // There is no complete date component, but there is a year and a month -> gYearMonth
146
                        return new DateTime(years, months, 1, 0, 0, 0, 0, timezone, ValueType.XSGYEARMONTH);
45✔
147
                }
148

149
                if (months !== null && days !== null) {
166✔
150
                        // There is no complete date component, but there is a month and a day -> gMonthDay
151
                        return new DateTime(1972, months, days, 0, 0, 0, 0, timezone, ValueType.XSGMONTHDAY);
45✔
152
                }
153

154
                if (years !== null) {
121✔
155
                        // There is only a year -> gYear
156
                        return new DateTime(years, 1, 1, 0, 0, 0, 0, timezone, ValueType.XSGYEAR);
43✔
157
                }
158

159
                if (months !== null) {
78✔
160
                        // There is only a month -> gMonth
161
                        return new DateTime(1972, months, 1, 0, 0, 0, 0, timezone, ValueType.XSGMONTH);
39✔
162
                }
163

164
                // There is only one option left -> gDay
165
                return new DateTime(1972, 12, days, 0, 0, 0, 0, timezone, ValueType.XSGDAY);
39✔
166
        }
167

168
        public convertToType(type: ValueType) {
169
                // xs:date       xxxx-xx-xxT00:00:00
170
                // xs:time       1972-12-31Txx:xx:xx
171
                // xs:gYearMonth xxxx-xx-01T00:00:00
172
                // xs:gYear      xxxx-01-01T00:00:00
173
                // xs:gMonthDay  1972-xx-xxT00:00:00
174
                // xs:gMonth     1972-xx-01T00:00:00
175
                // xs:gDay       1972-12-xxT00:00:00
176

177
                switch (type) {
48✔
178
                        case ValueType.XSGDAY:
179
                                return new DateTime(
5✔
180
                                        1972,
181
                                        12,
182
                                        this._days,
183
                                        0,
184
                                        0,
185
                                        0,
186
                                        0,
187
                                        this._timezone,
188
                                        ValueType.XSGDAY
189
                                );
190
                        case ValueType.XSGMONTH:
191
                                return new DateTime(
5✔
192
                                        1972,
193
                                        this._months,
194
                                        1,
195
                                        0,
196
                                        0,
197
                                        0,
198
                                        0,
199
                                        this._timezone,
200
                                        ValueType.XSGMONTH
201
                                );
202
                        case ValueType.XSGYEAR:
203
                                return new DateTime(
5✔
204
                                        this._years,
205
                                        1,
206
                                        1,
207
                                        0,
208
                                        0,
209
                                        0,
210
                                        0,
211
                                        this._timezone,
212
                                        ValueType.XSGYEAR
213
                                );
214
                        case ValueType.XSGMONTHDAY:
215
                                return new DateTime(
5✔
216
                                        1972,
217
                                        this._months,
218
                                        this._days,
219
                                        0,
220
                                        0,
221
                                        0,
222
                                        0,
223
                                        this._timezone,
224
                                        ValueType.XSGMONTHDAY
225
                                );
226
                        case ValueType.XSGYEARMONTH:
227
                                return new DateTime(
5✔
228
                                        this._years,
229
                                        this._months,
230
                                        1,
231
                                        0,
232
                                        0,
233
                                        0,
234
                                        0,
235
                                        this._timezone,
236
                                        ValueType.XSGYEARMONTH
237
                                );
238
                        case ValueType.XSTIME:
239
                                return new DateTime(
5✔
240
                                        1972,
241
                                        12,
242
                                        31,
243
                                        this._hours,
244
                                        this._minutes,
245
                                        this._seconds,
246
                                        this.secondFraction,
247
                                        this._timezone,
248
                                        ValueType.XSTIME
249
                                );
250
                        case ValueType.XSDATE:
251
                                return new DateTime(
14✔
252
                                        this._years,
253
                                        this._months,
254
                                        this._days,
255
                                        0,
256
                                        0,
257
                                        0,
258
                                        0,
259
                                        this._timezone,
260
                                        ValueType.XSDATE
261
                                );
262
                        case ValueType.XSDATETIME:
263
                        default:
264
                                return new DateTime(
4✔
265
                                        this._years,
266
                                        this._months,
267
                                        this._days,
268
                                        this._hours,
269
                                        this._minutes,
270
                                        this._seconds,
271
                                        this.secondFraction,
272
                                        this._timezone,
273
                                        ValueType.XSDATETIME
274
                                );
275
                }
276
        }
277

278
        public getDay() {
279
                return this._days;
4✔
280
        }
281

282
        public getFullSeconds() {
283
                return this._seconds + this.secondFraction;
2✔
284
        }
285

286
        public getHours() {
287
                return this._hours;
4✔
288
        }
289

290
        public getMinutes() {
291
                return this._minutes;
4✔
292
        }
293

294
        public getMonth() {
295
                return this._months;
4✔
296
        }
297

298
        public getSecondFraction() {
299
                return this.secondFraction;
2✔
300
        }
301

302
        public getSeconds() {
303
                return this._seconds;
2✔
304
        }
305

306
        public getTimezone() {
307
                return this._timezone;
7✔
308
        }
309

310
        public getYear() {
311
                return this._years;
4✔
312
        }
313

314
        public isPositive() {
315
                return this._years >= 0;
×
316
        }
317

318
        public toJavaScriptDate(implicitTimezone?: DayTimeDuration): Date {
319
                const timezoneToUse =
320
                        this._timezone || implicitTimezone || DayTimeDuration.fromTimezoneString('Z');
216✔
321
                return new Date(
216✔
322
                        Date.UTC(
323
                                this._years,
324
                                this._months - 1,
325
                                this._days,
326
                                this._hours - timezoneToUse.getHours(),
327
                                this._minutes - timezoneToUse.getMinutes(),
328
                                this._seconds,
329
                                this.secondFraction * 1000
330
                        )
331
                );
332
        }
333

334
        public toString() {
335
                switch (this.type) {
34✔
336
                        case ValueType.XSDATETIME:
337
                                return (
9✔
338
                                        convertYearToString(this._years) +
339
                                        '-' +
340
                                        convertToTwoCharString(this._months) +
341
                                        '-' +
342
                                        convertToTwoCharString(this._days) +
343
                                        'T' +
344
                                        convertToTwoCharString(this._hours) +
345
                                        ':' +
346
                                        convertToTwoCharString(this._minutes) +
347
                                        ':' +
348
                                        convertSecondsToString(this._seconds + this.secondFraction) +
349
                                        (this._timezone ? timezoneToString(this._timezone) : '')
9✔
350
                                );
351
                        case ValueType.XSDATE:
352
                                return (
6✔
353
                                        convertYearToString(this._years) +
354
                                        '-' +
355
                                        convertToTwoCharString(this._months) +
356
                                        '-' +
357
                                        convertToTwoCharString(this._days) +
358
                                        (this._timezone ? timezoneToString(this._timezone) : '')
6!
359
                                );
360
                        case ValueType.XSTIME:
361
                                return (
4✔
362
                                        convertToTwoCharString(this._hours) +
363
                                        ':' +
364
                                        convertToTwoCharString(this._minutes) +
365
                                        ':' +
366
                                        convertSecondsToString(this._seconds + this.secondFraction) +
367
                                        (this._timezone ? timezoneToString(this._timezone) : '')
4!
368
                                );
369
                        case ValueType.XSGDAY:
370
                                return (
3✔
371
                                        '---' +
372
                                        convertToTwoCharString(this._days) +
373
                                        (this._timezone ? timezoneToString(this._timezone) : '')
3!
374
                                );
375
                        case ValueType.XSGMONTH:
376
                                return (
3✔
377
                                        '--' +
378
                                        convertToTwoCharString(this._months) +
379
                                        (this._timezone ? timezoneToString(this._timezone) : '')
3!
380
                                );
381
                        case ValueType.XSGMONTHDAY:
382
                                return (
3✔
383
                                        '--' +
384
                                        convertToTwoCharString(this._months) +
385
                                        '-' +
386
                                        convertToTwoCharString(this._days) +
387
                                        (this._timezone ? timezoneToString(this._timezone) : '')
3!
388
                                );
389
                        case ValueType.XSGYEAR:
390
                                return (
3✔
391
                                        convertYearToString(this._years) +
392
                                        (this._timezone ? timezoneToString(this._timezone) : '')
3!
393
                                );
394
                        case ValueType.XSGYEARMONTH:
395
                                return (
3✔
396
                                        convertYearToString(this._years) +
397
                                        '-' +
398
                                        convertToTwoCharString(this._months) +
399
                                        (this._timezone ? timezoneToString(this._timezone) : '')
3!
400
                                );
401
                }
402
                throw new Error('Unexpected subType');
×
403
        }
404
}
405

406
export function compare(
3✔
407
        dateTime1: DateTime,
408
        dateTime2: DateTime,
409
        implicitTimezone?: DayTimeDuration | null
410
): number {
411
        const jsTime1 = dateTime1.toJavaScriptDate(implicitTimezone).getTime();
100✔
412
        const jsTime2 = dateTime2.toJavaScriptDate(implicitTimezone).getTime();
100✔
413

414
        if (jsTime1 === jsTime2) {
100✔
415
                // We should break the tie on the secondFraction property, which has no counterpart in JS dates
416
                if (dateTime1.secondFraction === dateTime2.secondFraction) {
46!
417
                        return 0;
46✔
418
                }
419
                if (dateTime1.secondFraction > dateTime2.secondFraction) {
×
420
                        return 1;
×
421
                }
422
                return -1;
×
423
        }
424

425
        if (jsTime1 > jsTime2) {
54✔
426
                return 1;
36✔
427
        }
428

429
        return -1;
18✔
430
}
431

432
export function equal(
3✔
433
        dateTime1: DateTime,
434
        dateTime2: DateTime,
435
        implicitTimezone?: DayTimeDuration | null
436
): boolean {
437
        return compare(dateTime1, dateTime2, implicitTimezone) === 0;
50✔
438
}
439

440
export function lessThan(
3✔
441
        dateTime1: DateTime,
442
        dateTime2: DateTime,
443
        implicitTimezone?: DayTimeDuration | null
444
): boolean {
445
        return compare(dateTime1, dateTime2, implicitTimezone) < 0;
25✔
446
}
447

448
export function greaterThan(
3✔
449
        dateTime1: DateTime,
450
        dateTime2: DateTime,
451
        implicitTimezone?: DayTimeDuration | null
452
): boolean {
453
        return compare(dateTime1, dateTime2, implicitTimezone) > 0;
25✔
454
}
455

456
export function subtract(
3✔
457
        dateTime1: DateTime,
458
        dateTime2: DateTime,
459
        implicitTimezone?: DayTimeDuration | null
460
): DayTimeDuration {
461
        // Divided by 1000 because date subtraction results in milliseconds
462
        const secondsOfDuration =
463
                (dateTime1.toJavaScriptDate(implicitTimezone).getTime() -
1✔
464
                        dateTime2.toJavaScriptDate(implicitTimezone).getTime()) /
465
                1000;
466
        return new DayTimeDuration(secondsOfDuration);
1✔
467
}
468

469
export function addDuration(dateTime: DateTime, duration: AbstractDuration): DateTime {
3✔
NEW
470
        const jsDateTime = dateTime.toJavaScriptDate();
×
NEW
471
        jsDateTime.setUTCFullYear(jsDateTime.getUTCFullYear() + duration.getYears())
×
NEW
472
        jsDateTime.setUTCMonth(jsDateTime.getUTCMonth() + duration.getMonths())
×
NEW
473
        jsDateTime.setUTCDate(jsDateTime.getUTCDate() + duration.getDays())
×
NEW
474
        jsDateTime.setUTCHours(jsDateTime.getUTCHours() + duration.getHours())
×
NEW
475
        jsDateTime.setUTCMinutes(jsDateTime.getUTCMinutes() + duration.getMinutes());
×
NEW
476
        jsDateTime.setUTCSeconds(jsDateTime.getUTCSeconds() + Math.floor(duration.getSeconds()));
×
NEW
477
        jsDateTime.setUTCMilliseconds(jsDateTime.getUTCMilliseconds() + duration.getSeconds() % 1);
×
478

NEW
479
        return new DateTime(
×
480
                jsDateTime.getUTCFullYear(),
481
                jsDateTime.getUTCMonth() + 1,
482
                jsDateTime.getUTCDate(),
483
                jsDateTime.getUTCHours(),
484
                jsDateTime.getUTCMinutes(),
485
                jsDateTime.getUTCSeconds(),
486
                jsDateTime.getUTCMilliseconds(),
487

488
                dateTime.getTimezone(),
489
        );
490
}
491

492
export function subtractDuration(dateTime: DateTime, duration: AbstractDuration): DateTime {
3✔
NEW
493
        const jsDateTime = dateTime.toJavaScriptDate();
×
NEW
494
        jsDateTime.setUTCFullYear(jsDateTime.getUTCFullYear() - duration.getYears())
×
NEW
495
        jsDateTime.setUTCMonth(jsDateTime.getUTCMonth() - duration.getMonths())
×
NEW
496
        jsDateTime.setUTCDate(jsDateTime.getUTCDate() - duration.getDays())
×
NEW
497
        jsDateTime.setUTCHours(jsDateTime.getUTCHours() - duration.getHours())
×
NEW
498
        jsDateTime.setUTCMinutes(jsDateTime.getUTCMinutes() - duration.getMinutes());
×
NEW
499
        jsDateTime.setUTCSeconds(jsDateTime.getUTCSeconds() - Math.floor(duration.getSeconds()));
×
NEW
500
        jsDateTime.setUTCMilliseconds(jsDateTime.getUTCMilliseconds() - duration.getSeconds() % 1);
×
501

NEW
502
        return new DateTime(
×
503
                jsDateTime.getUTCFullYear(),
504
                jsDateTime.getUTCMonth() + 1,
505
                jsDateTime.getUTCDate(),
506
                jsDateTime.getUTCHours(),
507
                jsDateTime.getUTCMinutes(),
508
                jsDateTime.getUTCSeconds(),
509
                jsDateTime.getUTCMilliseconds(),
510

511
                dateTime.getTimezone(),
512
        );
513
}
514

515
export default DateTime;
3✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc