Работа с датой и временем в Zennoposter с использованием методов C#

semafor

Client
Регистрация
27.12.2016
Сообщения
161
Благодарностей
84
Баллы
28
56011



ВСЕМ ДОБРА!

У любого труженика Zennoposter, а особенно у тех, кто работает с соцсетями, рано или поздно возникали задачи связанные с получением, сравнением или преобразованием даты и времени. В этой статье я рассмотрю работу с датой и временем с использованием методов языка C#. (Ни фига себе лонгрид получился o_O)

В коробочном функционале Zenno имеется некоторый набор инструментов, однако он достаточно ограничен и позволяет получить лишь текущие значения даты и времени. Для работы с датой и временем я не пользуюсь стандартным функционалом ZP и не планировал о нем говорить, но в целях самообразования решил заглянуть в доступные методы ZP. Но начнем мы с пары теоретических замечаний:

Первое.
UTC (Coordinated Universal Time) — всемирное координированное время это универсальное время, имеющее нулевой часовой пояс. Конечно, определение в Википедии отличается от сказанного мной, но для нас важно только то, что UTC выступает как точка отсчета для часовых поясов, а так же то, что все сервера в Интернете, независимо от местоположения, оперируют именно временем UTC.

Второе — это некоторые способы записи даты-времени, которые используются в разных культурах. Наиболее часто используемый формат — это сокращенное числовое отображение (для России 22.12.2020), где на первом месте идет число месяца, затем, после разделителя порядковый номер месяца, и далее (опять же после разделителя) год. Та же дата, записанная в американской традиции — 12/22/2020 — мы видим, что на первом месте идет месяц, а не число. Та же дата в международном формате выглядит так — 2020-12-23. Способы записи даты-времени, а так же разделители отличаются у разных народов, и это вносит изрядную сложность в понимание и обработку дат.

Предлагаю подробнее остановиться на описании некоторых строковых (т.е. типа string) форматов даты и времени, используемых в C# — это позволит лучше ориентироваться в форматах отображения даты и времени. С полным их описанием можно ознакомиться на странице Microsoft, которую рекомендую добавить в закладки. Итак, поехали:

"d" - день месяца, в диапазоне от 1 до 31. Числа месяца 1-9 при указании этого формата отображаются без нулей (пример формата d.M.yyyy и реальные даты - 1.1.2020, 12.10.2020 - ниже буду просто писать формат и соответствующее ему отображение);
"dd" — день месяца, в диапазоне от 01 до 31 (dd.MM.yyyy - 01.01.2020, 12.10.2020);
"M" — месяц, в диапазоне от 1 до 12 (d.M.yyyy - 1.1.2020, 12.10.2020; dd.M.yyyy - 01.1.2020);
"MM " — месяц, в диапазоне от 01 до 12 (d.MM.yyyy - 1.01.2020; dd.MM.yyyy - 01.01.2020);
"yyyy" — год в виде четырехзначного числа (dd.MM.yyyy - 01.01.0001, 01.01.2020);
"h" — часы в 12-часовом формате от 1 до 12 (h — 1, 10; MM/dd/yyyy h:mm tt - 01/01/2020 1:12 PM (1 января 2020 13:12), 01/24/2020 12:54 AM (24 января 2020 00:54));
"hh" — час в 12-часовом формате от 01 до 12 (hh — 01, 10; MM/dd/yyyy hh:mm tt - 01/01/2020 01:12 PM (1 января 2020 13:12), 01/24/2020 12:54 AM (24 января 2020 00:54));
"H" час в 24-часовом формате от 0 до 23 (H - 1, 23; dd.MM.yyyy H:m - 01.01.2020 1:1, 01.01.2020 14:50);
"HH" Час в 24-часовом формате от 00 до 23 (HH - 1, 23; dd.MM.yyyy HH:mm - 01.01.2020 01:01, 01.01.2020 14:50);
"m" — минуты, в диапазоне от 0 до 59 (m - 1, 12; MM/dd/yyyy h:m tt - 01/01/2020 1:1 PM (1 января 2020 13:01), 01/24/2020 12:54 AM (24 января 2020 00:54));
"mm" — минуты, в диапазоне от 00 до 59 (mm - 01, 12; MM/dd/yyyy hh:mm tt - 01/01/2020 01:01 PM (1 января 2020 13:01), 01/24/2020 12:54 AM (24 января 2020 00:54));
"s" секунды в диапазоне от 0 до 59; (dd.MM.yyyy HH:mm:s - 01.01.2020 13:52:1);
"ss" секунды в диапазоне от 00 до 59; (dd.MM.yyyy HH:mm:ss - 01.01.2020 13:52:01);
"tt" — указатель AM/PM (до полудня/после полудня) (MM/dd/yyyy hh:mm tt - 01/01/2020 01:01 PM (1 января 2020 13:01), 01/24/2020 12:54 AM (24 января 2020 00:54));
"zz" — часовой сдвиг от времени в формате UTC (универсального времени) с нулями в начале для значений из одной цифры (dd.MM.yyyy HH:mm zz - 01.01.2020 13:52 -07)

Разделители даты и времени так же можно указать явно — в России для разделения дат используется ".", в Англии-Америке "/" в универсальном представлении "-". То же и с временем — ru-RU ":", it-IT "." и т.д.

А теперь, вернемся к методам Zennoposter.

Для получения значения текущего времени в ZP используется макрос {-TimeNow.Date-} — в результате его выполнения у меня возвращается значение 05/18/2020 14:13:38. Я не знаю почему вывод даты происходит не в локальном формате Cultureinfo (установленном в русский стандарт), а в американской традиции — может потому, что основной язык моей системы английский?

Untitled-2.jpg

