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

semafor

Client
Регистрация
27.12.2016
Сообщения
152
Благодарностей
78
Баллы
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 194
Благодарностей
203
Баллы
63
Как я раньше мучался с юникстаймом, а тут такая годнота! Спасибо! Очень подробно и доходчиво, сниппеты супер
 
  • Спасибо
Реакции: artomka и semafor

radv

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

semafor

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

radv

Client
Регистрация
11.05.2015
Сообщения
870
Благодарностей
427
Баллы
63

sergio197675

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

Alexmd

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

AZANIR

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

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

Alexmd

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

AZANIR

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

semafor

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

AZANIR

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

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

semafor

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

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

Fedor5588

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

ТРОН

Client
Регистрация
31.07.2016
Сообщения
337
Благодарностей
355
Баллы
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
Сообщения
152
Благодарностей
78
Баллы
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)