gifts2017

Преобразование строки в дату

Опубликовал Serg G (Serg G) в раздел Программирование - Практика программирования

Функция преобразует строку в дату.
Возможные варианты: "27/янв\2015 3-15-22", "1 янв 2005г. ::01", 20150320220315, "2/3/55", "05/3/15", "1 ФеВраЛЯ 05 г. 20:01:0", "1 янв. 2005 г ::",  "1 янв 2005г ::01", "1 ФеВраЛь 05 г. 0:02:", "1 янВа 2005 г. 20:01", "02/04/2015", "20150320220315", "27.08.2015 3:15:22", "27.08-2015 5:24"


Причиной создания этой функции послужил вот такой формат даты «1 Января 2015 г. 13:25». И чтобы сто раз не возвращаться к теме, сделал что-то универсальное, дабы не тратить время в будущем.

В функции много комментариев.

Для более быстрой адаптации к др.языкам (локализация) создан массив п_мФорматы()

// для тестов:
	//Сообщить(СтрВДату("20:01:0"));	
	//Сообщить(СтрВДату("27/08\2015 3-15-22"));
	//Сообщить(СтрВДату(20150320220315));
	//Сообщить(СтрВДату("2/3/55"));
	//Сообщить(СтрВДату("05/3/15"));
	//Сообщить(СтрВДату("1 ФеВраЛя 05 г. 20:01:0"));	
	//Сообщить(СтрВДату("1 янв. 2005 г ::"));	
	//Сообщить(СтрВДату("1 янв 2005г. ::01"));
	//Сообщить(СтрВДату("1 янв 2005г ::01"));	
	//Сообщить(СтрВДату("1 ФеВраЛь 05 г. 0:02:"));
	//Сообщить(СтрВДату("1 янВа 2005 г. 20:01"));
	//Сообщить(СтрВДату("02/04/2015"));
	//Сообщить(СтрВДату("20150320220315"));
	//Сообщить(СтрВДату("27.08.2015 3:15:22"));
	//Сообщить(СтрВДату("27.08-2015 5:24"));
	
// Преобразует строку в дату
// Параметры
//  сДата  – строка
// Возвращаемое значение:
//   дата - тип.дата 
//
Функция СтрВДату(Знач сДата) Экспорт
	
	Перем п_мТЕМП; // массив временных переменных
	
//-----для локализации ------------------------------
	Перем п_мФорматы;
	п_мФорматы = Новый Массив(4);
	п_мФорматы[0] = "г";	// знак года
	п_мФорматы[1] = "ЧЦ=2; ЧН=; ЧВН=";
	п_мФорматы[2] = "ЧГ=0";	
	п_мФорматы[3] = "ДФ=МММ";
