вторник, 13 марта 2012 г.

Сравнение библиотек для архивации в .Net

Недавно для моего проекта понадобилась мне библиотека для архивирования. С полгода назад по работе я пользовался библиотекой zlibnet и впечатления остались не очень приятные, так что решил поискать альтернативу. После недолгих поисков наткнулся на обзор библиотек для архивации, которая и сподвигла меня написать этот обзор.

Участники
Я буду тестировать четыре библиотеки: ZLibNet, #ZipLib, DotNetZip и ZipStorer. Теперь о каждой поподробней:

ZLibNet
Лицензия: Свободная
Размер: 35 кБ + 137 кБ(ZLib)
Эта библиотека представляет собой обёртку над широко известной сишной библиотекой ZLib. Так как большую часть составляет unmanaged код, то авторы обещают высокую производительность.
Пример кода для архивирования:
Zipper zip = new Zipper();

zip.ItemList.Add(inPath);

zip.ZipFile = outPath;

zip.PathInZip = enPathInZip.None;

zip.Zip();

#ZipLib
Лицензия: Модифицированный GPL. Можно использовать в коммерческих проектах
Размер: 196 кБ
Библиотека полностью написана на C#. Заявлена поддержка так же GZip, Tar, BZip2 форматов.
Пример кода для архивирования:
using (ZipOutputStream s = new ZipOutputStream(File.Create(outPath)))

{

s.UseZip64 = UseZip64.Off;

if (level != -1)

s.SetLevel(level);



byte[] buffer = new byte[4096];

ZipEntry entry = new ZipEntry(Path.GetFileName(inPath));

s.PutNextEntry(entry);

using (FileStream fs = File.OpenRead(inPath))

{

int sourceBytes;

do

{

sourceBytes = fs.Read(buffer, 0, buffer.Length);

s.Write(buffer, 0, sourceBytes);

}

while (sourceBytes > 0);

}

s.Finish();

s.Close();

}

DotNetZip
Лицензия: Ms-Pl
Размер: 277 кБ(урезанная версия)
Библиотека позиционируется как самая удобная для использования в .net и моно проектах. Заявлена поддержка AES шифрования. Полезным так же может оказаться наличие многих версий: полная, уменьшенная версия(оставлен основной функционал и существенно уменьшен размер), для компакт фреймворка, для SilverLight. Имеется также отдельные версии поддерживающие bzip2 и обёртка на ZLib.
Пример кода для архивирования:
using (ZipFile zip = new ZipFile())

{

zip.CompressionLevel = compressionLevel;

ZipEntry ze = zip.AddFile(inPath, "");

zip.Save(outPath);

}

ZipStorer
Лицензия: Ms-Pl
Размер: 33 кБ(исходник)
Строго говоря является не библиотекой, а отдельным классом, в следствии чего крайне просто интегрируется в проект, да и изменения в случае чего произвести будет несложно. Возможности крайне ограничены, но учитывая размер, на это можно закрыть глаза. Старая версия может работать в Silverlight.
Вот вкратце и всё, что можно сказать об испытуемых.
Пример кода для архивирования:
ZipStorer zip = ZipStorer.Create(outPath, "About");

zip.AddFile(compressionLevel, inPath, inputFileName, "");

// Updates and closes the zip file

zip.Close();


Тестирование

Тестирование будет проводиться следующим образом: делается 100 прогонов каждого метода, измеряется время, убираются 20 худших и 20 лучших времён, по остальным рассчитывается среднее. Платформа: основные тесты проводились на процессоре E8400(3.0GHz) с четырьмя гигабайтами DDRII памяти. Все четыре библиотеки тестируются в одной программе, которая логирует все свои действия и отписывает результаты в csv файл, который потом можно легко открыть в редакторе таблиц. IDE — VS2010. Использовался .net 4.0.

Архивирование

Тесты будут проводиться на 3х файлах: первый — большой текстовый файл, второй — база данных SQLite, и третий — книга «История государства Российского» Карамзина в формате pdf.
Для каждого файла проводятся два теста на первом будут тестироваться все библиотеки с настройками обеспечивающими максимальное быстродействие, на втором будет включено максимальное сжатие, к сожалению функцию уровня сжатия поддерживают только #ZipLib и DotNetZip, поэтому во втором этапе будут участвовать только они.

TXT файл.

Это текстовый файл размером в 9 373 180 байт. Документ по структуре — логи переписки по аське из квипа.
Итак, результаты с максимальной скоростью:



Итак, по скорости #ZipLib и zlibnet одинаковы, однако, вторая библиотека показывает куда лучший результат. Остальные библиотеки показывают серьёзное отставание по скорости. Теперь результаты теста на максимальное сжатие:



Тут результаты более ровные.

DB файл.