Однако, можно получить дату и в нужном формате, явно задав строку вывода —
{-TimeNow.Date dd.MM.yyyy HH:mm:ss-} (результат — 05.05.2020 15:46:24),
или {-TimeNow.Date HH:mm:ss-}(15:46:24),
или так {-TimeNow.Date MM/dd/yyyy HH-mm-ss-} ( 05/05/2020 15-47-42),
или даже так {-TimeNow.Date dddd dd MMMM yyyy-} (Tuesday 05 May 2020).

Подводя итог — можно задать любой разумный (а может и не очень) формат вывода даты в строку. Дата выводимая макросом представляется в локальном времени ПК, на котором выполняется.

Еще один доступный макрос ZP — {-TimeNow.TimeNow-} у меня он возвращает значение вида 2020-05-05 16-11-48--720. Дата указана в международном формате, соответствующем ISO 8601, а вот разделители во времени я привык видеть в виде ":", хотя это конечно не принципиально. Так же, меня смущает присутствие миллисекунд (мне не попадались задачи в которых они нужны). В этом макросе вывод в свой формат строки недоступен. Придумать применение для него в своих шаблонах я не смог.

Следующий стандартный макрос, о котором стоит сказать — {-TimeNow.UnixTime-}. Возвращает значение вида 1588674065.50537, где до точки указано количество целых секунд прошедших с наступления эпохи Unix(подробнее о ней ниже). UnixTime выводится по времени UTC. Для чего нужны милли или микросекунды (а может пико или нано, не вникал), следующие после запятой, мне не ясно — имхо это лишний кубик по их удалению, но может имеется и возможность задать формат вывода.

Остальные макросы Zenno позволяют получать месяц, день недели, день месяца, часы, минуты, секунды и миллисекунды текущей даты. Так как те же данные можно вывести подставляя свой формат в макрос {-TimeNow.Date-}, говорить о них отдельно не вижу смысла.

Работа с датой и временем с использованием методов C#

Небольшое количество встроенных методов ZP могло бы оказаться большой проблемой, если бы не великий и могучий C#, доступный из кубика C#-кода. Академиев по программированию мы не заканчивали :D , с C# не на ты, поэтому прочитанная по теме инфа забывается, а на перечитывание микрософтовских доков и поиск готовых решений на форуме (здесь на самом деле немало инфы на тему) уходит приличное количество времени. Поэтому при очередной задаче, для экономии времени я решил создать для себя небольшой конспект, содержащий основные сведения по теме, который и лег в основу данной статьи. Естественно, при написании статьи использовались и материалы с форума — большое спасибо всем, кто помогает, отвечая на вопросы!

Примечание:
Локальный часовой пояс моей машины равен +07 UTC (или +04 по Москве) — это так, чтобы в дальнейшем было понятно откуда берутся те или иные цифры в примерах. Весь приведенный код проверялся на работоспособность в PM 7.1. На момент публикации приведенные пути xpath для поиска элементов могут оказаться нерабочими в связи с изменением верстки на сайтах.

Для операций с датой и временем в C# имеются три (на самом деле конечно больше, но это слишком тонкие токости) основных объекта — DateTime (представляет конкретную дату и время), TimeSpan (представляет временной интервал), b DateTimeOffset (указывает конкретную дату и время, а так же смещение относительно времени в формате UTC (часовой пояс)).

Первое, что однозначно понадобится нам в работе, это получение текущей даты и времени. Для этого в структуре DateTime имеются специальные свойства. Если это нужно для дальнейшей работы с форматом DateTime — сравнения дат, преобразований и т.п., то получать время стоит в виде объекта DateTime:

C#:
//свойство возвращает дату и время по часовому поясу, установленному на локальном компьютере в формате, выбранном в регинальных и языковых настройках т.е. для ru-RU  — dd.MM.yyyy HH:mm:ss (01.01.2020 01:56:00)
DateTime dt = DateTime.Now;
//А таким способом мы получим универсальное время UTC в региональном формате локали
DateTime dt = DateTime.UtcNow;
//это свойство вернет только текущие число, месяц и год, с нулями вместо времени — 29.02.2020 00:00:00
DateTime dt = DateTime.Today;
Полагаю что именно UTC время будет наиболее востребовано в ваших взаимодействиях с интернет ресурсами — все крупные площадки в своих внутренних действиях оперируют исключительно универсальным временем (тот же VK, что в своем API, что в коде на страницах (например личных сообщений) использует timestamp (об этом чуть позже) с UTC временем, хотя для пользователя отображает время с учетом его часового пояса). Те же свойства, только выведенные в строку:

C#:
//возвращает дату и время по часовому поясу, установленному на локальном компьютере и в формате, выбранном в регинальных и языковых настройках, например для ru-RU 01.01.2020 01:56
string dt = DateTime.Now.ToString();
//А таким способом мы получим время UTC
string dt = DateTime.UtcNow.ToString();
Еще одна нужная штука — получить какую-то произвольную дату. Здесь мы используем конструктор, инициализируя новый экземпляр даты с помощью ключевого слова new, и в скобках указываем нужную нам дату в виде чисел типа int:

C#:
DateTime dt = new DateTime(2010,02,12);// 12.02.2010 00:00:00
DateTime dt = new DateTime(2010,02,12,22,15,41); //12.02.2010 22:15:41
Для получения даты из строки, в структуре DateTime имеются специальные методы Parse и ParseExact. Получаемый объект DateTime принимает вид в соответствии с региональными стандартами (для RU dd.MM.yyyy HH:mm:ss). Прелесть метода Parse состоит в том, что любой допустимый формат, с учетом региональных настроек даты будет распознан:

