Содержание

Производственный календарь

Пришла мне как-то в голову идея изобразить производственный календарь в двоичном новогоднем виде. Напрограммил макрос в экселе http://nzdr.ru/files/hny2012.xls, потом это дело в картинку сконвертил. Позже переписал на php, а после и вовсе на яваскрипте. Получилось как-то так:

на 2012 год

Код на VBA для Microsoft Excel:

'календарь
Sub main()
Dim iYear As Integer, iMonth As Integer, iDay As Integer
Dim iDayOfWeek As Integer, iDaysInMonth As Integer
Dim iOffset As Integer, iColor As Integer, j
 
    'чистим ячейки
    Cells.ClearContents
    Cells.Interior.ColorIndex = xlNone
    ActiveWindow.DisplayGridlines = False
 
    'задаём год
    iYear = 2012
 
    For iMonth = 1 To 12
        iDayOfWeek = Weekday(DateSerial(iYear, iMonth, 1), vbMonday)
        If (iDayOfWeek > 5) Then
            iOffset = iDayOfWeek - 4
        Else
            iOffset = iDayOfWeek + 3
        End If
 
        iDaysInMonth = Day(DateSerial(iYear, iMonth + 1, 0))
 
        For iDay = 1 To iDaysInMonth
            iColor = 10
            If isOffDay(DateSerial(iYear, iMonth, iDay)) Then iColor = 3
            If isPreHolyday(DateSerial(iYear, iMonth, iDay)) Then iColor = 6
            For j = 0 To 5
                If Mid(Right("00000" & Bin(CLng(iDay)), 5), j + 1, 1) = "1" 
                  Then Cells(iOffset + iDay - 1, iMonth * 6 + j - 4).Interior.ColorIndex = iColor
            Next j
        Next iDay
    Next iMonth
 
End Sub 'является ли указанная дата праздничным днём
 
Function isHolyday(dtDate As Date) As Boolean
Dim y As Integer
 
    y = Year(dtDate)
    isHolyday = False
 
    'Российские праздники
    '1, 2, 3, 4 и 5 января — Новогодние каникулы;
    If dtDate >= DateSerial(y, 1, 1) And dtDate <= DateSerial(y, 1, 5) Then isHolyday = True
    '7 января — Рождество Христово;
    If dtDate = DateSerial(y, 1, 7) Then isHolyday = True
    '23 февраля — День защитника Отечества;
    If dtDate = DateSerial(y, 2, 23) Then isHolyday = True
    '8 марта — Международный женский день;
    If dtDate = DateSerial(y, 3, 8) Then isHolyday = True
    '1 мая — Праздник Весны и Труда;
    If dtDate = DateSerial(y, 5, 1) Then isHolyday = True
    '9 мая — День Победы;
    If dtDate = DateSerial(y, 5, 9) Then isHolyday = True
    '12 июня — День России;
    If dtDate = DateSerial(y, 6, 12) Then isHolyday = True
    '4 ноября — День народного единства.
    If dtDate = DateSerial(y, 11, 4) Then isHolyday = True
 
End Function
 
'исключения из "стандартной схемы" выходных и рабочих дней
Function isExcept(dtDate As Date) As Boolean
 
    isExcept = False
 
    'обрабатываем переносы в 2012 году:
    'на Новый год и Рождество
    If dtDate = DateSerial(2012, 1, 6) Or dtDate = DateSerial(2012, 1, 9) Then isExcept = True
    'на 8 марта
    If dtDate = DateSerial(2012, 3, 9) Or dtDate = DateSerial(2012, 3, 11) Then isExcept = True
    'на 1 мая
    If dtDate = DateSerial(2012, 4, 28) Or dtDate = DateSerial(2012, 4, 30) Then isExcept = True
    'на 12 июня
    If dtDate = DateSerial(2012, 6, 9) Or dtDate = DateSerial(2012, 6, 11) Then isExcept = True
    'перед Новым Годом
    If dtDate = DateSerial(2012, 12, 29) Or dtDate = DateSerial(2012, 12, 31) Then isExcept = True
 
End Function
 
'является ли указанная дата выходным днем
Function isOffDay(dtDate As Date) As Boolean
 
    'выходные обычно суббота, воскресенье и праздники; остальные дни - рабочие
    Select Case Weekday(dtDate, vbMonday)
        Case 6, 7 'суббота, воскресенье
            isOffDay = True
        Case Else
            isOffDay = False
            If isHolyday(dtDate) Then isOffDay = True
    End Select
 
    'если день находится в исключениях, значит инвертируем выходной/рабочий
    If isExcept(dtDate) Then isOffDay = Not isOffDay
 
End Function
 
