Simatic Step 7. STL. Вычисление числа дней в месяце (DayOfMonth).

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

Источник: Григорианский календарь
  Продолжительность тропического года (время между двумя весенними равноденствиями) составляет 365 суток 5 часов 48 минут 46 секунд. Различие в продолжительности тропического года и среднего юлианского календарного года (365,25 суток) составляет 11 минут 14 секунд. Из этих 11 минут и 14 секунд приблизительно за 128 лет складываются одни сутки.
  По истечении нескольких столетий было замечено смещение дня весеннего равноденствия, с которым связаны церковные праздники. К XVI веку весеннее равноденствие наступало примерно на 10 суток раньше 21 марта, используемого для определения дня Пасхи.
  Чтобы компенсировать накопившуюся ошибку и избежать подобного смещения в будущем, в 1582 году римский папа Григорий XIII провёл реформу календаря. Чтобы средний календарный год лучше соответствовал солнечному, было решено изменить правило високосных лет. По-прежнему високосным оставался год, номер которого кратен четырём, но исключение делалось для тех, которые были кратны 100. Такие годы были високосными только тогда, когда делились ещё и на 400.
  Другими словами, год является високосным в двух случаях: либо он кратен 4, но при этом не кратен 100, либо кратен 400. Год не является високосным, если он не кратен 4, либо он кратен 100, но при этом не кратен 400.
  Последние годы столетий, оканчивающиеся на два нуля, в трёх случаях из четырёх не являются високосными. Так, годы 1700, 1800 и 1900 не являются високосными, так как они кратны 100 и не кратны 400. Годы 1600 и 2000 — високосные, так как они кратны 400. Годы 2100, 2200 и 2300 — невисокосные. В високосные годы вводится дополнительный день — 29 февраля.
  2016 год является високосным годом. Предыдущим високосным годом был 2012 год, следующим будет 2020.
  Если бы не было високосных лет, то календарь повторялся бы каждые 7 лет, так как остаток от деления 365 на 7 равен 1. Наличие високосных лет приводит к тому, что календарь полностью повторяется через каждые 7*4=28 лет.

Есть приколисты, сумевшие составить универсальную формулу нахождения кол-ва дней в месяце, если известны номера месяца и года. Оцените полет мысли:



  На полном серьезе (просто прикалываются) дают переложение этого алгоритма на C с использованием библиотечных функций модуля Math:

function f(x, y) { return 28 + ((x + Math.floor(x / 8)) % 2) +   2 % x + 
  Math.floor((1 + (1 - (y % 4 + 2) % (y % 4 + 1)) * ((y % 100 + 2) % (y % 100 + 1))+ 
  (1 - (y % 400 + 2) % (y % 400 + 1))) / x) + Math.floor(1/x) - 
  Math.floor(((1 - (y % 4 + 2) % (y % 4 + 1)) * ((y % 100 + 2) % (y % 100 + 1)) + 
  (1 - (y % 400 + 2) % (y % 400 + 1)))/x); }

Для желающих там же есть вариант на C#.
  Красиво для представления начальству при отчете о затраченных средствах и потраченном времени. Но мне это было не нужно.

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

function DaysInMonth(const Year: Word, Month: Byte): Byte;
const
  MonthDays: array[1..12] of Byte = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
begin
  Result := MonthDays(Month);
  if (Month <> 2) and not 
    ((Year mod 400 = 0) or ((Year mod 4 = 0) and (Year mod 100 <> 0))) then Exit;
  Inc(Result);
end;

Эквивалентный функциональный блок на STL для Step 7:

FUNCTION_BLOCK FB50

TITLE = Возвращает кол-во дней в месяце

AUTHOR:   Anakost	//
FAMILY:   SOHO   	//
NAME:     DeyInMon 	//Deys In Month 
VERSION:  1.0    	//

VAR_INPUT  
  YEAR: WORD;		//номер года          
  MONTH: BYTE;		//номер месяца
END_VAR

VAR_OUTPUT            
  DEIS: BYTE;		//кол-во дней
END_VAR

VAR //шаблон                 
  PATTERN: ARRAY[1..12] OF BYTE := 	
    B#16#1F, 		// январь
    B#16#1C, 		// февраль
    B#16#1F, 		// март
    B#16#1E, 		// апрель
    B#16#1F, 		// май
    B#16#1E, 		// июнь
    B#16#1F, 		// июдь
    B#16#1F, 		// август
    B#16#1E, 		// сентябрь
    B#16#1F, 		// октябрь
    B#16#1E, 		// ноябрь
    B#16#1F; 		// декабрь
END_VAR

VAR_TEMP             
  LeapYear: BOOL;	//високосный год
END_VAR

BEGIN