База данных, размером 19 407 754 байт. Содержимое базы — большое количество строк.



Ситуация идентична текстовому файлу, теперь результаты теста на максимальное сжатие:



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

PDF файл.

ПДФка с отсканированными страницами, размером 19 407 754 байт. Можно ожидать, что сжатие особого толка не даст, однако, давайте это проверим:



Разрывы по размеру оказались несущественны, однако же скорость отличается очень значительно. На этот раз в лидерах ZipStorer, что объяснимо — у него худшее сжатие. DotNetZip и #ZipLib показывают практически одинаковые результаты. Теперь результаты теста на максимальное сжатие:



Ситуация изменилась несильно, разве что, несколько увеличилась разница по времени.
Стоит отметить что изначально название файла содержало кириллицу и только ZipStorer достойно справилась с ним(для этого нужно было добавить строчку: zip.EncodeUTF8 = true), ZlibNet вообще отказалась с ним работать(выбрасывало исключение в котором говорилось что поддерживаются только ASCII символы в названии), а #ZipLib и DotNetZip упаковали странно: стандартный windows просмотрщик показывал пустой архив. Распаковка дала исходный файл, но в названии все кириллические символы были заменены.

Разархивирование.

Для начала я проверил все архивы на чтение. Все библиотеки могут открывать архивы, созданные другими библиотеками, никаких неожиданностей не возникло.
Непосредственно само разархивирование я буду тестировать на двух архивах: первый — база из первого теста запакованная быстрейшим способом, второй — тот же файл, но с ультра сжатием. Оба архива получены с помощью 7-zip. Рассмотрим результаты:



Парадоксально, но факт: все библиотеки распаковывали архив с максимальным сжатием быстрей, чем с минимальным. Удивляют так же гигансткие разрывы между библиотеками.

Сравнение процессоров.

Теперь запущу тест на архивацию тестового файла ещё на двух компьютерах. Первый — годовалый чертырёхядерный i7-870(2.93GHz) 16Gb, и второй — трёхлетний ноутбук Dell 1525 T2370(1.73GHz) 2Gb.
Архивирование базы данных:



Результаты удивляют. Значительный прирост показала только DotNetZip. ZlibNet и ZipStorer показали двухкратный рост производительности при переходе от ноутбука к двухядерному процессору, но вот уже 4х ядерник особого прироста не дал, т.е. можно сделать вывод что они зависят скорее от частоты, чем от количества ядер. Но наиболее поразительным является результат #ZipLib — на i7-870 сжатие занимает больше времени чем на старом ноутбучном процессоре. Объяснить подобную разницу я затрудняюсь.
А вот результаты распаковки архива:



Тут ситуация тоже необычная: и #ZipLib и DotNetZip на i7-870 работают медленней, чем на E8400.
Напоследок, приведу скриншоты диспетчера задач во время работы приложения:



Тут красным выделен период работы ZLibNet, синим — #ZipLib, жёлтым — DotNetZip, и фиолетовым — ZipStorer. Хорошо заметно, что DotNetZip грузит оба ядра на полную катушку. Что и подтверждает результаты теста на сравние.

Итоги.

Тесты получились неоднозначными. У каждой из библиотек имеется свои плюсы и минусы. Лично для себя я выбрал DotNetZip, мне понравился её простой интерфейс и предсказуемый рост производительности относительно мощи процессора.
Исходный код можно посмотреть тут.

ASP.NET приложение с богатым web-интерфейсом на qooxdoo? Легко!

На просторах Интернета есть очень хороший Javascript фреймворк под названием qooxdoo. Наверное, многие знают о нем и видели его в действии. А если кто не знает, это фреймворк с большим количеством классов, в том числе отвечающих за UI. Подробности можно почитать и посмотреть там. Хорош он всем, но при разработке ASP.NET приложений его использовать крайне сложно. Так было раньше. Теперь я рад поделиться новостью: появился новый проект, который позволяет просто и быстро создавать ASP.NET приложения с пользовательским интерфейсом на qooxdoo, при этом писать на javascript совсем не придется. Проект называется qxdotnet и распространяется под лицензией LGPL.






Главная идея проекта заключается в том, чтобы создать классы-обертки на C# для всех основных классов qooxdoo, отвечающих за пользовательский интерфейс. Экземпляры этих классов-оберток генерирую javascript-код, который создает аналогичные объекты в браузере. Сообщения о событиях (нажатие на кнопку, ввод данных в текстовое поле) передаются на сервер через AJAX.

Иными словами, Вы просто создаете ASP.NET приложение, пишите на C# примерно следующий код:
btn = new qxDotNet.UI.Form.Button();
btn.Label = "push me";
root.Add(btn);

Далее запускаете приложение и видите свою кнопочку в браузере. Набор свойств и методов класса qxDotNet.UI.Form.Button почти такой же, как у javascript-ового класса qx.ui.form.Button. При нажатии на кнопку на сервер передается сообщение и вызывается соответствующее событие.

Исходники и пример можно скачать тут. Для компиляции потребуется MS Visual Studio 2008. Можно использовать VS 2010, но потребуется конвертировать файлы решения.

Проект qxdotnet реализован как приложение для qooxdoo, поэтому для компиляции такого приложения (как и любого другого приложения для qooxdoo) потребуется исполнить ряд скриптов, написанных на Python-е. Для выполнения этих скриптов нужно, чтобы в системе был установлен ActivePython. Скачать можно тут. Вызов Python-а производится самой VS во время компиляции решения.

Ну и третье, что потребуется для компиляции проекта, это qooxdoo 1.6 SDK. Его нужно скачать отсюда и распаковать в папку рядом с файлом решения. Т.е. в итоге должно получиться в одной папке следующие папки и файлы: DemoApplication, qooxdoo-1.6-sdk, source, qxDotNet.sln…

Далее можно открыть файл решения qxDotNet.sln в VS, установить в качестве начального проект DemoApplication и запустить его. Код главной формы демо-приложения можно посмотреть в файле MainForm.cs.

В заключении стоит отметить, что, хотя проект находится на стадии Alpha, почти все элементы пользовательского интерфейса уже работают. Есть на что посмотреть и попробовать в действии.

Производительность: LINQ to XML vs XmlDocument vs XmlReader на Desktop и Windows Phone

Не так давно мне пришлось делать приложение для Windows Phone работающее с xml-файлами. Всё было неплохо, но когда в файле стало ~100.000 записей, чтение их занимало ну уж очень много времени. И я решил сравненить производительность различных способов чтения данных из xml возможных на платформе .Net.

Подробности под катом.


Оборудование

Для лучшего понимания показателей проведенных тестов стоить рассказать на чём они были проведены. Тесты из разряда «Desktop» я выполял на домашнем компьютере:
Процессор: Pentium Dual-Core T4300 2100 Mhz
RAM: DDR2 2048Mb

Тесты на Windows Phone были выполнены на HTC 7 Mozart.

Подготовка к тестированию

Для тестирования использовался простой xml-файл. ID для каждого элемента генерировались рандомно, а количество записей различалось в зависимости от теста и составляло: 1, 10, 100, 1 000, 100 000 штук соответственно. Итоговый файл выглядел примерно следующим образом:










........







* This source code was highlighted with Source Code Highlighter.

Для уменьшение погрешностей каждый тест был выполен 100 раз и полученные данные усреднены. А для имитации некоторых действий над записью вызывался пустой метод ProcessId(id).

XmlDocument.Load

На мой взгляд реализация чтения данных этим способом наиболее простая и понятная. Но, как мы увидим в конце, достигается это уж очень большой ценой (в конце статьи приведена реализация этого способа без использования XPath, но результаты, лично у меня, не сильно отличаются). Код метода следующий:
private static void XmlDocumentReader(string filename)

{

var doc = new XmlDocument();

doc.Load(filename);

XmlNodeList nodes = doc.SelectNodes("//item");

if (nodes == null)

throw new ApplicationException("invalid data");



foreach (XmlNode node in nodes)

{

string id = node.Attributes["id"].Value;

ProcessId(id);

}

}



* This source code was highlighted with Source Code Highlighter.


LINQ to XML

Использование Linq-to-XML также оставляет реализацию метода довольно простой и понятной.
private static void XDocumentReader(string filename)

{

XDocument doc = XDocument.Load(filename);

if (doc == null || doc.Root == null)

throw new ApplicationException("invalid data");



foreach (XElement child in doc.Root.Elements("item"))

{

XAttribute attr = child.Attribute("id");

if (attr == null)

throw new ApplicationException("invalid data");



string id = attr.Value;

ProcessId(id);

}

}



* This source code was highlighted with Source Code Highlighter.


XmlReader

Ну и наконец последний способ чтения данных из XML — использование XmlTextReader. Стоит сказать, что этот метод самый сложный для понимания. В процессе чтения xml-файла вы двигаетесь по нему сверху вниз (без возможности движения в обратном направлении), и вам каждый раз необходимо проверять, те ли данные вам нужно извлечь? Соответственно, код метода выглядит так:
private static void XmlReaderReader(string filename)

{

using (var reader = new XmlTextReader(filename))

{

while (reader.Read())

{

if (reader.NodeType == XmlNodeType.Element)

{

if (reader.Name == "item")

{

reader.MoveToAttribute("id");

string id = reader.Value;

ProcessId(id);

}

}

}

}

}



* This source code was highlighted with Source Code Highlighter.

* Для упрощения, в методах были опущены проверки.

Результаты для Desktop

Ниже представлены результаты тестирования. Для запуска каждого теста время измерялось отедельно и затем усреднялось. Время в таблице в миллисекундах.
1 10 100 1 000 10 000 100 000
XmlDocument 0,59 мс 0,5 мс 0,67 мс 2,49 мс 21,73 мс 398,91 мс
XmlReader 0,51 мс 0,47 мс 0,55 мс 1,31 мс 8,62 мс 79,65 мс
Linq to XML 0,57 мс 0,59 мс 0,64 мс 2,09 мс 15,6 мс 192,66 мс



Как видно из таблицы, XmlReader при чтении больших xml файлов, выигрывает в производительности Linq To XML в 2,42 раза, а XmlDocument в более чем 5 раз!

Тестирование на Windows Phone

Теперь настало время провести тесты на телефоне. Стоит заметить, что на Windows Phone установлена более старая версия .Net Framework'а, поэтому метод с использованием XmlDocument.Load не работает, а код для XmlReader пришлось немного переписать:
private static void XmlReaderReader(string filename)

{

using (var reader = XmlReader.Create(filename)) {

while (reader.Read()) {

if (reader.NodeType == XmlNodeType.Element) {

if (reader.Name == "item") {

reader.MoveToAttribute("id");

string id = reader.Value;

ProcessId(id);

}

}

}

}

}



* This source code was highlighted with Source Code Highlighter.


Результаты для Windows Phone

Предсказуемо, что и на телефоне быстрее оказался XmlReader. Но в отличии от настольного компьютера, разница в производительности на больших файлах у них различна. На телефоне XmlReader быстрее LINQ to XML в 1,91 раз, а на десктопе в 2,42 раза.
1 10 100 1 000 10 000 100 000
XmlReader 1,67 мс 1,74 мс 3,19 мс 19,5 мс 173,84 мс 1736,18 мс
Linq to XML 1,73 мс 2,21 мс 4,75 мс 31,39 мс 314,39 мс 3315,13 мс


Разница в скорости чтения 100 элементов из файла на Desktop и Windows Phone.


Разница в скорости чтения 100 000 элементов из файла на Desktop и Windows Phone.

Как можно видеть, скорость чтения данных на телефоне и настольном компьютере, в зависимости от объема данных, изменяется нелинейно. Интересно узнать почему это так?

Заключение

Как мы вяснили, самым произоводительным способом чтения данных из xml является использование XmlReader'a вне зависимости от платформы. Но неудобство его использования заключается в довольно сложном способое выборки данных — нам каждый раз приходиться проверять на каком элементе стоит указатель.

Если же для вас производительность не является краеугольным камнем, а главное — ясность и простота сопровождаемости кода, то наиболее подходящим является использование LINQ to XML. Также необходимо стараться избегать использования XmlDocument.Load в рабочих проектах из-за его низкой производительности.

P.S. Стоит упомянуть, что на написание всего этого меня вдохновила эта статья.

Update: по предложению alex_rusсделал тест для XmlDocument без использования XPath. Результыты получились лучше, но все равно этот способ остался самым медленным.

Таблица № 3. Сравнение производительности XmlDocument с и без использования XPath.
1 10 100 1 000 10 000 100 000
XmlDocument (c XPath) 0,59 мс 0,5 мс 0,67 мс 2,49 мс 21,73 мс 398,91 мс
XmlDocument (без XPath) 0,56 мс 0,5 мс 0,65 мс 2,24 мс 19,47 мс 362,75 мс


Как видно из таблицы (и рисунка) производительность увеличилась только на 10%. Хотя были предположения, что это значение будет гораздо выше.

Собственно, код для XmlDocument без XPath ниже. Надеюсь, знающие люди покажут где у меня ошибки, в результате которых скорость обработки увеличилась всего лишь на 10%, а не «в разы».
private static void XmlDocumentReader2(string filename)

{

var doc = new XmlDocument();

doc.Load(filename);



XmlElement root = doc.DocumentElement;

foreach (XmlElement el in root.ChildNodes)

{

if (el.Name != "item") continue;



string id = el.Attributes["id"].Value;

ProcessId(id);

}

}



* This source code was highlighted with Source Code Highlighter.

Альтернативная проверка предусловий в Code Contracts

При попытке использования библиотеки Code Contracts в реальном проекте может возникнуть небольшая сложность: хотя сам класс Contract с методами проверки предусловий и постусловий, располагается в mscorlib начиная с 4-й версии .NET Framework, но без установки самой библиотеки Code Contracts, они не попадают в результирующую сборку.

Это может вызвать определенные сложности в крупных распределенных командах, поскольку для нормального использования контрактов всем разработчикам придется установить дополнительное расширение. А поскольку у ключевых людей проекта может не быть четкой уверенности в том, а нужно ли вообще нам это добро, то такой переход может быть затруднительным.