'предпраздничные дни (рабочие, но на час короче)
Function isPreHolyday(dtDate As Date) As Boolean
 
    isPreHolyday = False
    If dtDate = DateSerial(2012, 2, 22) _
    Or dtDate = DateSerial(2012, 3, 7) _
    Or dtDate = DateSerial(2012, 4, 28) _
    Or dtDate = DateSerial(2012, 5, 8) _
    Or dtDate = DateSerial(2012, 6, 9) _
    Or dtDate = DateSerial(2012, 12, 29) _
    Then isPreHolyday = True
 
End Function
 
'перевод десятичного числа в двоичное
Function Bin(ByVal x As Long) As String
  Dim s As String, d As Long, f As Boolean
  If x < 0 Then s = "1": f = True
  d = &H40000000
  Do
    If x And d Then
      s = s & "1": f = True
    Else
      If f Then s = s & "0"
    End If
    If d = 1 Then Exit Do
    d = d \ 2
  Loop
  Bin = s
End Function

на 2013 год

Код на PHP:

<php?
 /* А сам код я куда-то проглаголил. Ну и, в общем, не нужен он, так как ниже приведена реализация на javascript. */
?>

на 2016 год

Чуток CSS:

.c0,.c1,.c2,.c3 {width:5px;height:5px;float:left;margin:1px 1px 0 0;}
.c0{background-color:transparent !important;}
.c1{background-color:#F00;}
.c2{background-color:#080;}
.c3{background-color:#FF0;}
.n_bin_cal {min-width:700px;}

Код на javascript:

function inArray(arr,val) {var i=arr.length; while (i--) {if (arr[i].valueOf()==val.valueOf()) return true;} return false;}
 
//праздники
function isHolyday(d) {
  var year=d.getFullYear();
  return inArray(new Array(
    new Date(year,0,1),new Date(year,0,5), new Date(year,4,1), 
    new Date(year,0,2),new Date(year,0,7), new Date(year,4,9), 
    new Date(year,0,3),new Date(year,1,23),new Date(year,5,12),
    new Date(year,0,4),new Date(year,2,8), new Date(year,10,4)),d);
}
 
//дни - исключения (переносы)
function isExcept(d) {  
  return inArray(new Array(
    new Date(2016,0,6), new Date(2016,0,8),new Date(2016,1,22),
    new Date(2016,1,27),new Date(2016,2,7),new Date(2016,4,2),
    new Date(2016,4,3), new Date(2016,5,13)),d);
}
 
//предпраздничные дни (рабочие, но на час короче)
function isPreHolyday(d) {return inArray(new Array(new Date(2016,1,20),new Date(2016,10,3)),d);}
 
//является ли указанная дата выходным днём
function isOffDay(d) {
  switch (d.getDay()) {
    case 0: case 6: isOff=true; break; //субботы и воскресенья по умолчанию выходные
    default: isOff=false; if (isHolyday(d)) isOff=true; //работаем, если не праздник
  }
  if (isExcept(d)) isOff=!isOff; //инвертируем переносы
  return isOff;
}
 
function draw(y) {
  var bin=[1,2,4,8,16,32]; //это степени двойки, чтобы не считать
  var days_in_month=[31,28,31,30,31,30,31,31,30,31,30,31]; //дней в месяцах
  if ((y%4==0&&y%100!=0)||(y%400==0)) {days_in_month[1]=29;} //если високосный год, то в феврале 29 дней
  //заполняем матрицу - 72 (12 месяцев по 6 знакомест) столбцов на 37 строк
  // (31 день в месяце + сдвиг внутри недели до 6 дней)
  var a=new Array(72); for (i=0;i<a.length;i++) {a[i] = new Array(37);}
  for (i=0;i<a.length;i++) for (j=0;j<a[i].length;j++) a[i][j]=0;
 
  for (m=0;m<12;m++) { //перебираем месяцы
    w=(new Date(y,m,1)).getDay(); //вычисляем первый день месяца (для сдвига)
    k=1; //счётчик дней в месяце
    for (i=w;i<w+days_in_month[m];i++) {
      d=new Date(y,m,k); //текущая дата в календаре
      //номер класса в css: 0-пустышка, 1-красный, 2-зелёный, 3-жёлтый
      c=isOffDay(d)?1:2; if (isPreHolyday(d)) c=3; 
      for (b=0;b<6;b++) a[m*6+(5-b)][i]=(k&bin[b])?c:0;
      k++;
    }
  }
  //заполнили матрицу кодами классов, рисуем раскрашенную таблицу
  document.write("<center><table class='noborder n_bin_cal'><tr width=100% align=center><td width=100% align=center>");
  for (i=0;i<37;i++) {
    document.write("<div style='clear:both;'>");
    for (j=0;j<72;j++) {document.write("<div class='c"+a[j][i]+"'></div>");}
    document.write("</div>");
  }
  document.write("</td></tr></table></center>");
}
draw(2016);

на 2018 год

А на 2018 год пришлось немного допилить скрипт - заполнил массивы переносами и немного увеличил квадратики :). Получилось вот так:

<style>.c0,.c1,.c2,.c3 {width:8px;height:8px;float:left;margin:1px 1px 0 0;}</style>
<script type="text/javascript">
<!--
 
//дни - исключения (из-за переносов)
function isExcept(d) {  
  return inArray(new Array(
    new Date(2018,0,8),
    new Date(2018,2,9),
    new Date(2018,3,30),
    new Date(2018,4,2),
    new Date(2018,5,11),
    new Date(2018,11,31)
    ),d);
}
 
//предпраздничные дни (рабочие, но на час короче)
function isPreHolyday(d) {return inArray(new Array(
    new Date(2018,1,22),
    new Date(2018,2,7),
    new Date(2018,3,28),
    new Date(2018,4,8),
    new Date(2018,5,9),
    new Date(2018,11,29)
    ),d);
}
 
draw(2018);
//-->
</script>

на 2019 год

<script type="text/javascript">
<!--
 
//дни - исключения (из-за переносов)
function isExcept(d) {  
  return inArray(new Array(
    new Date(2019,0,8),
    new Date(2019,4,2),
    new Date(2019,4,3),
    new Date(2019,4,10),
    ),d);
}
 
//предпраздничные дни (рабочие, но на час короче)
function isPreHolyday(d) {return inArray(new Array(
    new Date(2019,1,22),
    new Date(2019,2,7),
    new Date(2019,3,30),
    new Date(2019,4,8),
    new Date(2019,5,11),
    new Date(2019,11,31)
    ),d);
}
 
draw(2019);
//-->
</script>

на 2020 год

<script type="text/javascript">
<!--
 
//дни - исключения (из-за переносов)
function isExcept(d) {  
  return inArray(new Array(
    new Date(2020,0,6),
    new Date(2020,0,8),
    new Date(2020,1,24),
    new Date(2020,2,9),
    new Date(2020,4,4),
    new Date(2020,4,5),
    new Date(2020,4,11),
    ),d);
}
 
//предпраздничные дни (рабочие, но на час короче)
function isPreHolyday(d) {return inArray(new Array(
    new Date(2020,3,30),
    new Date(2020,4,8),
    new Date(2020,5,11),
    new Date(2020,10,3),
    new Date(2019,11,31)
    ),d);
}
 
draw(2020);
//-->
</script>

на 2022 год

<script type="text/javascript">
<!--
 
//дни - исключения (из-за переносов)
function isExcept(d) {  
  return inArray(new Array(
    new Date(2022,0,6),
    new Date(2022,2,7),
    new Date(2022,4,2),
    new Date(2022,4,3),
    new Date(2022,4,10),
    new Date(2022,5,13),
    ),d);
}
 
//предпраздничные дни (рабочие, но на час короче)
function isPreHolyday(d) {return inArray(new Array(
    new Date(2022,1,22),
    new Date(2022,2,5),
    new Date(2022,10,3),
    ),d);
}
 
draw(2022);
//-->
</script>

на 2023 год

В этом году решил добавить ещё номер года над календариком :) Пришлось немного расширить массивчик степеней:

  var bin=[1,2,4,8,16,32,64,128,256,512,1024,2048]; //это степени двойки, чтобы не считать

и добавить чуток кода в функцию draw():

  //заголовок года в двоичном виде
  document.write("<div style='display:table; margin:0 auto; text-align:center; padding-bottom:10px;'>");
  for (i=11;i>=0;i--) {
    document.write("<div class='c"+((y&bin[i])?1:2)+"'></div>");
    if (i%4==0) {document.write("<div class='c0'></div>");}
  }
  document.write("</div>");

Ну и переменная добавочка в этом году выглядит вот так:

<script type="text/javascript">
<!--
 
//дни - исключения (из-за переносов)
function isExcept(d) {  
  return inArray(new Array(
    new Date(2023,0,6),
    new Date(2023,1,24),
    new Date(2023,4,8),
    ),d);
}
 
//предпраздничные дни (рабочие, но на час короче)
function isPreHolyday(d) {return inArray(new Array(
    new Date(2023,1,22),
    new Date(2023,2,7),
    new Date(2023,10,3),
    ),d);
}
 
draw(2023);
//-->
</script>

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

на 2024 год

В этом году много всяких переносов, поэтому исключений больше.

<script type="text/javascript">
<!--
//дни - исключения (из-за переносов)
function isExcept(d) {  
  return inArray(new Array(
    new Date(2024,3,27),
    new Date(2024,3,29),
    new Date(2024,3,30),
    new Date(2024,4,10),
    new Date(2024,10,2),
    new Date(2024,11,28),
    new Date(2024,11,30),
    new Date(2024,11,31),
    ),d);
}
 
//предпраздничные дни (рабочие, но на час короче)
function isPreHolyday(d) {return inArray(new Array(
    new Date(2024,1,22),
    new Date(2024,2,7),
    new Date(2024,4,8),
    new Date(2024,5,11),
    new Date(2024,10,2),
    ),d);
}
 
draw(2024);
//-->
</script>