My experiments with Dart continue. Today I will explore the semantics of isUtc and when to use it while converting a unix timestamp to a DateTime instance.

First, let’s look at a basic example.

void main() {
  final now = DateTime.now();
  print(now);
  print(DateTime.fromMillisecondsSinceEpoch(now.millisecondsSinceEpoch));
}

Output:

2023-01-21 20:21:04.334
2023-01-21 20:21:04.334

The first line DateTime.now() gives us an instance of DateTime in local time. My local time is UTC+0530. In the 2nd print instruction, we parse the milliseconds from the previously mentioned instance and create a new instance of DateTime. Not surprisingly, they are both the same!

Now, let’s spice things up a bit. I’ll convert the first DateTime instance into UTC. Then we’ll see what we get.

void main() {
  final now = DateTime.now().toUtc();
  print(now);
  print(DateTime.fromMillisecondsSinceEpoch(now.millisecondsSinceEpoch));
}

Output:

2023-01-21 14:58:10.181Z
2023-01-21 20:28:10.181

Interesting… As you can see, there’s a trailing Z at the end of the first timestamp. This indicates it is in UTC, whereas the second timestamp is in local time because it does not have the Z! What may surprise you is that both their values are actually the same. Shall I prove it to you?

void main() {
  final now = DateTime.now().toUtc();
  print(now.millisecondsSinceEpoch);
  print(DateTime.fromMillisecondsSinceEpoch(now.millisecondsSinceEpoch).millisecondsSinceEpoch);
}

Here’s the output:

1677325594446
1677325594446

There you go! What did I tell ya 😉

Essentially, what’s happening is this. Milliseconds or microseconds since epoch will give you the same time regardless of the time zone. By same time, I mean the clocks at respective local times in different time zones might be different, but the time since epoch will be the same. This way regardless of what time zone you are in, you will have an accurate sense of time, and consequently this makes it a good practice to store time since epoch in database, something that SQLite does by default.

When you parse this duration from epoch using either millisecondsSinceEpoch or microsecondsSinceEpoch methods without providing the isUtc or setting it to false, a DateTime object is created which prints the local time when toString() is called on it. However, when you set isUtc to true, it creates a DateTime object, but this time it assumes that the value of date and time in the object is converted to UTC, but at the same time retaining its value of milliseconds/microseconds from epoch.

To give you an example, if your program was running at a location which is in UTC time zone, but synchronized with the program running at UTC+05:30, so both programs running DateTime.millisecondsSinceEpoch(...) (with same parameter and isUtc not set) will return a DateTime object that prints a different result with toString but the same result with millisecondsSinceEpoch. However, if we were to set isUtc to true, then we’d end up with two DateTime objects that not only print the same value on toString, but also print the same value with millisecondsSinceEpoch.

Hope this clarifies any confusion!