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); } } } }
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 } }