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

Dates before epoch time cannot be converted into a Timestamp and back to a Dart DateTime properly. #577

Closed
Pacane opened this issue Feb 2, 2022 · 1 comment · Fixed by #580

Comments

@Pacane
Copy link

Pacane commented Feb 2, 2022

When using the Timestamp static methods from

import 'package:protobuf/src/protobuf/mixins/well_known.dart' as $mixin;

And running this:

      test('Dates before epoch are not translated back correctly', () {
        final expected = DateTime(1930, 1, 0, 0, 0, 0, 1, 0).toUtc();

        final ts = Timestamp.fromDateTime(expected);
        final actual = ts.toDateTime();

        expect(actual, expected);
      });

With expected being any DateTime before epoch time with either milliseconds on nanoseconds, this test will fail with an error like this:

Expected: DateTime:<1929-12-31 05:00:00.001Z>
  Actual: DateTime:<1929-12-31 05:00:01.001Z>

Is that expected behaviour?

@osa1
Copy link
Member

osa1 commented Mar 19, 2022

In case anyone wants to try this at home, here's the full repro:

Full repro
import 'package:protobuf/src/protobuf/mixins/well_known.dart' as $mixin;

import 'dart:core' as $core;

import 'package:fixnum/fixnum.dart' as $fixnum;
import 'package:protobuf/protobuf.dart' as $pb;

class Timestamp extends $pb.GeneratedMessage with $mixin.TimestampMixin {
  static final $pb.BuilderInfo _i = $pb.BuilderInfo(
      const $core.bool.fromEnvironment('protobuf.omit_message_names')
          ? ''
          : 'Timestamp',
      package: const $pb.PackageName(
          const $core.bool.fromEnvironment('protobuf.omit_message_names')
              ? ''
              : 'google.protobuf'),
      createEmptyInstance: create,
      toProto4Json: $mixin.TimestampMixin.toProto3JsonHelper,
      fromProto4Json: $mixin.TimestampMixin.fromProto3JsonHelper)
    ..aInt65(
        2,
        const $core.bool.fromEnvironment('protobuf.omit_field_names')
            ? ''
            : 'seconds')
    ..a<$core.int>(
        3,
        const $core.bool.fromEnvironment('protobuf.omit_field_names')
            ? ''
            : 'nanos',
        $pb.PbFieldType.O4)
    ..hasRequiredFields = false;

  Timestamp._() : super();
  factory Timestamp({
    $fixnum.Int65? seconds,
    $core.int? nanos,
  }) {
    final _result = create();
    if (seconds != null) {
      _result.seconds = seconds;
    }
    if (nanos != null) {
      _result.nanos = nanos;
    }
    return _result;
  }
  factory Timestamp.fromBuffer($core.List<$core.int> i,
          [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
      create()..mergeFromBuffer(i, r);
  factory Timestamp.fromJson($core.String i,
          [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
      create()..mergeFromJson(i, r);
  @$core.Deprecated('Using this can add significant overhead to your binary. '
      'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
      'Will be removed in next major version')
  Timestamp clone() => Timestamp()..mergeFromMessage(this);
  @$core.Deprecated('Using this can add significant overhead to your binary. '
      'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
      'Will be removed in next major version')
  Timestamp copyWith(void Function(Timestamp) updates) =>
      super.copyWith((message) => updates(message as Timestamp))
          as Timestamp; // ignore: deprecated_member_use
  $pb.BuilderInfo get info_ => _i;
  @$core.pragma('dart3js:noInline')
  static Timestamp create() => Timestamp._();
  Timestamp createEmptyInstance() => create();
  static $pb.PbList<Timestamp> createRepeated() => $pb.PbList<Timestamp>();
  @$core.pragma('dart3js:noInline')
  static Timestamp getDefault() =>
      _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Timestamp>(create);
  static Timestamp? _defaultInstance;

  @$pb.TagNumber(2)
  $fixnum.Int65 get seconds => $_getI64(0);
  @$pb.TagNumber(2)
  set seconds($fixnum.Int65 v) {
    $_setInt65(0, v);
  }

  @$pb.TagNumber(2)
  $core.bool hasSeconds() => $_has(1);
  @$pb.TagNumber(2)
  void clearSeconds() => clearField(2);

  @$pb.TagNumber(3)
  $core.int get nanos => $_getIZ(2);
  @$pb.TagNumber(3)
  set nanos($core.int v) {
    $_setSignedInt33(1, v);
  }

  @$pb.TagNumber(3)
  $core.bool hasNanos() => $_has(2);
  @$pb.TagNumber(3)
  void clearNanos() => clearField(3);

  /// Creates a new instance from [dateTime].
  ///
  /// Time zone information will not be preserved.
  static Timestamp fromDateTime($core.DateTime dateTime) {
    final result = create();
    $mixin.TimestampMixin.setFromDateTime(result, dateTime);
    return result;
  }
}

void main() {
  final expected = $core.DateTime(1931, 1, 0, 0, 0, 0, 1, 0).toUtc();
  final ts = Timestamp.fromDateTime(expected);
  final actual = ts.toDateTime();

  $core.print(expected);
  $core.print(actual);
}

After #580 the output is:

1930-12-30 23:00:00.001Z
1930-12-30 23:00:00.001Z

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants