Даты и время, вероятно, звучат как простая концепция, пока вам не придется иметь дело с пользователями и данными со всего мира.
Тогда вы понимаете, что это действительно сложно!
TL; DR: шпаргалка
1. Анализировать строки даты
import datetime import pytz import dateutil.parser format = '%Y-%m-%dT%H:%M:%S%z' datestring = '2016-09-20T16:43:45-07:00' d = dateutil.parser.parse(datestring) # python 2.7 d = datetime.datetime.strptime(datestring, format) #3.2+
2. Строка даты ISO-8601 для объекта datetime UTC
import datetime import pytz import dateutil.parser format = '%Y-%m-%dT%H:%M:%S%z' datestring = '2016-09-20T16:43:45-07:00' d = dateutil.parser.parse(datestring) # python 2.7 d = d.replace(tzinfo=utc) - d.utcoffset() >>> datetime.datetime(2016, 9, 20, 23, 43, 45, tzinfo=<UTC>)
3. Отметка времени в формате UTC (сейчас)
import datetime import pytz # define epoch, the beginning of times in the UTC timestamp world epoch = datetime.datetime(1970,1,1,0,0,0) now = datetime.datetime.utcnow() timestamp = (now - epoch).total_seconds() >>> 1505329554.617216 # subtracting datetime objects result in a datetime.timedelta object which can be expressed in seconds. # this works because both datetime are implicitly in UTC time
4. Отметка времени в формате UTC из простой строки даты.
import datetime import pytz # naive datetime d = datetime.datetime.strptime('01/12/2011', '%d/%m/%Y') >>> datetime.datetime(2011, 12, 1, 0, 0) # add proper timezone for the date pst = pytz.timezone('America/Los_Angeles') d = pst.localize(d) >>> datetime.datetime(2011, 12, 1, 0, 0, tzinfo=<DstTzInfo 'America/Los_Angeles' PST-1 day, 16:00:00 STD>) # convert to UTC timezone utc = pytz.UTC d = d.astimezone(utc) >>> datetime.datetime(2011, 12, 1, 8, 0, tzinfo=<UTC>) # epoch is the beginning of time in the UTC timestamp world epoch = datetime.datetime(1970,1,1,0,0,0,tzinfo=pytz.UTC) >>> datetime.datetime(1970, 1, 1, 0, 0, tzinfo=<UTC>) # get the total second difference ts = (d - epoch).total_seconds() >>> 1322726400.0
5. Добавьте часовой пояс к наивному datetime
import datetime import pytz # naive datetime d = datetime.datetime.strptime('01/12/2011 16:43:45', '%d/%m/%Y %H:%M:%S') >>> datetime.datetime(2011, 12, 1, 16, 43, 45) # add proper timezone pst = pytz.timezone('America/Los_Angeles') d = pst.localize(d) >>> datetime.datetime(2011, 12, 1, 16, 43, 45, tzinfo=<DstTzInfo 'America/Los_Angeles' PST-1 day, 16:00:00 STD>)
НЕ используйте datetime.replace
для установки часового пояса:
import datetime import pytz ### DON'T DO THIS, THIS CODE IS WRONG!!! d = datetime.datetime.utcfromtimestamp(1505325217) >>> datetime.datetime(2017, 9, 13, 17, 53, 37) pst = pytz.timezone('America/Los_Angeles') d = d.replace(tzinfo=pst) >>> datetime.datetime(2017, 9, 13, 17, 53, 37, tzinfo=<DstTzInfo 'America/Los_Angeles' LMT-1 day, 16:07:00 STD>)
В приведенном выше коде есть две ошибки:
datetime.replace
заменяетtzinfo
: он вообще не преобразует время. (utcfromtimestamp
даетdatetime
в UTC, поэтому изменениеtzinfo
изменяет представленное время)pytz
плохо работает сtzinfo
. Заменаtzinfo
на любое значение, кроме UTC, может привести к непредвиденным последствиям. В приведенном выше примере обратите внимание, как параметр часового пояса PST фактически становится временем LMT (местное среднее время), которое смещено на 16:07:00 от UTC, НЕ 16:00:00.
В итоге получается смещение 16h07m: плохо!
6. datetime в строку ISO 8601
import datetime import pytz # naive datetime d = datetime.datetime.strptime('01/12/2011 16:43:45', '%d/%m/%Y %H:%M:%S') >>> datetime.datetime(2011, 12, 1, 16, 43, 45) # add proper timezone pst = pytz.timezone('America/Los_Angeles') d = pst.localize(d) >>> datetime.datetime(2011, 12, 1, 16, 43, 45, tzinfo=<DstTzInfo 'America/Los_Angeles' PST-1 day, 16:00:00 STD>) d.isoformat() >>> "2011-12-01T16:43:45-08:00"
7. дата и время из метки времени
import datetime import pytz d = datetime.datetime.utcfromtimestamp(1505325217) >>> datetime.datetime(2017, 9, 13, 17, 53, 37) # add timezone info d = pytz.UTC.localize(d) >>> datetime.datetime(2017, 9, 13, 17, 53, 37, tzinfo=<UTC>)
8. преобразовать из UTC в другой часовой пояс
import datetime import pytz d = datetime.datetime.utcfromtimestamp(1505325217) >>> datetime.datetime(2017, 9, 13, 17, 53, 37) # add timezone info d = pytz.UTC.localize(d) >>> datetime.datetime(2017, 9, 13, 17, 53, 37, tzinfo=<UTC>) pst = pytz.timezone('America/Los_Angeles') d.astimezone(pst) >>> datetime.datetime(2017, 9, 13, 10, 53, 37, tzinfo=<DstTzInfo 'America/Los_Angeles' PDT-1 day, 17:00:00 DST>)
9. Сложение и вычитание времени с помощью timedelta
Летнее время было 6 ноября 2016 г .:
import datetime import pytz d = datetime.datetime(2016, 11, 5, 16, 43, 45) # naive datetime utc = pytz.UTC pst = pytz.timezone('America/Los_Angeles') d = utc.localize(d) # UTC timezone aware >>> datetime.datetime(2016, 11, 5, 16, 43, 45, tzinfo=<UTC>) # add 1 day to UTC date d = d + datetime.timedelta(days=1) >>> datetime.datetime(2016, 11, 6, 16, 43, 45, tzinfo=<UTC>) # now convert to local timezone d = d.astimezone(pst) >>> datetime.datetime(2016, 11, 6, 8, 43, 45, tzinfo=<DstTzInfo 'America/Los_Angeles' PST-1 day, 16:00:00 STD>) # daylight saving was respected
НЕ используйте timedelta
ни с чем, кроме времени UTC:
import datetime import pytz ### DON'T USE THIS CODE, THIS CODE IS WRONG !!! d = datetime.datetime(2016, 11, 5, 16, 43, 45) # naive datetime utc = pytz.UTC pst = pytz.timezone('America/Los_Angeles') d = utc.localize(d) # UTC timezone aware >>> datetime.datetime(2016, 11, 5, 16, 43, 45, tzinfo=<UTC>) # convert d to 'America/Los_Angeles' timezone d = d.astimezone(pst) >>> datetime.datetime(2016, 11, 5, 9, 43, 45, tzinfo=<DstTzInfo 'America/Los_Angeles' PDT-1 day, 17:00:00 DST>) # add 1 day to PDT date: DON'T DO THAT d = d + datetime.timedelta(days=1) >>> datetime.datetime(2016, 11, 6, 9, 43, 45, tzinfo=<DstTzInfo 'America/Los_Angeles' PDT-1 day, 17:00:00 DST>) # daylight saving was not respected, still PDT time, not PST as it should be
Итак, теперь длинная версия:
1. Анализ строк даты и времени
Во-первых, существует так много форматов даты и времени, находитесь ли вы в Северной Америке или Европе, говорите ли вы в 12-часовом или 24-часовом или даже военном времени:
- «Среда, 21 сентября, 18:30»
- “09/21/2016 06:30PM”
- “2016/09/21 18:30”
- “21/09/2016 18:30”
Разбирать дату и время может быть немного неудобно, но это не самое сложное:
import datetime format = '%Y-%m-%d %H:%M:%S' datestring = '2016-09-20 16:43:45' d = datetime.datetime.strptime(datestring, format) # Note ISO string format is special. More about that later
То, что мы только что получили, называется наивным датой и временем.
Вы спросите, почему наивный? Потому что это субъективно.
2. Время относительно
Если сейчас в Лондоне 16.43, то в Сан-Франциско через 8 часов будет только 16.43 (16:43). Одна и та же строка даты может представлять разные моменты времени в разных местах.
При наивном datetime нет четкого способа узнать, в какой фактический момент времени произошло событие, и, следовательно, нет четкого способа упорядочить события.
Приходит время в формате UTC
3. Временные метки UTC.
Всемирное координированное время (дословный перевод французского TUC (Temps Universal Coordiné)
Временная метка UTC - это количество секунд с эпохи, то есть 1 января 1970 года в 00:00 по Гринвичу (среднее время по Гринвичу).
эпоха определяется в определенном месте в мире (часовой пояс по Гринвичу), что позволяет представить фактический момент времени.
Примечание: вы можете услышать о времени по Гринвичу, UTC или даже о времени Зулу. Все они представляют время на Гринвичском меридиане, который проходит с севера на юг и пересекает Гринвич в Великобритании.
Отметки времени в формате UTC используются для упорядочивания событий (данных журнала) и расчета разницы во времени с точки зрения прошедшего времени.
Наивный подход к получению метки времени в python может выглядеть примерно так:
# define epoch, the beginning of times in the UTC timestamp world epoch = datetime.datetime(1970,1,1,0,0,0) now = datetime.datetime.utcnow() timestamp = (now - epoch).total_seconds() # subtracting datetime objects result in a datetime.timedelta object which can be expressed in seconds.
Это работает, потому что метод utcnow
по умолчанию возвращает наивный datetime
объект во времени в формате UTC, но не делает этого с датами с учетом часового пояса не в формате UTC.
Мы немного увидим, как преобразование заданной даты в метку времени в формате UTC требует немного больше усилий.
Хотя метки времени в формате UTC - отличный способ стандартизировать представление момента времени, одна и та же метка времени в формате UTC преобразуется в разные строки даты в разных местах мира. В конце концов, он определяется по Гринвичу.
Используя временную метку, мы потеряли понятие местоположения и, следовательно, местного времени.
Так одно или другое?
4. Смещение времени, ISO-8601 и местное время
Если мы хотим знать о времени суток события, отправленного пользователем, или нам просто нужно представить время события в местном времени, как в случае большинства пользователей -в приложении, вам нужно будет преобразовать время сервера (которое на самом деле всегда должно быть в формате UTC) в местное время.
Смещение времени - это смещение часового пояса, которое необходимо добавить ко времени UTC для представления местного времени.
ISO-8601 - это стандарт для представления строк даты, включая временные смещения.
Он действительно может представлять строку даты во многих различных форматах, но наиболее часто используемый формат в вычислениях:
2017–09–13T20:58:41+07:00
YYYY-MM-DDTHH:mm:ss+/-HH:mm
В формате синтаксического анализа Python, который становится
'%Y-%m-%dT%H:%M:%S%z'
для синтаксического анализа строки ISO-8601 в python ›3.2 вы можете использовать ту же функцию, что и в 1., но если вы используете python 2.7, вам понадобится dateutil.parser
:
import datetime import pytz import dateutil.parser format = '%Y-%m-%dT%H:%M:%S%z' datestring = '2016-09-20T16:43:45-07:00' d = dateutil.parser.parse(datestring) # python 2.7 d = datetime.datetime.strptime(datestring, format) #3.2+
Теперь очень важно понять:
Строка даты ISO-8601 содержит:
- местное время (часть
YYYY-MM-DDTHH:mm:ss
) - средство для преобразования в UTC (смещение времени, т.е. часть
+/-HH:mm
),
Однако смещение времени действительно только для этой местной даты / времени.
Да, временные сдвиги не высечены на камне: они фактически меняются из-за перехода на летнее время.
2017–07–13T20:58:41+07:00
или 2017–12–13T20:58:41+08:00
- время для одного и того же места в тихоокеанском часовом поясе, но одно - летом (когда наблюдается переход на летнее время, летнее время), а другое - зимой, когда это не так.
Это означает, что вы не можете использовать только временное смещение для определения местоположения или вычисления временных дельт.
Строку ISO-8601 можно безопасно использовать для перевода местного времени между часовыми поясами, но не следует использовать как есть для манипуляций со временем.
5. Часовые пояса
Компенсации по времени недостаточно. Нам нужна информация о часовом поясе.
Что такое часовой пояс?
Это немного сбивает с толку, потому что я говорил о времени в формате UTC, но UTC не является часовым поясом. Вы, вероятно, слышали о EST, CST, MST или PST, если вы живете в Северной Америке, и о GMT и CET, если вы находитесь в Западной Европе, и вам сказали, что это часовые пояса, но на самом деле это не часовые пояса, они называют представляющий смещение по времени.
Часовой пояс обычно определяется континентом и местоположением, например America/Los_Angeles
, Europe/Paris
или Africa/Tunis
.
Обратите внимание, что в названии нет понятия перехода на летнее время.
PST: Pacific Standard Time (GMT-08:00)
иPDT: Pacific Daylight Time (GMT-07:00)
- это временное смещение, применяемое вAmerica/Los_Angeles
часовом поясе в зависимости от времени года.CET: Central European Time (GMT+01:00)
иCEST: Central European Summer Time (GMT+02:00)
- оба смещения времени, применяемые вEurope/Paris
часовом поясе.CET: Central European Time (GMT+01:00)
также применяется вAfrica/Tunis
часовом поясе, но переход на летнее время не выполняется, поэтомуCET
время используется круглый год.
чтобы иметь дело с часовым поясом, pytz
- ваш друг ... в большинстве случаев.
Мы еще не упоминали об этом, но когда мы ранее анализировали ISO-8601, вы могли заметить, что объект datetime
выглядит так:
datetime.datetime(2016, 9, 20, 16, 43, 45, tzinfo=tzoffset(None, -25200))
Есть смещение по времени, но нет информации о часовом поясе.
Все, что мы действительно можем с этим сделать, - это преобразовать обратно в формат UTC.
import datetime import pytz import dateutil.parser format = '%Y-%m-%dT%H:%M:%S%z' datestring = '2016-09-20T16:43:45-07:00' d = dateutil.parser.parse(datestring) d = d.replace(tzinfo=pytz.UTC) - d.utcoffset() >>> datetime.datetime(2016, 9, 20, 23, 43, 45, tzinfo=<UTC>) # now a timezone aware UTC datetime.
Подождите, мы снова потеряли часовой пояс.
Нет, мы этого не сделали. Для начала у нас никогда не было часового пояса. У нас был зачет. Как видно выше, часовой пояс может иметь несколько смещений, поэтому нет простого способа получить имя часового пояса из смещения.
Имя часового пояса - это то, что вы можете легко получить на клиенте (браузере, в Javascript) и которое вы должны отправить на свой сервер вместе со строкой ISO-8601, если она вам нужна на сервере.
Если вы знаете часовой пояс, в который хотите преобразовать, вы можете:
import datetime import pytz import dateutil.parser format = '%Y-%m-%dT%H:%M:%S%z' datestring = '2016-09-20T16:43:45-07:00' d = dateutil.parser.parse(datestring) # UTC datetime d = d.replace(tzinfo=pytz.UTC) - d.utcoffset() pst = pytz.timezone('America/Los_Angeles') d.astimezone(pst) >>> datetime.datetime(2016, 9, 20, 16, 43, 45, tzinfo=<DstTzInfo 'America/Los_Angeles' PDT-1 day, 17:00:00 DST>)
но не волнуйтесь:
Вы не должны выполнять никаких операций с датой и временем в локализованном времени. Всегда используйте UTC.
Посмотрите на этот пример, где мы находимся за день до перехода на летнее время и хотим добавить один день к объекту datetime
:
import datetime import pytz ### DON'T USE THIS CODE, THIS CODE IS WRONG !!! d = datetime.datetime(2016, 11, 5, 16, 43, 45) # naive datetime utc = pytz.UTC pst = pytz.timezone('America/Los_Angeles') d = utc.localize(d) # UTC timezone aware >>> datetime.datetime(2016, 11, 5, 16, 43, 45, tzinfo=<UTC>) # convert d to 'America/Los_Angeles' timezone d = d.astimezone(pst) >>> datetime.datetime(2016, 11, 5, 9, 43, 45, tzinfo=<DstTzInfo 'America/Los_Angeles' PDT-1 day, 17:00:00 DST>) # add 1 day to PDT date: DON'T DO THAT d = d + datetime.timedelta(days=1) >>> datetime.datetime(2016, 11, 6, 9, 43, 45, tzinfo=<DstTzInfo 'America/Los_Angeles' PDT-1 day, 17:00:00 DST>) # daylight saving was not respected, still PDT time, not PST as it should be
вместо этого, используя дату и время в формате UTC для всех операций и конвертируя только в местное время в конце:
import datetime import pytz d = datetime.datetime(2016, 11, 5, 16, 43, 45) # naive datetime utc = pytz.UTC pst = pytz.timezone('America/Los_Angeles') d = utc.localize(d) # UTC timezone aware >>> datetime.datetime(2016, 11, 5, 16, 43, 45, tzinfo=<UTC>) # add 1 day to UTC date d = d + datetime.timedelta(days=1) >>> datetime.datetime(2016, 11, 6, 16, 43, 45, tzinfo=<UTC>) # now convert to local timezone d = d.astimezone(pst) >>> datetime.datetime(2016, 11, 6, 8, 43, 45, tzinfo=<DstTzInfo 'America/Los_Angeles' PST-1 day, 16:00:00 STD>) # daylight saving was respected
7. Преобразование простой даты в метку времени.
Иногда вам нужно иметь дело с наивными датами, что означает, что вам нужно знать часовой пояс, чтобы делать с ними что-нибудь полезное, например преобразовывать в метку времени.
Преобразование даты в метку времени в формате UTC, как упоминалось выше, требует немного больше усилий, чтобы убедиться, что дата находится в формате UTC, чтобы начать с:
# naive datetime d = datetime.datetime.strptime('01/12/2011', '%d/%m/%Y') >>> datetime.datetime(2011, 12, 1, 0, 0) # add proper timezone for this naive date pst = pytz.timezone('America/Los_Angeles') d = pst.localize(d) >>> datetime.datetime(2011, 12, 1, 0, 0, tzinfo=<DstTzInfo 'America/Los_Angeles' PST-1 day, 16:00:00 STD>) # convert to UTC timezone utc = pytz.UTC d = d.astimezone(utc) >>> datetime.datetime(2011, 12, 1, 8, 0, tzinfo=<UTC>) # epoch is the beginning of time in the UTC timestamp world epoch = datetime.datetime(1970,1,1,0,0,0,tzinfo=pytz.UTC) >>> datetime.datetime(1970, 1, 1, 0, 0, tzinfo=<UTC>) # get the total second difference ts = (d - epoch).total_seconds() >>> 1322726400.0
Вот так.
Надеюсь, это было полезно.
Если вам нужно запомнить всего несколько вещей, запомните вот что:
- Всегда используйте
datetime
часовой пояс в формате UTC на сервере и для любых операций. - Используйте строки даты ISO-8601 со смещением для передачи информации о дате между клиентом и сервером.
- Отправьте название часового пояса от клиента, не пытайтесь угадать.
Удачи!