Solução para ‘bug’ de Horário de Verão (Daylight Saving)
Ao som de: Audioslave – Original Fire ♫
Fala galera, depois de quase 1 ano sem passar por aqui, dessa vez não por falta de tempo e sim por um pouco de preguiça confesso, venho trazer a vocês uma possivel solução para um problema que passei na empresa onde trabalho. Trago a solução
Problema: Ao utilizar o componente CalendarExtender so .net (configurado para apresentar em pt-BR) não é possivel selecionar a data 17/10/2010 (ou qualquer outra data que sejá ‘virada’ de horário de verão), onde ao selecionar o dia 17 ele automaticamente apresentava dia 16 e se inserir manualmente dia 17 o componente retorna como uma ‘Data Inválida”.
Causa: No Brasil o horário de verão entra em vigor à meia noite, adiantando assim o relógio para 1h da manhã, nosso GMT é -3 e no horário de verão -2, o que acontece, é que como à meia noite ele automaticamente vai para 1h a hora “17/10/2010 00:00:00″ passa a não mais existir, o problema não é o GMT, o problema é que não dá para configurar a função getDate() do javascript (imbutido no Calendar Extender).
Tentativa de solução: Tentar interceptar no global.asax ao solicitar uma data ao .net adicionar 1 hora, assim o “limbo da meia noite” seria contornado. Achei muitas dificuldades em conseguir ‘setar’ essa 1 hora, todos os métodos e propriedades referentes a Daylight Saving são apenas get e não é possivel modificar o getDate() utilizado.
Foi aí que lembrei que em um dos meus projetos pessoais utilizei jQuery para apresentar um calendário ao usuário, chama-se Date Picker, para utiliza-lo basta adicionar em seu formulário a API jQuery UI, é um componente totalmente personalizavel. Porém, pensando no conceito de reutilização de código criei um UserControl onde já é adicionado todas as referências de Javascript cabendo apenas referenciar o CSS nas páginas. Isso porque se eu adicionar o CSS no controle, a cada chamada do controle será colocado uma tag <link..>.
Criei um novo website (C#) utilizado o MS Visual Studio 2010, nomeei de DatePickerTest, modifiquei o web.config para poder utilizar alguns recursos do MS AjaxToolkit, criei uma pasta chamada dll, coloquei minha dll do toolkit e referenciei no projeto (o post não é para aprender a utilizar o toolkit então não entrarei muito em detalhes nesse post. No final colocarei um link com todo projeto configurado e funcionando).
Criei uma nova pasta chamada Controls, que será onde colocaremos os controles do projeto (tenho mania de organização), dentro dela adicionei um novo UserControl chamado DateBox.ascx
DateBox.ascx
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="DateBox.ascx.cs" Inherits="DatePickerTest.Controls.DateBox" %> <asp:ScriptManagerProxy ID="sm" runat="server" ClientIDMode="Predictable" /> <asp:TextBox runat="server" ClientIDMode="Predictable" ID="dateTextBox" Width="70px" MaxLength="10" /> <asp:RequiredFieldValidator ID="required" runat="server" ClientIDMode="Predictable" ControlToValidate="dateTextBox" ErrorMessage="Campo obrigatório" Display="None" Enabled="false" /> <asp:ValidatorCalloutExtender ID="ValCallExtRequired" TargetControlID="required" runat="server" ClientIDMode="Predictable" HighlightCssClass="ReqWarn" /> <asp:CustomValidator runat="server" ID="CustomVal" ControlToValidate="dateTextBox" ClientValidationFunction="validateDate" Display="None" EnableClientScript="true" ErrorMessage="Data inválida." ClientIDMode="Predictable" HighlightCssClass="ReqWarn" /> <asp:ValidatorCalloutExtender ID="ValidatorCalloutExtender1" TargetControlID="CustomVal" runat="server" ClientIDMode="Predictable" HighlightCssClass="ReqWarn" />
DateBox.ascx.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace DatePickerTest.Controls
{
[ValidationProperty("Date")]
public partial class DateBox : UserControl
{
protected void Page_Init(object sender, EventArgs e)
{
sm.Scripts.Add(new ScriptReference("~/scripts/jquery-1.3.2.js"));
sm.Scripts.Add(new ScriptReference("~/scripts/jquery.ui.core.js"));
sm.Scripts.Add(new ScriptReference("~/scripts/jquery.ui.datepicker.js"));
sm.Scripts.Add(new ScriptReference("~/scripts/util.js"));
}
protected void Page_PreRender(object sender, EventArgs e)
{
string script = @"$(function () {
$('#" + dateTextBox.ClientID + @"').datepicker({
showButtonPanel: true,
currentText: 'Hoje',
duration: 100,
dateFormat: 'dd/mm/yy',
showOn: 'button',
buttonImage: '/mapfrecap/img/calendario.gif',
buttonImageOnly: true,
dayNames: ['Domingo', 'Segunda', 'Terça', 'Quarta', 'Quinta', 'Sexta', 'Sábado', 'Domingo'],
dayNamesMin: ['D', 'S', 'T', 'Q', 'Q', 'S', 'S', 'D'],
dayNamesShort: ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb', 'Dom'],
monthNames: ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'],
monthNamesShort: ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'],
nextText: 'Próximo',
prevText: 'Anterior',
closeText: 'Fechar',
buttonText: 'Calendário',
showOtherMonths: true,
selectOtherMonths: true
});";
if (!Enabled)
script += "$('#" + dateTextBox.ClientID + @"').datepicker('disable');";
script += @"});";
ScriptManager.RegisterStartupScript(this, typeof(DateBox), "initDateBox" + dateTextBox.ClientID, script, true);
}
public DateTime? Date
{
get
{
return dateTextBox.Text != string.Empty ? (DateTime?)DateTime.Parse(dateTextBox.Text) : null;
}
set
{
dateTextBox.Text = value.Value.ToString("d");
}
}
public String ValidationGroup
{
get { return required.ValidationGroup; }
set { required.ValidationGroup = value; CustomVal.ValidationGroup = value; }
}
public bool Required
{
get { return required.Enabled; }
set { required.Enabled = value; }
}
public bool Enabled { get { return dateTextBox.Enabled; } set { dateTextBox.Enabled = value; } }
public bool ReadOnly { get { return dateTextBox.ReadOnly; } set { dateTextBox.ReadOnly = value; } }
public event EventHandler TextChanged { add { dateTextBox.TextChanged += value; } remove { dateTextBox.TextChanged -= value; } }
public bool AutoPostBack
{
get { return dateTextBox.AutoPostBack; }
set { dateTextBox.AutoPostBack = value; }
}
public override string ClientID
{
get
{
return dateTextBox.ClientID;
}
}
}
}
Até aqui o componente está finalizado, com algumas particularidades que eu mesmo implementei, como uma validação de o campo será obrigatório ou nao (Required), se o componente estará ativo ou nao (Enabled), propriedade de AutoPostBack, ValidationGroup, etc. Caso surja dúvidas sobre o código “[ValidationProperty("Date")]” explico aqui… Esse código é para ‘expor’ o valor de Date (que é onde fica armazenado o valor selecionado pelo calendário) à pagina que irá utiliza-lo, podendo assim fazer validações com CompareValidator.
No nosso DateBox.ascx temos um ClientValidationFunction=”validateDate”, essa é a função javascript que eu fiz para validar a data do nosso calendário, é aqui que contornamos o problema de horario de verão, dentro da nossa pasta ‘scripts’ teremos um arquivo chamado ‘util.js’ com o seguinte conteúdo:
util.js
function validateDate(oSrc, args) {
var iDay, iMonth, iYear;
var arrValues;
arrValues = args.Value.split("/");
iDay = arrValues[0];
iMonth = arrValues[1];
iYear = arrValues[2];
var testDate = new Date(iYear, iMonth - 1, iDay, 4, 0, 0, 0);
if ((testDate.getDate() != iDay) ||
(testDate.getMonth() != iMonth - 1) ||
(testDate.getFullYear() != iYear)) {
args.IsValid = false;
return;
}
return true;
}
O que nos importa nessa validação é parte em negrito => var testDate = new Date(iYear, iMonth – 1, iDay, 4, 0, 0, 0);
Ali estamos adicionando 4 horas à data selecionada, eliminando assim o “limbo” da meia noite não existente, como só queremos mesmo é a Data e nao o horário, isso resolve nosso problema. Tudo isso porque não conseguimos sobrescrever a função de getDate() do CalendarExtender.
Agora um pequeno exemplo do como utiliza-lo:
Default.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="DatePickerTest.Default" %> <%@ Register Src="~/Controls/DateBox.ascx" TagName="DateBox" TagPrefix="uc" %> <!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></title> <link rel="stylesheet" href="~/styles/jquery-ui-1.7.3.custom.css" type="text/css" media="screen" runat="server" /> </head> <body> <form id="form1" runat="server"> <asp:ScriptManager ID="sm" runat="server" /> <div> <uc:DateBox runat="server" ID="edtData" /> </div> </form> </body> </html>
Simple não? Só precisamos fazer 3 coisas:
1 – Registrar o componente => <%@ Register Src=”~/Controls/DateBox.ascx” TagName=”DateBox” TagPrefix=”uc” %>
2 – Adicionar o CSS (pelo motivo dito na introdução do post)
3 – Adicionar o componente onde você precisar => <uc:DateBox runat=”server” ID=”edtData” />
Para obter o valor pelo seu CS é só utilizar ‘nomeComponente.Date’ como é um nullable em alguns casos você pode precisar utilizar um .Value (nomeComponente.Date).
Galera, espero que todos tenham entendido, caso contrario só comentar dizendo qual linha não entendeu que eu tiro as dúvidas, e em casos de erro especificar exatamente o erro, com linha e o código da linha para que fique mais facil de ajudar. Abaixo o projeto com nosso DateBox funcionando pra quem quiser, não coloquei online pois minha hospedagem é PHP =/
Grande abraço, até a próxima.