C#:
//Любая из этих строк преобразуется в объект DateTime 21.05.2020 00:22:10
string input0 = "21.05.2020 00:22:10";
string input1 = "21-05-2020 00:22:10";
string input2 = "21/05/2020 00:22:10";//а вот если строка будет вида "05/21/2020 00:22:10", т.е. сначала месяц, а после число (как приято в en-US) — получим ошибку распознавания
string input3 = "21/05.2020 00:22:10";
string input4 = "21 мая 2020 00:22:10";

DateTime dt = DateTime.Parse(input[0-4]); //все строки от input0 до input4 преобразуются в одинаковый формат 21.05.2020 00:22:10

//Если не указан год и время — год принимает значение текущего года, время устанавливается по нулям
string input5 = "21/05"; //так же можно указать 21.05 или 21-05 т.е. 21/05 = 21.05 = 21-05

DateTime dt = DateTime.Parse(input5); //результат 21.05.2020 00:00:00
Есть еще один метод парсинга объкта DateTime из строкиParseExact. Этот метод в качестве аргументов, помимо собственно строки с датой, принимает еще строку в которой указан формат представления даты в обрабатываемой строке, а так же строку с указанием культуры, в которой принято такое представление. В случае несоответствия строки с датой формату или культуре, метод возвращает ошибку. Пара примеров:

C#:
string input6 = "06/23/2020"; //23.06.2020 по en-US
System.Globalization.CultureInfo culture = System.Globalization.CultureInfo.CreateSpecificCulture("en-US");//задаем языковые (региональные) стандарты
DateTime dt = DateTime.ParseExact(input6, "d", culture);//Выполнение кода C#  Результат: 23.06.2020 0:00:00

string input6 = "06/05/2020 12:25 AM";
System.Globalization.CultureInfo culture = System.Globalization.CultureInfo.CreateSpecificCulture("en-US");
DateTime dt = DateTime.ParseExact(input6, "g", culture);//Выполнение кода C#  Результат: 23.06.2020 0:25:00
Второй аргумент, формат строки, может принимать как спецификаторы стандартных форматов даты и времени (как в примерах - "d", "g", подробнее о них в доках Микрософт — тоже советую добавить в закладки)так и кастомные пользовательские форматы, которым должна соответствовать дата в обрабатываемой строке, для последнего примера был бы корректен формат "MM/dd/yyyy hh:mm tt". Пример такого пользовательского формата с какого нибудь ресурса учеников Йоды:

C#:
string input7 = "2020 Вторник июня 23 14:35";
System.Globalization.CultureInfo culture = System.Globalization.CultureInfo.CreateSpecificCulture("ru-RU");
DateTime dt = DateTime.ParseExact(input7, "yyyy dddd MMMM dd HH:mm", culture);//Выполнение кода C#  Результат: 23.06.2020 14:35:00
Стоит отметить, что метод Parse, прекрасно справился с распознанием этой даты без всяких дополнительных аргументов. Мое мнение — метод ParseExact стоит использовать лишь при парсинге дат, вид которых отличается от используемых в системе языковых и региональных стандартов (например вот на такого вида дате "2/16/2008 12:15:12 PM" Parse выдал ошибку, а ParseExact, при указании правильных региональных настроек ("en-US") и кастомного шаблона "M/dd/yyyy hh:mm:ss tt" успешно конвертировал строку).

Но есть в Psrse одна особенность, о которой стоит помнить — если в строке явно указан часовой пояс (например 21.05.2020 00:22:10 +03), и мы попытаемся спарисить его на локальной системе:

C#:
string input ="21.05.2020 00:22:10 +03";
DateTime dt = DateTime.Parse(input);
Нам возвращается локальное время компьютера для указанных даты — в результате выполнения данного кода, на моем ПК (со смещением +07 UTC) я получил 21.05.2020 4:22:10. (т.е. "21.05.2020 00:22:10 +03" - "21.05.2020 00:22:10 +07" = "21.05.2020 00:22:10 +04" )

56083


На скрине выше еще несколько примеров парсинга с разными значениями timezone. Если же нужно получить только время, без учета часового пояса, стоит воспользоваться DateTimeOffset, в которой смещение сохраняется отдельно, и из которой можно получить объект DateTime без него:

C#:
string input ="21.05.2020 00:22:10 +03:00";
DateTimeOffset dto = DateTimeOffset.Parse(input);//Выполнение кода C#  Результат: 21.05.2020 0:22:10 +03:00
//А теперь без труда получаем DateTime
DateTime dt = dto.DateTime;//Выполнение кода C#  Результат: 21.05.2020 0:22:10
Так же, используя DateTimeOffset, несложно получить дату-время в формате UTC:

C#:
string input ="21.05.2020 00:22:10 +03:00";
DateTimeOffset dto = DateTimeOffset.Parse(input);//Выполнение кода C#  Результат: 21.05.2020 0:22:10 +03:00
//А теперь без труда получаем DateTime UTC
DateTime dt = dto.DateTime;//Выполнение кода C# 20.05.2020 21:22:10
Наш талмуд был бы сухим и неинтересным повторением кучи мануалов из сети без примеров использованиея DateTime в шаблонах ZP:

1.
Для регистраций я (да и вы наверное тоже), пользуюсь автоматически сгенерированными профилями ZP, используя их данные. Эти профили, в своей структуре содержат такие св-ва как BornDay(день рождения), BornMonth(месяц рождения), BornYear (год рождения), представленные в числовом формате (int). На странице регистрации мыла всеми любимого ресурса Майлру, есть поле «месяц рождения», где месяцы указаны не в числовом значении, а по названиям (на данный момент майл изменил верстку, так что этот пример из прошлого, но для посмотреть сойдет).

month_select_mailru.jpg

Для клика по нужному месяцу, элемент можно искать по xpath по числовому значению месяца:

