How do you store your user's timezone information?

Curious how you store your user’s timezone information to be able to send them an email at 12PM local time for example.

Assuming I default a dropdown with getTimezoneOffset clientside and allow the user to pick their timezone, what would be the best way to store this information?

Ideally whatever I save should be easy to use in Timex functions to calculate “schedule this Oban worker at 12PM user’s local time.”

Curious what you’ve guys done for this kind of issue in the past.

I could save the offset like:

timezone_offset: 360 # UTC-6 hours

Then schedule the job for tomorrow at 12pm local time:

timezone_offset = user.timezone_offset # 360 

schedule_time = 
  Timex.now()
  |> Timex.shift(days: 1)
  |> Timex.beginning_of_day()
  |> Timex.shift(hours: 12) 
  |> Timex.shift(minutes: timezone_offset)

Oban.insert!(%Oban.Job{
  worker: MyWorker,
  scheduled_at: schedule_time
})

Any sharp edges I’m not considering before going this route?

1 Like

tz can do it for You…

You provide a naive_datetime (virtual attribute), a timezone, and tz can handle offset and utc itself.

UPDATE: Ouups, I think it’s tzdata that does this

In fact, it’s tz_datetime, sorry

https://hexdocs.pm/tz_datetime/TzDatetime.html#handle_datetime/2

1 Like

Timezone offsets are not timezones. It tells you almost nothing about the future. Sure the offset might be X today for the user, but how do you know the same still applies tomorrow?

You need the timezone, not just the offset, to do this stuff reliably. I’d also suggest to stick to elixir core APIs nowadays, as besides parsing they can do everything you need. There’s no need to attempt to build your own timezone handling.

tomorrow = "Europe/Berlin" |> DateTime.now!() |> Date.add(1)
DateTime.new(tomorrow, ~T[12:00:00], "Europe/Berlin")
# {:ok, #DateTime<2023-08-25 12:00:00+02:00 CEST Europe/Berlin>}
6 Likes

That’s interesting and makes sense. Are you saying Elixirs core will keep up to date with time zone changes so I should rely on them?

No, you need a library like tz or tzdata to bring in a database of timezones, but elixir itself brings all the necessary APIs to make use of those databases.

2 Likes

To get a little more concrete than my last post:

  • Timex.now might already return a different date than the current date of the user (australia is long in the new year before midnight in the usa)
  • Shifting a day from an already potentially incorrect base won’t make things more correct.
    • Do you know if this is actually adding a day to the date or plainly adding 24h? That’s quite the difference with timezones in play.
  • Shifting the hours expects that the mapping between hours in UTC to the users timezone are fixed. That’s not the case most obviously around DST switches.
  • Manually applying any offset should probably be considered a problem on its own. Always let a library do that for you, it probably knows better where to apply which offset.
2 Likes

I store the timezone name (e.g. America/Chicago) in users.time_zone and use that when using Elixir DateTime functions, and they also work with postgres’s AT TIME ZONE in case you want to query users against a given time or datetime.

9 Likes

Ha, didn’t know the part about Postgres, thank you.

2 Likes