Closed ethicnology closed 1 week ago
No.
A Dart DateTime
has no way to store a time zone, so it cannot remember which time zone the original was parsed from.
Dart DateTime
(like the JavaScript Date
that is used to implement it on web) can either be UTC time, or it can be "local time", which has no time zone. Either is stored as microseconds since Epoch.
It is a timestamp representing a specific point in time, which can be displayed as either a UTC time or as whatever the current system says its time zone would be.
That is, the time zone is either UTC, or there is no time zone.
When a local-time DateTime
needs a time zone, like someone reading timeZoneOffset
, it asks the local system what the time zone would be for that point in time. It doesn't remember it for later, and if your OS decides that your local time zone has changed (because you told it, or you took your laptop to another time zone), and ask about the timezoneOffset
of the same local-time DateTime
object again, you'll get another answer.
There is just no way for the value to represent a time zone of -05:00
, it simply has no place to store that information.
It could have had, but adding too much extra functionality on top of what the JavaScript Date
class has, was not a priority for the platform DateTime
. That should rather be handled by a dedicated package, or packages, depending on what features are actually needed.
You'll need a library that actually understands time zones. I don't think package:intl
handles time zones, so something else.
(Or just someone providing a (DateTime utcDateTime, (int hours, int minutes) timeZoneOffset) parseWithTimeZone(String isoString)
and toTimeZoneString(DateTime, [(int hours, int minutes)? timeZone])
which parses and emits ISO strings for that point in time..)
Thank you for the detailed answer.
Yes, I got frustrated when I realized that I couldn't modify timeZoneOffset
to any timezone.
Since it's important to my use case to keep tracking timezones, I created something like this to keep the time in UTC and the timezone separately with the ability to convert toLocal
timezone. But it would be great if we can handle this case with standard DateTime.
class DateTimeTz {
late DateTime datetime;
late Duration timezone;
DateTimeTz({required this.datetime, required this.timezone});
DateTimeTz.now() {
datetime = DateTime.now().toUtc();
timezone = datetime.toLocal().timeZoneOffset;
}
// Returns 2024-02-22 15:39:17-05:00
String toRfc3339() => _toCustom() + _formatTimeZone();
// Parse a RFC3339 string with timezone
// Cancel the timezone conversion by adding up the timezone
// Keep the timezone as it
static DateTimeTz fromRfc3339(String input) {
var timezone = DateTimeTz._parseTimeZone(input);
return DateTimeTz(
datetime: DateTime.parse(input).add(timezone),
timezone: timezone,
);
}
// Add the timezone and remove the trailing Z
String toLocal() => datetime.add(timezone).toString().replaceAll("Z", "");
// Returns -05:00
String _formatTimeZone() {
int minutes = timezone.inMinutes;
return '${minutes >= 0 ? '+' : '-'}'
'${(minutes ~/ 60).abs().toString().padLeft(2, '0')}:'
'${(minutes % 60).abs().toString().padLeft(2, '0')}';
}
// Returns 2024-02-22 15:39:17
String _toCustom() {
return '${datetime.year.toString().padLeft(4, '0')}-'
'${datetime.month.toString().padLeft(2, '0')}-'
'${datetime.day.toString().padLeft(2, '0')} '
'${datetime.hour.toString().padLeft(2, '0')}:'
'${datetime.minute.toString().padLeft(2, '0')}:'
'${datetime.second.toString().padLeft(2, '0')}';
}
// Returns timezone Duration from a rfc3339 string
static Duration _parseTimeZone(String rfc3339) {
if (rfc3339.endsWith('Z')) {
return const Duration(minutes: 0);
} else {
RegExp regex = RegExp(r'([-+]\d{2}:\d{2}|Z)$');
Match? match = regex.firstMatch(rfc3339);
if (match != null) {
String timezone = match.group(1)!;
int hours = int.parse(timezone.substring(0, 3));
int minutes = int.parse(timezone.substring(4));
int offsetMinutes = hours * 60 + minutes;
return Duration(minutes: offsetMinutes);
} else {
throw Exception("No timezone found.");
}
}
}
}
@ethicnology Are you aware of the excellent package timezone on pub.dev? It does parsing with timezones as you need.
Actually, I see it as a way to have a database of timezone and DateTime make easy to convert a DateTime to one of the timezone available in the package.
I already have the timezoneOffset
returned as timestamptz
from postgres
, but I need to keep the information of the timezone when I deserialize such value and to ability to display it as a rfc3339.
I may misunderstand it, but how would you refactor the provided code using timezone ?
Hi,
You will stop using Dart's DateTime from the sdk completely and only use TZDateTime. Something like:
import 'package:timezone/data/latest.dart';
import 'package:timezone/standalone.dart';
void main() {
initializeTimeZones();
final dt = TZDateTime.utc(2024, 2, 24, 15);
print(dt.toIso8601String()); // 2024-02-24T15:00:00.000Z
// If I understand you correctly, you'll store the UTC time and the timezone separately.
// Then this is how you construct the datetime in the timezone of interest.
final location = getLocation('America/New_York');
final dt1 = TZDateTime.fromMillisecondsSinceEpoch(location, dt.millisecondsSinceEpoch);
print(dt1.toIso8601String()); // 2024-02-24T10:00:00.000-0500
// to parse a string in a given timezone
final dt2 = TZDateTime.parse(location, '2024-02-22 15:00:00-05:00');
print(dt2.toIso8601String()); // 2024-02-22T15:00:00.000-0500
}
Feel free to email me directly if you need more help. I'm pretty sure the package timezone has you covered.
Best, T
Look at DateTime.parse implementation. It could be so simple...
I ended up by extending DateTime
with a toIso8601WithTz(Duration timezone)
and I'm storing the timezone independently.
Thank you all for your contributions
DateTime is able to parse a string with timezone such as
2024-02-22 16:00:00-05:00
and convert it into2024-02-22 21:00:00.000Z
settingtimeZoneOffset
to 0, we are losing the timezone information in the conversion.I know
toLocal()
to convert the DateTime to the timezone of the device, but not matching the timezone parsed in the original stringIs there a way to