Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No TimeZone information in toIso8601String of DateTime #43391

Closed
rubiktubik opened this issue Sep 11, 2020 · 15 comments
Closed

No TimeZone information in toIso8601String of DateTime #43391

rubiktubik opened this issue Sep 11, 2020 · 15 comments
Labels
area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. library-core type-question A question about expected behavior or functionality

Comments

@rubiktubik
Copy link

Is there a reason why the toIso8601 Code not contains the ISO8601 Timezone information?

Code

final parsedDateTime = DateTime(2020, 09, 10, 09, 03);
final localTime = parsedDateTime.toLocal(); // In my case +2h
final isoString = localTime.toIso8601String(); // -> 2020-09-10T09:03:00.000

I wondering why not 2020-09-10T09:03:00.000+02:00 ?
The difference between UTC is stored in timeZoneOffset and it would be ISO8601 standard.

@lrhn lrhn added area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. library-core type-question A question about expected behavior or functionality labels Sep 11, 2020
@lrhn
Copy link
Member

lrhn commented Oct 2, 2020

The format used for local time is https://en.wikipedia.org/wiki/ISO_8601#Local_time_(unqualified), which means that it has no time zone designation.

Dart's DateTime generally can't handle time zones other than UTC and "local". We chose to not include the time zone here because if you parse the output back through DateTime.parse, you will get local time again. If we added +02:00 to the output, the parse function would convert it to UTC time and correct for the offset, because it can't handle offsets in general (and we didn't want to special case inputs where the specified time zone exactly matches local time).

@lrhn lrhn closed this as completed Oct 2, 2020
@duzenko
Copy link

duzenko commented Apr 27, 2021

The format used for local time is https://en.wikipedia.org/wiki/ISO_8601#Local_time_(unqualified), which means that it has no time zone designation.

Dart's DateTime generally can't handle time zones other than UTC and "local". We chose to not include the time zone here because if you parse the output back through DateTime.parse, you will get local time again. If we added +02:00 to the output, the parse function would convert it to UTC time and correct for the offset, because it can't handle offsets in general (and we didn't want to special case inputs where the specified time zone exactly matches local time).

Can you advise an alternative for toIso8601String() that respects the timezone?
As for "you will get local time again" what if the system changes timezone between save and parse (manually by user or summer time or government regulation)?

@lrhn
Copy link
Member

lrhn commented Apr 27, 2021

The Dart DateTime doesn't really understand time zones, so it's not clear what you mean by respecting the timezone.

If you want a text representation of the local time and the local time zone, you can do date.toIso8601String() + myOwnTimeZoneFormatter(date.timeZoneOffset). It won't parse back to the same DateTime using DateTime.parse. Instead it'll parses as a UTC date-time adjusted for the offset. It will represent the same point in time, but not the same clock time.

That's the real distinction between UTC-times and local times:

  • UTC DateTime objects represents a precise point in time. They're universally applicable.
  • A local DateTime object represents a wall-clock time of a point in time in the local, current, and only known time zone.

The wall-clock time is found by looking up the time-zone offset at the absolute point in time in the local time zone information. That information covers all past, present and future time-zone changes at the current location, but not at any other location. It basically assigns a unique local time-zone offset to any UTC time.

If you store the microsecondsSinceEpoch of the local-time date-time, you can restore the same point in time.
If you store the toString, you can restore the wall-clock time of it.
You cannot always store and recreate both of these, because the receiver, if it has a different locale, cannot represent that combination of wall-clock time and point in time (it's not a UTC time and it's not the one available local time).

So, if you want to store local wall-clock time and time-zone offset, which you obviously can do yourself, be aware that Dart's DateTime class cannot represent all such combinations, and therefore cannot safely parse it back to a local time.

About the system changing time zones (the Traveling Laptop Problem), that follows directly from the above.
If the locale changes between serializing a local-time DateTime object and recreating it, there is no guarantee that you can get the original combination of clock time and time zone offset back, it might not be representable as a DateTime at all.

Daylight saving time isn't an issue, the locale can have a full history of the current location's offsets at any time in the past, it just doesn't know anything about any other time zones. If you actually change the time zone, manually or because the OS detects that you have moved, you get a new complete history of local time zones

So, always use UTC times if you need to pass points in time between computers, to record when something happened, or even to keep them on the same computer for any extended duration. Only use local time for displaying locally, not for storage.

String myOwnTimeZoneFormatter(Duration offset) =>
  "${offset.isNegative ? "-": "+"}${offset.inHours.toString().padLeft(2, "0")}:${
          (offset.inMinutes - offset.inHours * 60).toString().padLeft(2, "0")}";

@duzenko
Copy link

duzenko commented Apr 29, 2021

The Dart DateTime doesn't really understand time zones, so it's not clear what you mean by respecting the timezone.
...
So, always use UTC times if you need to pass points in time between computers, to record when something happened, or even to keep them on the same computer for any extended duration. Only use local time for displaying locally, not for storage.

Thanks, it makes sense
I still think this needs to be stated somewhere in the dart docs in bold, best at https://api.dart.dev/stable/2.12.4/dart-core/DateTime-class.html
It does not help either that the default time format is local. It kinda implies that local should be preferred. It's counter-intuitive that we are forced to do all the converting manually all the time in real-life usage scenarios.

@denya
Copy link

denya commented Nov 5, 2021

Published code snippet doesn't work with negative timezones (it produces 12:00:00.000--05:00)
Please, don't repeat my error, do not just copy-n-paste code from the Internet :)

