How to convert DateTime to TZDateTime in flutter?

17.1k views Asked by At

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);
5

There are 5 answers

2
iamdavidlam On

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

4
Andrious Solutions On

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.

0
M Awais Adil On

You may use the following code:

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

......

This is my function to show local schedule notification

static final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin  = FlutterLocalNotificationsPlugin();


Future<void> showScheduleNotification(DateTime scheduleTime) async {

tz.initializeTimeZones();

final String currentTimeZone = await FlutterNativeTimezone.getLocalTimezone();

 //if you don't want to use FlutterNativeTimezone package you can directly pass your desired timezone like in my example (Asia/Karachi)

// tz.setLocalLocation(tz.getLocation("Asia/Karachi"));

tz.setLocalLocation(tz.getLocation(currentTimeZone));

AndroidNotificationChannel channel = const AndroidNotificationChannel(
  '83jznxsb-xms9n',
  '9dhlacnzvxaqs0',
  importance: Importance.max,
  showBadge: true,
);

AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails(
  channel.id.toString(),
  channel.name.toString(),
  channelDescription: 'Notifications for welfare alter',
  importance: Importance.high,
  priority: Priority.high,
  ticker: 'ticker',
);

const DarwinNotificationDetails darwinNotificationDetails = DarwinNotificationDetails(
    presentAlert: true,
    presentBadge: true,
    presentSound: true
) ;

NotificationDetails notificationDetails = NotificationDetails(
    android: androidNotificationDetails,
    iOS: darwinNotificationDetails
);


await flutterLocalNotificationsPlugin.zonedSchedule(
    29,
    "Alert!",
    "Your notification text...",
    tz.TZDateTime.from(scheduleTime, tz.local),
    notificationDetails,
    androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
    uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime
);

}

..... Now you can call this method .....

showScheduleNotification(DateTime.now().add(const Duration(seconds: 30)))

And I hope this will work. In my case it worked perfectly.

0
Shreyansh Sharma On

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.

0
jRicardo On

DateTime to TZDateTime:

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

...

DateTime dt = DateTime.now(); //Or whatever DateTime you want
final tzdatetime = tz.TZDateTime.from(dt, tz.local); //could be var instead of final