Skip to content

Commit f24ddc4

Browse files
authored
Implement Timestamp.valueOf() (#2662)
This enables comparison of Timestamp objects using the arithmetic comparison operators, such as < and >. Fixes: #2632
1 parent 488e7f4 commit f24ddc4

File tree

7 files changed

+130
-2
lines changed

7 files changed

+130
-2
lines changed

config/tsconfig.base.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"es2015.symbol.wellknown",
1515
"es2015.core",
1616
"es2017.object",
17+
"es2017.string",
1718
],
1819
"module": "ES2015",
1920
"moduleResolution": "node",

packages/firebase/index.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7521,6 +7521,12 @@ declare namespace firebase.firestore {
75217521
* @return true if this `Timestamp` is equal to the provided one.
75227522
*/
75237523
isEqual(other: Timestamp): boolean;
7524+
7525+
/**
7526+
* Converts this object to a primitive string, which allows Timestamp objects to be compared
7527+
* using the `>`, `<=`, `>=` and `>` operators.
7528+
*/
7529+
valueOf(): string;
75247530
}
75257531

75267532
/**

packages/firestore-types/index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ export class Timestamp {
113113
toMillis(): number;
114114

115115
isEqual(other: Timestamp): boolean;
116+
117+
valueOf(): string;
116118
}
117119

118120
export class Blob {

packages/firestore/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
# Unreleased
2+
- [feature] Implemented `Timestamp.valueOf()` so that `Timestamp` objects can be
3+
compared for relative ordering using the JavaScript arithmetic comparison
4+
operators (#2632).
25
- [fixed] Fixed an issue where auth credentials were not respected in Cordova
36
environments (#2626).
47
- [fixed] Fixed a performance regression introduced by the addition of

packages/firestore/externs.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts",
1111
"node_modules/typescript/lib/lib.es2015.core.d.ts",
1212
"node_modules/typescript/lib/lib.es2017.object.d.ts",
13+
"node_modules/typescript/lib/lib.es2017.string.d.ts",
1314
"packages/app-types/index.d.ts",
1415
"packages/app-types/private.d.ts",
1516
"packages/auth-interop-types/index.d.ts",

packages/firestore/src/api/timestamp.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
import { Code, FirestoreError } from '../util/error';
1919
import { primitiveComparator } from '../util/misc';
2020

21+
// The earlist date supported by Firestore timestamps (0001-01-01T00:00:00Z).
22+
const MIN_SECONDS = -62135596800;
23+
2124
export class Timestamp {
2225
static now(): Timestamp {
2326
return Timestamp.fromMillis(Date.now());
@@ -46,8 +49,7 @@ export class Timestamp {
4649
'Timestamp nanoseconds out of range: ' + nanoseconds
4750
);
4851
}
49-
// Midnight at the beginning of 1/1/1 is the earliest Firestore supports.
50-
if (seconds < -62135596800) {
52+
if (seconds < MIN_SECONDS) {
5153
throw new FirestoreError(
5254
Code.INVALID_ARGUMENT,
5355
'Timestamp seconds out of range: ' + seconds
@@ -92,4 +94,18 @@ export class Timestamp {
9294
')'
9395
);
9496
}
97+
98+
valueOf(): string {
99+
// This method returns a string of the form <seconds>.<nanoseconds> where <seconds> is
100+
// translated to have a non-negative value and both <seconds> and <nanoseconds> are left-padded
101+
// with zeroes to be a consistent length. Strings with this format then have a lexiographical
102+
// ordering that matches the expected ordering. The <seconds> translation is done to avoid
103+
// having a leading negative sign (i.e. a leading '-' character) in its string representation,
104+
// which would affect its lexiographical ordering.
105+
const adjustedSeconds = this.seconds - MIN_SECONDS;
106+
// Note: Up to 12 decimal digits are required to represent all valid 'seconds' values.
107+
const formattedSeconds = String(adjustedSeconds).padStart(12, '0');
108+
const formattedNanoseconds = String(this.nanoseconds).padStart(9, '0');
109+
return formattedSeconds + '.' + formattedNanoseconds;
110+
}
95111
}

packages/firestore/test/unit/api/timestamp.test.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,55 @@
1616
*/
1717

1818
import { expect } from 'chai';
19+
import { Code } from '../../../src/util/error';
1920
import { Timestamp } from '../../../src/api/timestamp';
2021
import { addEqualityMatcher } from '../../util/equality_matcher';
2122

2223
describe('Timestamp', () => {
2324
addEqualityMatcher();
2425

26+
it('constructor should validate the "seconds" argument and store it', () => {
27+
expect(new Timestamp(1, 0)).to.have.property('seconds', 1);
28+
expect(new Timestamp(-62135596800, 0)).to.have.property(
29+
'seconds',
30+
-62135596800
31+
);
32+
expect(new Timestamp(253402300799, 0)).to.have.property(
33+
'seconds',
34+
253402300799
35+
);
36+
37+
expect(() => {
38+
new Timestamp(-62135596801, 0);
39+
})
40+
.to.throw(/seconds/)
41+
.with.property('code', Code.INVALID_ARGUMENT);
42+
43+
expect(() => {
44+
new Timestamp(253402300800, 0);
45+
})
46+
.to.throw(/seconds/)
47+
.with.property('code', Code.INVALID_ARGUMENT);
48+
});
49+
50+
it('constructor should validate the "nanoseconds" argument and store it', () => {
51+
expect(new Timestamp(0, 1)).to.have.property('nanoseconds', 1);
52+
expect(new Timestamp(0, 0)).to.have.property('nanoseconds', 0);
53+
expect(new Timestamp(0, 1e9 - 1)).to.have.property('nanoseconds', 1e9 - 1);
54+
55+
expect(() => {
56+
new Timestamp(0, -1);
57+
})
58+
.to.throw(/nanoseconds/)
59+
.with.property('code', Code.INVALID_ARGUMENT);
60+
61+
expect(() => {
62+
new Timestamp(0, 1e9);
63+
})
64+
.to.throw(/nanoseconds/)
65+
.with.property('code', Code.INVALID_ARGUMENT);
66+
});
67+
2568
it('fromDate', () => {
2669
expect(Timestamp.fromDate(new Date(1488872578916))).to.deep.equal({
2770
seconds: 1488872578,
@@ -33,4 +76,60 @@ describe('Timestamp', () => {
3376
nanoseconds: 750000000
3477
});
3578
});
79+
80+
it('valueOf', () => {
81+
expect(new Timestamp(-62135596677, 456).valueOf()).to.equal(
82+
'000000000123.000000456'
83+
);
84+
expect(new Timestamp(-62135596800, 0).valueOf()).to.equal(
85+
'000000000000.000000000'
86+
);
87+
expect(new Timestamp(253402300799, 1e9 - 1).valueOf()).to.equal(
88+
'315537897599.999999999'
89+
);
90+
});
91+
92+
it('arithmetic comparison of a Timestamp object to itself', () => {
93+
const timestamp = new Timestamp(1, 1);
94+
expect(timestamp < timestamp).to.be.false;
95+
expect(timestamp <= timestamp).to.be.true;
96+
expect(timestamp > timestamp).to.be.false;
97+
expect(timestamp >= timestamp).to.be.true;
98+
});
99+
100+
it('arithmetic comparison of equivalent, but distinct, Timestamp objects', () => {
101+
const t1 = new Timestamp(1, 1);
102+
const t2 = new Timestamp(1, 1);
103+
expect(t1 < t2).to.be.false;
104+
expect(t1 <= t2).to.be.true;
105+
expect(t1 > t2).to.be.false;
106+
expect(t1 >= t2).to.be.true;
107+
});
108+
109+
it('arithmetic comparison of Timestamp objects whose nanoseconds differ', () => {
110+
const t1 = new Timestamp(1, 1);
111+
const t2 = new Timestamp(1, 2);
112+
expect(t1 < t2).to.be.true;
113+
expect(t1 <= t2).to.be.true;
114+
expect(t1 > t2).to.be.false;
115+
expect(t1 >= t2).to.be.false;
116+
});
117+
118+
it('arithmetic comparison of Timestamp objects whose seconds differ', () => {
119+
const t1 = new Timestamp(100, 0);
120+
const t2 = new Timestamp(200, 0);
121+
expect(t1 < t2).to.be.true;
122+
expect(t1 <= t2).to.be.true;
123+
expect(t1 > t2).to.be.false;
124+
expect(t1 >= t2).to.be.false;
125+
});
126+
127+
it('arithmetic comparison of the smallest and largest Timestamp objects', () => {
128+
const t1 = new Timestamp(-62135596800, 0);
129+
const t2 = new Timestamp(253402300799, 999999999);
130+
expect(t1 < t2).to.be.true;
131+
expect(t1 <= t2).to.be.true;
132+
expect(t1 > t2).to.be.false;
133+
expect(t1 >= t2).to.be.false;
134+
});
36135
});

0 commit comments

Comments
 (0)