Open bartekpacia opened 1 year ago
Okay so this comes up a lot, so thought I would take the time to give a full answer here. Maybe someone can add this to the docs as away of explanation. It took me a while to figure this all out.
The problem is that this package doesn't support many timezone abbreviations such as Etc/UTC
, which is what the flutter_timezone
package sometimes returns. This is a longstanding issue which won't be fixed, because this is intentional: abbreviations like this are ambiguous.
The problem is that timezones are really, really complicated, and to deal with them you have to understand a bit of this complexity. I'm not an expert, but I think the problem is that Etc/UTC
isn't actually a proper timezone, it's a 'fixed offset'. A timezone is a whole history of what a particular group of people decided how they would handle time, daylight savings, etc. and the historical changes made over time. For example, during World War II the UK went 2 hours ahead at daylight saving time instead of the normal 1 hour, to save fuel. To be accurate with time, you have to take all these little historical details into account, because if you have a date from that time during World War II you could end up being an hour out if you don't.
Etc/UTC
(and all other abbreviations with ETC in them) is not a history of timekeeping for a particular country, it's just a fixed number (0 hours offset from UTC). This means, depending on exactly where on earth you're talking about, converting a date from Etc/UTC
might give you different results.
Most of the time, the timezone we get back from FlutterTimezone.getLocalTimezone()
is a proper timezone, like 'Europe/London'. But occasionally, it will return an ambiguous abbreviation. This is unfortunate, and is a result of the underlying device-specific implementation. For Android, this is what FlutterTimezone does under the hood:
private fun getLocalTimezone(): String {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
ZoneId.systemDefault().id
} else {
TimeZone.getDefault().id
}
}
And if we [look at the docs for TimeZone.getDefault()
](https://developer.android.com/reference/java/util/TimeZone#getDefault()) (which is what ZoneId.systemDefault() also calls under the hood
) it says:
Gets the default TimeZone for this host. The source of the default TimeZone may vary with implementation.
"may vary with implementation" basically means we have to deal with all kinds of garbage results, eg from old devices, SDKs, emulators etc. So, unfortunately this is just a problem we have to deal with ourselves. The docs state this on the front page:
We don't provide any functions to get locations by time zone abbreviations because of the ambiguities.
Alphabetic time zone abbreviations should not be used as unique identifiers for UTC offsets as they are ambiguous in practice. For example, "EST" denotes 5 hours behind UTC in English-speaking North America, but it denotes 10 or 11 hours ahead of UTC in Australia; and French-speaking North Americans prefer "HNE" to "EST".
Ideally you should only pass unambiguous abbreviations, or full timezone identifiers. For example see the list in Wikipedia: the full timezone identifier is in the 'TZ identifier' column. Notice how the ETC abbreviations don't have proper timezone identifiers.
As a workaround, we have to manually assign these ambiguous timezones to a best guess. For example:
import 'package:timezone/data/latest_all.dart' as tzdatabase;
import 'package:timezone/timezone.dart' as tz;
...
tzdatabase.initializeTimeZones();
String localTimezone = await FlutterTimezone.getLocalTimezone();
if(localTimezone == "Etc/UTC") {
localTimezone = "??????"; <- replace this with a valid timezone identifier
}
tz.setLocalLocation(tz.getLocation(localTimezone));
// then use tz.local whenever you need the local timezone
Alternatively you could try setting the timezone on the emulator, or trying a different emulator image which returns a valid timezone.
Also note the use of import 'package:timezone/data/latest_all.dart' as tzdatabase;
here: importing the ALL version of the timezone database will include some old / invalid / deprecated timezones such as Etc/GMT+5
but will increase the load time. Again, this is explained in the docs.
See this old closed issue for more discussion.
Thank you very much @jamesncl for such a thorough explanation. Looks like I unknowingly stepped into an uncanny valley of ambiguity :D
Should I close the issue or leave it open for future wanderers?
And again, I really appreciate your comment. Thank you!
Stumbled across this issue here with my own app. Using the all database does indeed fix it, but the page on pub.dev also says:
default: doesn't contain deprecated and historical zones with some exceptions like "US/Eastern" and "Etc/UTC"; this is about 75% the size of the all database.
I would assume then that Etc/UTC is supported.
That's how I initialize timezone data in my app:
When I run this code on Android Lollipop emulator (API level 21), the following exception is thrown:
I'd be grateful for any support or even an idea how this could be mitigated.