Fixed code:

String myOwnTimeZoneFormatter(Duration offset) =>
    "${offset.isNegative ? "-" : "+"}${offset.inHours.abs().toString().padLeft(2, "0")}:${(offset.inMinutes - offset.inHours * 60).toString().padLeft(2, "0")}";

@WeiCongcong
Copy link

WeiCongcong commented Aug 29, 2022

Published code snippet doesn't work with negative timezones which has half an hour offset (It could be located in Newfoundland where the timezone is -2:30).
Please, don't repeat my error, do not just copy-n-paste code from the Internet :)

Fixed code:

String myOwnTimeZoneFormatter(Duration offset) =>
    "${offset.isNegative ? "-" : "+"}${offset.inHours.abs().toString().padLeft(2, "0")}:${(offset.inMinutes - offset.inHours * 60).abs().toString().padLeft(2, "0")}";

@mrbeardad
Copy link

The format used for local time is https://en.wikipedia.org/wiki/ISO_8601#Local_time_(unqualified), which means that it has no time zone designation.

Dart's DateTime generally can't handle time zones other than UTC and "local". We chose to not include the time zone here because if you parse the output back through DateTime.parse, you will get local time again. If we added +02:00 to the output, the parse function would convert it to UTC time and correct for the offset, because it can't handle offsets in general (and we didn't want to special case inputs where the specified time zone exactly matches local time).

So why do I use t.toIso8610String() without offset instead of t.toString()?

@subzero911
Copy link

subzero911 commented Jun 9, 2023

We chose to not include the time zone here because if you parse the output back through DateTime.parse, you will get local time again.

That's the desired behaviour! In our app, we can have a situation when users are in the different time zones, and the 1st user sends his local time to the server, which converts it to UTC, and the 2nd user receives it and converts to his local time.
Why this topic has been closed? It's still an unresolved issue! You can't just discard the timezone.

Consider adding a parameter .toIso8601(includeTimezoneOffset: true)

@lrhn

@duzenko
Copy link

duzenko commented Jun 9, 2023

@subzero911 Just do everything in UTC and only convert to local in the very end

@opsb
Copy link

opsb commented Aug 3, 2023

@subzero911 Just do everything in UTC and only convert to local in the very end

this only works if you're not interested in the timezone where the event was recorded e.g. you log the time somebody went for a bike ride. If you show that bike ride in your app it wouldn't make much sense to show it in the middle of the night just because the user went on holiday.

@duzenko
Copy link

duzenko commented Aug 3, 2023

@subzero911 Just do everything in UTC and only convert to local in the very end

this only works if you're not interested in the timezone where the event was recorded e.g. you log the time somebody went for a bike ride. If you show that bike ride in your app it wouldn't make much sense to show it in the middle of the night just because the user went on holiday.

What about storing bike rides in a database? It will have to be UTC anyway, so you don't really have other options but saving timezone separately and then converting from UTC to local in your frontend.

@opsb
Copy link

opsb commented Aug 4, 2023

What about storing bike rides in a database? It will have to be UTC anyway, so you don't really have other options but saving timezone separately and then converting from UTC to local in your frontend.

If you do this you're showing the time of the bike ride in the user's current timezone, not the timezone where they recorded the activity.

@aedifyfwi
Copy link

Hej all,

in our case, the offset gives georeferenced information about where the record was created, which in turn is necessarily needed to determine the day and the difference from 00:00 in the corresponding timezone.
Here is an example:
time-zone: "+02:00"
value = "2024-08-30T00:00:00"
value.toUtc().toIso8601String() => "2024-08-29T22:00:00.000Z"
Unfortunately, this value is then sent to our API. There, a date is evaluated from it in python:
value.toDate() => "2024-08-29".

But actually we want:
value = "2024-08-30T00:00:00+02:00"
value.toIso8601String() => "2024-08-30T00:00:00+02:00"
Evaluated in python to:
value.toDate() => "2024-08-30"

BTW: For this reason we always store the DateTime object in our database with origin time zone.

@razorness
Copy link

razorness commented Nov 30, 2023

@subzero911 Just do everything in UTC and only convert to local in the very end
What about storing bike rides in a database? It will have to be UTC anyway, so you don't really have other options but saving timezone separately and then converting from UTC to local in your frontend.

My company works on an asset tracking system. Everything is time zone aware. The database, the complete backend written in Go and the web client. Our API accepts only RFC3339 compliant timestamps which requires a timezone. It's a little bit annoying to be required to ensure DateTime instances are in UTC.

@tirendus
Copy link

A possible workaround, put this in main.dart

extension DateTimeExtension on DateTime {
  String toIso8601StringWithTz() {
    // Get offset
    final timeZoneOffset = this.timeZoneOffset;
    final sign = timeZoneOffset.isNegative ? '-' : '+';
    final hours = timeZoneOffset.inHours.abs().toString().padLeft(2, '0');
    final minutes =
        timeZoneOffset.inMinutes.abs().remainder(60).toString().padLeft(2, '0');
    final offsetString = '$sign$hours:$minutes';

    // Get first part of properly formatted ISO 8601 date
    final formattedDate = toIso8601String().split('.').first;

    return '$formattedDate$offsetString';
  }
}

void main() {
    print(DateTime.now().toIso8601StringWithTz());
    // flutter: 2024-02-20T16:22:29+02:00
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. library-core type-question A question about expected behavior or functionality
Projects
None yet
Development

No branches or pull requests