srawlins/timezone

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

jamesncl opened this issue · 0 comments

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<Example> createState() => _ExampleState();
}

class _ExampleState extends State<Example> {

  @override
  void initState() {
    _test();
    super.initState();
  }

  Future<void> _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<TZDateTime>(() {
      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();
  }
}