Mostrando entradas con la etiqueta FormatSettings. Mostrar todas las entradas
Mostrando entradas con la etiqueta FormatSettings. Mostrar todas las entradas

jueves, 23 de enero de 2020

Clase para medir tiempo de ejecución.

Si bien esta entrada que escribí hace exactamente dos años (increíble, juro que es coincidencia) acerca de medir el tiempo de ejecución de un proceso es totalmente válida y funcional, mejor aún es hacer una clase (class) que haga lo mismo. Las ventajas son muchas, desde la legibilidad del código hasta la manera más simple se su reutilización.

La clase es simple:

type
  TMedirTiempo=class(TObject)
private
  FInicio:TTime;
  FFinaliza:TTime;
  FTranscurrido:TTime;
public
  constructor Create (Owner:TComponent);
  function Iniciar:TTime;
  function Finalizar:TTime;
  function Transcurrido:TTime;
end;


Cómo estoy comenzando a implementar clases, luego de leer bastante, voy a explicar un poco algunas cosas que la mayoría de los colegas programadores ya saben, pero los que retomamos la programación luego de muchos años, no.

Por ejemplo, la clase la definimos como class(TObject) porque es una clase que no hereda de ninguna otra clase y la más "primitiva" es TObject. Ahora:
TMedirTiempo=class así sin TObject también es válido, pero no recomendado.
FInicio es un campo (Field) por eso se acostumbra a empezar con la letra F; los campos son variables que pertenecen a una clase. Si se declaran en la "sección" (en realidad no es sección aunque lo aparenta muy bien, private es un identificador) private solo podrá se accedido por otros miembros de la clase y clases heredadas pero no por la instancia de la clase, por ejemplo:

var
  Medidor:TMedirTiempo;
begin
  Medir:=TMedirTiempo.Create(nil);
  Medir.Finicio:= // esto no se puede.

¿Por qué usamos un constructor? Porque en él inicializamos los campos, pero muchas veces no es necesario definirlo, en tal caso se utiliza el heredado de TObject. Y sí, el constructor es público, lo mismo que las funciones, lo que implica que se pueden invocar en la unidad donde instanciamos la clase.

Hasta aquí la interfaz, vamos ahora a la implementación de la clase.

constructor TMedirTiempo.Create(Owner: TComponent);
begin
  FInicio:=StrToTime('0');
  FFinaliza:=StrToTime('0');
  FTranscurrido:=StrToTime('0');
end;

function TMedirTiempo.Iniciar: TTime;
begin
  FInicio:=Now;
end;

function TMedirTiempo.Finalizar: TTime;
begin
  FFinaliza:=Now;
end;

function TMedirTiempo.Transcurrido: TTime;
begin
  FTranscurrido:=FFinaliza-FInicio;
  Result:=FTranscurrido;
end;


Y para utilizarla es muy simple, por ejemplo:

Procedure CargarSList;
var
  MedirTiempo:TMedirTiempo;
  sl:TStringList;
  i:Integer;
begin
  sl:=TStringList.Create;
  MedirTiempo:=TMedirTiempo.Create(nil);
  MedirTiempo.Iniciar;
  for i:=1 to 100000 do
    sl.Add(IntToStr(i));
  MedirTiempo.Finalizar;
  ShowMessage('Tiempo transcurrido: '+TimeToStr(MedirTiempo.Transcurrido));
  sl.Free;
  MedirTiempo.Free;
end;


También podríamos mostrar la hora de inicio y fin de la medición, porque ambas son funciones que devuelven el tiempo.

Puede pasar (y pasa) que el tiempo es menor a un segundo, entonces se muestra 0 (cero). Una opción para agregar los milisegundos es utilizar la misma función TimeToStr (que se ubica en la unidad DateUtils) con la opción FormatSettings.
Sería algo así:

Procedure CargarSList;
var
  MedirTiempo:TMedirTiempo;
  sl:TStringList;
  i:Integer;
  fs:TFormatSettings;
begin
  fs:=DefaultFormatSettings;
  fs.LongTimeFormat:='hh:nn:ss.zzz';
  sl:=TStringList.Create;
  MedirTiempo:=TMedirTiempo.Create(nil);
  MedirTiempo.Iniciar;
  for i:=1 to 100000 do
    sl.Add(IntToStr(i));
  MedirTiempo.Finalizar;
  ShowMessage('Tiempo transcurrido: '+TimeToStr(MedirTiempo.Transcurrido,fs));
  sl.Free;
  MedirTiempo.Free;
end;


TFormatSettings es un registro (record) por lo tanto ni se crea ni se libera.

viernes, 21 de junio de 2019

Formato de fecha y hora en SQLite.

En SQLite el formato de la fecha/hora es YYYY-MM-DD HH:MM.SS.MMM por ejemplo: 2019-06-22 01:00:27.123

Si bien Free Pascal tiene muchas funciones para el tratamiento de fecha y hora, a veces por la configuración regional hay que hacer conversiones.

Usando TFormatSettings:

aFormatSettings.LongDateFormat:='yyyy-mm-dd hh:nn:ss';
aFormatSettings.DateSeparator := '-';
aFormatSettings.TimeSeparator := ':';


Donde aFormatSettings es una variable del tipo TFormatSettings y luego utilizando la función FormatDateTime que devuelve un string:

function FormatDateTime(

const FormatStr: string;

DateTime: TDateTime;

Options: TFormatDateTimeOptions = []

):string;

function FormatDateTime(

const FormatStr: string;

DateTime: TDateTime;

const FormatSettings: TFormatSettings;

Options: TFormatDateTimeOptions = []

):string;


Por ejemplo en una sentencia SQL en WHERE:

sqlWhere:='WHERE regfecha BETWEEN '+QuotedStr(FormatDateTime('YYYY-MM-DD',edFechaDesde.Date))+
' AND '+QuotedStr(FormatDateTime('YYYY-MM-DD',edFechaHasta.Date))';


En este caso no fue necesario TFormatSettings y solo fue fecha, sin hora.

Con TZquery.FieldByName y utilizando componentes TDateTimePicker esto funciona:

ZQReg.FieldByName('regfechahora').AsDateTime:=dtpFecha.Date+dtpHora.Time;

En cuanto los campos del tipo TIME en SQLite; StrToTime('00:00:00') evita el valor nulo que muchas veces conviene evitar.

En SQLite la fecha debe ir entre comillas simples.

Y ante cualquier complicación siempre podemos hacer una función como la siguiente:

function FormatoFechaHoraSQLite(lafechahora: TDateTime): String;
var
  d,m,a,h,mi,s,ms:Word;
  dd,mm,hh,mmi,ss,mms:String;
  lfecha, lhora:String;
begin
  DecodeDate(lafechahora,a,m,d);
  if d < 10 then dd:='0'+IntToStr(d) else dd:=IntToStr(d);

  if m < 10 then mm:='0'+IntToStr(m) else mm:=IntToStr(m);
  lfecha:=IntToStr(a)+'-'+mm+'-'+dd;
  DecodeTime(lafechahora,h,mi,s,ms);
  if h < 10 then hh:='0'+IntToStr(h) else hh:=IntToStr(h);
  if mi < 10 then mmi:='0'+IntToStr(mi) else mmi:=IntToStr(mi);
  if s < 10 then ss:='0'+IntToStr(s) else ss:=IntToStr(s);
  if ms < 10 then mms:='00'+IntToStr(ms) else
    if ms < 100 then mms:='0'+IntToStr(ms) else
      mms:=IntToStr(ms);
  lhora:=hh+':'+mmi+':'+ss+'.'+mms;
  Result:=lfecha+' '+lhora;
end;

O más sencillo usando la función AddChar que se encuentra en la unidad strutils.

function FormatoFechaHoraSQLite2(lafechahora: TDateTime): String;
var
  d,m,a,h,mi,s,ms:Word;
  dd,mm,hh,mmi,ss,mms:String;
  lfecha, lhora:String;
begin
  DecodeDate(lafechahora,a,m,d);
  dd:=AddChar('0',IntToStr(d),2);
  mm:=AddChar('0',IntToStr(m),2);
  lfecha:=IntToStr(a)+'-'+mm+'-'+dd;
  DecodeTime(lafechahora,h,mi,s,ms);
  hh:=AddChar('0',IntToStr(h),2);
  mmi:=AddChar('0',IntToStr(mi),2);
  ss:=AddChar('0',IntToStr(s),2);
  mms:=AddChar('0',IntToStr(ms),3);
  lhora:=hh+':'+mmi+':'+ss+'.'+mms;
  Result:=lfecha+' '+lhora;
end;


DecodeDate y DecodeTime son procedimientos y utiliza variables del tipo Word que son enteros sin signo entre 0 y 65535. Le enviamos un TDateTime y las variables del tipo Word donde se escribirán el año, mes y día (en DecodeDate). No es necesario inicializar las variables, el procedimiento utiliza out:

procedure DecodeDate(

Date: TDateTime;

out Year: Word;

out Month: Word;

out Day: Word

);

martes, 18 de junio de 2019

Número a texto con formato.

Pasar de número a string utilizando la función Format y el registro TFormatSettings. También se puede usar Format para cadenas de texto (strings), básicamente para alinearlas y/o recortarlas, no obstante su potencia radica en lo numérico, no solo enteros y decimales, también hexadecimales, exponenciales y hasta punteros. Ejemplos de todos ellos en: https://www.freepascal.org/docs-html/rtl/sysutils/format.html la cual recomiendo leer antes de continuar. Como también las referidas a TFormatSettings y DefaultFormatSettings.

Como lo indica su nombre, DefaultFormatSettings, es una variable global del tipo TFormatSettings que contiene los valores actuales de formato, si se altera, afectará a todo el proyecto, por ende debe usarse con algunos recaudos, como ser, por ejemplo, si necesito que en una función se utilice el separador decimal punto pero luego continuar utilizando la coma, entonces una opción es, antes de llamar a la función usar DefaultFormatSettings.DecimalSeparator:='.' e inmediatamente en la siguiente línea de código que sucede a la función DefaultFormatSettings.DecimalSeparator:=',' o, lo mismo pero dentro de la función.

Otra opción, y según las necesidades, es crear una variable del tipo TFormatSettings (que es del tipo record) y especificar en ella únicamente los formatos que vamos a utilizar.

Recordemos que Format devuelve un string así que puede usarse casi para cualquier cosa, pero, siempre hay un pero, a nivel IDE, más precisamente en el componente TMemo, no produce siempre los resultados deseados, el problema es con los espacios y el ancho del caracter, a no ser que exista alguna Font de tamaño fijo que desconozco.

En mi caso, necesitaba imprimir unas tablas (no tablas de bases de datos) y no tenía ganas de recurrir a LazReport, porque era algo muy simple, por eso mi primera opción fue TMemo. Pero por qué no aprovechar lo que ya está hecho, por ejemplo un editor de texto plano que cualquier sistema operativo posee.
Fue entonces que opté por TStringList que dispone de dos procedimientos elementales: LoadFromFile y SaveToFile. Finalmente utilizo OpenDocument y listo, de ahí en más se encarga el sistema operativo.

procedure TForm1.BTablaRetGcias2019Click(Sender: TObject);
var
  mes:Integer=0;
  ind:Integer=0;
  FS:TFormatSettings; //Creo la variable del tipo TFormatSettings
  sl:TStringList; //Creo la variable del tipo TStringList
begin
  sl:=TStringList.Create;  //Creo la instancia porque TStringList es una clase
  FS.CurrencyFormat:=1;  //FS es un type record y no necesita instanciasión
  FS.CurrencyDecimals:=2;
  FS.DecimalSeparator:=',';
  FS.ThousandSeparator:='.';
  FS.CurrencyString:='';
  sl.Add('Año 2019');
  sl.Add('');
  sl.Add('Mes Fijo Alíc.% Exedente');
  for mes:=1 to 12 do
  begin
    sl.Add('--------------------------------------');
    for ind:=1 to 9 do
      sl.Add(
      Format('%2.0d',[mes])+
      Format('%14.2m',[TablaGcias19[mes,ind].fijo],FS)+
      Format('%6.0m',[TablaGcias19[mes,ind].alicuota],FS)+
      Format('%16.2m',[TablaGcias19[mes,ind].exedente],FS));
      //%16 cantidad de caracteres .2 es 2 decimales y m es que el argumento es currency
  end;
  sl.SaveToFile('SL_Tabla_2019.txt');
  OpenDocument('SL_Tabla_2019.txt');
  sl.Free;
end;


Algunas aclaraciones:

FS.CurrencyFormat: en este caso no importa el valor, porque lo anulo con FS.CurrencyString:=''. Pero ya que estamos, lo valores para este campo (CurrencyFormat) pueden ser:
0 : $1
1 : 1$
2 : $ 1
3 : 1 $

En este caso TablaGcias19 es una matriz del registro TTablaGcias

TTablaGcias = record
  fijo:Currency;
  exedente:Currency;
  alicuota:Currency;
end;


Y con muy poco código se obtiene esto:



Al final se libera el TStringList y de la variable FS que usamos para FormatSettings nos olvidamos ya que es local a la función y del tipo record.

miércoles, 6 de septiembre de 2017

DBGrid: Formato de la columna según el tipo de campo

En tiempo de diseño y conociendo la tabla es una cosa, pero en tiempo de ejecución y sin saber nada de la tabla, ya es distinto. Para empezar nos encontramos con tipos de datos enumerados y por ende, debemos conocerlos. Luego hay que recorrer las columnas o campos y definir el formato según lo que necesitemos, siendo lo normal los tipos de dato numéricos y de fecha. Para el ejemplo se recorrerán las columnas de una tabla SQL y se cambiará el formato del DBGird si el campo es numérico decimal.

procedure TForm1.FormatoColumnasGrid;
var
  ind:Integer;
begin
  FormatSettings.DecimalSeparator:='.';
  for ind:=0 to ZQ.FieldCount-1 do
    if (ZQ.FieldDefs.Items[ind].DataType=ftFloat) or (ZQ.FieldDefs.Items[ind].DataType=ftCurrency)
    or (ZQ.FieldDefs.Items[ind].DataType=ftBCD) then
      DBGrid1.Columns[ind].DisplayFormat:='#0.00';
end;


Este procedimiento se llama luego de habilitarse el DBGrid. Se establece el separador decimal para establecer correctamente el formato aunque también se podría hacer de forma automática o preguntarle al usuario.

Se utiliza FieldCount – 1 porque la indexación del ZQuery (de ZeosLib) comienza por cero. DataType es del tipo enumerado, para SQL los valores de numéricos con decimales son: ftFloat, ftCurrency y ftBCD,

Listado de tipos de datos completo en español 

Documentación de TFieldType