C#:
//Ищем нужный месяц, подставляя свойство профиля BornMonth
var he = instance.ActiveTab.FindElementByXPath(@"//div[@class='b-date__month']//div[contains(@class,'b-dropdown__list')]/a[@data-value="+project.Profile.BornMonth.ToString()+"]", 0);
А можно и по буквенному (хотя на строку длиннее получится, и привязываться к локальному названию месяца не совсем комильфо, но имеет право на жизнь):

C#:
//получаем название месяца с учетом языковых настроек локальной системы
string month = new DateTime(project.Profile.BornYear,project.Profile.BornMonth,project.Profile.BornDay).ToString("MMMM");
//Ищем нужный месяц
var he = instance.ActiveTab.FindElementByXPath(@"//div[@class='b-date__month']//div[contains(@class,'b-dropdown__list')]/a[@data-text="+month+"]", 0);
А вот чтобы получить тот же вариант, но с использованием английского наименования месяца, придется пошаманить, но зато такую конструкцию можно привязать к языковым параметрам профиля (например, подставляя вместо "en-EN" значение из поля UserAgentBrowserLanguage), и получать наименование месяца на нужном языке:

C#:
//здесь через класс System.Globalization.CultureInfo с помощью метода CreateSpecificCulture мы указываем в каких языковых стандартах нам надо вернуть наименование месяца ("en-EN"), а затем методом GetMonthName, принадлежащего
//классу DateTimeFormat, получаем наименование месяца. Сложно? Я б$#ть, сам в шоке, но не удивлюсь если есть методы попроще, которых я не нашел
string month = System.Globalization.CultureInfo.CreateSpecificCulture("en-EN").DateTimeFormat.GetMonthName(new DateTime(project.Profile.BornYear,project.Profile.BornMonth,project.Profile.BornDay).Month);
//Ищем нужный месяц
var he = instance.ActiveTab.FindElementByXPath(@"//div[@class='b-date__month']//div[contains(@class,'b-dropdown__list')]/a[@data-text="+month+"]", 0);
2. Старый добрый ВК. Вот так выглядят даты рождений, спаршенные у юзеров 15.4.1985, 21.5, 7.9.1999, 12.5, 25.12 и т.д. Мы видим, что многие не указывают года рождения (это женский контингент, может быть у мужиков по другому). А нам надо сравнить полученные даты с сегодняшней, и отправить поздравления с нашим крео тем, у кого сегодня днюха.

vk_bday.jpg

Теоретически, это можно сделать с помощью регулярок, но зачем, если есть DateTime? Допустим, что дни родждения хранятся в списке (скорее это будет таблица, но не будем усложнять), сегодняшних именинников сохраняем тоже в список. Поехали:

C#:
//массив дат для примера
string[] input = {"25.12",
                 "01.01.1985",
                 "19.07",
                  DateTime.Now.ToString("dd.MM"),
                 "05.07.1900"};
//создаем временный список и добавляем в него наш массив
List<string> bday = new List<string>(input);
//список куда будем сохранять сегодняшних именинников
List<string> bdayToday = new List<string>();
//текущая дата в строку, для сравнения с элементами списка
string dtnow = DateTime.Now.ToString("dd.MM");


foreach(string item in bday)
{
    //парсим строку из списка
    DateTime dtbday = DateTime.Parse(item);//результат будет вида 21.05.1981 00:00:00
    //опять преобразуем объект DateTime в строку, чтобы избавиться лишних элементов — года, часов, минут, секунд
    //т.е. оставляем только день и месяц
    string bd = dtbday.ToString("dd.MM");//получим строку вида 21.05
    //сравниваем элемент списка и строку с текущей датой
    if(dtnow == bd)
    {
        //если строки совпадают, т.е. день и месяц рождения соотв. сегодняшнему дню-месяцу,
        //сохраняем совпавшую строку в список сегодняшних именинников
        bdayToday.Add(bd);
        return bd;
    }
    else
    {
        project.SendInfoToLog(bd+" не совпадает с сегодняшней датой "+dtnow);
    }
}
Здесь главная проблема это входная строка, в которой может быть указан год, а может быть и не указан. Если бы нам было известно, что года точно не будет, можно было бы сравнивать объекты DateTime, но в нашем случае мы вынуждены провести обратное преобразование в строку. В результате этих манипуляций мы приводим сравниваемые строки к одному формату, и можем быть уверены, что если во входящем списке окажется сегдняшний день и месяц (с годом или без), то этот элемент списка будет сохранен в список сегодняшних именинников.

Продолжаем знакомиться с методами C#

Следующий необходимый пласт знаний — операции с объектами DateTime. Даты можно сравнивать, складывать, вычитать, добавлять к указанной дате годы, месяцы, дни,часы и т.п. И всему этому можно найти практическое применение. При сравнении стоит обратить внимание вот на что — часовой пояс сравниваемых дат должен быть одинаков, иначе можно получить неожиданный результат и баги в дальнейших действиях. Наиболее логично выяснить часовой пояс, возвращаемый сервером до построения логики обработки полученного времени и ориентироваться на него.

В структуре DateTime имеется свойство DateTimeKind, позволяющее указать какой часовой пояс использовался для даты — UTC, Local, или Unspecifed. Небольшая демонстрация:

C#:
//создаем объект DateTime 01.01.1970 00:00:00 указывая, что это локальное время
    DateTime dt = new DateTime(1970,1,1,0,0,0,DateTimeKind.Local); // 01.01.1970 00:00:00 по локальному времени
    //Преобразуем его в универсальное время
    DateTime dtu = dt.ToUniversalTime();
В результате выполнения кода мы получили 2 различных времени: dt = 01.01.1970 0:00:00 (локальное время), dtu = 31.12.1969 17:00:00 (UTC). Если же в DateTimeKind указать UTC, в переменных окажутся две одинаковых даты-времени:

C#:
//создаем объект DateTime 01.01.1970 00:00:00 указывая, что это время UTC
    DateTime dt = new DateTime(1970,1,1,0,0,0,DateTimeKind.Utc);
    //Преобразуем его в универсальное время
    DateTime dtu = dt.ToUniversalTime();//Выполнение кода C#  Результат: dt = 01.01.1970 0:00:00, dtu = 01.01.1970 0:00:00
Ситуация с DateTimeKind.Unspecified вообще интересна — если вы применяете метод ToUniversalTime() к «unspecified» значению DateTime, метод сделает предположение о том, что вы пытаетесь конвертировать локальное значение. С другой стороны, если вы применяете метод ToLocalTime() к «unspecified» значению DateTime, то будет сделано предположение о том, что изначально у вас было значение в виде UTC:

C#:
//создаем объект DateTime 01.01.1970 00:00:00
    DateTime dt = new DateTime(1970,1,1,0,0,0,DateTimeKind.Unspecified);
    //Преобразуем его в универсальное время
    DateTime dtu = dt.ToUniversalTime(); //Выполнение кода C#  Результат: dt = 01.01.1970 0:00:00, dtu = 31.12.1969 17:00:00

    //создаем объект DateTime 01.01.1970 00:00:00
    DateTime dt = new DateTime(1970,1,1,0,0,0,DateTimeKind.Unspecified);
    //Преобразуем его в локальное время
    DateTime dtu = dt.ToLocalTime();//Выполнение кода C#  Результат: dt = 01.01.1970 0:00:00, dtu = 01.01.1970 7:00:00
Сравнение дат. Для сравнения двух дат, в языке C# имеются методы Compare и CompareTo. Результаты выполнения одинаковы — оба метода возвращают число (int) — меньше нуля, если время, указанное в первой сравниваемой переменной меньше времени, указанного во второй переменной, ноль, если моменты времени указанные в переменных равны, и больше нуля, если момент времени в переменной 1 больше момента времени в переменной 2. Отличаются методы только способом записи:

C#:
DateTime dt1 = new DateTime(2010,10,25,20,40,00);//25.10.2010 20:40:00
DateTime dt2 = new DateTime(2020,02,25,10,15,40);//25.02.2020 10:15:40
DateTime dt3 = new DateTime(2010,10,25);//25.10.2010 00:00:00
DateTime dt4 = new DateTime(2010,10,25);//25.10.2010 00:00:00

//оба метода вернут 1
int res = dt1.CompareTo(dt3);
res = DateTime.Compare(dt1, dt3);

//возвратят -1
int res = dt3.CompareTo(dt2);
res = DateTime.Compare(dt3, dt2);

//возвратят 0
int res = dt3.CompareTo(dt4);
res = DateTime.Compare(dt3, dt4);
А вот пример простецкого таймера. Здесь, используя метод AddSeconds() мы добавляем к текущему времени 30сек, сохранив результат в переменную dt1, а затем в цикле, используя метод CompareTo, проверяем наступление временного момента dt1, сравнивая его с текущим временным моментом:

C#:
//получаем текущую дату и добавляем к ней 30 сек
DateTime dt1 = DateTime.Now.AddSeconds(30);
//В цикле ожидаем момента, когда наступит время dt1
//количество итераций и паузу выполнения потока можно подобрать под свои нужды
for(int i=0; i<10; i++)
{
    //Указана пауза 5сек, естсественно можно заменить на свою
    Thread.Sleep(5000);
    //Проверку наступления момента осуществляем с помощью метода CompareTo
    if(DateTime.Now.CompareTo(dt1)==-1)
    {
        project.SendInfoToLog("Время еще не пришло",false);
    }
    else
    {
        project.SendInfoToLog("Время dt1 наступило");
       //прерываем цикл
        break;
    }
}
return dt1;
Вместо AddSeconds() можно использовать и AddMinutes() (в принципе и AddHours, AddDays — но для этих нужд в ZP имеется планировщик), не забывая изменить количество итераций цикла и паузу выполнения потока. С помощью такого таймера можно, например, кликать на кнопку каждые 5сек, пока не наступит время dt1 (т.е. в течение 30 сек).

Еще один метод сравнения двух дат DateTime.Equals — возвращает значение bool. Обратите внимание на пример, где dt1 — текущее локальное время, а dt2 — текущее время UTC. Сравнение этих, вроде бы одинаковых дат возвращает false — еще одно напоминание о необходимости приведения дат к одной таймзоне:

C#:
//текущее время в формате UTC
DateTime dt1 = DateTime.UtcNow;
//локальное текущее время
DateTime dt2 = DateTime.Now;

bool eq = DateTime.Equals(dt1, dt2); //Выполнение кода C#  Результат: False
Получение разницы между двумя датами. Возвращается в виде временного интервала, принадлежащего классу TimeSpan. Если дата из которой вычитают меньше даты которую вычитают, метод вернет отрицательный интервал времени. Вычитать можно как обычные числа:

C#:
DateTime dt1 = new DateTime(2010,10,25,20,40,00);//25.10.2010 20:40:00
DateTime dt2 = new DateTime(2020,02,25,10,15,40);//25.02.2020 10:15:40
TimeSpan diff = dt2-dt1;//Выполнение кода C#  Результат: 3409.13:35:40
А можно использовать метод Subtract, где впереди стоит уменьшаемая дата, а качестве аргумента — вычитаемая

C#:
DateTime dt1 = new DateTime(2010,10,25,20,40,00);//25.10.2010 20:40:00
DateTime dt2 = new DateTime(2020,02,25,10,15,40);//25.02.2020 10:15:40
TimeSpan diff = dt2.Subtract(dt1); //Выполнение кода C#  Результат: 3409.13:35:40
Результат разности diff в данном случае равен 3409 суткам 13 часам 35 минутам и 40 секундам. Так же мы можем получить отдельные части этого интервала, в таком случае, вместо типа TimeSpan, мы получим число int:

C#:
DateTime dt1 = new DateTime(2010,10,25,20,40,00);//25.10.2010 20:40:00
DateTime dt2 = new DateTime(2020,02,25,10,15,40);//25.02.2020 10:15:40

int diff = dt2.Subtract(dt1).Days;// вернет целое количество дней - 3409
int diff = dt2.Subtract(dt1).Hours;// вернет целое количество часов -13
int diff = dt2.Subtract(dt1).Minutes; //соответственно минуты - 35
int diff = dt2.Subtract(dt1).Seconds; //ну и секунд - 40
Методы DateTime.AddYears, DateTime.AddMonth, DateTime.AddDays, DateTime.AddHours, DateTime.AddMinutes и DateTime.AddSeconds работают одинаково, добавляя к объекту указанное количество лет, месяцев, дней и т.д. Количество добавляемых единиц указывается в виде числа int. Возвращается новый объект DiteTime:

C#:
//создаем объект DateTime 01.01.2000
DateTime dt = new DateTime(2000,1,1);
//прибавим к нему 20 лет
DateTime dtAdd = dt.AddYears(20);//Выполнение кода C#  Результат: 01.01.2020 0:00:00


//создаем объект DateTime 01.01.2000
DateTime dt1 = new DateTime(2000,1,1);
//прибавим к нему 12 мес
DateTime dt1Add = dt1.AddMonths(12);//Выполнение кода C#  Результат: 01.01.2001 0:00:00
Метод DateTime.Add позволяет сделать то же самое, но с указанием не одной величины, а сразу нескольких. В качестве аргумента уазывается временной интервал класса TimeSpan (10,0,0,0,0,0)

C#:
DateTime dtAdd = dt.Add(10,0,0,0,0,0); //здесь указан интервал 10 лет
Из неявного. Как с помощью DateTime.AddSeconds обнулить секунды имеющейся даты:

C#:
DateTime dt = new DateTime(2000,1,1,20,20,59);//новая дата 01.01.2000 20:20:59
//обнуляем секунды
dt = dt.AddSeconds(-dt.Second); //Выполнение кода C#  Результат: 01.01.2000 20:20:00
Тот же фокус доступен и с другими методами Add.

Ну а теперь поговорим об одном из самых необходимых для интернета понятий — timestamp, или, по-другому, Unixtime.

Unixtime — это количество секунд, прошедших до текущего момента с так называемой эпохи Unix (Unix epoch). Момент ее наступления определен как 01.01.1970 00:00:00 UTC. Записывается unixtime в виде числа формата long (Int64). Используется повсеместно. Сразу хочу обратить внимание — все даты unixtime выражены в универсальном времени UTC, и если об этом забыть и попытаться получить timestamp приведенным ниже способом, получатся косяки и непонятки:

C#:
//unix time UTC
long udts = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
//локальное время
DateTime dt = DateTime.Now;
//01.01.1970 00:00:00 по локальному времени
DateTime uepoch = new DateTime(1970,1,1);
//локальное значение unixtime
long dts = Convert.ToInt64((dt-uepoch).TotalSeconds);
В результате выполнения этого кода, мне вернулся реальный unixtime udts = 1582121850; и странное число, похожее на него, но совершенно далекое от реальности dts = 1582147051(я не помню дату когда выполнялись сниппеты, да и часовые пояса у нас разные, так что числа у вас будут отличаться от моих ). Причина — получение dt и uepoch в локальном времени. А вот так, несмотря, на то, что в первом случае мы получаем DateTimeOffset.UtcNow, а во-втором DateTimeOffset.Now, числа будут однаковыми:

C#:
//unix time UTC
long udts = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
//unix time local
long ldts = DateTimeOffset.Now.ToUnixTimeSeconds();
//Выполнение кода C#  Результат: udts = 1582122062; ldts = 1582122062
Как получить unixtime для текущего момента я уже показал в примере выше, но повторюсь отдельно:

C#:
//unix time UTC
long udts = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
А для какой-либо даты? Чем прекрасно программирование, так это тем, что одну и ту же задачу можно решить разными способами. В сниппетах сообщества Zenno, на GitHub (пользуясь случаем, настоятельно рекомендую добавить в закладки https://github.com/ZennoHelpers) предложена такая реализация:

C#:
// время, которое необходимо получить в UnixTime
DateTime time = new DateTime(2020,2,19,20,34,0,DateTimeKind.Utc);

// установка времени
DateTime dateTime = new DateTime(1970, 1, 1,0,0,0,DateTimeKind.Utc);

// время в UnixTime
long dts = (time - dateTime).TotalSeconds;
То есть, мы получаем в DateTime дату эпохи Unix и дату для которой нужно вычислить timestamp, отнимаем от второй первую, получив таким образом временной интервал от UnixEpoch до нужного нам момента, после чего пересчитываем этот интервал в количество секунд. В свою очередь, ковыряясь с данной статьей, я нашел более простой способ получения с использованием структуры DateTimeOffset (для чистоты эксперимента получал timestamp обоими способами для одного момента времени — возвращаемеое число одинаково):

C#:
// время, которое необходимо получить в UnixTime
DateTime time = new DateTime(2020,2,19,20,34,0,DateTimeKind.Utc);
// установка времени
DateTime dateTime = new DateTime(1970, 1, 1,0,0,0,DateTimeKind.Utc);
// время в UnixTime
long dts = Convert.ToInt64((time - dateTime).TotalSeconds);

//РАБОТАЕТ ТОЛЬКО В .NET 4.6 и выше. unix time для даты 19.02.2020 20:34:00 UTC
long dt = new DateTimeOffset(2020,2,19,20,34,0,TimeSpan.Zero).ToUnixTimeSeconds();//Выполнение кода C#  Результат: dt = 1582144440; 1 способ - dts = 1582144440
Метод DateTimeOffset.ToUnixTimeSeconds возвращает количество секунд, прошедших с 01.01.1970 00:00:00, а свойство TimeSpan.Zero указывает, что объект DateTimeOffset имеет нулевой сдвиг, т.е. указана дата по времени UTC. Первый способ удобнее, если у вас уже имеется объект DateTime.

Получение даты из unixtime — не менее востребованная фигня. Например, когда-то в одном из своих первых шаблонов, в группе VK методами API я собирал комменты к фотографиям альбома, и если комментарий был старше некоторой даты — выполнял определенные действия. Дата публикации через API возвращается как раз в виде timestamp, и чтобы сравнить ее с нужной датой можно либо конвертировать ее в дату, либо свою дату перегнать в unixtime (я пользовался именно этим вариантом, и по неопытности как раз использовал для получения дату с локальным временем вместо UTC, и после долго выяснял, почему комент оставленный в нужном диапазоне в него не попадает). Опять же сниппет с github (https://github.com/ZennoHelpers):

C#:
//ИЗ UNIXTIME В DATETIME

// если время UnixTime хранится в переменной проекта
long unixTime = long.Parse(project.Variables["unixTime"].Value);//так мы получаем timestamp из переменной проекта

//а это timestamp, полученный в предыдущем примере, дата 19.02.2020 20:34:00 UTC
long udts = 1582144440;

DateTime dateTime = (new DateTime(1970, 1, 1, 0, 0, 0, 0)).AddSeconds(udts).ToLocalTime();
Причем, здесь переменная dateTime сразу возвращается в локальном времени. Вот то же преобразование, только с использованием DateTimeOffset (для .NET 4.6+):

C#:
// переменная с UnixTime
long udts = 1582144440;

//Получаем дату по UTC
DateTimeOffset dto = DateTimeOffset.FromUnixTimeSeconds(udts);
//Преобразуем в локальное время
DateTime dt2 = dto.DateTime.ToLocalTime();
Результат выполнения обоих сниппетов, с учетом локального времени ПК на котором выполнялись вычисления UTC +07: dt1 = 20.02.2020 3:34:00; dt2 = 20.02.2020 3:34:00 Какой из вариантов выбрать — дело ваше.

И еще пара слов о timestamp — так как тип данных этого формата обычное целое число, то делать с ним можно ровно то же самое что и с числом — суммировать, умножать делить и т.п. Например timestamp для 01.01.1970 00:00:00 будет равен 0. Получить момент времени 02.01.1970 00:00:00 можно простым сложением 0 + 86400= 86400., где 86400 — это количество секунд в сутках. Чтобы из unixtime даты 19.02.2020 20:34:00 UTC получить unixtime 18.02.2020 20:34:00 UTC:

C#:
//отнимаем от timestamp 19.02.2020 20:34:00 UTC сутки
long diff = 1582144440 - 86400;
//Преобразуем в дату по UTC
DateTimeOffset dto = DateTimeOffset.FromUnixTimeSeconds(diff);
DateTime dt = dto.DateTime; //Выполнение кода C#  Результат: 18.02.2020 20:34:00
А вот еще один пример конвертации timestamp в DateTimeOffset, затем в DateTime и обратно в timestamp. Сначала поясню нафига такой баян нужен — жил я себе и не тужил, слал сообщения участникам группы в личку через API VK (проект белый, аудитория лояльная, так что без напрягов). А потом счастье кончилось, в API такую возможность отрубили, и пришлось слать через веб, причем с подтверждением отправки. Вот для этого самого подтверждения вся эта чехарда и понадобилась — на странице диалога с пользователем некоторые html-элементы имеют аттрибут с timestamp отправки сообщения.

vk_im_exemple.jpg

Однако все не так просто — timestamp сделанный на локали (синхронизируемой с ntp-серверами мелкософтов), имеет неопределенную разницу в несколько секунд с timestamp отправленного сообщения. В связи с этой неопределенностью решил обнулять секунды и в локальном и в выпаршенном timestamp, и проверять отправку без них, т.к. вариант отправки сразу нескольких сообщений одному и тому же пользователю ничтожно мал. С локальным unixtime все прошло гладко, а вот с vk-шным получилось не очень:

C#:
long uts = 1582392137; //некоторый timestamp спаршенный из ВК (по времени UTC)
//конвертим в DateTimeOffset
DateTimeOffset dt = DateTimeOffset.FromUnixTimeSeconds(udts);
//удаляем секунды и конвертим в DateTime
DateTime ddt = dt.AddSeconds(-dt.Second).DateTime; //Вот тут закралась неприятность. Предполагаю, что при такой конвертации, свойство DateTimeKind принимает значение Unspecifed, что влечет за собой появление ошибки при возврате к формату юникс
//Мы получаем timestamp с временем, равным DateTime ddt с учетом timezone локальной системы (т.е., если на моем пк время UTC +07, то при конвертации ddt в unixtime, из его значение вычтется 7 часов сдвига)
long udts1 = new DateTimeOffset(ddt).ToUnixTimeSeconds();
return udts1;
Чтобы этого не произошло, при конвертации из DateTimeOffset В DateTime нужно явно указывать свойство DateTimeKind. Полный правильный сниппет:

C#:
//конвертим в DateTimeOffset
DateTimeOffset dt = DateTimeOffset.FromUnixTimeSeconds(udts);
//получаем объект DateTime, с указанием, что это UTC время, и удаляем из него секунды
DateTime ddt = DateTime.SpecifyKind(dt.AddSeconds(-dt.Second).DateTime, DateTimeKind.Utc);
//ну и обратно в  unixtime
long udts1 = new DateTimeOffset(ddt).ToUnixTimeSeconds();

return udts1;
Если статья оказалась полезной для вас, не забудьте проголосовать за нее! Автору будет приятно! 8-)

P.S. В прилагаемом шаблоне, для вашего удобства представлен весь приведенный в статье код. Удачи в работе!
 

Вложения

Последнее редактирование:

Dexio

Client
Регистрация
09.05.2014
Сообщения
1 217
Благодарностей
209
Баллы
63
Как я раньше мучался с юникстаймом, а тут такая годнота! Спасибо! Очень подробно и доходчиво, сниппеты супер
 
  • Спасибо
Реакции: artomka и semafor

radv

Client
Регистрация
11.05.2015
Сообщения
1 098
Благодарностей
548
Баллы
113
Полезная статья. Все в одном месте. Я раньше по разным сайтам собирал информацию по работе с датами с C#
 
  • Спасибо
Реакции: semafor

semafor

Client
Регистрация
27.12.2016
Сообщения
161
Благодарностей
84
Баллы
28
Полезная статья. Все в одном месте. Я раньше по разным сайтам собирал информацию по работе с датами с C#
Вот и я запарился каждый раз репу чесать, когда нюанс возникает )))
 

