-
Notifications
You must be signed in to change notification settings - Fork 3.4k
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
Timezone support #8383
Timezone support #8383
Conversation
This should be ready for one last review. @lau, can you please take a final look? Note that instead of Please approve or reject the PR accordingly so we can go ahead and merge it. |
Co-Authored-By: josevalim <[email protected]>
Also, should it be called |
|
||
config :elixir, :time_zone_database, CustomTimeZoneDatabase | ||
|
||
or by calling `Calendar.put_time_zone_database/1`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really want to duplicate the documentation text like that? I'm pretty sure they will get out of sync pretty quickly. I would forward from one to another - I'm not sure which way, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@michalmuskala yeah. I am ok with them getting out of sync though, I think eventually we will contextualize them better... I am just not sure how people will look for this information, so I want to err on the size of over explaining.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, that's fair. Should we add some comment around the docs that you should change the other place as well?
@doc since: "1.8.0" | ||
@spec shift_zone(t, Calendar.time_zone(), Calendar.time_zone_database()) :: | ||
{:ok, t} | {:error, :time_zone_not_found | :utc_only_time_zone_database} | ||
def shift_zone(datetime, time_zone, time_zone_database \\ Calendar.get_time_zone_database()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we have an accompanying force_zone
function that uses the same naive dt, but in a specific zone? For example, if you got time in the wrong zone, but the numeric values are correct. From experience, I know such a function is useful.
If we don't want to provide such a function, maybe in the docs for shift_zone
we describe how to do it by first converting to native and then from naive to the target zone.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure what you mean here, can you please provide an example? Or an sketch of how it would look like? Or open up a separate issue. There are other features we likely want to look at, such as finally adding DateTime.add/3
but this PR is already big as is. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for not being clear. The use case I have in mind would be exactly equivalent to something like:
def force_zone(datetime, time_zone) do
from_naive(to_naive(datetime), time_zone)
end
This means basically that if I have a 9 AM CET and I want to have 9 AM UTC or similar. It's generally useful in many cases, where you actually don't want to do proper time zone conversion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@michalmuskala If you have a DateTime and want the same "wall time" in a different time zone you can pass it to a function only expecting a naive datetime. E.g. 2018-11-13 09:00:00 Europe/Copenhagen and want 9:00:00 for the same date but in UTC instead.
DateTime.from_naive(cph_dt_9oclock, "Etc/UTC")
should return the same "wall time" as the one provided, but in UTC instead. Because from_naive
just looks at the fields required by the Calendar.naive_datetime type and ignores the time zone part.
optional(any) => any, | ||
utc_offset: Calendar.utc_offset(), | ||
std_offset: Calendar.std_offset(), | ||
zone_abbr: Calendar.zone_abbr() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest that we use a record or a raw tuple here. I know it's much less legible, but it's more performant and in things like this we probably want to squeeze every last bit of performance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not worried about legibility but rather about extensibility. Using a tuple would be very unforgiving in case we need to add new information. A record is a bit better but adding custom fields would be tricky. Btw, if we have many maps, all with the same keys, do they share the keys structure when loading it in memory?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It depends on how it is loaded. In general, though, if we're loading it from ETS, the table will always copy everything. ETS does not do literal sharing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was wondering about modules. But keep in mind we can always store it in ets as a tuple and then convert it to a map when reading out. :)
I would go with |
@josevalim I guess it's pretty common to have a |
@michalmuskala do we have examples of that convention in the codebase? I found examples of I also found examples of |
I believe this is the convention @michalmuskala suggestion. :) I am honestly torn on which one to pick. I can say though the ones in Application/System are usually more meta (it is a general key-value) and the ones in Registry is more about setting something, which is more inline with what we have here. |
We have |
I went with |
@fertapric one place where this is used is |
Squashed and merged manually, thanks everyone! |
|
||
describe "from_naive" do | ||
test "uses default time zone database from config" do | ||
Calendar.put_time_zone_database(FakeTimeZoneDatabase) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test module is has async
set to true
for tests. I assume this and Calendar.put_time_zone_database
can cause race conditions. Should the tests that call Calendar.put_time_zone_database
be put in a separate test module? Or just make the existing module async: false
.
cc @josevalim
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. Let's make remove the async: true
from this particular module. I was planning to break this test file apart, so I will do that and fix the async bit. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pushed!
Continuation of #7914.
It removes leap seconds support, so we can have a more fine grained discussion on the topic. It also streamlines the
shift_zone
operation.We still need to discuss the API for the timezone client.