Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   Firebird e Interbase (https://www.clubdelphi.com/foros/forumdisplay.php?f=19)
-   -   Firebird 2.5 y UTF8 (https://www.clubdelphi.com/foros/showthread.php?t=70685)

Chandra_ 04-11-2010 21:17:50

Firebird 2.5 y UTF8
 
Hola, estaba preparando una aplicación que probablemente se tenga que ver en idiomas distintos del español y he pensado que, lógicamente, debería de crear la base de datos Firebird 2.5 con el charset UTF8 (en lugar del WIN1252, que es el que he venido usando hasta ahora para español, porque alguien me lo recomendó).

¿Es esto correcto? ¿Esta es la solución para que funcione bien en inglés, francés, alemán...?

Perdonad si la pregunta es muy obvia, pero es que nunca había programado antes algo que se usase con un idioma distinto al español.

Gracias

EDITO: Acabo de caer en la cuenta de que con Delphi 2007 puedo tener problemas, al no ser unicode. ¿Debería pensar en actualizarme a Delphi 2010?

Gallosuarez 04-11-2010 22:54:06

Iso8859_1
 
Chandra:

En tu caso, yo utilizaría el juego de caracteres ISO8859_1. Este es suficiente para manejar los idiomas que mencionas en tu pregunta, incluso trae "Collates" que no son sensitivos a mayúsculas/minúsculas ni a los acentos (como es el caso del español y del francés [creo que también del portugués, aunque no estoy muy seguro]). Otras de las ventajas del ISO8859_1 es que es "unicode", es decir, la representación gráfica de un caracter solo utiliza 1 byte, lo cual resulta después benéfico en el tamaño y desempeño de la base de datos.

Saludos,
Gerardo Suárez Trejo

guillotmarc 05-11-2010 12:36:35

Como dice el compañero, con el juego de carácteres iso8859_1 no vas a tener ningún problema con los idiomas que has comentado.

Aquí puedes ver los idiomas soportados por cada charset

http://www.destructor.de/firebird/charsets.htm

Solo necesitas pasarte a Unicode si también vas a usar lenguas como árabe, chino, japonés, o lenguas de europa del este como ruso, búlgaro, serbio, etc. ... Pero para toda Europa Occidental y América, con ISO8859_1 tienes suficiente.

Donde se he equivocado un poco Gallosuarez es en los beneficios de no usar Unicode. Por ejemplo, respecto al tamaño Firebird utiliza UTF8, que o bien estoy muy equivocado o bien para la inmensa mayoría de los símbolos de nuestro alfabeto solo necesita 1 byte (aunque los símbolos especiales como la ñ, van a usar más bytes, ya que UTF8 tiene un tamaño de letra variable). Y respecto a los "Collates", Firebird 2.5 ya ha incorporado un UNICODE_CI_AI, por lo que ya no hay muchas razones para no usar Unicode.

NOTA: La verdad es que aún no he programado nunca en Unicode, así que no me atrevo a hacerte ninguna recomendación.

Saludos.

Chandra_ 05-11-2010 13:29:02

Muchísimas gracias a los dos por despejar tan detalladamente mis dudas. Con gente así, da gusto preguntar :)

Gracias por la dirección, guillotmarc, me viene de perlas para futuras referencias.

Lo que no termino de tener del todo claro es las diferencias entre el estándar ISO 8859_1 y el Unicode. La Wikipedia, por ejemplo, define el ISO 8859-1 como "una norma de la ISO que define la codificación del alfabeto latino..." y el Unicode como "un estándar de codificación de caracteres diseñado para facilitar el tratamiento informático, transmisión y visualización de textos de múltiples lenguajes". A primera vista, parecen la misma cosa... no entiendo la diferencia y por qué unos ISOs son Unicode y otros no...

Lo de los Collates es interesantísimo en las búsquedas para evitar diferencias entre mayúsculas/minúsculas y palabras con tildes. Buscaré por ahí los comandos necesarios para lanzar consultas a Firebird usando dichos Collates. Espero que no me den problemas con dbExpress, porque, como comentaba en otros hilos, no uso driver específico de Firebird, sólo el de Interbase de D2007. Pero, en fin, eso es ya otra historia ;)

EDITO:

Ya he encontrado lo de los collates. Supongo que algo así me serviría:

SELECT * FROM tabla
ORDER BY lo_que_sea COLLATE ES_ES_CI_AI

Entiendo que así es para español y, además de ser case insensitive, no tiene en cuenta los acentos.

Me acabáis de descubrir un mundo :) Menuda solución a todos los problemas que siempre he tenido con mayúsculas/minúsculas/acentos! (sí... soy un bruto por no saber que esto existía ;) )

guillotmarc 05-11-2010 14:12:03

Hola.

Cita:

Empezado por Chandra_ (Mensaje 381409)
Muchísimas gracias a los dos por despejar tan detalladamente mis dudas. Con gente así, da gusto preguntar :)

Gracias por la dirección, guillotmarc, me viene de perlas para futuras referencias.

Lo que no termino de tener del todo claro es las diferencias entre el estándar ISO 8859_1 y el Unicode. La Wikipedia, por ejemplo, define el ISO 8859-1 como "una norma de la ISO que define la codificación del alfabeto latino..." y el Unicode como "un estándar de codificación de caracteres diseñado para facilitar el tratamiento informático, transmisión y visualización de textos de múltiples lenguajes". A primera vista, parecen la misma cosa... no entiendo la diferencia y por qué unos ISOs son Unicode y otros no...

Todos los charsets sirven para codificar los alfabetos de un grupo de lenguas. Pero el iso8859_1 solo sirve para codificar el alfabeto de las lenguas de Europa Occidental, mientras que Unicode pretende poder codificar los alfabetos de todas las lenguas del mundo.

Como el iso8859_1 solo codifica un pequeño grupo de lenguas, puede guardar cada letra del alfabeto en un solo byte. En cambio Unicode tiene que codificar un montón de letras de distintos alfabetos, las letras más comunes A B C D ... las codifica en un solo byte, pero para codificar letras de alfabetos más complicados como el chino va a necesita bastantes más bytes para cada letra.

Esta es una de las grandes diferencias entre Unicode y los demás charsets (iso8859_1 y compañía) que Unicode tiene un tamaño de letra variable y los charsets tradicionales tienen un tamaño de letra fijo. Por eso a los lenguajes de programación, bases de datos, sistemas operativos, ... les resulta más complejo tener que lidiar con Unicode.

Cita:

Empezado por Chandra_ (Mensaje 381409)
Lo de los Collates es interesantísimo en las búsquedas para evitar diferencias entre mayúsculas/minúsculas y palabras con tildes. Buscaré por ahí los comandos necesarios para lanzar consultas a Firebird usando dichos Collates. Espero que no me den problemas con dbExpress, porque, como comentaba en otros hilos, no uso driver específico de Firebird, sólo el de Interbase de D2007. Pero, en fin, eso es ya otra historia ;)

EDITO:

Ya he encontrado lo de los collates. Supongo que algo así me serviría:

SELECT * FROM tabla
ORDER BY lo_que_sea COLLATE ES_ES_CI_AI

Entiendo que así es para español y, además de ser case insensitive, no tiene en cuenta los acentos.

Me acabáis de descubrir un mundo :) Menuda solución a todos los problemas que siempre he tenido con mayúsculas/minúsculas/acentos! (sí... soy un bruto por no saber que esto existía ;) )

Yo no lo especifico en las consultas, porqué mis campos ya tienen definido que usan este Collate (fíjate en IBExpert o en el programa que uses para gestionar tu base de datos que puedes especificar los Collates a nivel de campos). De esta forma, sin decirle nada a las consultas, ya saben que ordenación (Collate) hay que aplicar en función del campo implicado.

Para no complicarme la vida y no tener que andar siempre especificándolo cada vez que doy de alta un campo, lo tengo definido en los Dominios que utilizo para especificar los tipos de los campos. Como puedes ver, en los Dominios también puedes marcar el Charset y Collate de los campos que lo apliquen.

Casimiro Notevi 05-11-2010 14:29:22

Cita:

Empezado por guillotmarc (Mensaje 381410)
[..
Yo no lo especifico en las consultas, porqué mis campos ya tienen definido que usan este Collate (fíjate en IBExpert o en el programa que uses para gestionar tu base de datos que puedes especificar los Collates a nivel de campos). De esta forma, sin decirle nada a las consultas, ya saben que ordenación (Collate) hay que aplicar en función del campo implicado.

Para no complicarme la vida y no tener que andar siempre especificándolo cada vez que doy de alta un campo, lo tengo definido en los Dominios que utilizo para especificar los tipos de los campos. Como puedes ver, en los Dominios también puedes marcar el Charset y Collate de los campos que lo apliquen.

Para que Chandra_ lo tenga más claro, pongo un ejemplo:
Código SQL [-]
set sql dialect 3; 
 
set names none; 
 
create database "rankings.fdb" PAGE_SIZE 8192 user "SYSDBA" password "masterkey"; 
 
/**/ 
create domain domCodigoNoNulo integer not null; 
create domain domNombre varchar(64) character set ISO8859_1;  /* fb < 2.1 */ 
/*create domain domNombre varchar(64) character set UTF8 collate ES_ES_CI_AI default '';*/  /* fb >= 2.1 */ 
create domain domImagen blob sub_type 0; 
create domain domFecha date; 
create domain domHora time; 
create domain domFechaHora timestamp;
...

Gallosuarez 05-11-2010 15:44:15

Algunas razones...
 
Cita:

Empezado por guillotmarc (Mensaje 381406)
Donde se he equivocado un poco Gallosuarez es en los beneficios de no usar Unicode. Por ejemplo, respecto al tamaño Firebird utiliza UTF8, que o bien estoy muy equivocado o bien para la inmensa mayoría de los símbolos de nuestro alfabeto solo necesita 1 byte (aunque los símbolos especiales como la ñ, van a usar más bytes, ya que UTF8 tiene un tamaño de letra variable). Y respecto a los "Collates", Firebird 2.5 ya ha incorporado un UNICODE_CI_AI, por lo que ya no hay muchas razones para no usar Unicode.

De acuerdo a las pruebas que he realizado, me he encontrado que al tratar de utilizar un UUID (Universal Unique Identifier [Identificador Único Universal]) utilizando el juego de caracteres UTF8, al tratar de insertar dicho campo Firebird protesta. La primera suposición que hice fue que al utilizar UTF8 el tamaño del campo ID tendría que ser mayor (Firebird especifica que un UUID debe de ser un VARCHAR(16), y puesto que UTF8 puede utilizar 1,2,3 y hasta 4 bytes para representar un caracter; entonces tendría que utilizar un campo mayor. Por lo tanto, el campo debe de ser mayor, el índice que se crea es mayor y al final de cuentas tu tabla crece mas, y esto repercute en tamaño y en desempeño (de allí mi afirmación en el primer "post").

Otra cosa que note es que al insertar campos que contienen á, é, í, ó, ú, ñ, Ñ (y algunos otros como la u con diéresis), efectivamente utilizaban dos caracteres. Esto me acarreo el siguiente problema: la función CHAR_LENGTH me reporta un conteo mayor en la longitud de la cadena. Tengo funciones que hacen el cálculo automático del RFC (CIF para otro países) y dejaron de funcionar correctamente.

Como pueden ver, estos son los problemas que me he encontrado al tratar de utilizar UTF8. Al menos para mi si son algunas razones para no utilizarlo (por el momento), sin embargo, me pregunto si hay alguien en el foro que ya haya sorteado todos estos problemas. Tal vez nos pueda comentar y nos convenza de que es mejor opción UTF8.

Saludos,
Gerardo Suárez Trejo

Un saludo a Guillotemarc, sus comentarios siempre me parecen atinados y enriquecedores en la mayoría de los casos.

Casimiro Notevi 05-11-2010 16:27:24

Cita:

Empezado por Gallosuarez (Mensaje 381420)
[..]
Otra cosa que note es que al insertar campos que contienen á, é, í, ó, ú, ñ, Ñ (y algunos otros como la u con diéresis), efectivamente utilizaban dos caracteres. Esto me acarreo el siguiente problema: la función CHAR_LENGTH me reporta un conteo mayor en la longitud de la cadena. Tengo funciones que hacen el cálculo automático del RFC (CIF para otro países) y dejaron de funcionar correctamente.
[..]

Ese caso en particular es fácil de solucionar, para los CIF sólo valen letras "normales" sin tildes: A,B,C,D,E, etc.

Gallosuarez 05-11-2010 17:17:05

Confundido...
 
Casimiro:

O no te sabes explicar :confused:, o yo no te entiendo..... porque no te sabes explicar.... (je, je, je) una pequeña broma.

Código SQL [-]
create or alter procedure PROPER_VOWEL (
    C char(1))
returns (
    VOWEL char(1))
AS
begin
  vowel = C;
  if (C in ('Á', 'a', 'á')) then vowel = 'A';
  if (C in ('É', 'e', 'é')) then vowel = 'E';
  if (C in ('Í', 'i', 'í')) then vowel = 'I';
  if (C in ('Ó', 'o', 'ó')) then vowel = 'O';
  if (C in ('Ú', 'u', 'ú')) then vowel = 'U';
end

Procedimiento almacenado que convierte un vocal acentuado a una vocal sin acento.... (se puede extender fácilmente para vocales con acento circunflejo en caso de idiomas como el Francés y el Portugués (creo).

Código SQL [-]
create or alter procedure CALC_RFC (
    A_PATERNO varchar(25),
    A_MATERNO varchar(25),
    NOMBRE varchar(30),
    F_NACIMIENTO date)
returns (
    RFC varchar(13))
AS
declare variable dd char(2); /* día de nacimiento */
declare variable mm char(2); /* mes de nacimiento */
declare variable aa char(2); /* año de nacimiento */
declare variable pv char(1) = ''; /* primera vocal en la cadena*/
declare variable cadena varchar(24);
declare variable i smallint = 1;
declare variable len smallint; /* longitud de cadena*/
begin
  dd = lpad(cast(extract(day from :f_nacimiento) as varchar(2)), 2, '0');
  mm = lpad(cast(extract(month from :f_nacimiento) as varchar(2)), 2, '0');
  aa = right(cast(extract(year from :f_nacimiento) as varchar(4)), 2);
  cadena = substring(a_paterno from 2);
  len = char_length(cadena);
  i = 1;
  while (i < len) do
  begin
    execute procedure proper_vowel(substring(cadena from 1 for 1))
    returning_values pv;
    if (pv in ('A', 'E', 'I', 'O', 'U')) then leave;
    cadena = substring(cadena from 2);
    i = i + 1;
  end
  rfc = left(:a_paterno, 1) || pv || coalesce(left(:a_materno, 1), 'X') ||
    left(:nombre, 1) || aa || mm || dd;
end

Procedimiento almacenado que calcula (en automático) el RFC de una persona utilizando su Nombre, Apellido Paterno, Apellido Materno y su fecha de nacimiento. Como te puedes dar cuenta, este último procedimiento almacenado uso la función CHAR_LENGTH. Si utilizo UTF8 la longitud de la cadena es errónea, además de no encontrar las vocales acentuadas puesto que ahora se representa en dos bytes, ¿me explico?

Saludos,
Gerardo Suárez Trejo

Casimiro Notevi 05-11-2010 18:02:49

Será que no me explico bien :)

guillotmarc 05-11-2010 18:44:53

Hola Gerardo.

Cita:

Empezado por Gallosuarez (Mensaje 381420)
De acuerdo a las pruebas que he realizado, me he encontrado que al tratar de utilizar un UUID (Universal Unique Identifier [Identificador Único Universal]) utilizando el juego de caracteres UTF8, al tratar de insertar dicho campo Firebird protesta. La primera suposición que hice fue que al utilizar UTF8 el tamaño del campo ID tendría que ser mayor (Firebird especifica que un UUID debe de ser un VARCHAR(16), y puesto que UTF8 puede utilizar 1,2,3 y hasta 4 bytes para representar un caracter; entonces tendría que utilizar un campo mayor. Por lo tanto, el campo debe de ser mayor, el índice que se crea es mayor y al final de cuentas tu tabla crece mas, y esto repercute en tamaño y en desempeño (de allí mi afirmación en el primer "post").

Yo tengo entendido que los campos UUID los tienes que definir como varchar(16) pero con el charset OCTETS.

Ejplo. CREATE DOMAIN UUID AS VARCHAR(16) CHARACTER SET OCTETS;

NOTA: De la misma forma que te daba problemas con Unicode, si intentas guardar un UUID en un varchar(16) con iso8859_1 lo más probable es que te de un transliteration error (o sea que los carácteres que intentas guardar no son válidos en el alfabeto soportado por iso8859_1).

El charset OCTETS acepta los caracteres que se le envían en el mismo binario en que los recibe, no hace ni intenta hacer ninguna manipulación con ellos.

Cita:

Empezado por Gallosuarez (Mensaje 381420)
Otra cosa que note es que al insertar campos que contienen á, é, í, ó, ú, ñ, Ñ (y algunos otros como la u con diéresis), efectivamente utilizaban dos caracteres. Esto me acarreo el siguiente problema: la función CHAR_LENGTH me reporta un conteo mayor en la longitud de la cadena. Tengo funciones que hacen el cálculo automático del RFC (CIF para otro países) y dejaron de funcionar correctamente.

Como pueden ver, estos son los problemas que me he encontrado al tratar de utilizar UTF8. Al menos para mi si son algunas razones para no utilizarlo (por el momento), sin embargo, me pregunto si hay alguien en el foro que ya haya sorteado todos estos problemas. Tal vez nos pueda comentar y nos convenza de que es mejor opción UTF8.

¿ Estás seguro ?. No debería.

CHAR_LENGTH (o CHARACTER_LENGTH) debería devolver el número de caracteres de la cadena (independientemente de su tamaño).

OCTET_LENGTH sí que devuelve el tamaño en bytes ocupado por la cadena.

Cita:

Empezado por Gallosuarez (Mensaje 381420)
Un saludo a Guillotemarc, sus comentarios siempre me parecen atinados y enriquecedores en la mayoría de los casos.

Gracias por la confianza :).

guillotmarc 05-11-2010 19:00:16

Cita:

Empezado por Gallosuarez (Mensaje 381437)
Casimiro:

O no te sabes explicar :confused:, o yo no te entiendo..... porque no te sabes explicar.... (je, je, je) una pequeña broma.

Código SQL [-]create or alter procedure PROPER_VOWEL ( C char(1)) returns ( VOWEL char(1)) AS begin vowel = C; if (C in ('Á', 'a', 'á')) then vowel = 'A'; if (C in ('É', 'e', 'é')) then vowel = 'E'; if (C in ('Í', 'i', 'í')) then vowel = 'I'; if (C in ('Ó', 'o', 'ó')) then vowel = 'O'; if (C in ('Ú', 'u', 'ú')) then vowel = 'U'; end


Procedimiento almacenado que convierte un vocal acentuado a una vocal sin acento.... (se puede extender fácilmente para vocales con acento circunflejo en caso de idiomas como el Francés y el Portugués (creo).

Código SQL [-]create or alter procedure CALC_RFC ( A_PATERNO varchar(25), A_MATERNO varchar(25), NOMBRE varchar(30), F_NACIMIENTO date) returns ( RFC varchar(13)) AS declare variable dd char(2); /* día de nacimiento */ declare variable mm char(2); /* mes de nacimiento */ declare variable aa char(2); /* año de nacimiento */ declare variable pv char(1) = ''; /* primera vocal en la cadena*/ declare variable cadena varchar(24); declare variable i smallint = 1; declare variable len smallint; /* longitud de cadena*/ begin dd = lpad(cast(extract(day from :f_nacimiento) as varchar(2)), 2, '0'); mm = lpad(cast(extract(month from :f_nacimiento) as varchar(2)), 2, '0'); aa = right(cast(extract(year from :f_nacimiento) as varchar(4)), 2); cadena = substring(a_paterno from 2); len = char_length(cadena); i = 1; while (i < len) do begin execute procedure proper_vowel(substring(cadena from 1 for 1)) returning_values pv; if (pv in ('A', 'E', 'I', 'O', 'U')) then leave; cadena = substring(cadena from 2); i = i + 1; end rfc = left(:a_paterno, 1) || pv || coalesce(left(:a_materno, 1), 'X') || left(:nombre, 1) || aa || mm || dd; end


Procedimiento almacenado que calcula (en automático) el RFC de una persona utilizando su Nombre, Apellido Paterno, Apellido Materno y su fecha de nacimiento. Como te puedes dar cuenta, este último procedimiento almacenado uso la función CHAR_LENGTH. Si utilizo UTF8 la longitud de la cadena es errónea, además de no encontrar las vocales acentuadas puesto que ahora se representa en dos bytes, ¿me explico?

Saludos,
Gerardo Suárez Trejo

Esta claro que no deberías tener estos problemas.

Me pregunto si las variables varchar y char de tu procedimiento almacenado están realmente en UTF8. Los campos ya me imagino que sí, pero estas variables en ningún momento se les dice su charset, por lo tanto quizás estén en el charset predeterminado de la base de datos.

Comprueba cual es el charset predeterminado de tu base de datos :

Código SQL [-]
select rdb$character_set_name from rdb$database

Es probable que tus procedimientos almacenados solo funcionen correctamente si el charset de la base de datos es UTF8.

NOTA: Asegúrate mirando cuando vale la variable len de tu SP, y cuanto es esa misma longitud si lo consultas directamente sobre el campo.

Código SQL [-]
select char_length(a_paterno) from tabla where id = x

Saludos.

Chandra_ 05-11-2010 19:02:58

Cita:

Empezado por guillotmarc (Mensaje 381410)
Hola.
Todos los charsets sirven para codificar los alfabetos de un grupo de lenguas. Pero el iso8859_1 solo sirve para codificar el alfabeto de las lenguas de Europa Occidental, mientras que Unicode pretende poder codificar los alfabetos de todas las lenguas del mundo.

Como el iso8859_1 solo codifica un pequeño grupo de lenguas, puede guardar cada letra del alfabeto en un solo byte. En cambio Unicode tiene que codificar un montón de letras de distintos alfabetos, las letras más comunes A B C D ... las codifica en un solo byte, pero para codificar letras de alfabetos más complicados como el chino va a necesita bastantes más bytes para cada letra.

Esta es una de las grandes diferencias entre Unicode y los demás charsets (iso8859_1 y compañía) que Unicode tiene un tamaño de letra variable y los charsets tradicionales tienen un tamaño de letra fijo. Por eso a los lenguajes de programación, bases de datos, sistemas operativos, ... les resulta más complejo tener que lidiar con Unicode.

Eso es lo que yo llamo una clase magistral sobre extándares ISO :) . Ahora ya queda todo claro, muchas gracias.

Cita:

Yo no lo especifico en las consultas, porqué mis campos ya tienen definido que usan este Collate (fíjate en IBExpert o en el programa que uses para gestionar tu base de datos que puedes especificar los Collates a nivel de campos). De esta forma, sin decirle nada a las consultas, ya saben que ordenación (Collate) hay que aplicar en función del campo implicado.

Para no complicarme la vida y no tener que andar siempre especificándolo cada vez que doy de alta un campo, lo tengo definido en los Dominios que utilizo para especificar los tipos de los campos. Como puedes ver, en los Dominios también puedes marcar el Charset y Collate de los campos que lo apliquen.
Pues así lo voy a hacer a partir de ahora. Yo también uso IBExpert (que, por ciuerto, creo que está hecho con Delphi) y voy a definirlo también en los Dominios, como dices.

Muchísimas gracias, guillotmarc, por una respuesta tan currada y esclarecedora. Qué buena sensación da cuando te explican bien las cosas y las entiendes :)

Cita:

Empezado por Casimiro Notevi (Mensaje 381412)
Para que Chandra_ lo tenga más claro, pongo un ejemplo: (...)

Un millón de gracias, Casimiro; muy interesante y útil el código. Has tenido incluso el detalle de ponermelo por si uso Firebird 2.5 (como es el caso) o por si uso versiones anteriores. Me lo guardo para futuras referencias. Muchas gracias por tu tiempo. ;)

Qué gusto da saber que si hay problemas, siempre está ahí la gente del ClubDelphi para echarte una mano :)

Gallosuarez 05-11-2010 19:41:39

Aclaraciones...
 
Guillotmarc:

Gracias por todas las aclaraciones.... déjame hacer pruebas (a profundida), confieso que las anteriores solo fueron superficiales. Si esto es así, entonces si que no habría ninguna razón para no utilizar UTF8.

Saludos,
Gerardo Suárez Trejo

Casimiro Notevi 05-11-2010 20:27:44

Cita:

Empezado por Gallosuarez (Mensaje 381464)
Si esto es así, entonces si que no habría ninguna razón para no utilizar UTF8.

Código Delphi [-]
if razon then
  not razon = not usar

:D


La franja horaria es GMT +2. Ahora son las 00:42:58.

Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi