srawlins / timezone

Time zone database and time zone aware DateTime object for Dart.
BSD 2-Clause "Simplified" License
101 stars 52 forks source link

TZDateTimes at same instant and location from different isolates are not equal #185

Open jamesncl opened 7 months ago

jamesncl commented 7 months ago

Comparing two identical TZDateTimes created in different isolates fails, because Location uses identity not equality for ==. For example:

  final rootIsolateToken = RootIsolateToken.instance!;

  TZDateTime fromRootIsolateA = TZDateTime(getLocation('America/Detroit'), 2023);
  TZDateTime fromRootIsolateB = TZDateTime(getLocation('America/Detroit'), 2023);
  TZDateTime fromComputeIsolate = await Isolate.run<TZDateTime>(() {
    BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken);
    initializeTimeZones();
    return TZDateTime(getLocation('America/Detroit'), 2023);
  });

  print("fromRootIsolateA == fromRootIsolateB ${fromRootIsolateA == fromRootIsolateB}");
  print("fromRootIsolateA == fromComputeIsolate ${fromRootIsolateA == fromComputeIsolate}");

Actual results:

I/flutter (24040): fromRootIsolateA == fromRootIsolateB true I/flutter (24040): fromRootIsolateA == fromComputeIsolate false

I would expect both tests to return true, as per the documentation for the == operator on TZDateTime:

/// Returns true if [other] is a [TZDateTime] at the same moment and in the
  /// same [Location].
  /// ...
  @override
  bool operator ==(Object other) {
    return identical(this, other) ||
        other is TZDateTime &&
            _native.isAtSameMomentAs(other._native) &&
            location == other.location;
  }

I think this is failing when the TZDateTime is created in a different isolate because Location does not override == so falls back on an identity check, which fails because it comes from a different instance of the location which was created inside the second isolate.

I suggest that Location should implement == and return true if the location names are the same?

Minimal reproducible example ``` import 'dart:isolate'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:timezone/data/latest.dart'; import 'package:timezone/timezone.dart'; void main() { initializeTimeZones(); runApp(const MaterialApp( home: Example(), )); } class Example extends StatefulWidget { const Example(); @override State createState() => _ExampleState(); } class _ExampleState extends State { @override void initState() { _test(); super.initState(); } Future _test() async { final rootIsolateToken = RootIsolateToken.instance!; TZDateTime fromRootIsolateA = TZDateTime(getLocation('America/Detroit'), 2023); TZDateTime fromRootIsolateB = TZDateTime(getLocation('America/Detroit'), 2023); TZDateTime fromComputeIsolate = await Isolate.run(() { BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken); initializeTimeZones(); return TZDateTime(getLocation('America/Detroit'), 2023); }); print("fromRootIsolateA == fromRootIsolateB ${fromRootIsolateA == fromRootIsolateB}"); print("fromRootIsolateA == fromComputeIsolate ${fromRootIsolateA == fromComputeIsolate}"); } @override Widget build(BuildContext context) { return Container(); } } ```