Skip to content

Implement Timestamp.valueOf(). - #2662

Merged
dconeybe merged 11 commits into
masterfrom
dconeybe/TimestampValueOf
Mar 2, 2020
Merged

Implement Timestamp.valueOf().#2662
dconeybe merged 11 commits into
masterfrom
dconeybe/TimestampValueOf

Conversation

@dconeybe

@dconeybe dconeybe commented Feb 21, 2020

Copy link
Copy Markdown
Contributor

Implement Timestamp.valueOf() so that Timestamp objects can be compared for relative ordering using the arithmetic comparison operators (i.e. <, <=, >, and >=).

Fixes #2632

…es for the bounds of seconds and nanoseconds.
This enables comparison of Timestamp objects using the arithmetic comparison operators, such as < and >.

#2632

@schmidt-sebastian schmidt-sebastian left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whee! This is a great PR. Left some feedback to make sure you don't run out of work :)

Comment thread packages/firestore/src/api/timestamp.ts Outdated
// This will break in the year 10,000.
private static readonly MAX_SECONDS = 253402300799;
private static readonly MIN_NANOSECONDS = 0;
private static readonly MAX_NANOSECONDS = 1e9 - 1;

@schmidt-sebastian schmidt-sebastian Feb 21, 2020

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that "private" is not a thing in the final JavaScript build. When TypeScript transpiles this class to JS, these static members essentially become public. To indicate that we want these member to be treated as private members, we generally prefix all private members of our API classes with underscores.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, you could declare these as constants within the file rather than the class and then they wouldn't be exported at all.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have moved these variables and the normalizeAndPad() private method to outside the class declaration so that they will be truly private.

Comment thread packages/firestore/src/api/timestamp.ts Outdated
export class Timestamp {
// Midnight at the beginning of 1/1/1 is the earliest Firestore supports.
private static readonly MIN_SECONDS = -62135596800;
// This will break in the year 10,000.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: This constant won't break. It will continue to point to the year 10,000 even in the year 10,001.
You can probably just mention that the value corresponds to the year 10,000.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha true. I just copied this comment from the code in its old location. I've updated these comments to state the date to which they correspond and removed the mention of "breaking".

Comment thread packages/firestore/src/api/timestamp.ts Outdated
): string {
const padLength = Math.ceil(Math.log10(maxValue - minValue));
const normalizedValue = value - minValue;
return normalizedValue.toString().padStart(padLength, '0');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: I started a discussion in the JS room whether we can use this API. See https://caniuse.com/#feat=pad-start-end for context.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The padStart() method is covered by the "required" polyfills documented at https://firebase.google.com/support/guides/environments_js-sdk#polyfills. So I think we're okay.

Comment thread packages/firestore/src/api/timestamp.ts Outdated
minValue: number,
maxValue: number
): string {
const padLength = Math.ceil(Math.log10(maxValue - minValue));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most pad() methods that I am aware of take as an argument the desired length to pad to. I would suggest you do the same thing here: The nanosecond length is always 9, and the MAX_SECONDS value is a constant that you could define as const or via Math.ceil(Math.log10(MAX_SECONDS))).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion. I'll simply hardcode the pad lengths, since there is really no value in computing those constants over and over again.