//---------------------------------------------------

	Если ПустаяСтрока(сДата) Тогда
		п_мТЕМП[6] =Дата("00010101000000");
	    Возврат п_мТЕМП[6];
	КонецЕсли;
	
	сДата = Формат(сДата, п_мФорматы[2]);		//на случай если в формате 1с числом: 20150320220315. (+локализация)
 
 //Если использовать конструкцию "Если Найти(сДата,..." , то перебор букв строки будет происходить 2а раза: Найти и СтрЗаменить //Использование Найти в данном случае бессмысленно
	// можно добавить любой разделитель - @, #, %, и тд, неважно. Главное, заменить их на "."
	сДата = СтрЗаменить(сДата, "«", ""); 			//«1 Фев 05 г.»
	сДата = СтрЗаменить(сДата, "»", "");			//«1 Фев 05 г.»
	 сДата = СтрЗаменить(сДата, п_мФорматы[0] + ".", "");   //1 Фев 05 г. 17:20:00 //просто "г" нельзя из-за "авГуст", например. Но в далее,после парсинга месяца, любое "г" в строке,  удаляется
	сДата = СтрЗаменить(сДата, "/", ".");			// 1/2/5
	сДата = СтрЗаменить(сДата, "\", ".");			// 1\02\05
	сДата = СтрЗаменить(сДата, "-", ".");			// 1-02-05
	сДата = СтрЗаменить(сДата, Символы.Таб, ".");		// 01.02.2005	17:20:00	
	сДата = СтрЗаменить(сДата, " ", ".");			// 1/02 2005 17:20:00
	сДата = СтрЗаменить(сДата, "..",  ".");			//Возможны, появление двойных точек из за "янв." в "янв" или из-за " г. ".	
	сДата = НРег(СокрЛП(сДата)); 				// строку в нижний регистр, чтоб проще было с Янв Январь янв и тд
	
	п_мТЕМП = Новый массив(7);//массив для временных переменных	
	
	п_мТЕМП[0] = Найти(сДата,".");
	
	Если п_мТЕМП[0] = 0 Тогда
		//если точек нет
		
		Если Найти(сДата,":") Тогда
				//только время (или ошибка)
				сДата = "01.01.0001." + сДата;
				п_мТЕМП[0] = Найти(сДата,".");
		иначе			
				//похоже на формат 1с. Можно добавить "попытку"...
				п_мТЕМП[6] = дата(сДата);
				Возврат п_мТЕМП[6];

		КонецЕсли;	
		
		
	//иначеЕсли п_мТЕМП[0] = 1 Тогда
	//		//например, месяц и год
	КонецЕсли;
	

	// ----- ДНИ -------------------------------------
	п_мТЕМП[0] = Лев(сДата, п_мТЕМП[0] - 1);//дни 
	п_мТЕМП[4] = Прав(сДата, СтрДлина(сДата) - СтрДлина(п_мТЕМП[0])-1);// месяц и все что справа
	п_мТЕМП[0] = Формат(Число(п_мТЕМП[0]),п_мФорматы[1]);//дни в формат двух чисел
	// ----- Месяц -----------------------------------
	п_мТЕМП[1] = Лев(п_мТЕМП[4], найти(п_мТЕМП[4],".") - 1);// месяц
	п_мТЕМП[4] = Прав(п_мТЕМП[4], СтрДлина(п_мТЕМП[4]) - СтрДлина(п_мТЕМП[1])-1);// год и все что справа
	//Возможны варианты месяца "янв" "янв." "январь" "января" 
	Если СтрДлина(п_мТЕМП[1]) > 2 Тогда // месяц в виде янв или январь
		
		п_мТЕМП[2] = 0;
		Пока п_мТЕМП[2] < 12 Цикл 
			
			п_мТЕМП[2] = п_мТЕМП[2]+1;
			// берем из "янв." только "янв"
			п_мТЕМП[3] = СтрЗаменить(Формат(Дата("2001" + Формат(п_мТЕМП[2],п_мФорматы[1]) + "01"), п_мФорматы[3]),".","");// + локализация
			
			п_мТЕМП[3] = найти(п_мТЕМП[1], п_мТЕМП[3]);
			
			Если п_мТЕМП[3] > 0 тогда
				п_мТЕМП[1] = Формат(п_мТЕМП[2],п_мФорматы[1]);
				прервать;
			КонецЕсли;
		КонецЦикла;	
	Иначе
		п_мТЕМП[1] = Формат(Число(п_мТЕМП[1]),п_мФорматы[1]);
	КонецЕсли;
	// ----- ГОД -----------------------------------
	//Если в строке было "г" без точки("г.")
	п_мТЕМП[4] = СтрЗаменить(п_мТЕМП[4], п_мФорматы[0], "");
	//ищем год. Дата может быть без времени, т.е. год последний в строке
	п_мТЕМП[2] = Найти(п_мТЕМП[4],".");
	
	Если п_мТЕМП[2]>0 Тогда
		п_мТЕМП[2] =  Лев(п_мТЕМП[4], п_мТЕМП[2] - 1);// год
		п_мТЕМП[4] = Прав(п_мТЕМП[4], СтрДлина(п_мТЕМП[4]) - СтрДлина(п_мТЕМП[2])-1);//время и все что справа
	Иначе
		п_мТЕМП[2] = п_мТЕМП[4];
		п_мТЕМП[4] = "";
	КонецЕсли;	
	
	//проверяем год
	п_мТЕМП[3] = СтрДлина(п_мТЕМП[2]);
	//если год из двух цыфр
	Если п_мТЕМП[3] = 2 или п_мТЕМП[3] = 1 Тогда
		п_мТЕМП[3] = Число(п_мТЕМП[2]);// год как число
		
		//что означает 15 в "20.03.15"? это 2015г или 1915г? (Настраиваем под себя или выдаём ошибку)
		// в моем варианте если  < 50 то это 2000г. иначе 1900г.
		Если п_мТЕМП[3] < 50 Тогда 
			п_мТЕМП[2] = "20" + Формат(п_мТЕМП[3],п_мФорматы[1]);
		Иначе
			п_мТЕМП[2] = "19" + Формат(п_мТЕМП[3],п_мФорматы[1]);
		КонецЕсли;	
		
	КонецЕсли;
 
	// =======================  Форматируем время ==============================
	п_мТЕМП[6] = СтрЗаменить(п_мТЕМП[4],":", ".");// если дата была, например: 17-30-10, то сейчас 17.30.10 
	//"попытка" на преобразование даты, по времени, занимает столько же, а по ресурсам больше, чем сам парсинг времени. 
	//поэтому, убиваем двух зайцев перебором часы/мин/сек сразу
	Если СтрДлина(п_мТЕМП[6]) > 0 Тогда
		
		п_мТЕМП[5] = найти(п_мТЕМП[6],".");
		Если п_мТЕМП[5] > 0 Тогда
			// ========= часы	=================
			п_мТЕМП[3] = Лев(п_мТЕМП[6], найти(п_мТЕМП[6],".") - 1);//часы 
			п_мТЕМП[6] = Прав(п_мТЕМП[6], СтрДлина(п_мТЕМП[6]) - СтрДлина(п_мТЕМП[3])-1);// минуты и все что справа
			
			Если п_мТЕМП[3] = "" тогда
				п_мТЕМП[3] = "00";		
			Иначе
				//при переводе в дату лидирующий 0 у часов удаляется. Т.е. след. строка бесполезна
				//п_мТЕМП[3] = Формат(Число(п_мТЕМП[3]),п_мФорматы[1]);//часы в формат двух чисел	 
			КонецЕсли;		
			
			п_мТЕМП[5] = найти(п_мТЕМП[6],".");
			Если п_мТЕМП[5] > 0 Тогда
				// ========= минуты	=================
				п_мТЕМП[4] = Лев(п_мТЕМП[6], найти(п_мТЕМП[6],".") - 1);
				п_мТЕМП[6] = Прав(п_мТЕМП[6], СтрДлина(п_мТЕМП[6]) - СтрДлина(п_мТЕМП[4])-1);// секунды и все что справа
				Если п_мТЕМП[4] = "" тогда
					п_мТЕМП[4] = "00";		
				Иначе
					п_мТЕМП[4] = Формат(Число(п_мТЕМП[4]),п_мФорматы[1]);//минуты в формат двух чисел	 
				КонецЕсли;
				
				// ========= секунды	=================
				Если СтрДлина(п_мТЕМП[6]) = 0 Тогда
					п_мТЕМП[5] = "00";		
				Иначе
					п_мТЕМП[5] = Формат(Число(п_мТЕМП[6]),п_мФорматы[1]);//секунды в формат двух чисел	 
				КонецЕсли;
				
			Иначе
				п_мТЕМП[4] = Формат(Число(п_мТЕМП[6]),п_мФорматы[1]);	
				п_мТЕМП[5] = "00";			
			КонецЕсли;	 
		Иначе
			п_мТЕМП[3] = Формат(Число(п_мТЕМП[6]),п_мФорматы[1]);		
			п_мТЕМП[4] = "00";
			п_мТЕМП[5] = "00";
		КонецЕсли;
		
	Иначе
		п_мТЕМП[3] = "00";		
		п_мТЕМП[4] = "00";
		п_мТЕМП[5] = "00";			
	КонецЕсли;
	
	п_мТЕМП[6] = п_мТЕМП[0] + "." + п_мТЕМП[1] + "." + п_мТЕМП[2] + " " + п_мТЕМП[3] + ":" + п_мТЕМП[4] + ":" + п_мТЕМП[5];
	п_мТЕМП[6] = дата(п_мТЕМП[6]);
	
	Возврат п_мТЕМП[6];
	
	
КонецФункции

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

Также, задумывалось, что будут возвращаться параметрами функции (Год, Месяц, День....), но в последствии я от этого отказался. В коде присутствуют остатки от этой затеи, которые не влияют на скорость,а  лишь несколько строк(присвоение значений промежуточным переменным) можно сократить.

Конечно, замечания и критика приветствуются. Спасибо.



Пс:
Можно добавить в поиск/замену слово "год".
А в идеале, нужно заменить всё, кроме букв и цифр, на "."(точку) - 100% универсальность. Только вот, слишком медленная функция получится, наверное. Хотя... надо тестировать. Пока, не критично.  Как идея ВРег(стр)<>НРег(стр) = только буквы - может получится быстрее, чем СтрЗаменить()

Похожие темы:

http://infostart.ru/public/200111/
http://infostart.ru/public/92455/

См. также

Подписаться Добавить вознаграждение

Комментарии

1. Павел И. (3.14159) 22.09.15 10:00
Причиной создания этой функции послужил вот такой формат даты «1 Января 2015 г. 13:25»


конструктор форматной строки для даты: ДФ='d ММММ yyyy "г." чч:мм'

"йогурт без даты..." :)
2. Serg G (Serg G) 22.09.15 10:59
(1) 3.14159, это когда вам известно, что в источнике все даты одного формата, а если нет? а если источник не один, а даты заполняют произвольно? и тд.
Форматы, к сожалению, не всегда "делают погоду".
3. anry mc (AnryMc) 22.09.15 11:25
;-)

