Instantly share code, notes, and snippets.
Save RhetTbull/db70c113efd03029c6ff619f4699ce34 to your computer and use it in GitHub Desktop.
Get the named timezone for a given location in python using native macOS CoreLocation APIs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
"""Get named timezone for a location on macOS using CoreLocation | |
To use this script, you need to have pyobjc-core and pyobjc-framework-CoreLocation installed: | |
pip install pyobjc-core pyobjc-framework-CoreLocation | |
This script uses CoreLocation to get the timezone for a given latitude and longitude | |
as well as the timezone offset for a given date. | |
It effectively does the same thing as python packages like [tzfpy](https://pypi.org/project/tzfpy/) | |
or [timezonefinder](https://pypi.org/project/timezonefinder/) but uses macOS native APIs. | |
""" | |
# /// script | |
# dependencies = [ | |
# "pyobjc-core", | |
# "pyobjc-framework-CoreLocation" | |
# ] | |
# /// | |
importdatetime | |
importobjc | |
fromCoreLocationimportCLGeocoder,CLLocation | |
fromFoundationimportNSDate,NSRunLoop,NSTimeZone | |
WAIT_FOR_COMPLETION=0.01# wait time for async completion in seconds | |
COMPLETION_TIMEOUT=5.0# timeout for async completion in seconds | |
deftimezone_for_location(latitude:float,longitude:float)->NSTimeZone: | |
withobjc.autorelease_pool(): | |
location=CLLocation.alloc().initWithLatitude_longitude_(latitude,longitude) | |
geocoder=CLGeocoder.alloc().init() | |
result= {"timezone":None,"error":None} | |
completed=False | |
defcompletion(placemarks,error): | |
nonlocalcompleted | |
iferror: | |
result["error"]=error.localizedDescription() | |
else: | |
placemark=placemarks[0]ifplacemarkselseNone | |
ifplacemarkandplacemark.timeZone(): | |
result["timezone"]=placemark.timeZone() | |
else: | |
result["error"]="Unable to determine timezone" | |
completed=True | |
geocoder.reverseGeocodeLocation_completionHandler_(location,completion) | |
# reverseGeocodeLocation_completionHandler_ is async so run the event loop until completion | |
# I usuall use threading.Event for this type of thing in pyobjc but the the thread blocked forever | |
waiting=0 | |
whilenotcompleted: | |
NSRunLoop.currentRunLoop().runMode_beforeDate_( | |
"NSDefaultRunLoopMode", | |
NSDate.dateWithTimeIntervalSinceNow_(WAIT_FOR_COMPLETION), | |
) | |
waiting+=WAIT_FOR_COMPLETION | |
ifwaiting>=COMPLETION_TIMEOUT: | |
raiseTimeoutError( | |
f"Timeout waiting for completion of reverseGeocodeLocation_completionHandler_:{waiting} seconds" | |
) | |
ifresult["error"]: | |
raiseException(f"Error:{result['error']}") | |
returnresult["timezone"] | |
if__name__=="__main__": | |
importargparse | |
importtime | |
defparse_date(date_str): | |
try: | |
returndatetime.datetime.strptime(date_str,"%Y-%m-%d %H:%M:%S") | |
exceptValueError: | |
returndatetime.datetime.strptime(date_str,"%Y-%m-%d") | |
parser=argparse.ArgumentParser(description="Get timezone for a location") | |
parser.add_argument("latitude",type=float,help="Latitude of location") | |
parser.add_argument("longitude",type=float,help="Longitude of location") | |
parser.add_argument( | |
"--date", | |
type=lambdad:parse_date(d), | |
default=datetime.datetime.now(), | |
help="Date/time for timezone offset in format 'YYYY-MM-DD HH:MM:SS' or 'YYYY-MM-DD'", | |
) | |
args=parser.parse_args() | |
try: | |
start_t=time.time_ns() | |
timezone=timezone_for_location(args.latitude,args.longitude) | |
offset=timezone.secondsFromGMTForDate_( | |
args.date | |
)# takes an NSDate but pybojc will convert | |
end_t=time.time_ns() | |
print( | |
f"Timezone:{timezone.name()}, offset:{offset}, took:{(end_t-start_t)/1e6:.2f} ms" | |
) | |
exceptExceptionase: | |
print(f"Error:{e}") |
MIT License
Run like this:
uv run"https://gist.githubusercontent.com/RhetTbull/db70c113efd03029c6ff619f4699ce34/raw/06f34e6d64f054faa88c1caea4a4fa3968e14848/tzname.py" 34 -118
Which ouputs:
Timezone: America/Los_Angeles, offset: -28800, took: 317.20 ms
Or
uv run"https://gist.githubusercontent.com/RhetTbull/db70c113efd03029c6ff619f4699ce34/raw/06f34e6d64f054faa88c1caea4a4fa3968e14848/tzname.py" 34 -118 --date"2024-06-01"
which outputs
Timezone: America/Los_Angeles, offset: -25200, took: 264.47 ms
Thanks to@simonw for the tip on including uvscript
block.
oPromessa commentedJan 26, 2025 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Amazing. Offset accounts for DST based on sample date.
Tries to extract DST status viatimezone.isDaylightSavingTime()
but it comes as False in both cases. Sorry don't master Apple Foundation framework.
$ python tzname.py 43 -9 --date"2024-06-01 10:00:00"Timezone: Europe/Madrid, offset: 7200, took: 254.66 ms$ python tzname.py 43 -9 --date"2024-01-01 10:00:00" Timezone: Europe/Madrid, offset: 3600, took: 304.73 ms
Added:
# Get DST informationis_dst=timezone.isDaylightSavingTimeForDate_(args.date)dst_offset=timezone.daylightSavingTimeOffsetForDate_(args.date)dst_timezone_name=timezone.abbreviationForDate_(args.date)print(f"Timezone:{timezone.name()}, offset:{offset}, "f"DST:{is_dst}, DST offset:{dst_offset}, "f"DST timezone name:{dst_timezone_name}, "f"took:{(end_t-start_t)/1e6:.2f} ms" )
Author
RhetTbull commentedJan 27, 2025 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Weird.timezone.isDaylightSavingTimeForDate_(args.date)
works correctly for me.
❯ python tz.py 34 -118 --date"2021-06-01"Timezone: America/Los_Angeles, offset: -25200, DST: True, took: 281.25 ms❯ python tz.py 34 -118 --date"2021-01-01"Timezone: America/Los_Angeles, offset: -28800, DST: False, took: 292.69 ms
I added:
is_dst=timezone.isDaylightSavingTimeForDate_(args.date)print(f"Timezone:{timezone.name()}, offset:{offset}, DST:{is_dst}, took:{(end_t-start_t)/1e6:.2f} ms" )
agouliel commentedJan 30, 2025
Very nice, thank you
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment