Formato de fecha JavaScript

Actualización: La documentación a continuación se ha actualizado para el nuevo formato de fecha 1.2. ¡Consíguelo ahora!

Aunque JavaScript proporciona un montón de métodos para obtener y configurar partes de un objeto de fecha, carece de una forma sencilla de formatear fechas y horas de acuerdo con una máscara especificada por el usuario. Existen algunos scripts que brindan esta funcionalidad, pero nunca he visto uno que funcione bien para mí... La mayoría son innecesariamente voluminosos o lentos, vinculan funcionalidades no relacionadas, usan sintaxis de máscaras complicadas que más o menos requieren que lea el documentación cada vez que quiera usarlos, o no tenga en cuenta casos especiales como caracteres de máscara de escape dentro de la cadena generada.

Al elegir qué caracteres especiales de máscara usar para mi formateador de fecha de JavaScript, observé la función de fecha de PHP y las funciones discretas dateFormat y timeFormat de ColdFusion. PHP usa una combinación loca de letras (al menos para mí, ya que no soy un programador de PHP) para representar varias entidades de fecha, y aunque probablemente nunca memorice la lista completa, ofrece las ventajas de que puede aplicar ambos formato de fecha y hora con una función, y que ninguno de los caracteres especiales se superponga (a diferencia de ColdFusion donde m y mm significan cosas diferentes dependiendo de si se trata de fechas u horas). Por otro lado, ColdFusion utiliza caracteres especiales muy fáciles de recordar para las máscaras.

Con mi formateador de fechas, he tratado de tomar las mejores características de ambos y agregar algo de azúcar por mi cuenta. Sin embargo, terminó muy parecido a la implementación de ColdFusion, ya que principalmente usé la sintaxis de máscara de CF.

Antes de entrar en más detalles, aquí hay algunos ejemplos de cómo se puede usar este script:

var now = new Date();

now.format("m/dd/yy");
// Returns, e.g., 6/09/07

// Can also be used as a standalone function
dateFormat(now, "dddd, mmmm dS, yyyy, h:MM:ss TT");
// Saturday, June 9th, 2007, 5:46:21 PM

// You can use one of several named masks
now.format("isoDateTime");
// 2007-06-09T17:46:21

// ...Or add your own
dateFormat.masks.hammerTime = 'HH:MM! "Can\'t touch this!"';
now.format("hammerTime");
// 17:46! Can't touch this!

// When using the standalone dateFormat function,
// you can also provide the date as a string
dateFormat("Jun 9 2007", "fullDate");
// Saturday, June 9, 2007

// Note that if you don't include the mask argument,
// dateFormat.masks.default is used
now.format();
// Sat Jun 09 2007 17:46:21

// And if you don't include the date argument,
// the current date and time is used
dateFormat();
// Sat Jun 09 2007 17:46:22

// You can also skip the date argument (as long as your mask doesn't
// contain any numbers), in which case the current date/time is used
dateFormat("longTime");
// 5:46:22 PM EST

// And finally, you can convert local time to UTC time. Either pass in
// true as an additional argument (no argument skipping allowed in this case):
dateFormat(now, "longTime", true);
now.format("longTime", true);
// Both lines return, e.g., 10:46:21 PM UTC

// ...Or add the prefix "UTC:" to your mask.
now.format("UTC:h:MM:ss TT Z");
// 10:46:21 PM UTC

Los siguientes son los caracteres especiales admitidos. Cualquier diferencia en el significado de dateFormat de ColdFusion y timeFormat se anotan las funciones.

