Implement Timestamp.valueOf(). - #2662
Conversation
…es for the bounds of seconds and nanoseconds.
This enables comparison of Timestamp objects using the arithmetic comparison operators, such as < and >. #2632
| // 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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
FWIW, you could declare these as constants within the file rather than the class and then they wouldn't be exported at all.
There was a problem hiding this comment.
I have moved these variables and the normalizeAndPad() private method to outside the class declaration so that they will be truly private.
| 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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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".
| ): string { | ||
| const padLength = Math.ceil(Math.log10(maxValue - minValue)); | ||
| const normalizedValue = value - minValue; | ||
| return normalizedValue.toString().padStart(padLength, '0'); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
| minValue: number, | ||
| maxValue: number | ||
| ): string { | ||
| const padLength = Math.ceil(Math.log10(maxValue - minValue)); |
There was a problem hiding this comment.
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))).
There was a problem hiding this comment.
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.', () => { |
There was a problem hiding this comment.
Nit: I think in general we don't use period in our test names.
|
Uhm, I think GitHub ate my last comment.... Please also add a test that shows that 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
left a comment
There was a problem hiding this comment.
Some comment nit.
I also checked with @Feiyang1 and he says to go through API review.
| `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 |
There was a problem hiding this comment.
Nit: Enclose method name in backticks.
You also want to move this to the top, since we are using reverse chronological order.
| @@ -47,7 +49,7 @@ export class Timestamp { | |||
| ); | |||
| } | |||
| // Midnight at the beginning of 1/1/1 is the earliest Firestore supports. | |||
There was a problem hiding this comment.
Please move this comment to line 21, maybe worded a bit differently ("The beginning of 1/1/1")
| // 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 '-' |
There was a problem hiding this comment.
s/relative ordering of the Timestamp objects whose valueOf() method returned them/expected ordering.
| ); | ||
| } | ||
|
|
||
| // Overriding valueOf() allows Timestamp objects to be compared in JavaScript using the |
There was a problem hiding this comment.
Nit: The top part should be a JSDoc-style method comment.
| // 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 |
There was a problem hiding this comment.
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.
| // 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'); |
There was a problem hiding this comment.
Maybe add comment that the year 10,000 as 12 digits.
| ); | ||
| } | ||
|
|
||
| /** |
There was a problem hiding this comment.
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
Feiyang1
left a comment
There was a problem hiding this comment.
LGTM for tsconfig.base.json and firebase/index.d.ts.
Implement Timestamp.valueOf() so that Timestamp objects can be compared for relative ordering using the arithmetic comparison operators (i.e. <, <=, >, and >=).
Fixes #2632