-
Notifications
You must be signed in to change notification settings - Fork 944
Implement Timestamp.valueOf(). #2662
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…es for the bounds of seconds and nanoseconds.
This enables comparison of Timestamp objects using the arithmetic comparison operators, such as < and >. #2632
There was a problem hiding this comment.
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 :)
// 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.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
@@ -19,6 +19,13 @@ import { Code, FirestoreError } from '../util/error'; | |||
import { primitiveComparator } from '../util/misc'; | |||
|
|||
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.
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.
There was a problem hiding this comment.
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".
): 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.
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.
There was a problem hiding this comment.
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.
minValue: number, | ||
maxValue: number | ||
): string { | ||
const padLength = Math.ceil(Math.log10(maxValue - minValue)); |
There was a problem hiding this comment.
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)))
.
There was a problem hiding this comment.
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.
import { Timestamp } from '../../../src/api/timestamp'; | ||
import { addEqualityMatcher } from '../../util/equality_matcher'; | ||
|
||
describe('Timestamp', () => { | ||
addEqualityMatcher(); | ||
|
||
it('constructor should validate the "seconds" argument and store it.', () => { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
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().
There was a problem hiding this comment.
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.
packages/firestore/CHANGELOG.md
Outdated
@@ -5,6 +5,9 @@ | |||
`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.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -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.
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")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
// 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.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@@ -92,4 +94,21 @@ export class Timestamp { | |||
')' | |||
); | |||
} | |||
|
|||
// Overriding valueOf() allows Timestamp objects to be compared in JavaScript using the |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
// 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.
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.
There was a problem hiding this comment.
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'); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@@ -92,4 +94,25 @@ export class Timestamp { | |||
')' | |||
); | |||
} | |||
|
|||
/** |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
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
.
Implement Timestamp.valueOf() so that Timestamp objects can be compared for relative ordering using the arithmetic comparison operators (i.e. <, <=, >, and >=).
Fixes #2632