+ return dt.tzinfo is not None and dt.tzinfo.utcoffset(dt) is not None
+
+
+def is_timezone_naive(dt: datetime.datetime) -> bool:
+ """Inverse of is_timezone_aware.
+
+ >>> is_timezone_naive(datetime.datetime.now())
+ True
+
+ >>> is_timezone_naive(now_pacific())
+ False
+
+ """
+ return not is_timezone_aware(dt)
+
+
+def strip_timezone(dt: datetime.datetime) -> datetime.datetime:
+ """Remove the timezone from a datetime. Does not change the
+ hours, minutes, seconds, months, days, years, etc... Thus the
+ instant to which this timestamp refers will change. Silently
+ ignores datetimes which are already timezone naive.
+
+ >>> now = now_pacific()
+ >>> now.tzinfo == None
+ False
+
+ >>> dt = strip_timezone(now)
+ >>> dt == now
+ False
+
+ >>> dt.tzinfo == None
+ True
+
+ >>> dt.hour == now.hour
+ True
+
+ """
+ if is_timezone_naive(dt):
+ return dt
+ return replace_timezone(dt, None)
+
+
+def add_timezone(dt: datetime.datetime, tz: datetime.tzinfo) -> datetime.datetime:
+ """
+ Adds a timezone to a timezone naive datetime. This does not
+ change the instant to which the timestamp refers. See also:
+ replace_timezone.
+
+ >>> now = datetime.datetime.now()
+ >>> is_timezone_aware(now)
+ False
+
+ >>> now_pacific = add_timezone(now, pytz.timezone('US/Pacific'))
+ >>> is_timezone_aware(now_pacific)
+ True
+
+ >>> now.hour == now_pacific.hour
+ True
+ >>> now.minute == now_pacific.minute
+ True
+
+ """
+
+ # This doesn't work, tz requires a timezone naive dt. Two options
+ # here:
+ # 1. Use strip_timezone and try again.
+ # 2. Replace the timezone on your dt object via replace_timezone.
+ # Be aware that this changes the instant to which the dt refers
+ # and, further, can introduce weirdness like UTC offsets that
+ # are weird (e.g. not an even multiple of an hour, etc...)
+ if is_timezone_aware(dt):
+ if dt.tzinfo == tz:
+ return dt
+ raise Exception(
+ f'{dt} is already timezone aware; use replace_timezone or translate_timezone '
+ + 'depending on the semantics you want.'
+ )
+ return dt.replace(tzinfo=tz)
+
+
+def replace_timezone(dt: datetime.datetime, tz: Optional[datetime.tzinfo]) -> datetime.datetime:
+ """Replaces the timezone on a timezone aware datetime object directly
+ (leaving the year, month, day, hour, minute, second, micro,
+ etc... alone).
+
+ Works with timezone aware and timezone naive dts but for the
+ latter it is probably better to use add_timezone or just create it
+ with a tz parameter. Using this can have weird side effects like
+ UTC offsets that are not an even multiple of an hour, etc...
+
+ Note: this changes the instant to which this dt refers.