C#: Calcular Semana Santa

Siempre he pensado que todo programador, de vez en cuando necesitamos alguna ida de olla y, sencillamente, nos sentamos delante del ordenador más por entretenimiento que por necesidad. En mi caso por lo menos, cuando ando muy agobiado con el resto del entorno, es algo que me libera bastante. Claro, que suele liberarme mucho más salir a tomar unas cuantas cervezas con los amiguetes, pero como en este caso no tenía ganas de estar resacoso a la mañana siguiente, pues opté por hacer algo que me entretuviese un rato y que fuera más sano.

Uno de los últimos artículos que publiqué en mi blog anterior versaba sobre el cálculo de ciertos días relacionados con la Semana Santa. No es que el día que lo hice tuviese también una pájara, es que ya se me van acabando las ideas para los ejercicios que tengo que ir haciendo para los alumnos (y eso que el ejercicio era de Excel).

Bueno, nos dejaremos de charlas superfluas como si estuviésemos en la barra del bar y, ya que lo hice, lo dejo para todo aquel que quiera aprovecharlo o, sencillamente, quiera distraerse un rato como yo. El caso es que me entretuve en crear un calendario en el que se pueden ver resaltados los días de Semana Santa de los años comprendidos en el intervalo (1900,2100). He dejado una versión del calendario en la página http://velasco.biz/html/SemanaSanta/Default.aspx, no para hacer publicidad, puesto que es una página que actualizo casi cada año (quizás dos) sino para poder ver el ejemplo funcionando.

Para la creación del calendario, una página con un combo para poder seleccionar el año (Vale, sé que es un DropDownList, pero no sé dónde habré oído lo del Combo) y una tabla de servidor sin ninguna celda es suficiente (ya la rellenamos dinámicamente)

 

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" 
    Inherits="JnSoftware.Samples.WebCalendar.Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Semana Santa - Sample</title>
</head>
<body>
    <form 
            id="form1" 
            runat="server">
    <h1>Semana Santa</h1>
    <br />
    Selecciona el año:
    <asp:DropDownList 
            ID="lstAnios" 
            runat="server" 
            Width="100px" 
            AutoPostBack="True"
            OnSelectedIndexChanged="lstAnios_SelectedIndexChanged">
    </asp:DropDownList>
    <div>
        <asp:Table 
            runat="server" 
            ID="tbContenido">
        </asp:Table>
    </div>
    </form>
</body>
</html>

 

El code-behind de la página tampoco es que sea excesivamente complicado. Lo que hago es que, una vez cargado los años que sé calcular en el combo (método CargaComboAnios), tan sólo es cuestión de crear un objeto Calendar para cada uno de los meses del año (método CargaCalendarios).

Quizás, el método que más me costó, imagino que por no estar lo suficientemente despierto es el encargado de marcar los días que quería (CargaDiasSemanaSanta), puesto que me empeciné en hacer cosas raras al ver que los días de Semana Santa podían llegar a dividirse en dos meses diferentes (no pongo nada de lo que intenté porque realmente me avergüenza). Al final opté por la creación de un Array de fechas y un recorrido por el mismo, de esta manera, incluso, puede aprovecharse para añadir otras fechas.

 

using System;
using System.Web.UI.WebControls;
using JnSoftware.Calculos;

namespace JnSoftware.Samples.WebCalendar
{
    public partial class Default : System.Web.UI.Page
    {
        // Personalización del número de filas y columnas de la tabla.
        int columnas = 3;
        int filas = 4;

        #region Eventos del WebForm
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
            {
                cargaComboAnios();
                cargaCalendarios();
            }
        }

        protected void lstAnios_SelectedIndexChanged(object sender, EventArgs e)
        {
            DropDownList combo = (DropDownList)sender;
            if (combo.SelectedValue != null)
                cargaCalendarios();
        }
        #endregion

        /// <summary>
        /// Realiza la carga de los años en el combo.
        /// </summary>
        private void cargaComboAnios()
        {
            DropDownList combo = lstAnios;
            for (int i = 1900; i <= 2100; i++)
                combo.Items.Add(
                    new ListItem(
                        text: i.ToString("#,#"),
                        value: i.ToString()
                    )
                );
            // Selección del año actual
            combo.SelectedValue = DateTime.Today.Year.ToString();
        }

        /// <summary>
        /// Realiza la carga de los calendarios en la tabla.
        /// </summary>
        private void cargaCalendarios()
        {
            Table tabla = tbContenido;
            tabla.Rows.Clear();

            // Formato de la tabla
            tabla.CellSpacing = 5;

            // Bucle de filas
            int mes = 1;
            for (int f = 0; f < filas; f++)
            {
                TableRow tr = new TableRow();

                // Bucle de celdas
                for (int c = 0; c < columnas; c++)
                {
                    TableCell tc = new TableCell();
                    tc.Controls.Add(GetCalendar(mes++));
                    tr.Cells.Add(tc);
                }
                tabla.Rows.Add(tr);
            }

            /* Cargados los calendarios, se marcan los días de 
             * semana santa */
            cargaDiasSemanaSanta();
        }

        /// <summary>
        /// Obtiene un objeto Calendar del mes pasado como parámetro.
        /// Este objeto Calendar se agregará a la tabla.
        /// </summary>
        private Calendar GetCalendar(int mes)
        {
            int anio = int.Parse(lstAnios.SelectedValue);

            Calendar c = new Calendar();
            // Id del control
            c.ID = "mes" + mes.ToString("00");
            // Selección del mes
            c.VisibleDate = new DateTime(anio, mes, 1);
            // Se desactiva la navegación
            c.ShowNextPrevMonth = false;
            // Sólo se muestra el mes en el encabezado.
            c.TitleFormat = TitleFormat.Month;
            // Desactivamos la selección
            c.SelectionMode = CalendarSelectionMode.None;
            // Pongamos la firma
            c.ToolTip = "JnSoftware - Calendario de "
                    + c.VisibleDate.ToString("MMM/yy");
            return c;
        }

