Skip to content

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

Merged
merged 11 commits into from
Mar 2, 2020
Merged

Implement Timestamp.valueOf(). #2662

merged 11 commits into from
Mar 2, 2020

Conversation

dconeybe
Copy link
Contributor

@dconeybe dconeybe commented Feb 21, 2020

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
Copy link
Contributor

@schmidt-sebastian schmidt-sebastian left a 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;
Copy link
Contributor

@schmidt-sebastian schmidt-sebastian Feb 21, 2020

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
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
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.

@@ -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.
Copy link
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
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".

): string {
const padLength = Math.ceil(Math.log10(maxValue - minValue));
const normalizedValue = value - minValue;
return normalizedValue.toString().padStart(padLength, '0');
Copy link
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
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.

minValue: number,
maxValue: number
): string {
const padLength = Math.ceil(Math.log10(maxValue - minValue));
Copy link
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
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.

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.', () => {
Copy link
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
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
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
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().
Copy link
Contributor

@schmidt-sebastian schmidt-sebastian left a 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.

@@ -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
Copy link
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
Contributor Author

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.
Copy link
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
Contributor Author

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 '-'
Copy link
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
Contributor Author

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
Copy link
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
Contributor Author

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
Copy link
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
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
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
Contributor Author

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 {
')'
);
}

/**
Copy link
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
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
Copy link
Member

@Feiyang1 Feiyang1 left a 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.

@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
Projects
None yet
Development

Successfully merging this pull request may close these issues.

FR: Add valueOf() to Timestamp instances
7 participants