describe('Timestamp', () => {
addEqualityMatcher();

it('constructor should validate the "seconds" argument and store it.', () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I think in general we don't use period in our test names.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@schmidt-sebastian

Copy link
Copy Markdown
Contributor

Uhm, I think GitHub ate my last comment....

Please also add a test that shows that <, = and > now work correctly. You should also now be able to use Timestamps in Map objects, and object equality should now be based on the Timestamp data rather than the address of the Timestamp object. You can test this like this:

Map m = new Map();
m.add(new Timestamp(1,1));
expect(m.has(new Timestamp(1,1)).to.be.true;

Furthermore, can you also add a Changelog entry: https://github.com/firebase/firebase-js-sdk/blob/master/packages/firestore/CHANGELOG.md

Your PR description should also include "Fixes: " which will auto-close the issue on merging.

@dconeybe

Copy link
Copy Markdown
Contributor Author

Uhm, I think GitHub ate my last comment....

Please also add a test that shows that <, = and > now work correctly. You should also now be able to use Timestamps in Map objects, and object equality should now be based on the Timestamp data rather than the address of the Timestamp object. You can test this like this:

Map m = new Map();
m.add(new Timestamp(1,1));
expect(m.has(new Timestamp(1,1)).to.be.true;

Furthermore, can you also add a Changelog entry: https://github.com/firebase/firebase-js-sdk/blob/master/packages/firestore/CHANGELOG.md

Your PR description should also include "Fixes: " which will auto-close the issue on merging.

Implementing valueOf() does not affect the == or === operators when used on Timestamp objects. These operators just test if two objects reference the exact same object and do not test the properties of the objects. By extension, using Timestamp objects as keys in a Map will not change as a result of this PR.

I have updated the PR description as suggested and will update the changelog as well.

This fixes the issue where the minified js contains __PRIVATE_padStart() instead of just padStart().

@schmidt-sebastian schmidt-sebastian left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comment nit.

I also checked with @Feiyang1 and he says to go through API review.

Comment thread packages/firestore/CHANGELOG.md Outdated
`Query.limitToLast(n: number)` in Firestore 1.7.0 (Firebase 7.3.0) (#2620).
- [fixed] Fixed an issue where `CollectionReference.add()` would reject
custom types when using `withConverter()` (#2606).
- [feature] Implemented Timestamp.valueOf() so that Timestamp objects can be

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Enclose method name in backticks.

You also want to move this to the top, since we are using reverse chronological order.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment thread packages/firestore/src/api/timestamp.ts Outdated
@@ -47,7 +49,7 @@ export class Timestamp {
);
}
// Midnight at the beginning of 1/1/1 is the earliest Firestore supports.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move this comment to line 21, maybe worded a bit differently ("The beginning of 1/1/1")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread packages/firestore/src/api/timestamp.ts Outdated
// to have a non-negative value and both <seconds> and <nanoseconds> are left-padded with zeroes
// to be a consistent length. Strings with this format then have a lexiographical ordering that
// matches the relative ordering of the Timestamp objects whose valueOf() method returned them.
// The <seconds> translation is done to avoid having a leading negative sign (i.e. a leading '-'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/relative ordering of the Timestamp objects whose valueOf() method returned them/expected ordering.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread packages/firestore/src/api/timestamp.ts Outdated
);
}

// Overriding valueOf() allows Timestamp objects to be compared in JavaScript using the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: The top part should be a JSDoc-style method comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread packages/firestore/src/api/timestamp.ts Outdated
// arithmetic comparison operators, such as < and >.
// https://github.com/firebase/firebase-js-sdk/issues/2632
//
// This method returns a string of the form <seconds>.<nanoseconds> where <seconds> is translated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you move this comment into the method? You are describing implementation details. The consumer of the method should only now that the valueOf() return type should sort naturally.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

// having a leading negative sign (i.e. a leading '-' character) in its string representation,
// which would affect its lexiographical ordering.
const adjustedSeconds = this.seconds - MIN_SECONDS;
const formattedSeconds = String(adjustedSeconds).padStart(12, '0');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add comment that the year 10,000 as 12 digits.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread packages/firestore/src/api/timestamp.ts Outdated
);
}

/**

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about:

/**
   * Converts this object to a primitive string, which allows Timestamp objects to be compared
   * using the `>`, `<=, `>=` and `>` operators.
   */

Rationale:

  • You are providing the override. A user doesn't need to do further overriding.
  • We generally don't link to GitHub issues in top-level method comments. Feel free to move the link to the implementation if you think it provides value.
  • We are in JavaScript land, so I dropped the explicit mention of the language name.

You also need to copy this API to https://github.com/firebase/firebase-js-sdk/blob/master/packages/firebase/index.d.ts and https://github.com/firebase/firebase-js-sdk/blob/master/packages/firestore-types/index.d.ts

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@schmidt-sebastian schmidt-sebastian removed their assignment Feb 28, 2020
@dconeybe dconeybe assigned dconeybe and Feiyang1 and unassigned dconeybe Feb 28, 2020

@Feiyang1 Feiyang1 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM for tsconfig.base.json and firebase/index.d.ts.

@dconeybe
dconeybe merged commit f24ddc4 into master Mar 2, 2020
@dconeybe
dconeybe deleted the dconeybe/TimestampValueOf branch March 2, 2020 18:53
@firebase firebase locked and limited conversation to collaborators Apr 2, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

7 participants