Анализ устаревших временных меток в локальное время (до UTC) Во время наблюдения за летним временем
У меня есть файлы данных CSV с временными записями, которые находятся в местном времени. К сожалению, файлы данных охватывают период, в котором изменяется переход на летнее время (3 ноября 2013 года), поэтому временной компонент временных меток для записей: 12:45, 1:00, 1:15, 1:30, 1:45, 1:00, 1:15, 1:30, 1:45, 2:00
. Я хочу иметь возможность конвертировать и сохранять значения в базе данных в формате UTC.
К сожалению, стандартная функция DateTime.Parse()
.NET будет анализироваться как это (все 3 ноября 2013 года):
| Time String | Parsed Local Time | In DST | Parsed Local Time to UTC
| 12:45 am | 12:45 am | Yes | 4:45 am
| 12:59:59 am | 12:59:59 am | Yes | 4:59:59 am
| 01:00 am | 1:00 am | No | 6:00 am
| 01:15 am | 1:15 am | No | 6:15 am
Таким образом, он никогда не видит диапазон 1:00-1:59:59 am
как находящийся в DST, и мои разобранные временные метки в UTC перескакивают через час.
Есть ли библиотека или класс, который позволит мне анализировать временные метки и учитывать изменения в DST? Как какой-то экземплярный класс, который будет помнить поток временных меток, который он уже получил, и соответствующим образом отредактируйте раздельную метку времени?
Предположения о данных, которые могут быть сделаны при разборе:
- У меня есть время начала файла (отметка времени первой записи) в разделе заголовка файла как в локальном, так и в UTC.
- Записи упорядочены по метке
- Все локальные времена в Восточном стандарте
- Данные могут также пойти другим путем: из-за DST в него
- Записи содержат полную временную метку в формате:
yyyy/mm/dd HH:mm:ss
(2013/11/03 00:45:00
)
Примечание. Хотя мое программное обеспечение находится на С#, я не использовал тег С#/. NET, поскольку я решил, что могу использовать любую языковую реализацию решения и, если необходимо, перекодировать.
Ответы
Ответ 1
В С#:
// Define the input values.
string[] input =
{
"2013-11-03 00:45:00",
"2013-11-03 01:00:00",
"2013-11-03 01:15:00",
"2013-11-03 01:30:00",
"2013-11-03 01:45:00",
"2013-11-03 01:00:00",
"2013-11-03 01:15:00",
"2013-11-03 01:30:00",
"2013-11-03 01:45:00",
"2013-11-03 02:00:00",
};
// Get the time zone the input is meant to be interpreted in.
TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
// Create an array for the output values
DateTimeOffset[] output = new DateTimeOffset[input.Length];
// Start with the assumption that DST is active, as ambiguities occur when moving
// out of daylight time into standard time.
bool dst = true;
// Iterate through the input.
for (int i = 0; i < input.Length; i++)
{
// Parse the input string as a DateTime with Unspecified kind
DateTime dt = DateTime.ParseExact(input[i], "yyyy-MM-dd HH:mm:ss",
CultureInfo.InvariantCulture);
// Determine the offset.
TimeSpan offset;
if (tz.IsAmbiguousTime(dt))
{
// Get the possible offsets, and use the DST flag and the previous entry
// to determine if we are past the transition point. This only works
// because we have outside knowledge that the items are in sequence.
TimeSpan[] offsets = tz.GetAmbiguousTimeOffsets(dt);
offset = dst && (i == 0 || dt >= output[i - 1].DateTime)
? offsets[1] : offsets[0];
}
else
{
// The value is unambiguous, so just get the single offset it can be.
offset = tz.GetUtcOffset(dt);
}
// Use the determined values to construct a DateTimeOffset
DateTimeOffset dto = new DateTimeOffset(dt, offset);
// We can unambiguously check a DateTimeOffset for daylight saving time,
// which sets up the DST flag for the next iteration.
dst = tz.IsDaylightSavingTime(dto);
// Save the DateTimeOffset to the output array.
output[i] = dto;
}
// Show the output for debugging
foreach (var dto in output)
{
Console.WriteLine("{0:yyyy-MM-dd HH:mm:ss zzzz} => {1:yyyy-MM-dd HH:mm:ss} UTC",
dto, dto.UtcDateTime);
}
Вывод:
2013-11-03 00:45:00 -04:00 => 2013-11-03 04:45:00 UTC
2013-11-03 01:00:00 -04:00 => 2013-11-03 05:00:00 UTC
2013-11-03 01:15:00 -04:00 => 2013-11-03 05:15:00 UTC
2013-11-03 01:30:00 -04:00 => 2013-11-03 05:30:00 UTC
2013-11-03 01:45:00 -04:00 => 2013-11-03 05:45:00 UTC
2013-11-03 01:00:00 -05:00 => 2013-11-03 06:00:00 UTC
2013-11-03 01:15:00 -05:00 => 2013-11-03 06:15:00 UTC
2013-11-03 01:30:00 -05:00 => 2013-11-03 06:30:00 UTC
2013-11-03 01:45:00 -05:00 => 2013-11-03 06:45:00 UTC
2013-11-03 02:00:00 -05:00 => 2013-11-03 07:00:00 UTC
Обратите внимание, что это предполагает, что в первый раз вы столкнетесь с неопределенным временем, например, 1:00, которое будет в DST. Скажите, что ваш список был усечен до последних 5 записей - вы не знали бы, что это было в стандартное время. В этом конкретном случае вы не можете многое сделать.
Ответ 2
Если последующие временные метки не могут вернуться назад, если они выражены как время в UTC, тогда этот Python script может преобразовать локальное время в UTC:
#!/usr/bin/env python3
import sys
from datetime import datetime, timedelta
import pytz # $ pip install pytz
tz = pytz.timezone('America/New_York' if len(sys.argv) < 2 else sys.argv[1])
previous = None #XXX set it from UTC time: `first_entry_utc.astimezone(tz)`
for line in sys.stdin: # read from stdin
naive = datetime.strptime(line.strip(), "%Y/%m/%d %H:%M:%S") # no timezone
try:
local = tz.localize(naive, is_dst=None) # attach timezone info
except pytz.AmbiguousTimeError:
# assume ambiguous time always corresponds to True -> False transition
local = tz.localize(naive, is_dst=True)
if previous >= local: # timestamps must be increasing
local = tz.localize(naive, is_dst=False)
assert previous < local
#NOTE: allow NonExistentTimeError to propagate (there shouldn't be
# invalid local times in the input)
previous = local
utc = local.astimezone(pytz.utc)
timestamp = utc.timestamp()
time_format = "%Y-%m-%d %H:%M:%S %Z%z"
print("{local:{time_format}}; {utc:{time_format}}; {timestamp:.0f}"
.format_map(vars()))
Ввод
2013/11/03 00:45:00
2013/11/03 01:00:00
2013/11/03 01:15:00
2013/11/03 01:30:00
2013/11/03 01:45:00
2013/11/03 01:00:00
2013/11/03 01:15:00
2013/11/03 01:30:00
2013/11/03 01:45:00
2013/11/03 02:00:00
Выход
2013-11-03 00:45:00 EDT-0400; 2013-11-03 04:45:00 UTC+0000; 1383453900
2013-11-03 01:00:00 EDT-0400; 2013-11-03 05:00:00 UTC+0000; 1383454800
2013-11-03 01:15:00 EDT-0400; 2013-11-03 05:15:00 UTC+0000; 1383455700
2013-11-03 01:30:00 EDT-0400; 2013-11-03 05:30:00 UTC+0000; 1383456600
2013-11-03 01:45:00 EDT-0400; 2013-11-03 05:45:00 UTC+0000; 1383457500
2013-11-03 01:00:00 EST-0500; 2013-11-03 06:00:00 UTC+0000; 1383458400
2013-11-03 01:15:00 EST-0500; 2013-11-03 06:15:00 UTC+0000; 1383459300
2013-11-03 01:30:00 EST-0500; 2013-11-03 06:30:00 UTC+0000; 1383460200
2013-11-03 01:45:00 EST-0500; 2013-11-03 06:45:00 UTC+0000; 1383461100
2013-11-03 02:00:00 EST-0500; 2013-11-03 07:00:00 UTC+0000; 1383462000