radv

Client
Регистрация
11.05.2015
Сообщения
1 098
Благодарностей
548
Баллы
113

sergio197675

Client
Регистрация
21.09.2019
Сообщения
151
Благодарностей
129
Баллы
43
Тут как раз , один товарищ искал - как это "время к фейсбук комбайну прикрутить" )
 

Alexmd

Client
Регистрация
10.12.2018
Сообщения
230
Благодарностей
145
Баллы
43
Вот побольше бы таких статей! да и собрать их в один раздел, а то действительно в поисках такой элементарщины иной раз по полчаса проводишь и утомляет очень.
 

AZANIR

Client
Регистрация
09.06.2014
Сообщения
387
Благодарностей
175
Баллы
43
А что так можно было ? , я думал этого добра хватает на форуме.

с час как выложу все свои сниппеты и распишу , то глядишь и ипотеку закрою))))))))
 
Последнее редактирование:

Alexmd

Client
Регистрация
10.12.2018
Сообщения
230
Благодарностей
145
Баллы
43
с час как выложу все свои сниппеты и распишу , то глядишь и ипотеку закрою))))))))
давайте-давайте) соберите все полезняшки в одном конкурсе, а то поиск по форуму через гугл - это, как левой рукой правое ухо чесать
 

AZANIR

Client
Регистрация
09.06.2014
Сообщения
387
Благодарностей
175
Баллы
43

semafor

Client
Регистрация
27.12.2016
Сообщения
161
Благодарностей
84
Баллы
28
  • Спасибо
Реакции: Home_KreKer и AZANIR

AZANIR

Client
Регистрация
09.06.2014
Сообщения
387
Благодарностей
175
Баллы
43
Я хочу добавить , я против стараний автора ничего не имею он молодчина постарался, просто реально на 13 конкурсе хочется видеть реально технические статьи , а не расписанную статью с метанита или еще похожего ресурса . Напрягайте мозг. Это ваш главный инструмент!!!!

Точно. У меня в тексте ссылочка на GitHub присутствует. Только по дате-времени там всего 8 сниппетов...
вопрос в том что столько их и не нужно )
 
  • Спасибо
Реакции: Duser и vitashok

semafor

Client
Регистрация
27.12.2016
Сообщения
161
Благодарностей
84
Баллы
28
вопрос в том что столько их и не нужно )
У каждого свои нужды.
Для кого-то и DateTime.Now избыточен ))

Напрягайте мозг. Это ваш главный инструмент!!!!
С этим не поспоришь, но напряжение без знаний полезно только в туалете ))
 
Последнее редактирование:
  • Спасибо
Реакции: stanar

Fedor5588

Client
Регистрация
04.06.2017
Сообщения
127
Благодарностей
25
Баллы
28
Ооооо автору респект, тоже в свое время настрадался с поиском инфы по работе со временем
 
  • Спасибо
Реакции: semafor

ТРОН

Client
Регистрация
31.07.2016
Сообщения
337
Благодарностей
360
Баллы
63
При работе с запросами несколько раз было, когда Unixtime нужно было не в совсем привычном формате, а именно 13 цифр, вместо 10. Такой код спасал.
C#:
TimeSpan span = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 00, DateTimeKind.Utc));
var unixTime = span.TotalMilliseconds;
return new Regex(@"^.{13}").Match(unixTime.ToString()).ToString();
 
  • Спасибо
Реакции: semafor

semafor

Client
Регистрация
27.12.2016
Сообщения
161
Благодарностей
84
Баллы
28
При работе с запросами несколько раз было, когда Unixtime нужно было не в совсем привычном формате, а именно 13 цифр, вместо 10. Такой код спасал.
Блин, пока писал статью, даже не вспомнилось! А ведь у меня тоже так было, и тоже с запросами, только вроде бы 15-символьный. И нестандартный этот unixtime нужен был как идентификатор сессии кажись.

То же самое, только в профиль можно получить, возвращая не миллисекунды, а такты:
C#:
TimeSpan span = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 00, DateTimeKind.Utc));
//можно то же получить через ticks
var utst = span.Ticks;
return new Regex(@"\d{13}").Match(utst.ToString()).ToString();
А тем кто с кубиками предпочитает работать, в этом случае подошел бы встроенный макрос ZP {-TimeNow.UnixTime-} и регулярка от @ТРОН
 
Последнее редактирование:
  • Спасибо
Реакции: Alexmd

Кто просматривает тему: (Всего: 1, Пользователи: 0, Гости: 1)