NETWORK
TITLE = Вычисления
// проверка года на високосность
  L #MONTH;		//номер месяца
  L 2;			//
  ==I;			//февраль ?
  = LeapYear;		//запомним
  JCN A000;		//если нет, дальше не проверяем 
  L #YEAR;		//номер года
  L 4;			//
  MOD;			//остаток от деления на 4
  A ==0;		//у високосного остаток 0
  = LeapYear;		//запомним
  JCN A000;		//если нет, дальше не проверяем 
  POP;			//номер года
  L 100;		//
  MOD;			//остаток от деления на 100
  A <>0;		//у високосного остаток не 0
  = LeapYear;		//запомним
  JC A000;		//если не 0, дальше не проверяем 
  POP;			//номер года
  L 400;		//
  MOD;			//остаток от деления на 400
  A ==0;		//у високосного остаток 0
  = LeapYear;		//запомним
// определение кол-ва дней
A000:
  L #MONTH;		//номер месяца
  DEC 1;		//с нулевого индекса
  SLW 3;		//умножим на длину байта		
  L P##PATTERN;		//загрузка указателя на начало шаблона
  +D;			//сдвиг начального адреса на адрес слова
  LAR1;			//регистр-указатель на нужное слово
  L DIB [AR1,P#0.0];    //извлекаем кол-во дней
  A LeapYear;		//год високосный ?
  JCN A001;		//если нет - выходим
  INC 1;		//добавляем день
A001: 
  T #DEIS;		//кол-во дней

END_FUNCTION_BLOCK

  Это полный аналог функции на Pascal, но он для меня оказался избыточен. В каждом организационном блоке программы Step 7 уже определено стандартом 20 байт для системных данных. Первые 12 содержат общую информацию о ОB (класс приоритета, номер OB) и информацию зависящую от типа ОB. Последние 8 байт в каждом ОB занимает структура типа DATE_AND_TIME (DT). Она представляет из себя:


  DATE_TIME_BCD_VEAR : BYTE; 	//Текущий год
  DATE_TIME_BCD_MONTH : BYTE; 	//Текущий месяц
  DATE_TIME_BCD_DAY : BYTE; 	//Текущий день
  DATE_TIME_BCD_HOUR : BYTE; 	//Текущий час
  DATE_TIME_BCD_MIN : BYTE; 	//Текущая минута
  DATE_TIME_BCD_SEC : BYTE; 	//Текущая секунда
  DATE_TIME_BCD_MSEC : BYTE; 	//Текущая милисекунда
  DATE_TIME_BCD_WEEK : BYTE;	//Текущая неделя (7..4, 3..0 милисекунды)

Данные представлены в двоично-десятичном BCD формате, в каждом байте две цифры.



  Минимальная дата в структуре DATE_AND_TIME равна 01.01.1990 года, максимальная 31.12.2089 года. Это упрощает расчеты. В интервал допустимых значений структуры DATE_AND_TIME попадает только один год, требующий при проверке остатка от деления на 100 и 400. Это 2000 год, а мы уже знаем что он високосный. Поэтому достаточно остатка от деления на 4 для всего диапазона чисел DATE_AND_TIME.
  Упрощенный функциональный блок на STL для формата DATE_AND_TIME:


FUNCTION_BLOCK FB52

TITLE = Возвращает кол-во дней в месяце

AUTHOR:   Anakost	//
FAMILY:   SOHO   	//
NAME:     DeyInMon 	//Deys In Month 
VERSION:  1.0    	//

VAR_INPUT  
  YEAR: BYTE;		//номер года          
  MONTH: BYTE;		//номер месяца
END_VAR

VAR_OUTPUT            
  DEIS: BYTE;		//кол-во дней
END_VAR

VAR //шаблон                 
  PATTERN: ARRAY[1..12] OF BYTE := 	
    B#16#1F, 		// январь
    B#16#1C, 		// февраль
    B#16#1F, 		// март
    B#16#1E, 		// апрель
    B#16#1F, 		// май
    B#16#1E, 		// июнь
    B#16#1F, 		// июдь
    B#16#1F, 		// август
    B#16#1E, 		// сентябрь
    B#16#1F, 		// октябрь
    B#16#1E, 		// ноябрь
    B#16#1F; 		// декабрь
END_VAR

VAR_TEMP             
  LeapYear: BOOL;	//високосный год
END_VAR

BEGIN

NETWORK
TITLE = Вычисления
// проверка года на високосность
  L #MONTH;		//номер месяца
  L 2;			//
  ==I;			//февраль ?
  = LeapYear;		//запомним
  JCN A000;		//если нет, дальше не проверяем 
  L #YEAR;		//номер года
  L 4;			//
  MOD;			//остаток от деления на 4
  A ==0;		//у високосного остаток 0
  = LeapYear;		//запомним
// определение кол-ва дней
A000:
  L #MONTH;		//номер месяца
  DEC 1;		//с нулевого индекса
  SLW 3;		//умножим на длину байта		
  L P##PATTERN;		//загрузка указателя на начало шаблона
  +D;			//сдвиг начального адреса на адрес слова
  LAR1;			//регистр-указатель на нужное слово
  L DIB [AR1,P#0.0];    //извлекаем кол-во дней
  A LeapYear;		//год високосный ?
  JCN A001;		//если нет - выходим
  INC 1;		//добавляем день
A001: 
  T #DEIS;		//кол-во дней

END_FUNCTION_BLOCK

  • +1
  • 16 ноября 2016, 19:53
  • anakost

Комментарии (0)

RSS свернуть / развернуть
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.