Máscara Descripción
d Día del mes como dígitos; sin cero inicial para días de un solo dígito.
dd Día del mes como dígitos; cero inicial para días de un solo dígito.
ddd Día de la semana como abreviatura de tres letras.
dddd Día de la semana como su nombre completo.
m Mes como dígitos; sin cero inicial para meses de un solo dígito.
mm Mes como dígitos; cero inicial para meses de un solo dígito.
mmm Mes como abreviatura de tres letras.
mmmm Mes como su nombre completo.
yy Año como dos últimos dígitos; cero inicial para años menores de 10.
yyyy Año representado por cuatro dígitos.
h Horas; sin cero inicial para horas de un solo dígito (reloj de 12 horas).
hh Horas; cero inicial para horas de un solo dígito (reloj de 12 horas).
H Horas; sin cero inicial para horas de un solo dígito (reloj de 24 horas).
HH Horas; cero inicial para horas de un solo dígito (reloj de 24 horas).
M Minutos; sin cero inicial para minutos de un solo dígito.
M mayúscula a diferencia de CF timeFormat 's m para evitar conflictos con los meses.
MM Minutos; cero inicial para minutos de un solo dígito.
MM mayúscula a diferencia de CF timeFormat 's mm para evitar conflictos con los meses.
s Segundos; sin cero inicial para segundos de un solo dígito.
ss Segundos; cero inicial para segundos de un solo dígito.
l o L Milisegundos. l da 3 dígitos. L da 2 dígitos.
t Cadena de marcador de tiempo de un solo carácter en minúsculas:a o p .
Sin equivalente en CF.
tt Cadena de marcador de tiempo de dos caracteres en minúsculas:am o pm .
Sin equivalente en CF.
T Cadena de marcador de tiempo de un solo carácter en mayúsculas:A o P .
T mayúscula a diferencia de la t de CF para permitir mayúsculas y minúsculas especificadas por el usuario.
TT Cadena de marcador de tiempo de dos caracteres en mayúsculas:AM o PM .
TT en mayúsculas a diferencia de tt de CF para permitir mayúsculas y minúsculas especificadas por el usuario.
Z Abreviatura de la zona horaria de EE. UU., p. ej. EST o MDT . Con zonas horarias fuera de EE. UU. o en el navegador Opera, se devuelve el desplazamiento GMT/UTC, p. GMT-0500
Sin equivalente en CF.
o Compensación de zona horaria GMT/UTC, p. -0500 o +0230 .
Sin equivalente en CF.
S El sufijo ordinal de la fecha (st , seg , rd , o th ). Funciona bien con d .
Sin equivalente en CF.
'…' o "…" Secuencia de caracteres literales. Se eliminaron las comillas circundantes.
No hay equivalente en CF.
UTC: Deben ser los primeros cuatro caracteres de la máscara. Convierte la fecha de la hora local a la hora UTC/GMT/Zulu antes de aplicar la máscara. Se elimina el prefijo "UTC:".
No hay equivalente en CF.

Y aquí están las máscaras con nombre proporcionadas de forma predeterminada (puede cambiarlas fácilmente o agregar las suyas propias):

Nombre Máscara Ejemplo
predeterminado ddd mmm dd aaaa HH:MM:ss Sábado 09 de junio de 2007 17:46:21
fechacorta m/d/aa 6/9/07
fecha media mmm d, aaaa 9 de junio de 2007
fechalarga mmmm d, aaaa 9 de junio de 2007
fecha completa dddd, mmmm d, aaaa Sábado, 9 de junio de 2007
CortoTiempo h:MM TT 17:46
tiempo medio h:MM:ssTT 5:46:21 p. m.
tiempolargo h:MM:ss TT Z 5:46:21 p. m. EST
fechaiso aaaa-mm-dd 2007-06-09
tiempoiso HH:MM:ss 17:46:21
isoFechaHora aaaa-mm-dd'T'HH:MM:ss 2007-06-09T17:46:21
isoUtcDateTime UTC:aaaa-mm-dd'T'HH:MM:ss'Z' 2007-06-09T22:46:21Z

Un par de problemas:

  • En el improbable caso de que haya ambigüedad en el significado de su máscara (p. ej., m seguido de mm , sin caracteres de separación), coloque un par de comillas vacías entre sus metasecuencias. Las comillas se eliminarán automáticamente.
  • Si necesita incluir comillas literales en su máscara, se aplican las siguientes reglas:
    • Las cotizaciones no emparejadas no necesitan un manejo especial.
    • Para incluir comillas literales dentro de máscaras que contengan otras comillas del mismo tipo, debe encerrarlas con el tipo de comillas alternativo (es decir, comillas dobles para comillas simples y viceversa). Por ejemplo, date.format('h "o\'clock, y\'all!"') devuelve "6 en punto, todos ustedes". Esto puede ser un poco difícil, tal vez, pero dudo que la gente realmente se encuentre con eso tan a menudo. El ejemplo anterior también se puede escribir como date.format("h") + "o'clock, y'all!" .

Aquí está el código:

