K, been trying to get this figured out for a few hours. Time to ask for help.
Times being displayed in the app are not correct with the change in daylight savings time.
Dates are UTC(GMT) being stored and queried from Airtable.
Looks like the dates being served to the app are OK.
Server module returns a string from the API and converts to datetime object
** The third being after the DST Change
import anvil.server
import datetime as dt
import pytz
@anvil.server.callable
def get_dates(timezone):
tz = pytz.timezone(timezone)
noon_today = dt.datetime(2021, 10, 28, 12) # DST still in place
noon_next_monday = dt.datetime(2021, 11, 1, 12) # After the clocks go back - same as UTC
dates = [tz.localize(date) for date in (noon_today, noon_next_monday)]
print(f"server side: {dates}")
return dates
Owen, thank you very helpful.
I had looked at a number of posts using pytz but was stuck on trying to do in the client.
I have half implemented and seems to be working with a hard coded time zone.
Now just need to dynamically pass the timezone to the server.
anvil.tz.tzlocal() seems to give me the offset and not the time zone
<anvil.tz.tzlocal (-4 hour offset)>
Regardless: I have the default time zone of each user in a database so I can just use that.
That depends on what you’re trying to do. tzlocal will give you the localised time using the current offset of the browser - but it doesn’t know anything about dst.
If you need the localised time using the offset that would have applied at that time, you need to use the timezone (rather than just an offset) and a dst aware library like pytz.
As a Python novice I read “Aware” and assume that would include DST. Would have saved me good deal of tinkering to just know I need to take this back to the server and use pytz.
In the end my final solution displays “Local” time as the default timezone for the user. Not a dynamic time based on their browser. So it is kind of a half solution but good enough for now. I am fortunate enough to have the timezone of each user in a database because we collect in on-boarding.
If anyone know how to pull the time zone from the browser (some custom javascript?), it would be good to know.
Ok, so here is why this is hard information to find. It isn’t standard practice, so you wont find it suggested by almost any documentation. Why this is, is that this information is not really “ground truth” in programming, it is the local computers set computer time, which can be changed by any user at any time.
Programmers don’t like this, because it means the user can make themselves any time they like (1000’s of years in the future for example) which could create use cases that most people just don’t want to deal with.
That being said:
import time
if time.localtime().tm_isdst:
print("DST")
else:
print("NOT DST")
.tm_isdst just resovles to a 1 or a zero (…so falsey).
time.localtime().tm_zone will give you a zero padded string for your UTC timezone ( so “-05” for UTC -5:00 where I am.
time.strftime("%z", time.localtime())
…is going to give you an offset string with the adjusted for local client browsers computer time with daylight savings time. (so -0400 for me as of October 29th 2021, since daylight savings time is active where I am)
The nice thing about this is, if you tell a modern OS that you live in a place that does not have Daylight Savings time, time.localtime() should adjust for this.
As an example, Arizona in the United States does not observe but states it borders does, making it possible for your time to change by 1hr by traveling to Nevada, but only for about 1/2 the year
You can use this offset string to “re-create” a timezone aware datetime object by plugging the string back into an unaware datetime object.
This was a pain to research, My use case was that I had to interpret some live data that had timestamps from an old system that does not have offsets recorded, so my local system adds the local timezone (same location as the old system) before writing to an anvil data-table datetime column so I can compare datetimes using anvil. This is only accurate because it is live information, (actual people workers doing things) and the information is never generated at night during the dst shift.
from anvil.js.window import Intl
time_zone = Intl.DateTimeFormat().resolvedOptions().timeZone
print(f'JS TIMEZONE IS: {time_zone}')
. . . and in the console!
JS TIMEZONE IS: America/New_York
Which I can then pass to my function as the override.
Background on the function: I am consuming some data from multiple data sources, all return data as a list of dicts with all dates as string values in UTC. Wanted a single function that I could pass the response from any of these sources and provide dates in any flavor for other purposes.
Tried to be a little verbose in the doc string so other newbies like me might be able repurpose.
def dates_process(lst_of_dic, lst_date_keys, str_strip, str_pretty="%b %d %Y %I:%M %p", timezone_override=None):
"""
Create date objects and pretty strings for specified keys in passed dic.
For each dict in list and each date_key in dict. Convert the field to datetime object with tz=utc.
Then convert the utc to specified local timezone(ins) and America/New_York(est)
Then create pretty strings of all three.
The original data is returned with six additional key/values appended to each dict for each field.
Passed data needs a key "timezone" with string recognized by pytz or must be passed the override parameter.
https://gist.github.com/heyalexej/8bf688fd67d7199be4a1682b3eec7568
i.e. "America/New_York
Creates new key/values for:
[orig]_utc_dtm as aware datetime object in utc
[orig]_ins_dtm as aware datetime object in instructors default timezone
[orig]_est_dtm as aware datetime object in America/New_York timezone
[orig]_utc_str as string in utc
[orig]_ins_str as string in instructors default timezone
[orig]_est_str as string in America/New_York timezone
:param lst_of_dic: List of dicts as data rows
:type lst_of_dic: list
:param lst_date_keys: List of date dict keys to process exp. ['start_time', 'created_at']
:type lst_date_keys: list
:param str_strip: String format of the date string to convert to datetime
For each data source the original date string format of the time may be slightly different
and str_strip will depend on source data.
exp. "%Y-%m-%dT%H:%M:%S%fZ"
:type str_strip: str
:param str_pretty: String format for pretty presentation of date. A default is defined for consistent
presentation across the app but can be overridden
exp. "%b %d %Y %I:%M %p"
:type str_pretty: str
:param timezone_override:
:type timezone_override: str
:return: lst of dicts appended with new dtm fields
"""
# lst_date_keys = ['start_time', 'created_at']
tz_utc = pytz.timezone("UTC")
tz_est = pytz.timezone("America/New_York")
for m in lst_of_dic:
# Use either passed time zone override or read from 'timezone' key in dic
if timezone_override is not None:
tz_inst = timezone_override
else:
tz_inst = pytz.timezone(m['timezone'])
# for each date key passed, apply date functions derriving new key names from original
for f in lst_date_keys:
try:
# Create date objects
m[f'{f}_utc_dtm'] = dt.strptime(m[f], str_strip).replace(tzinfo=tz_utc)
m[f'{f}_ins_dtm'] = m[f'{f}_utc_dtm'].astimezone(tz_inst)
m[f'{f}_est_dtm'] = m[f'{f}_utc_dtm'].astimezone(tz_est)
# Create pretty strings
m[f'{f}_utc_str'] = m[f'{f}_utc_dtm'].strftime(str_pretty)
m[f'{f}_ins_str'] = m[f'{f}_ins_dtm'].strftime(str_pretty)
m[f'{f}_est_str'] = m[f'{f}_est_dtm'].strftime(str_pretty)
except:
continue
return lst_of_dic