Однако Code Contracts поддерживает дополнительный «режим совместимости», который позволяет «жестко зашить» проверки предусловий в результирующий код, так что они будут видны всем, не зависимо от того, установлены контракты на машине разработчика или нет.

Постановка проблемы

Давайте вначале рассмотрим пример, который более четко покажет, в чем проблема.

class SimpleClass
{
public int Foo(string s)
{
Contract.Requires(s != null);
return s.Length;
}
}


С этим кодом совершенно все в порядке и при попытке вызова метода Foo с null, мы получим нарушение контракта, что при установленной библиотеке Code Contracts и включенной проверке предусловий приведет к генерации исключения 'System.Diagnostics.Contracts.__ContractsRuntime.ContractException'.

Да, именно этого мы и ждем, но особенность заключается в том, что код генерации исключения генерируется не компилятором, а отдельным процессом, который запускается сразу после компиляции. А это значит, что без библиотеки Code Contracts, выполнение этого кода приведет к генерации NullReferenceExcpetion, поскольку никакой дополнительной валидации аргументов не останется и в помине. Я неоднократно сталкивался с тем, что такое поведение вызывало примерно такую реакцию: «WTF? Куда делась моя проверка!»

Поскольку мы не хотим слышать подобные “WTF?!?” от наших коллег, у которых контракты не установлены, то хотелось бы иметь способ зашить проверку предусловий более основательным образом.

Ручная проверка предусловий

Библиотека Code Contracts позволяет использовать предусловия в старом формате. Это значит, что если существующий метод уже содержит проверку входных параметров (т.е. проверку предусловий), то для преобразования их в полноценные предусловия после них достаточно добавить вызов Contract.EndContractBlock:

public class SimpleClass
{
public int Foo(string s)
{
if (s == null)
throw new ArgumentNullException("s");
Contract.EndContractBlock();

return s.Length;
}
}


Добавление вызова Contract.EndContractBlock превращает одну (или несколько) проверок входных параметров в полноценные предусловия. Теперь, для любого разработчика, у которого контракты не установлены, этот код будет выглядеть, как и раньше. В то время, как обладатели контрактов, смогут пользоваться всеми их преимуществами, такими как проверка валидности программы с помощью Static Checker-а, автоматическая генерация документации, возможность отлова всех нарушений контрактов (подробнее об этом будет ниже). Отличие этого способа проверки лишь в том, что их нельзя отключить и выпилить из кода полностью.

Данный подход можно совмещать с более продвинутыми техниками использования контрактов. Так, например, можно совмещать old-style проверку предусловий совместно с проверкой постусловий и инвариантов. Но поскольку постусловия и инварианты в большей мере касаются самого класса, а не его клиентов, то это никак не затронет всех тех разработчиков, у которых контракты не установлены.

ПРИМЕЧАНИЕ
Библиотека Code Contracts позволяет настраивать, какие проверки должны оставаться в результирующем коде. Если разработчики достаточно уверены в своем коде, то они могут убрать все проверки из кода и сэкономить несколько тактов процессора на каждой из них. Более подробно об уровнях мониторинга и возможностях по их управлению можно почитать в статье: «Мониторинг утверждений в период выполнения».

Использование существующих методов проверки

Еще одним стандартным способом валидации аргументов является использование специальных классов (guard-ов) с набором разных методов, типа NotNull, NotNullOrEmpty и т.п. Библиотека Code Contracts поддерживает возможность превращения подобных методов в полноценные контракты: для этого методы класса валидатора нужно пометить атрибутом ContractArgumentValidatorAttribute.

ПРИМЕЧАНИЕ
К сожалению атрибут ContractArgumentValidatorAttribute не входит в состав .NET Framework версии 4.0, он появится только в версии 4.5. Разруливается эта ситуация путем добавления в ваш проект файла ContractExtensions.cs, который появится в %ProgramFiles%\Microsoft\Contracts\Language\CSharp после установки библиотеки Code Contracts.

public static class Guard
{
[ContractArgumentValidatorAttribute]
public static void IsNotNull(T t) where T : class
{
if (t == null)
throw new ArgumentNullException("t");
Contract.EndContractBlock();
}
}


Теперь мы можем использовать старый добрый метод IsNotNull для проверки предусловий:

public int Foo(string s)
{
Guard.IsNotNull(s);

return s.Length;
}


Отступление от темы. Contract.ContractFailed

Возможно, вы обращали внимание на существование двух версии метода Contract.Requires, одна из которых является обобщенной (generic) и может использоваться для генерации нужного типа исключения, нарушение же необобщенной версии приводит к генерации внутреннего (internal) исключение типа ContractException.