/*
 * Date Format 1.2.3
 * (c) 2007-2009 Steven Levithan <stevenlevithan.com>
 * MIT license
 *
 * Includes enhancements by Scott Trenda <scott.trenda.net>
 * and Kris Kowal <cixar.com/~kris.kowal/>
 *
 * Accepts a date, a mask, or a date and a mask.
 * Returns a formatted version of the given date.
 * The date defaults to the current date/time.
 * The mask defaults to dateFormat.masks.default.
 */

var dateFormat = function () {
	var	token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g,
		timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,
		timezoneClip = /[^-+\dA-Z]/g,
		pad = function (val, len) {
			val = String(val);
			len = len || 2;
			while (val.length < len) val = "0" + val;
			return val;
		};

	// Regexes and supporting functions are cached through closure
	return function (date, mask, utc) {
		var dF = dateFormat;

		// You can't provide utc if you skip other args (use the "UTC:" mask prefix)
		if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) {
			mask = date;
			date = undefined;
		}

		// Passing date through Date applies Date.parse, if necessary
		date = date ? new Date(date) : new Date;
		if (isNaN(date)) throw SyntaxError("invalid date");

		mask = String(dF.masks[mask] || mask || dF.masks["default"]);

		// Allow setting the utc argument via the mask
		if (mask.slice(0, 4) == "UTC:") {
			mask = mask.slice(4);
			utc = true;
		}

		var	_ = utc ? "getUTC" : "get",
			d = date[_ + "Date"](),
			D = date[_ + "Day"](),
			m = date[_ + "Month"](),
			y = date[_ + "FullYear"](),
			H = date[_ + "Hours"](),
			M = date[_ + "Minutes"](),
			s = date[_ + "Seconds"](),
			L = date[_ + "Milliseconds"](),
			o = utc ? 0 : date.getTimezoneOffset(),
			flags = {
				d:    d,
				dd:   pad(d),
				ddd:  dF.i18n.dayNames[D],
				dddd: dF.i18n.dayNames[D + 7],
				m:    m + 1,
				mm:   pad(m + 1),
				mmm:  dF.i18n.monthNames[m],
				mmmm: dF.i18n.monthNames[m + 12],
				yy:   String(y).slice(2),
				yyyy: y,
				h:    H % 12 || 12,
				hh:   pad(H % 12 || 12),
				H:    H,
				HH:   pad(H),
				M:    M,
				MM:   pad(M),
				s:    s,
				ss:   pad(s),
				l:    pad(L, 3),
				L:    pad(L > 99 ? Math.round(L / 10) : L),
				t:    H < 12 ? "a"  : "p",
				tt:   H < 12 ? "am" : "pm",
				T:    H < 12 ? "A"  : "P",
				TT:   H < 12 ? "AM" : "PM",
				Z:    utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""),
				o:    (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4),
				S:    ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10]
			};

		return mask.replace(token, function ($0) {
			return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1);
		});
	};
}();

// Some common format strings
dateFormat.masks = {
	"default":      "ddd mmm dd yyyy HH:MM:ss",
	shortDate:      "m/d/yy",
	mediumDate:     "mmm d, yyyy",
	longDate:       "mmmm d, yyyy",
	fullDate:       "dddd, mmmm d, yyyy",
	shortTime:      "h:MM TT",
	mediumTime:     "h:MM:ss TT",
	longTime:       "h:MM:ss TT Z",
	isoDate:        "yyyy-mm-dd",
	isoTime:        "HH:MM:ss",
	isoDateTime:    "yyyy-mm-dd'T'HH:MM:ss",
	isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'"
};

// Internationalization strings
dateFormat.i18n = {
	dayNames: [
		"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
		"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
	],
	monthNames: [
		"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
		"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
	]
};

// For convenience...
Date.prototype.format = function (mask, utc) {
	return dateFormat(this, mask, utc);
};

Descárgalo aquí (1.2 KB cuando está minimizado y comprimido con gzip).

Tenga en cuenta que los nombres de los días y meses se pueden cambiar (para la internacionalización u otros fines) actualizando el dateFormat.i18n objeto.

Si tiene alguna sugerencia o encuentra algún problema, hágamelo saber.

¿Quiere aprender sobre la afantasisa y la hiperfantasía, la secta de Shen Yun o el desenmascaramiento de la líder de la secta Karen Zerby? Echa un vistazo a mi nuevo blog en Life After Tech.