How to convert DateTime to TZDateTime in flutter?

7,461

Solution 1

Indeed, zonedSchedule() requires the TZDateTime value. The approach I took was to utilize another package to convert the original DateTime value to a TZDateTime value. Below is an excerpt of one implementation I've used. See the fifth line? It involves the function, TZDateTime.from(). It takes the DateTime you may have originally used and converts it to TZDateTime. However, it needs your current location. That's where the class, TimeZone, comes in.

    import 'package:timezone/timezone.dart' as tz;
    :
    :
    :

    final timeZone = TimeZone();

    // The device's timezone.
    String timeZoneName = await timeZone.getTimeZoneName();

    // Find the 'current location'
    final location = await timeZone.getLocation(timeZoneName);

    final scheduledDate = tz.TZDateTime.from(dateTime, location);

    try {
      await _flutterLocalNotificationsPlugin.zonedSchedule(
        id,
        title,
        body,
        scheduledDate,
        platformChannelSpecifics,
        androidAllowWhileIdle: androidAllowWhileIdle,
        payload: payload,
        uiLocalNotificationDateInterpretation:
            uiLocalNotificationDateInterpretation,
      );
    } catch (ex) {
      id = -1;
    }
    return id;

I quickly wrote the class, TimeZone. This class works with yet another plugin (flutter_native_timezone) that looks for the 'current location' of the device and provides the standard name for that locale:

import 'package:timezone/data/latest.dart';
import 'package:timezone/timezone.dart' as t;
import 'package:flutter_native_timezone/flutter_native_timezone.dart';

class TimeZone {
  factory TimeZone() => _this ?? TimeZone._();

  TimeZone._() {
    initializeTimeZones();
  }
  static TimeZone _this;

  Future<String> getTimeZoneName() async => FlutterNativeTimezone.getLocalTimezone();

  Future<t.Location> getLocation([String timeZoneName]) async {
    if(timeZoneName == null || timeZoneName.isEmpty){
      timeZoneName = await getTimeZoneName();
    }
    return t.getLocation(timeZoneName);
  }
}

Use that class, and you're good to go.

Solution 2

I had just tried to solve the same problem. To understand this, we will need to have a basic understanding of platform channel.

https://flutter.dev/docs/development/platform-integration/platform-channels?tab=android-channel-kotlin-tab

Once we understand that, the code in the sample should become more understandable: https://github.com/MaikuB/flutter_local_notifications/blob/1fe78b9d129d674cd97cfba49734577b24c56925/flutter_local_notifications/example/android/app/src/main/java/com/dexterous/flutterlocalnotificationsexample/MainActivity.java

Concept in action:

First of all, we will need to import tz like so. This can be done in your main.dart or a helper file.

import 'package:timezone/data/latest.dart' as tz;
import 'package:timezone/timezone.dart' as tz;

To create a TZDateTime from a plain dateTime, we will need to use the device timezone through queried through the platform channel.

Future<void> configureLocalTimeZone() async {
  tz.initializeTimeZones();
  final String timeZoneName = await platform.invokeMethod('getTimeZoneName');
  tz.setLocalLocation(tz.getLocation(timeZoneName));
}

Where getTimeZoneName is implemented in the platform specific implementation.

Be sure to call configureLocalTimeZone before the next step.

And when we want to create the TZDateTime from a DateTime object, we can usually just do like so

var time = tz.TZDateTime.from(
    scheduledNotificationDateTime,
    tz.local,
);

The full implementation is showcased in the example code here: https://github.com/MaikuB/flutter_local_notifications/blob/1fe78b9d129d674cd97cfba49734577b24c56925/flutter_local_notifications/example/lib/main.dart

I have not considered the different edge cases in my very simple work flow so YMMV. Hope this helps! :)

Solution 3

I also encountered the same problem and tried to find the solution but some solutions were either outdated or are using different packages. zonedSchedule() function requires TZDateTime. So here we can

Duration offsetTime= DateTime.now().timeZoneOffset;

which will give us the difference between your machine time and utc and then we will subtract it from the local time(which by default is UTC)

import 'package:timezone/data/latest.dart' as tz;
import 'package:timezone/timezone.dart' as tz; 
.
.
.
Future initializetimezone() async {
    tz.initializeTimeZones();
  }

Future<void> scheduleNotification()async{
await initializetimezone();
var androidChannel = AndroidNotificationDetails(
      'App',
      "Notifications",
      'Customize',
      importance: Importance.max,
      priority: Priority.high,
      playSound: true,
      enableLights: true,
      ongoing: false,
      visibility: NotificationVisibility.public,
    );
var iosChannel = IOSNotificationDetails();
var platfromChannel =
    NotificationDetails(android: androidChannel, iOS: iosChannel);
// year, month, etc can manually entered or you can use 
// DateTime.now() also for current time in local machine.
tz.TZDateTime zonedTime = tz.TZDateTime.local(year,month,day,hour,
                          minute).subtract(offsetTime);
 await flutterLocalNotificationsPlugin.zonedSchedule(
      0,
      'Some Notification',
      'Description',
      zonedTime ,
      platfromChannel,
      uiLocalNotificationDateInterpretation:
          UILocalNotificationDateInterpretation.absoluteTime,
      payload: 'Payload',
      androidAllowWhileIdle: true,
    );
}

I hope this helps. This worked for me when the time difference between UTC and local machine is positive(+5:30:00.000000),i am not sure about the negative time difference countries.

Share:
7,461
Satyam Pandey
Author by

Satyam Pandey

Updated on December 01, 2022

Comments

  • Satyam Pandey
    Satyam Pandey over 1 year

    I am trying to push local notification through my app by using flutter_local_notifications package and as on FlutterNotificationsPlugin the ".schedule" is deprecated I tried using ".zonedSchedule" but it requires TZDateTime value.

    var androidDetails = AndroidNotificationDetails(t.id, "Task Notifier",
            "Notifies if you have a task scheduled at a particular time");
        var generalNotificationDetails =
            NotificationDetails(android: androidDetails);
        var now = DateTime.now();
        var scheduledTime = DateTime(
                now.year, now.month, _dayTasks.date.day, t.time.hour, t.time.minute)
            .subtract(Duration(minutes: 5));
        //use zonedSchedule once you figure out how to convert datetime to TZdatetime
        // await notifications.zonedSchedule(
        //     0, "Task", t.description, scheduledTime, generalNotificationDetails,
        //     androidAllowWhileIdle: true,
        //     uiLocalNotificationDateInterpretation:
        //         UILocalNotificationDateInterpretation.wallClockTime);
        await notifications.schedule(0, "Ideal Day Task", t.description,
            scheduledTime, generalNotificationDetails);
    
  • Mariam Albarghouti
    Mariam Albarghouti about 3 years
    _this will always stay null, isn't it?
  • Oliver Dixon
    Oliver Dixon almost 3 years
    I hate this. DateTime has timezone in it... The function 'TimeZone' isn't defined...
  • Oliver Dixon
    Oliver Dixon almost 3 years
    Do you know why we have TZDateTime when Datetime has TZ inside it?
  • jamesdlin
    jamesdlin over 2 years
    @OliverDixon Because DateTime can represent only UTC or the local time zone, not a specified time zone.