Причина, по которой по умолчанию генерируется внутреннее исключение, заключается в том, что нарушение контракта не может быть восстановлено программным путем. Это баг в коде и для его устранения необходимо изменение этого кода. Однако при использовании любого подхода к проверке предусловий (Contract.Requires + 2 рассмотренных сегодня подхода), пользователь может «захавать» исключение, перехватив базовый тип исключения.

В классе Contract есть событие ContractFailed, которое будет вызываться при нарушении предусловий/постусловий/инвариантов. Например, перед запуском интеграционного или юнит-теста можно подписаться на это событие, и если падает предусловие, но тест остается зеленым, то можно закатывать рукава и идти искать того нерадивого программиста, который ловит исключения, не предназначенные для обработки.

Заключение

Использование одного из описанных здесь подходов для перехода к контрактному программированию позволяет плавно мигрировать код на контракты, не ломая жизнь остальной части команды. При этом вы можете использовать старые средства валидации со всеми достоинствами контрактов (включая возможность узнать о нарушении предусловий, описанную в предыдущем разделе), не заставляя всех и каждого устанавливать дополнительные утилиты на свои машины.

Маленькие чудеса C#/.NET – структура DateTimeOffset

Рассмотрим некоторые части .Net Framework'a, выглядящие тривиальными, но вполне способными сделать ваш код более простым как в написании, так и в сопровождении.


Пишущие на .NET (а если вы этого не делаете, то зря читаете этот пост) наверняка время от времени используют для своих нужд структуру DateTime. Эта структура удобна для хранения дат, времени или даты/времени, относящихся к локальной временной зоне (или же к UTC).


Однако, бывают случаи, когда вам необходимо сохранить время в виде смещения, а не конвертировать его в локальное время. И вот здесь вам на помощь придёт структура, впервые появившаяся в .NET 3.5 — DateTimeOffset.

Проблема: парсинг DateTime может привести к конвертации в локальное время

Представим себе, что вы используете файл, веб-сервис и т.п. некой сторонней фирмы, чьи сервера находятся в другой временной зоне. Более того, у них есть несколько полей, в возвращаемых данных, которые должны содержать даты, но на самом деле содержат сериализованные экземпляры структуры DateTime, время в которых установлено в полночь. Например, дату рождения пациента они передают вот в таком виде:

2012-03-01 00:00:00-05:00

Такая запись говорит о том, что человек родился 1 марта 2012 года в неуказанное время (в конце концов, большая часть форм не требует от вас заполнения времени вашего рождения). Но поскольку экземпляр структуры DateTime был сериализован «в лоб», то он и содержит время, установленное в полночь, согласно своей временной зоне.


Итак, зная, что эта дата совместима с Восточной временной зоной (Eastern Time Zone), а мы находимся в Центральной временной зоне (Central Time Zone) мы парсим её так:

// ясно что здесь выполняется чтение файла/потока/и т.п.

var dateString = "2012-03-01 00:00:00-05:00";

// парсим в DateTime

var birthDay = DateTime.Parse(dateString);


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

2012-02-29 11:00:00 PM

Что случилось? (Или как говорил один персонаж — Кто это сделал?) Да, метод DateTime.Parse() конвертировал дату в локальную временную зону поскольку оригинальная дата рождения хранилась с указанным смещением. Вам просто оказали услугу — конвертировали указанную дату и время в ваши локальные дату и время. Это не так и плохо, если бы речь не шла о дне рождения, которое с 1 марта переместилось на 29 февраля.


Конечно, мы можем созвониться с третьей стороной и попросить перестать включать время в строку с датой или же перестать высылать смещение вместе со временем (в этом случае она перестанет конвертироваться в локальное время, но будет отмечена как DateTimeKind.Unspecified).


Однако бывает, что у нас нет возможности таким образом изменить ситуацию.


Бывают случаи, когда вы хотите считывать дату и время со смещением, но не конвертировать его в локальную временную зону. И вот тут вам пригодится DateTimeOffset.

DateTimeOffset – хранит DateTime и Offset

Так что там про DateTimeOffset? Структура так же проста, как и её имя, DateTimeOffset это дата+время+смещение. Именно поэтому она представляет намного более точную точку во времени, поскольку включает информацию о смещении, по которому были установлены текущие дата и время.


По правде говоря, функциональность DateTime и DateTimeOffset во многом перекрывается, а поскольку у Microsoft есть руководство по выбору того или другого, то я рекомендую ознакомиться с ней в MSDN. Статья называется «Choosing Between DateTime, DateTimeOffset, and TimeZoneInfo».


В целом, вы можете использовать DateTime, если вы «прикреплены» к одной временной зоне или используете только универсальное время в формате UTC. Но если вы хотите использовать даты и время из разных временных зон, а также хотите сохранить информацию о смещении без конвертации в локальное время, то лучше использовать DateTimeOffset.


В структуре DateTimeOffset есть много таких же свойств, как и в структуре DateTime (Day, Month, Year, Hour, Minute, Second, и т.п.), потому здесь их я описывать не стану. Главное отличие состоит в нескольких новых свойствах:

DateTime

Возвращает DateTime без учёта смещения.

LocalDateTime

Возвращает конвертированный DateTime, с учётом смещения (т.е. в локальной временной зоне).

Offset

Возвращает смещение относительно UTC.

UtcDateTime

Возвращает DateTime как время UTC.


Свойство DateTime возвращает вам DateTime (не приведенное к локальной временной зоне), а свойство Offset имеет формат TimeSpan, представляющее смещение от времени UTC. Также есть свойства LocalDateTime и UtcDateTime, конвертирующие данный DateTimeOffset в DateTime для локальной временной зоны или UTC.


Также замечу, что свойства Now и UtcNow структуры DateTimeOffset возвращают не тип DateTime, а DateTimeOffsets с соответствующим смещением от UTC. Конечно, как и DateTime, DateTimeOffset обладает методами, оперирующими с датой/временем, возвращая тип DateTimeOffset вместо DateTime.


Так как нам всё это поможет в выше приведенном примере? Теперь мы знаем, что третья сторона высылает нам дату и время своей временной зоны, которое не нужно конвертировать в локальные дату/время. Поэтому можно использовать DateTimeOffset.Parse() (или TryParse()) и выбрать только дату:

// ясно что здесь выполняется чтение файла/потока/и т.п.

var dateString = "2012-03-01 00:00:00-05:00";

// парсим день рождения как смещение даты/времени (без конвертирования в локальные даты/время)

var dtOffset = DateTimeOffset.Parse(dateString);

// теперь если нам надо сравнить результат с другими объектами типа локального DateTime

// мы просто используем свойство Date для получения даты

// без времени или смещения

var theDay = dtOffset.Date;

Таким образом, вы можете легко парсить даты без необходимости отслеживания “полночного сдвига” или же использовать его там, где необходимо иметь дату, время и смещение без конвертирования в локальное время.

Итоги

Хоть структура DateTime и является достаточно мощной в плане парсинга, манипулирования и сравнения дат/времени, она может доставить немало неприятных минут в работе с датами в формате разных временных зон. DateTimeOffset в этом случае проявляет себя куда более гибкой, поскольку использует смещение от UTC.


Вольный перевод (с) В.Ф.Чужа ака hDrummer, оригинал здесь

Теряем посетителей — много и каждый день

По какой-то причине огромное количество проектов с радостью теряет самых хлебных посетителей — белых воротничков в рабочее время.

Большинство работодателей закрывают все социальные сети скопом, начиная, естественно, с контакта. Несмотря на это — у крупных и очень крупных проектов в начале страницы( уже не очень хорошо) можно встретить простую строчку:


или


В итоге пользователи с закрытым контактом не могут открыть сайт, перейти по ссылке и в конце концов — принести проекту прибыль.

Примеры c посещаемостью больше 50к в день:
vesti.ru
kp.ru
smotri.ru
oktogo.ru
lifehacker.ru
povarenok.ru

Будьте аккуратны, используйте асинхронный код, доступный для всех соц. сетей, в крайнем случае — его можно смастерить самому.



UPD: в комментариях просят пояснить — почему один скрипт помешает всему.

Встречая script или link браузер тут же начинает грузить содержимое(в несколько потоков). До тех пор пока загрузка не окончена — браузер не может отрендерить страницу и «виснет» пока от запрос не отвалится по таймауту(минута-две).

Согласен, что если нормальные админы настраивают файрвол так, что этой проблемы не будет, но как показывает практика многие об этом не задумываются.

UPD2: Самый массовая «блокировка рунета» случилась в прошлом году, когда пару часов лежал Яндекс и вместе с ним — все сайты с синхронным кодом Директа, Метрики и других виджетов яндекса.

DevBar — питейное заведение для работников IT-индустрии и не только (Петербург)

Добрый день, господа и дамы.

Долгое время мы с партнером вынашивали идею организации бара, ориентированного на IT-специалистов.




Мы разработчики, живем в Санкт-Петербурге и частенько после работы любим пропустить пару (если повезет) бокалов каких-либо алкогольных напитков, обсуждая различные рабочие и не очень вопросы.

В один момент мы поймали себя на том, что на рынке не хватает заведений, ориентированных конкретно на нас. Есть ирландские пабы, спортивные бары, байкерские бары. Но работников такой важной индустрии, почему-то, все забыли. Когда я был в Голландии, я заметил питейные заведения для медиков, полицейских, пожарных, садоводов, ассенизаторов, и еще кучу мест с профессиональной концепцией. В нашей стране подобная ориентированность пока не развита.

Собственно, пораскинув мозгами и записав все на бумагу, получилось следующее.

Фишки

У любого уважающего себя заведения должны быть свои «фишки», которые заставляют посетителей приходить снова, рассказывать о них друзьям. Да и вообще, на этом автоматически строится лицо заведения в обществе.
В IT за этим далеко ходить не нужно.

Коктейльная карта

Вот несколько примеров из списка, который мы составили с опытным барменом (который делает так, чтобы было не только интересно, но и вкусно).
Basic — простой коктейль, для разогрева
PHP — лонг, идет легко, но лучше не увлекаться
Java — лонг, вкусный, пьется долго и не зависит от сосуда
С++ — шот, намешано кучу всего, вставляет не по-детски

Полный список (а вы видели только восьмую его часть) и составы вскоре можно будет посмотреть на нашем сайте.
Мы ничего не имеет против одноименных языков программирования. Партнер, например, работает с PHP.

Конкурсы

Круглосуточно можно подойти к бармену, пройти тест на знание какой-либо технологии или тематики.
Также, в главном зале стоит сервер. При желании, можно попробовать его взломать на определенных условиях и получить вечер бесплатного алкоголя.

Сцена для докладов

Я всегда считал, что большинство IT-конференций — это скучно. Разумеется, бывают интересные доклады, но зачастую нужно все-таки совмещать полезное с приятным.
Поэтому планируется организовать небольшую сцену для докладчиков, которые за определенные бонусы могут поделиться своими знаниями с посетителями. Все оборудование, разумеется, предоставляется.

И в догонку

Карточные игры. Бесплатный Wi-Fi, разумеется. Розетки для ноутбуков, зарядные кабели для телефонов у каждого столика.
Соответствующий интерьер (со спектрумами и денди). Негромкая музыка. В общем все, что нужно для работы (если вы придете к нам за этим).

Ценовая политика

Представители IT-индустрии бывают разные. В какой-то мере это студенты, в большинстве же — трудоустроенные специалисты, т.е. достаточно платежеспособная аудитория. Но мы решили не становиться элитным дорогим баром и держаться между самыми дешевыми представителями рынка и средними игроками. Дабы и студентам было, где хорошо провести время в кругу интересных людей, и матерым спецам не приходилось уходить с пустым бумажником и кредиткой в овердрафте. По плану, наш «средний чек» на 30% ниже, чем в среднем по больнице.

Местоположение

Партнер изначально предлагал разместиться где-нибудь в спальном районе по принципу «раз уж так круто, доедут». Но поговорив с опытными людьми мы решили, что нужно сделать умнее. Мы посмотрели, где находится большинство IT-компаний в городе (буквально, на карте), прикинули вектор пути домой большинства сотрудников и выбрали оптимальное, достаточно проходное место. Как ни странно, это оказался не центр, а Петроградский район (сдвиг на север). Там сейчас подыскиваем подходящую площадь.

Привлечение аудитории

Собственно, подобному заведению, на наш взгляд, намного проще раскрутиться, чем очередному «Irish super pub». Неординарность заведения и узкая целевая аудитория сделает свое дело (был изучен опыт тех же Голландских коллег). Да и сарафанное радио никто не отменял.

Привлекать будем через социальные сети, легкую рекламу в IT-компаниях и тематические (в плане технологий) вечера. А специалисты на утро сами расскажут коллегам, почему у них такое бледное лицо.

Многие скажут, что подобными развлечениями мы подрываем индустрию изнутри. Мы же искренне верим, что хороший досуг и народные способы опохмеления обеспечивают повышение производительности каждого посетителя.
Тем более, как было описано выше, мы обеспечиваем условия не только для распития напитков, но и для работы. Отчасти, это даже коворкинг-центр с хай-лоу сплит покером и прекрасными дамами.

Говорить — не мешки ворочать

На данный момент у нас готовы расчеты затрат на запуск, списки начальных закупок (от туалетной бумаги до барной стойки) и подробный бизнес-план. Также имеется часть стартового капитала и связи с некоторыми представителями рынка. Сейчас мы активно ищем инвестора.

Что же мы хотим от вас?

Мы в любом случае будем воплощать эту идею, но нам интересно, как ее прокомментируют представители крупнейшего IT-сообщества нашей страны. Вероятно, проскочат полезные предложения или очевидные недостатки нашего плана, которые мы не усмотрели замыленным взглядом.

***


Через неделю мы опубликуем конкретные цифры плана, учтя комментарии и пожелания (к слову, публикуем мы подобный пресс-релиз не только здесь).

На данный момент следить за развитием проекта можно в твиттере.

Дабы не было рекламы:

Твиттер



Спасибо за внимание!