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

Add timezone to LocationResource #164

Closed
ghost opened this issue Oct 9, 2020 · 14 comments
Closed

Add timezone to LocationResource #164

ghost opened this issue Oct 9, 2020 · 14 comments
Labels
this-release Scheduled to be implemented for this release in development

Comments

@ghost
Copy link

ghost commented Oct 9, 2020

LocationResource should contain a timezone like "Europe/Berlin" (e.g. as defined here https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) to allow converting UTC dateTimes to local ones.

@ghost ghost mentioned this issue Oct 9, 2020
@cookeac
Copy link
Collaborator

cookeac commented Oct 22, 2020

Would we use the TZ Database Name field (from the Wikipedia list), or the UTC offset (+hh:mm or -hh:mm) which is called the TZD or Time Zone Designator in ISO 8601?

@cookeac cookeac added the this-release Scheduled to be implemented for this release in development label Oct 22, 2020
@ghost
Copy link
Author

ghost commented Oct 26, 2020

UTC offset does not reflect whether DST is used, or does it? If not, calculations using the Time Zone Designator will be wrong for approximately half of the year :)

@alamers
Copy link
Collaborator

alamers commented Oct 26, 2020

Well, it is correct in the sense that it points to a single point in time (since it is an offset wrt UTC). It does not tell you how to render it in a specific timezone (which may be using DST and thus vary its offset).

The timezone "Europe/Amsterdam" has two offsets defined: a UTC offset and a UTC DST offset. So, if you specify a time, you need to specify the offset (preferably Z, or at least GMT+1 or GMT+2) to point to a specific point on the timeline, and a timezone (e.g. "Europe/Amsterdam") to show how to render it locally (or how it was queried locally).

So theoretically, if you have a local time (without an offset) and a timezone, you could determine its offset assuming that DST was used. But that will be very confusing and prone to error. So, please lets use an offset for pointing to a point in time, and specify a timezone as additional information (e.g. how that specific farm likes to see a specific time).

@ghost
Copy link
Author

ghost commented Oct 27, 2020

IMHO there is no real use case that makes use of the offset. Either you have a global system in which you store only UTC date/times or you want to show it to the user. In the latter case, you probably always want to show the "real time as it was on the farm". Otherwise, the "4 o'clock milking" during DST becomes the "3 o'clock milking", which is confusing for the user.

I'd expect that every system would be capable of storing a local date/time (no matter whether DST or not) into UTC time. So the only issue would be the way back (i.e. I receive something via ICAR and need to do some conversion e.g. to present the data to the user).

Luckily, we just switched back from DST last weekend, so I created a small Java example. So, in Java I can do this:

    public static void main(String[] args) {
        ZonedDateTime cet = ZonedDateTime.parse("2020-10-25T10:00:00.000+01:00[Europe/Berlin]");
        ZonedDateTime cest = ZonedDateTime.parse("2020-10-24T10:00:00.000+02:00[Europe/Berlin]");

        System.out.println("original CET:\t\t\t\t\t" + cet);
        System.out.println("original CEST:\t\t\t\t\t" + cest);

        ZonedDateTime utcCet = cet.withZoneSameInstant(ZoneId.of("UTC"));
        ZonedDateTime utcCest = cest.withZoneSameInstant(ZoneId.of("UTC"));

        System.out.println("UTC of CET as sent via ICAR:\t" + utcCet);
        System.out.println("UTC of CEST as sent via ICAR:\t" + utcCest);

        OffsetDateTime offsetUtcCet = utcCet.toOffsetDateTime().withOffsetSameInstant(ZoneOffset.ofHours(1));
        OffsetDateTime offsetUtcCest = utcCest.toOffsetDateTime().withOffsetSameInstant(ZoneOffset.ofHours(1));
        ZonedDateTime zoneUtcCet = utcCet.withZoneSameInstant(ZoneId.of("Europe/Berlin"));
        ZonedDateTime zoneUtcCest = utcCest.withZoneSameInstant(ZoneId.of("Europe/Berlin"));

        System.out.println("Restored offset time of CET:\t" + offsetUtcCet);
        System.out.println("Restored offset time of CEST:\t" + offsetUtcCest);
        System.out.println("Restored zoned time of CET:\t\t"+ zoneUtcCet);
        System.out.println("Restored zoned time of CEST:\t" + zoneUtcCest);
    }

The output is this:

