summaryrefslogtreecommitdiffstats
path: root/js/src/builtin/temporal/TemporalTypes.h
blob: 654fd65a3b2e9c51ac40e56f22418da94932c514 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef builtin_temporal_TemporalTypes_h
#define builtin_temporal_TemporalTypes_h

#include "mozilla/Assertions.h"
#include "mozilla/CheckedInt.h"

#include <stdint.h>

#include "builtin/temporal/TemporalUnit.h"

namespace js::temporal {

// Use __builtin_assume when available, otherwise fall back to
// __builtin_unreachable.
#if defined __has_builtin
#  if __has_builtin(__builtin_assume)
#    define JS_ASSUME(x) __builtin_assume(x)
#  elif __has_builtin(__builtin_unreachable)
#    define JS_ASSUME(x) \
      if (!(x)) __builtin_unreachable()
#  else
#  endif
#endif

// Fallback to no-op if neither built-in is available.
#ifndef JS_ASSUME
#  define JS_ASSUME(x) \
    do {               \
    } while (false)
#endif

/**
 * Struct to represent a seconds and nanoseconds value. The nanoseconds value
 * is normalized to the range [0, 999'999'999].
 */
template <typename Derived>
struct SecondsAndNanoseconds {
  // Seconds part in the range allowed by the derived class.
  int64_t seconds = 0;

  // Nanoseconds part in the range [0, 999'999'999].
  int32_t nanoseconds = 0;

  constexpr bool operator==(const SecondsAndNanoseconds& other) const {
    return seconds == other.seconds && nanoseconds == other.nanoseconds;
  }

  constexpr bool operator<(const SecondsAndNanoseconds& other) const {
    // The compiler can optimize expressions like |instant < Instant{}| to a
    // single right-shift operation when we propagate the range of nanoseconds.
    JS_ASSUME(nanoseconds >= 0);
    JS_ASSUME(other.nanoseconds >= 0);
    return (seconds < other.seconds) ||
           (seconds == other.seconds && nanoseconds < other.nanoseconds);
  }

  // Other operators are implemented in terms of operator== and operator<.
  constexpr bool operator!=(const SecondsAndNanoseconds& other) const {
    return !(*this == other);
  }
  constexpr bool operator>(const SecondsAndNanoseconds& other) const {
    return other < *this;
  }
  constexpr bool operator<=(const SecondsAndNanoseconds& other) const {
    return !(other < *this);
  }
  constexpr bool operator>=(const SecondsAndNanoseconds& other) const {
    return !(*this < other);
  }

 protected:
  template <typename T, typename U, class R = Derived>
  static constexpr R add(const SecondsAndNanoseconds<T>& self,
                         const SecondsAndNanoseconds<U>& other) {
    // The caller needs to make sure integer overflow won't happen. CheckedInt
    // will assert on overflow and we intentionally don't try to recover from
    // overflow in this method.

    mozilla::CheckedInt64 secs = self.seconds;
    secs += other.seconds;

    mozilla::CheckedInt32 nanos = self.nanoseconds;
    nanos += other.nanoseconds;

    if (nanos.value() >= 1'000'000'000) {
      secs += 1;
      nanos -= 1'000'000'000;
    }
    MOZ_ASSERT(0 <= nanos.value() && nanos.value() < 1'000'000'000);

    return {secs.value(), nanos.value()};
  }

  template <class T, class U, class R = Derived>
  static constexpr R subtract(const SecondsAndNanoseconds<T>& self,
                              const SecondsAndNanoseconds<U>& other) {
    // The caller needs to make sure integer underflow won't happen. CheckedInt
    // will assert on underflow and we intentionally don't try to recover from
    // underflow in this method.

    mozilla::CheckedInt64 secs = self.seconds;
    secs -= other.seconds;

    mozilla::CheckedInt32 nanos = self.nanoseconds;
    nanos -= other.nanoseconds;

    if (nanos.value() < 0) {
      secs -= 1;
      nanos += 1'000'000'000;
    }
    MOZ_ASSERT(0 <= nanos.value() && nanos.value() < 1'000'000'000);

    return {secs.value(), nanos.value()};
  }

  static constexpr Derived negate(const Derived& self) {
    return subtract(Derived{}, self);
  }

 public:
  /**
   * Return the absolute value.
   */
  constexpr Derived abs() const {
    int64_t sec = seconds;
    int32_t nanos = nanoseconds;
    if (sec < 0) {
      if (nanos > 0) {
        sec += 1;
        nanos -= 1'000'000'000;
      }
      sec = -sec;
      nanos = -nanos;
    }
    return {sec, nanos};
  }

  /**
   * Return the seconds value, rounded towards zero.
   */
  constexpr int64_t toSeconds() const {
    int64_t sec = seconds;
    int32_t nanos = nanoseconds;
    if (sec < 0 && nanos > 0) {
      sec += 1;
    }
    return sec;
  }

  /**
   * Return the milliseconds value, rounded towards zero.
   */
  constexpr int64_t toMilliseconds() const {
    int64_t sec = seconds;
    int32_t nanos = nanoseconds;
    if (sec < 0 && nanos > 0) {
      sec += 1;
      nanos -= 1'000'000'000;
    }
    return (sec * 1'000) + (nanos / 1'000'000);
  }

  /**
   * Return the microseconds value, rounded towards zero.
   */
  constexpr int64_t toMicroseconds() const {
    int64_t sec = seconds;
    int32_t nanos = nanoseconds;
    if (sec < 0 && nanos > 0) {
      sec += 1;
      nanos -= 1'000'000'000;
    }
    return (sec * 1'000'000) + (nanos / 1'000);
  }

  /**
   * Return the nanoseconds value.
   *
   * The returned nanoseconds amount can be invalid on overflow. The caller is
   * responsible for handling the overflow case.
   */
  constexpr mozilla::CheckedInt64 toNanoseconds() const {
    mozilla::CheckedInt64 nanos = seconds;
    nanos *= ToNanoseconds(TemporalUnit::Second);
    nanos += nanoseconds;
    return nanos;
  }

  /**
   * Create from a minutes value.
   */
  static constexpr Derived fromMinutes(int64_t minutes) {
    return {minutes * 60, 0};
  }

  /**
   * Create from a seconds value.
   */
  static constexpr Derived fromSeconds(int64_t seconds) { return {seconds, 0}; }

  /**
   * Create from a milliseconds value.
   */
  static constexpr Derived fromMilliseconds(int64_t milliseconds) {
    int64_t seconds = milliseconds / 1'000;
    int32_t millis = milliseconds % 1'000;
    if (millis < 0) {
      seconds -= 1;
      millis += 1'000;
    }
    return {seconds, millis * 1'000'000};
  }

  /**
   * Create from a microseconds value.
   */
  static constexpr Derived fromMicroseconds(int64_t microseconds) {
    int64_t seconds = microseconds / 1'000'000;
    int32_t micros = microseconds % 1'000'000;
    if (micros < 0) {
      seconds -= 1;
      micros += 1'000'000;
    }
    return {seconds, micros * 1'000};
  }

  /**
   * Create from a nanoseconds value.
   */
  static constexpr Derived fromNanoseconds(int64_t nanoseconds) {
    int64_t seconds = nanoseconds / 1'000'000'000;
    int32_t nanos = nanoseconds % 1'000'000'000;
    if (nanos < 0) {
      seconds -= 1;
      nanos += 1'000'000'000;
    }
    return {seconds, nanos};
  }
};

#undef JS_ASSUME

/**
 * InstantSpan represents a span of time between two Instants, measured in
 * nanoseconds.
 */
struct InstantSpan final : SecondsAndNanoseconds<InstantSpan> {
  constexpr InstantSpan& operator+=(const InstantSpan& other) {
    *this = add(*this, other);
    return *this;
  }

  constexpr InstantSpan& operator-=(const InstantSpan& other) {
    *this = subtract(*this, other);
    return *this;
  }

  constexpr InstantSpan operator+(const InstantSpan& other) const {
    return add(*this, other);
  }

  constexpr InstantSpan operator-(const InstantSpan& other) const {
    return subtract(*this, other);
  }

  constexpr InstantSpan operator-() const { return negate(*this); }

  /**
   * Returns the maximum instant span value.
   */
  static constexpr InstantSpan max() {
    // The limit is 2×8.64 × 10^21 nanoseconds, which is 2×8.64 × 10^12 seconds.
    constexpr int64_t seconds = 2 * 8'640'000'000'000;
    constexpr int64_t nanos = 0;
    return {seconds, nanos};
  }

  /**
   * Returns the minimum instant span value.
   */
  static constexpr InstantSpan min() { return -max(); }
};

/**
 * Instant represents a time since the epoch value, measured in nanoseconds.
 *
 * Instant supports a range of ±8.64 × 10^21 nanoseconds, covering ±10^8 days
 * in either direction relative to midnight at the beginning of 1 January 1970
 * UTC. The range also exactly matches the supported range of JavaScript Date
 * objects.
 *
 * C++ doesn't provide a built-in type capable of storing an integer in the
 * range ±8.64 × 10^21, therefore we need to create our own abstraction. This
 * struct follows the design of `std::timespec` and splits the instant into a
 * signed seconds part and an unsigned nanoseconds part.
 */
struct Instant final : SecondsAndNanoseconds<Instant> {
  constexpr Instant& operator+=(const InstantSpan& other) {
    *this = add(*this, other);
    return *this;
  }

  constexpr Instant& operator-=(const InstantSpan& other) {
    *this = subtract(*this, other);
    return *this;
  }

  constexpr Instant operator+(const InstantSpan& other) const {
    return add(*this, other);
  }

  constexpr Instant operator-(const InstantSpan& other) const {
    return subtract(*this, other);
  }

  constexpr InstantSpan operator-(const Instant& other) const {
    return subtract<Instant, Instant, InstantSpan>(*this, other);
  }

  constexpr Instant operator-() const { return negate(*this); }

  /**
   * Return this instant as microseconds from the start of the epoch. (Rounds
   * towards negative infinity.)
   */
  constexpr int64_t floorToMicroseconds() const {
    return (seconds * 1'000'000) + (nanoseconds / 1'000);
  }

  /**
   * Return this instant as milliseconds from the start of the epoch. (Rounds
   * towards negative infinity.)
   */
  constexpr int64_t floorToMilliseconds() const {
    return (seconds * 1'000) + (nanoseconds / 1'000'000);
  }

  /**
   * Return this instant as milliseconds from the start of the epoch. (Rounds
   * towards positive infinity.)
   */
  constexpr int64_t ceilToMilliseconds() const {
    return floorToMilliseconds() + int64_t(nanoseconds % 1'000'000 != 0);
  }

  /**
   * Returns the maximum instant value.
   */
  static constexpr Instant max() {
    // The limit is 8.64 × 10^21 nanoseconds, which is 8.64 × 10^12 seconds.
    constexpr int64_t seconds = 8'640'000'000'000;
    constexpr int64_t nanos = 0;
    return {seconds, nanos};
  }

  /**
   * Returns the minimum instant value.
   */
  static constexpr Instant min() { return -max(); }
};

/**
 * Plain date represents a date in the ISO 8601 calendar.
 */
struct PlainDate final {
  // [-271821, 275760]
  //
  // Dates are limited to the range of ±100'000'000 days relative to midnight at
  // the beginning of 1 January 1970 UTC. This limits valid years to [-271821,
  // 275760].
  int32_t year = 0;

  // [1, 12]
  int32_t month = 0;

  // [1, 31]
  int32_t day = 0;

  bool operator==(const PlainDate& other) const {
    return year == other.year && month == other.month && day == other.day;
  }

  bool operator!=(const PlainDate& other) const { return !(*this == other); }
};

/**
 * Plain time represents a time value on a 24-hour clock. Leap seconds aren't
 * supported.
 */
struct PlainTime final {
  // [0, 23]
  int32_t hour = 0;

  // [0, 59]
  int32_t minute = 0;

  // [0, 59]
  int32_t second = 0;

  // [0, 999]
  int32_t millisecond = 0;

  // [0, 999]
  int32_t microsecond = 0;

  // [0, 999]
  int32_t nanosecond = 0;

  bool operator==(const PlainTime& other) const {
    return hour == other.hour && minute == other.minute &&
           second == other.second && millisecond == other.millisecond &&
           microsecond == other.microsecond && nanosecond == other.nanosecond;
  }

  bool operator!=(const PlainTime& other) const { return !(*this == other); }
};

/**
 * Plain date-time represents a date-time value in the ISO 8601 calendar.
 */
struct PlainDateTime final {
  PlainDate date;
  PlainTime time;

  bool operator==(const PlainDateTime& other) const {
    return date == other.date && time == other.time;
  }

  bool operator!=(const PlainDateTime& other) const {
    return !(*this == other);
  }
};

/**
 * Duration represents the difference between dates or times. Each duration
 * component is an integer and all components must have the same sign.
 */
struct Duration final {
  double years = 0;
  double months = 0;
  double weeks = 0;
  double days = 0;
  double hours = 0;
  double minutes = 0;
  double seconds = 0;
  double milliseconds = 0;
  double microseconds = 0;
  double nanoseconds = 0;

  bool operator==(const Duration& other) const {
    return years == other.years && months == other.months &&
           weeks == other.weeks && days == other.days && hours == other.hours &&
           minutes == other.minutes && seconds == other.seconds &&
           milliseconds == other.milliseconds &&
           microseconds == other.microseconds &&
           nanoseconds == other.nanoseconds;
  }

  bool operator!=(const Duration& other) const { return !(*this == other); }

  /**
   * Return the date components of this duration.
   */
  Duration date() const { return {years, months, weeks, days}; }

  /**
   * Return the time components of this duration.
   */
  Duration time() const {
    return {
        0,
        0,
        0,
        0,
        hours,
        minutes,
        seconds,
        milliseconds,
        microseconds,
        nanoseconds,
    };
  }

  /**
   * Return a new duration with every component negated.
   */
  Duration negate() const {
    // Add zero to convert -0 to +0.
    return {
        -years + (+0.0),       -months + (+0.0),       -weeks + (+0.0),
        -days + (+0.0),        -hours + (+0.0),        -minutes + (+0.0),
        -seconds + (+0.0),     -milliseconds + (+0.0), -microseconds + (+0.0),
        -nanoseconds + (+0.0),
    };
  }
};

/**
 * Date duration represents the difference between dates. Each duration
 * component is an integer and all components must have the same sign.
 */
struct DateDuration final {
  double years = 0;
  double months = 0;
  double weeks = 0;
  double days = 0;

  Duration toDuration() { return {years, months, weeks, days}; }
};

/**
 * Time duration represents the difference between times. Each duration
 * component is an integer and all components must have the same sign.
 */
struct TimeDuration final {
  double days = 0;
  double hours = 0;
  double minutes = 0;
  double seconds = 0;
  double milliseconds = 0;
  double microseconds = 0;
  double nanoseconds = 0;

  Duration toDuration() {
    return {0,
            0,
            0,
            days,
            hours,
            minutes,
            seconds,
            milliseconds,
            microseconds,
            nanoseconds};
  }
};

} /* namespace js::temporal */

#endif /* builtin_temporal_TemporalTypes_h */