        /// <summary>
        /// Marca los días de semana santa en el calendario
        /// </summary>
        private void cargaDiasSemanaSanta()
        {
            int anio = int.Parse(lstAnios.SelectedValue);

            SemanaSanta s = new SemanaSanta(anio);
            DateTime JuevesSanto = s.JuevesSanto;

            /* Tomamos como fiestas de semana Santa desde el 
             * jueves hasta el lunes */
            DateTime[] fechasSemanaSanta = new DateTime[5];
            for (int i = 0; i < 5; i++)
                fechasSemanaSanta[i] = JuevesSanto.AddDays(i);

            /* Se marcan los días en los calendarios */
            Table tabla = tbContenido;
            foreach (DateTime dia in fechasSemanaSanta)
            {
                int mes = dia.Month - 1;
                int fila = mes / columnas;
                int columna = mes % columnas;

                Calendar calendar = 
                    tabla.Rows[fila].Cells[columna].Controls[0] as Calendar;
                calendar.SelectedDates.Add(dia);
            }
        }
    }
}
 
 
Al final, este ha sido el resultado. Lo que no llegué a conseguir es la posibilidad de incluir algún tipo de ToolTip a las fechas añadidas para poder insertar el día de la fiesta o fecha señalada (si algún alma caritativa ha logrado algo similar, siempre sería de agradecer cualquier comentario).
Bueno, hasta aquí el post actual. Incluyo también la clase que ya publiqué en su día para el cálculo de los días de la Semana Santa, así queda todo como más ordenado.
 
using System;

namespace JnSoftware.Calculos
{

    /// <summary>
    /// Cálculo de Semana Santa
    /// </summary>
    public class SemanaSanta
    {

        #region Campos 
        private int a;
        private int b;
        private int c;
        private int d;
        private int e;
        private DateTime pascuaResurreccion;

        private int anio;

        #endregion

        /// <summary>
        /// Constructor de la clase
        /// </summary>
        /// <param name="anio">Entero que representa el año del que 
        /// se quiere calcular la semana santa.</param>
        /// <exception cref="ArgumentOutOfRangeException">
        /// Se produce cuando se intenta calcular
        /// la semana santa de un año no contemplado.</exception>
        public SemanaSanta(int anio)
        {
            try
            {
                this.anio = anio;
                calculaDomingoPascua();
            }
            catch { throw; }
        }

        /// <summary>
        /// Cálculo del domingo de Pascua o domingo de Resurrección.
        /// </summary>
        private void calculaDomingoPascua()
        {
            ParConstantes p = getPar(anio);
            a = anio % 19;
            b = anio % 4;
            c = anio % 7;
            d = (19 * a + p.M) % 30;
            e = (2 * b + 4 * c + 6 * d + p.N) % 7;

            if (d + e < 10)
                pascuaResurreccion = new DateTime(anio, 3, d + e + 22);
            else
                pascuaResurreccion = new DateTime(anio, 4, d + e - 9);

            // Excepciones
            if (pascuaResurreccion == new DateTime(anio, 4, 26))
                pascuaResurreccion = new DateTime(anio, 4, 19);

            if (pascuaResurreccion == new DateTime(anio, 4, 25) 
                    && d == 28 && e == 6 && a > 10)
                pascuaResurreccion = new DateTime(anio, 4, 18);
        }

        #region Constantes cálculo
        private struct ParConstantes
        {
            public int M { get; set; }
            public int N { get; set; }
        }

        private ParConstantes getPar(int anio)
        {
            ParConstantes p = new ParConstantes();
            if (anio < 1583)
            {
                throw
                    new ArgumentOutOfRangeException
                        ("El año deberá ser superior a 1583");
            }
            else if (anio < 1700) { p.M = 22; p.N = 2; }
            else if (anio < 1800) { p.M = 23; p.N = 3; }
            else if (anio < 1900) { p.M = 23; p.N = 4; }
            else if (anio < 2100) { p.M = 24; p.N = 5; }
            else if (anio < 2200) { p.M = 24; p.N = 6; }
            else if (anio < 2299) { p.M = 25; p.N = 0; }
            else
            {
                throw
                    new ArgumentOutOfRangeException
                        ("El año deberá ser inferior a 2299");
            }
            return p;
        }
        #endregion

        #region Propiedades públicas

     

        public DateTime MiercolesCeniza
        {
            get { return SabadoSanto.AddDays(7 * -6 - 3); }
        }

        public DateTime ViernesDolores
        {
            get { return pascuaResurreccion.AddDays(-9); }
        }

        public DateTime DomingoRamos
        {
            get { return pascuaResurreccion.AddDays(-7); }
        }

        public DateTime JuevesSanto
        {
            get { return pascuaResurreccion.AddDays(-3); }
        }

        public DateTime ViernesSanto
        {
            get { return pascuaResurreccion.AddDays(-2); }
        }

        public DateTime SabadoSanto
        {
            get { return pascuaResurreccion.AddDays(-1); }
        }

        public DateTime DomingoResurreccion
        {
            get { return pascuaResurreccion; }
        }

        #endregion

    }
}

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *