Skip to content
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

feat: Update Timestamp Constructor with nanoseconds #27

Merged
1 change: 1 addition & 0 deletions packages/dart_firebase_admin/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

- Fixes crash when updating users (thanks to @HeySreelal)
- Marked various classes that cannot be extended as base/final.
- Added a default constructor on `Timestamp` (thanks to @KKimj)

## 0.3.1

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,19 @@
return '${formattedDate}Z';
}

/// A Timestamp represents a point in time independent of any time zone or calendar,
/// represented as seconds and fractions of seconds at nanosecond resolution in UTC
/// Epoch time. It is encoded using the Proleptic Gregorian Calendar which extends
/// the Gregorian calendar backwards to year one. It is encoded assuming all minutes
/// are 60 seconds long, i.e. leap seconds are "smeared" so that no leap second table
/// is needed for interpretation. Range is from 0001-01-01T00:00:00Z to
/// 9999-12-31T23:59:59.999999999Z. By restricting to that range, we ensure that we
/// can convert to and from RFC 3339 date strings.
///
/// For more information, see [the reference timestamp definition](https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto)

Check warning on line 26 in packages/dart_firebase_admin/lib/src/google_cloud_firestore/timestamp.dart

View check run for this annotation

In Solidarity / Inclusive Language

Match Found

Please consider an alternative to `master`. Possibilities include: `primary`, `main`, `leader`, `active`, `writer`
Raw output
/master/gi
@immutable
final class Timestamp implements _Serializable {
Timestamp._({required this.seconds, required this.nanoseconds}) {
Timestamp({required this.seconds, required this.nanoseconds}) {
const minSeconds = -62135596800;
const maxSeconds = 253402300799;

Expand Down Expand Up @@ -63,7 +73,7 @@
/// Returns a new [Timestamp] representing the same point in time
/// as the given date.
factory Timestamp.fromDate(DateTime date) {
return Timestamp.fromMillis(date.millisecondsSinceEpoch);
return Timestamp.fromMicros(date.microsecondsSinceEpoch);
}

/// Creates a new timestamp from the given number of milliseconds.
Expand All @@ -82,7 +92,28 @@
factory Timestamp.fromMillis(int milliseconds) {
final seconds = (milliseconds / 1000).floor();
final nanos = (milliseconds - seconds * 1000) * _msToNanos;
return Timestamp._(seconds: seconds, nanoseconds: nanos);

return Timestamp(seconds: seconds, nanoseconds: nanos);
}

/// Creates a new timestamp from the given number of microseconds.
///
/// ```dart
/// final documentRef = firestore.doc('col/doc');
///
/// documentRef.set({ 'startTime': Timestamp.fromMicros(42) });
/// ```
///
/// - [microseconds]: Number of microseconds since Unix epoch
/// 1970-01-01T00:00:00Z.
///
/// Returns a new [Timestamp] representing the same point in time
/// as the given number of microseconds.
factory Timestamp.fromMicros(int microseconds) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This one needs a test too

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! Thanks : )

Copy link
Member

Choose a reason for hiding this comment

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

Is this constructor necessary? It makes sense to stick as close to the JavaScript implementation as possible imo, and I don't believe this exists there (could be wrong)

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 believe the Timestamp.fromMicros constructor is necessary. Considering the Dart native environment, the lack of this constructor would result in the loss of microsecond precision.

Additionally, while the microsecondsSinceEpoch property of the Dart DateTime API is available in the web environment, the limitations of JavaScript mean that there will still be a loss of precision in the microsecond unit.

Even though a code like

Timestamp.fromMicros(DateTime.now().microsecondsSinceEpoch);

would not throw an error

Copy link
Member

Choose a reason for hiding this comment

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

Interesting, thanks for the insight. I'd much prefer the name to be fromMicroseconds, but leave that one to Remi to call :D

final seconds = (microseconds / 1000 / 1000).floor();
final nanos = (microseconds - seconds * 1000 * 1000) * _usToNanos;

return Timestamp(seconds: seconds, nanoseconds: nanos);
}

factory Timestamp._fromString(String timestampValue) {
Expand All @@ -106,13 +137,14 @@
);
}

return Timestamp._(
return Timestamp(
seconds: date.millisecondsSinceEpoch ~/ 1000,
nanoseconds: nanos,
);
}

static const _msToNanos = 1000000;
static const _usToNanos = 1000;

final int seconds;
final int nanoseconds;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import 'package:dart_firebase_admin/firestore.dart';
import 'package:test/test.dart';

void main() {
group('Timestamp', () {
test('constructor', () {
final now = DateTime.now().toUtc();
final seconds = now.millisecondsSinceEpoch ~/ 1000;
final nanoseconds =
(now.microsecondsSinceEpoch - seconds * 1000 * 1000) * 1000;

expect(
Timestamp(seconds: seconds, nanoseconds: nanoseconds),
Timestamp.fromDate(now),
);
});

test('fromDate constructor', () {
final now = DateTime.now().toUtc();
final timestamp = Timestamp.fromDate(now);

expect(timestamp.seconds, now.millisecondsSinceEpoch ~/ 1000);
});

test('fromMillis constructor', () {
final now = DateTime.now().toUtc();
final timestamp = Timestamp.fromMillis(now.millisecondsSinceEpoch);

expect(timestamp.seconds, now.millisecondsSinceEpoch ~/ 1000);
expect(
timestamp.nanoseconds,
(now.millisecondsSinceEpoch % 1000) * (1000 * 1000),
);
});

test('fromMicros constructor', () {
final now = DateTime.now().toUtc();
final timestamp = Timestamp.fromMicros(now.microsecondsSinceEpoch);

expect(timestamp.seconds, now.microsecondsSinceEpoch ~/ (1000 * 1000));
expect(
timestamp.nanoseconds,
(now.microsecondsSinceEpoch % (1000 * 1000)) * 1000,
);
});
});
}
Loading