Забыли "обратный" вариант написания дат: YYYY-MM-DD
https://dev.1c-bitrix.ru/api_help/main/general/lang/format.php

ЗЫ http://dev.1c-bitrix.ru/api_help/main/functions/date/formatdatefromdb.php
Примеры использования
echo FormatDateFromDB('01/23/2013', 'DD MMMM YYYY'); // вернет 23 Enero 2013, если язык сайта испанский

echo FormatDateFromDB('7 June 2012 12:00pm', 'SHORT'); // вернет 7 Июня 2012, если язык сайта русский и короткий формат языка - DD MMMM YYYY

echo FormatDateFromDB('7 June 2012 12:00pm'); // вернет 7 Июня 2012 12:00pm, если язык русский и полный формат - DD MMMM YYYY G:MIT

echo FormatDateFromDB('7 June 2012 12:00pm', 'DD.MM'); // вернет 07.06 для любого языка
4. Serg G (Serg G) 22.09.15 11:46
(3) AnryMc, да, я знаю об этих национальных нюансах. Кстати, MS Excel и MS Access в коде VB используется нац.формат, а в запросах евро "mm/dd/yyyy" - вот где можно запутаться )))
Что касается yyyy/*/* , то можно добавить проверку первого отбора(дней) и если количество сиволов = 4, то ... и тут опять тупик: "а следующий месяц или дни?"
С месяцами mm/dd/yyyy сложнее. Разве что контролировать на >12.
Это сложно и сделать на автомате практически нереально. Нельзя определить 01/01/01 где тут месяцы, года и дни ) без дополнительных вопросов = формат
5. Марина Чирина (chmv) 22.09.15 13:15
6. Максим *** (premier) 23.09.15 13:52
Автор, а про регулярные выражения и объект "VBScript.RegExp" Вы слышали?
Вот такой шаблон, например: \d{1,2}\D{1}\d{1,2}\D{1}\d{2,4} позволит выделить в строке дату или время из тестовых строк :
27/08\2015 3-15-22 = 27/08\2015
2/3/55 = 2/3/55
05/3/15 = 05/3/15
02/04/2015 = 02/04/2015
27.08.2015 3:15:22 = 27.08.2015
27.08-2015 5:24 = 27.08-2015
Дальше с помощью тех же регулярных выражений можно заменить все не числовые символы в строке и анализировать, дата это или время.
Также можно написать шаблон для дат, в которых месяц указан прописью.
Например, шаблон \d{1,2}\s+\D+\d{2,4} выделит из строки "1 ФеВраЛя 05 г. 20:01:0" строку "1 ФеВраЛя 05".
В общем код сократится в разы!
7. Serg G (Serg G) 24.09.15 13:33
(6) premier, Знаю и использовал. Так же знаю, что использование функции разбивающей строку на подстроки сократит код в . И?
К слову, то, что вы не видите код и его количество в обьекте RegExp, это не значит, что его нет. ))
Функция самодостаточна в рамках 1с - это огромный плюс.
Протестируйте( в 1с), что будет быстрее работать, скажем с 100 000 строк. Но, можете не тратить время зря. )) Создание обьекта - это уже огромный "тормоз".А 100000 раз превращает такой вариант в кошмар. Так что, не вижу, ни единого плюса от RegExp в данном случае. Разве что, делать пакетную обработку столбца/структуры, что неудобно в рамках таблиц и циклов.
8. Ярослав Радкевич (WKBAPKA) 25.09.15 09:06
о, то что надо... недавно столкнулся с загрузкой счетов в которых один из вариантов даты представленный автором. в регулярных выражениях разбираться лень, а обработочка полезная
9. Максим *** (premier) 25.09.15 11:57
(7) Serg G, а кто запрещает создавать объект один раз за сеанс и сохранять его во временном хранилище? Где-то на Инфостарте я читал статью на эту тему. А то, что мы не видим код в объекте, позволяет нам не загромождать собственный код. Хотя, я впрочем - совсем не противник альтернативных вариантов. Просто механизм регулярных выражений достаточно мощный и что немаловажно язык - лаконичный. Ну и минусы, конечно есть - COM объект всё-таки, можно использовать только на клиенте.
А вообще, конечно, при отсутствии описания используемого формата, достаточно велика погрешность преобразования. Например:
Сообщить(СтрВДату("2/3/55")) вернёт 02.03.1955 0:00:00, хотя это может быть также и 01.01.0001 02:03:55 т.е. время, а может быть и 02.03.2055 0:00:00, в общем, вариантов много.
10. Serg G (Serg G) 25.09.15 15:17
(9) premier, (9) premier, вы протестируйте на нескольких сотнях тыщ и будете удивлены. Только не пакетную передачу, а по одной переменной.

))) Блин....вы занудствуете. Если вам известно, что "2/3/55" это время, так передайте в функцию "1/1/01 "+"2/3/55" и не нужны никакие форматы!!! Вам же известно, что это время.... если, конечно, вы не Чумак или Кашпировский ))) Могли бы добавить критики, что функция не отрабатывает "// 2/3/55", а такой "2/3/55 //" делает )) Ну, я же надеялся на вас )

Дам совет, а вы уже можете к нему прислушиваться или нет, это ваше дело. Не используйте никогда сторонние приложения если этого можно избежать. Причин для этого тьма, начиная от средств защиты системы, до самой ОС. Понимаете, между 1с и внешним обьектом может стоять анивирус/hips, зависит от настроек, и ваше приложение может работать нормально, а у пользователя жутко тормозить. Вы лысину заработает на правом яичке, но не догадаетесь, что у него стоит ESET NOD )))
Второй совет, бонус, делайте функции самодостаточными если возможно - это облегчает экспор/импорт/обновления. На самом деле, это, когда у вас есть опыт за пличами. наверное, главный приоритет в функциях/процедурах.

Эту функцию можно вставить в любой проект, даже "голый" и она отработает своё независимо, какие ActiveX/Com разрешены/запрещены/инфицированы/присутствуют и какая это версия - в этом ее огромный плюс.
Более того, разделители же можно добавить, убрать,... гибкость и вариативность присутствует на откупе программиста. Каждый может подстроить под свои нужды или добавить большей универсальности. Но смысл и суть функции это : " меньше вопросов - больше дела". Есть дата(кстати, это не время) и она отработает ее по основным, часто встречающимся форматам, а точнее разделителям/делиметерам. Всё! Если ваш формат какой-то особый - это не для вас. Но в гуще своей, если вам встречаются данные с разными форматами, заполняемые разными программами, сканируемые неизвестно откуда и всё это нужно класть в одно место, зычно и красиво, вот, когда эта функция очень нужна.

В общем, регулярные выражения - это очень крутой механизм, очень-очень, но не в данном, я бы сказал примитивном, случае.
11. Максим *** (premier) 26.09.15 06:34
(10) Serg G, убедили. "+" за идею, "-" за оформление кода. Мне, например, очень сложно было вникнуть в программный код, приведенный Вами. Ну, а вообще, конечно больше на "+" тянет. Большую работу проделали. Как говорится - "респект и уважуха".
12. Юрий Патласов (NoRazum) 30.08.16 11:19
10/02/2016 00:33:07

загнулась на таком дате (((
версия платформы 8.3.8.1784
Для написания сообщения необходимо авторизоваться
Прикрепить файл
Дополнительные параметры ответа