original CET:			2020-10-25T10:00+01:00[Europe/Berlin]
original CEST:			2020-10-24T10:00+02:00[Europe/Berlin]
UTC of CET as sent via ICAR:	2020-10-25T09:00Z[UTC]
UTC of CEST as sent via ICAR:	2020-10-24T08:00Z[UTC]
Restored offset time of CET:	2020-10-25T10:00+01:00
Restored offset time of CEST:	2020-10-24T09:00+01:00
Restored zoned time of CET:	2020-10-25T10:00+01:00[Europe/Berlin]
Restored zoned time of CEST:	2020-10-24T10:00+02:00[Europe/Berlin]

So, if I have the zone ID, I can just take the UTC time and let Java do the conversion back to the actual user time in CET or CEST respectively. I think, this is what a user would expect. Calculating a time with an offset, I will provide misleading information to a customer.

Nevertheless, providing both a zone ID AND an (optional?) offset would be OK for me, but I think at least I will never use the offset at all...

@alamers
Copy link
Collaborator

alamers commented Oct 27, 2020

Hi Thomas, not sure what you mean by 'you will not use the offset', since in your example you use the 'Z' (zulu/zero) offset. Or have you mixed zone and offset? In that case I agree ;)

Also, you are only converting in your example between timestamps that contain full information. A better example would be to start from a String in a proposed format.

So, with offsets it would be something like this:

String inputWithOffsetAndZone = "2020-10-25T03:30:00.000+01:00[Europe/Berlin]";
ZonedDateTime zdt = ZonedDateTime.parse(inputWithOffsetAndZone);
Instant instant = zdt.toInstant();
LocalDateTime localDateTime = zdt.toLocalDateTime();

System.out.println("zdt: " + zdt);
System.out.println("instant: " + instant);
System.out.println("localDateTime: " + localDateTime);

String inputWithOffset = "2020-10-25T03:30:00.000+01:00";
OffsetDateTime offsetDateTime = OffsetDateTime.parse(inputWithOffset);
Instant instant2 = offsetDateTime.toInstant();
ZonedDateTime zdt2 = instant2.atZone(ZoneId.of("Europe/Berlin"));
LocalDateTime localDateTime2 = zdt2.toLocalDateTime();

System.out.println("zdt2: " + zdt2);
System.out.println("instant2: " + instant2);
System.out.println("localDateTime2: " + localDateTime2);

And the output is:

zdt: 2020-10-25T04:30+01:00[Europe/Berlin]
instant: 2020-10-25T03:30:00Z
localDateTime: 2020-10-25T04:30
zdt2: 2020-10-25T04:30+01:00[Europe/Berlin]
instant2: 2020-10-25T03:30:00Z
localDateTime2: 2020-10-25T04:30

However, if we don't specify an offset, there's ambiguity with DST between 02:00 and 03:00, and by default it assumes summertime:

String inputLocalTime = "2020-10-25T02:30:00.000";
LocalDateTime localDateTime = LocalDateTime.parse(inputLocalTime);
ZonedDateTime zdt3 = localDateTime.atZone(ZoneId.of("Europe/Berlin"));
System.out.println("received localtime: " + inputLocalTime);
System.out.println("from localtime to zdt: " + zdt3);

Output:

received localtime: 2020-10-25T02:30:00.000
from localtime to zdt: 2020-10-25T02:30+02:00[Europe/Berlin]

To show that without an offset it is ambiguous:

System.out.println(OffsetDateTime.parse("2020-10-25T02:30+02:00").toLocalDateTime());
System.out.println(OffsetDateTime.parse("2020-10-25T02:30+01:00").toLocalDateTime());

Both render the same LocalTime:

2020-10-25T02:30
2020-10-25T02:30

@ghost
Copy link
Author

ghost commented Oct 27, 2020

Hi Arjan,

I think we are looking at the same topic from different angles :) The input CET/CEST times were only meant as "starting times" or "times expected by a user" in my example to have different UTC times that I can convert back to something the user wants to see. I'll try to describe more clearly what I mean:

Assumptions:

  1. all times transmitted via ICAR are UTC times (= offset of 0, no DST, = Zulu based; Instant class in Java)
  • UTC times are timestamps that "contain full information" (referring to your text)
  • this assumption implies that all systems are capable of converting local times to UTC times correctly for providing these through their ICAR interface (if needed, that is... and hopefully this assumption holds true)
    • this implies that we don't have to consider converting local times to UTC times, but only converting UTC times to local times (see also below)
  1. we are looking at the information to add to a location resource. Possible options right now:
  • add zone ID for the location
  • add offset for the location
  • add both offset and zone ID for the location
  1. we need some information at the location resource to be able to map the UTC times coming in through an ICAR interface to the local time of the location (e.g. Europe/Berlin)
  • examples: we want to aggregate daily data (day = day on location) or to present the data to a user in the time zone of the location

Coming from these assumptions, the question of this issue is: What data do I need in the location resource to be able to convert the UTC times that I receive through the ICAR interface back to local times. Hence, we don't need to look at local times as starting points at all, but only at UTC times.

Now that I have a UTC timestamp available, I still have the three options as per point 2 above: use zone id or offset or both:

  1. only zone ID available (note the DST switch in the offset part of the zoned time):
  • 2020-10-25T00:59Z (= UTC) can be converted to 2020-10-25T02:59+02:00[Europe/Berlin] (= zoned time Europe/Berlin)
  • 2020-10-25T01:00Z (= UTC) can be converted to 2020-10-25T02:00+01:00[Europe/Berlin] (= zoned time Europe/Berlin)
        Instant instant1 = Instant.parse("2020-10-25T00:59:00.000Z");
        ZonedDateTime localTime1 = instant1.atZone(ZoneId.of("Europe/Berlin"));

        Instant instant2 = Instant.parse("2020-10-25T01:00:00.000Z");
        ZonedDateTime localTime2 = instant2.atZone(ZoneId.of("Europe/Berlin"));
  1. only offset is available (example: 1 hour):
  • 2020-10-25T00:59Z (= UTC) can be converted to 2020-10-25T01:59+01:00 (= offset time)
  • 2020-10-25T01:00Z (= UTC) can be converted to 2020-10-25T02:00+01:00 (= offset time)
        Instant instant1 = Instant.parse("2020-10-25T00:59:00.000Z");
        OffsetDateTime offsetTime1= instant1.atOffset(ZoneOffset.ofHours(1));

        Instant instant2 = Instant.parse("2020-10-25T01:00:00.000Z");
        OffsetDateTime offsetTime2= instant2.atOffset(ZoneOffset.ofHours(1));
  1. offset AND zone id are available, so both conversions (1 and 2) are possible

For me, 2 is useless, as this tells nothing about the time the user had on his watch at the time at which an event happened. Would I rather store the "normal" or the "DST" offset? If both, then this is equal to the zone ID and we could just use that. However, since 3 contains also the zone ID, I could personally accept 3 (including the offset), but I'd never use the offset as I have the zone ID available, which is actually what I want. And for the offset I'd need to figure out whether it is DST or not and whether the location considers DST at all.

I think we do not have to think about how a local time (with or without zone id and/or offset) has to be converted to UTC and also the location resource is not related to that issue. It's a problem of the system providing ICAR events that should (or better have to?) reference UTC (= Zulu) timestamps.

@ghost
Copy link
Author

ghost commented Oct 27, 2020

Maybe it comes down to the point: do we want to have non-UTC times in the ICAR interface?

@alamers
Copy link
Collaborator

alamers commented Oct 27, 2020

Haha sorry yes I think we are on the same page:

  • for transferring timestamps require the use of UTC=Zulu time (or, if that is too strong, recommend Z and require an offset)
  • for adding timezone information to a location, then only the time-zone makes sense, the offset gives too little information

@ghost
Copy link
Author

ghost commented Oct 27, 2020

Great! Now we just need to convince the rest of the world :)

@cookeac
Copy link
Collaborator

cookeac commented Oct 27, 2020

This little part of the world is convinced :-)
So we are proposing to use the TZ Database Name (i.e. Zone ID) rather than an offset in the Location. This allows us for any UTC date time to calculate the local equivalent.

@erwinspeybroeck
Copy link
Collaborator

locations end point - add the time zone id = text version "Europe/Brussels" --> ISO standard 8601 --> @thomasd-gea will do
timestamps are always UTC --> has to be documented --> @alamers Arjan

@ghost
Copy link
Author

ghost commented Nov 19, 2020

FYI, the mentioned ISO standard does not reference zone IDs. However, most of the programming languages reference the IANA time zone database, so I referenced that one (https://www.iana.org/time-zones). A nice overview of the codes by global area can be found here: https://www.php.net/manual/en/timezones.php

@alamers
Copy link
Collaborator

alamers commented Dec 17, 2020

I've added a few notes on this on our Design Principles page.

@alamers
Copy link
Collaborator

alamers commented Dec 17, 2020

With #175 merged, I think this ticket can be closed?

@cookeac cookeac closed this as completed Jan 28, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
this-release Scheduled to be implemented for this release in development
Projects
None yet
Development

No branches or pull requests

3 participants