Return to Snippet

Revision: 50768
at September 17, 2011 04:18 by wizard04


Updated Code
/* ISO 8601 Date conversions v2.0
 * 
 * This work is licensed under a Creative Commons Attribution 3.0 Unported License
 * http://creativecommons.org/licenses/by/3.0/
 *
 * Author: Andy Harrison, http://dragonzreef.com/
 * Date: 16 September 2011
 */

//formats the Date into a string according to the Internet profile of ISO 8601 described in RFC 3339
//see http://tools.ietf.org/html/rfc3339
//time zone is always UTC
if(!Date.prototype.toISOString) Date.prototype.toISOString = function(){ return this.format("%Y-%M-%DT%I:%N:%S.%ZZ", true); }

//creates a Date object from a basic ISO 8601 formatted string
//expanded years are not supported
//see http://tools.ietf.org/html/rfc3339#section-5.6
//    http://en.wikipedia.org/wiki/Iso8601
Date.fromISO = function(isoStr)
{
	var rxpCalendarDate = "(\\d{4})(?:(-?)(\\d{2})(?:\\2(\\d{2}))?)?";
	var rxpWeekDate = "(\\d{4})(-?)[Ww](\\d{2})(?:\\2(\\d))?";
	var rxpOrdinalDate = "(\\d{4})-?(\\d{3})";
	var rxpTime = "(\\d{2})(?:(:?)(\\d{2})(?:\\2(\\d{2}))?)?(?:[.,](\\d+))?";
	var rxpOffset = "(?:([Zz])|([+-])(\\d{2})(?::?(\\d{2}))?)";
	var rxpFullDate = "^(?:(\\d{4})(?:(-?)(\\d{2})(?:\\2(\\d{2}))?)?|(\\d{4})(-?)[Ww](\\d{2})(?:\\6(\\d))?|(\\d{4})-?(\\d{3}))(?=[Tt]|$)"
	var rxpFullTime = rxpTime+rxpOffset+"?$";
	
	if(!( RegExp(rxpFullDate+"$").test(isoStr) || 
		  RegExp("^"+rxpFullTime).test(isoStr) || 
		  (RegExp(rxpFullDate+"[Tt].*").test(isoStr) && RegExp("^[^Tt]+[Tt]"+rxpFullTime).test(isoStr)) )) return null;	//not a supported ISO 8601 string
	
	var now = new Date();
	var parts = {year:now.getFullYear(), month:now.getMonth(), day:now.getDate(), hour:0, minute:0, second:0, fraction:0};	//default is midnight this morning, local time
	var rxp;
	
	var isoDate = RegExp(rxpFullDate).exec(isoStr);
	if(isoDate)	//string includes a date
	{
		try{
			isoDate = isoDate[0];
			if((rxp = RegExp("^"+rxpCalendarDate+"$")).exec(isoDate))	//calendar date
			{
				isoDate.replace(rxp, function(match, y, dash, m, d){
						if(m!="" && (m<1 || m>12)) throw new RangeError("Month is out of range");
						if(d!="" && (d<1 || d>31)) throw new RangeError("Day is out of range");
						
						parts.year = y;
						parts.month = m=="" ? 0 : m-1;
						parts.day = d=="" ? 1 : d;
						
						if(parts.day > Date.daysInMonth(parts.year, parts.month)) throw new RangeError("Day is out of range");
					});
			}
			else if((rxp = RegExp("^"+rxpWeekDate+"$")).exec(isoDate))	//week date
			{
				isoDate.replace(rxp, function(match, y, dash, w, d){
						if(w<1 || w>53) throw new RangeError("Week number is out of range");
						if(d!="" && (d<1 || d>7)) throw new RangeError("Weekday number is out of range");
						
						var jan4th = new Date(y, 0, 4);
						var dayOfJan4th = (jan4th.getDay()+6)%7;	//0-6 == Mon-Sun
						var firstMonday = new Date(jan4th.valueOf() - dayOfJan4th*86400000);
						if(w == 53)
						{
							var mondayAfter52Weeks = (new Date(firstMonday.valueOf() + 52*7*86400000)).getDate();
							//if the 53rd week is actually the first week of next year (i.e., it includes Jan 4th), throw range error
							if(!(mondayAfter52Weeks > 4 && mondayAfter52Weeks < 29)) throw new RangeError("Week number is out of range");
						}
						var weekday = d=="" ? 0 : d-1;	//0-6 == Mon-Sun
						var date = new Date(firstMonday.valueOf() + weekday*86400000 + (w-1)*7*86400000);
						parts.year = date.getFullYear();
						parts.month = date.getMonth();
						parts.day = date.getDate();
					});
			}
			else	//ordinal date
			{
				isoDate.replace(RegExp("^"+rxpOrdinalDate+"$"), function(match, y, d){
						if(d > (Date.isLeapYear(y)?366:365)) throw new RangeError("Day number is out of range");
						
						parts.year = y;
						var date = new Date((new Date(y, 0, 1)).valueOf() + (d-1)*86400000);
						parts.month = date.getMonth();
						parts.day = date.getDate();
					});
			}
		}catch(err){
			return null;	//date string is invalid
		}
	}
	
	var isoTime = RegExp("(?:^|[Tt])"+rxpFullTime).exec(isoStr);
	if(isoTime)	//string includes a time
	{
		try{
			isoTime = isoTime[0];
			if(isoTime[0].toUpperCase() == "T") isoTime = isoTime.slice(1);
			
			if(RegExp("^"+rxpTime+"$").exec(isoTime))	//if there is no time zone designator
			{
				//use local time
				var date = new Date(parts.year, parts.month, parts.day);
				//add hours, minutes, and seconds to date
				isoTime.replace(RegExp("^"+rxpTime+"$"), function(match, h, colon, m, s, f){
						if(h>24) throw new RangeError("Hour is out of range");
						if(m!="" && m>59) throw new RangeError("Minute is out of range");
						if(s!="" && s>60) throw new RangeError("Second is out of range");
						
						var val = date.valueOf();
						val += h*3600000;
						if(m) val += m*60000;
						if(s) val += s*1000;
						if(f) val += 1000*("."+f.substr(0,3));
						date = new Date(val);
					});
			}
			else	//there is a time zone designator
			{
				//use UTC time as base
				var date = new Date(Date.UTC(parts.year, parts.month, parts.day));
				//add hours, minutes, seconds, and offset to date
				isoTime.replace(RegExp("^"+rxpTime+rxpOffset+"$"), function(match, h, colon, m, s, f, z, sign, oh, om){
						if(h>24) throw new RangeError("Hour is out of range");
						if(m!="" && m>59) throw new RangeError("Minute is out of range");
						if(s!="" && s>60) throw new RangeError("Second is out of range");
						if(oh!="" && oh>23) throw new RangeError("Offset hour is out of range");
						if(om!="" && om>59) throw new RangeError("Offset minute is out of range");
						
						var val = date.valueOf();
						val += h*3600000;
						if(m) val += m*60000;
						if(s) val += s*1000;
						if(f) val += 1000*("."+f.substr(0,3));
						
						if(!z)	//if there is an offset from UTC time
						{
							//adjust time according to offset (i.e., convert to UTC time)
							sign = sign=="-" ? 1 : -1;
							val += sign*oh*3600000;
							if(om != "") val += sign*om*60000;
						}
						date = new Date(val);
					});
			}
			
			parts.year = date.getFullYear();
			parts.month = date.getMonth();
			parts.day = date.getDate();
			parts.hour = date.getHours();
			parts.minute = date.getMinutes();
			parts.second = date.getSeconds();
			parts.fraction = date.getMilliseconds();
		}catch(err){
			return null;	//time string is invalid
		}
	}
	
	return new Date(parts.year, parts.month, parts.day, parts.hour, parts.minute, parts.second, parts.fraction);
}

Revision: 50767
at September 17, 2011 04:16 by wizard04


Updated Code
/* ISO 8601 Date conversions v2.0
 * 
 * This work is licensed under a Creative Commons Attribution 3.0 Unported License
 * http://creativecommons.org/licenses/by/3.0/
 *
 * Author: Andy Harrison, http://dragonzreef.com/
 * Date: 16 September 2011
 */

//formats the Date into a string according to the Internet profile of ISO 8601 described in RFC 3339
//see http://tools.ietf.org/html/rfc3339
//time zone is always UTC
if(!Date.prototype.toISOString) Date.prototype.toISOString = function(){ return this.format("%Y-%M-%DT%I:%N:%S.%ZZ", true); }

//creates a Date object from a basic ISO 8601 formatted string
//expanded years, durations, and intervals are not supported
//see http://tools.ietf.org/html/rfc3339#section-5.6
//    http://en.wikipedia.org/wiki/Iso8601
Date.fromISO = function(isoStr)
{
	var rxpCalendarDate = "(\\d{4})(?:(-?)(\\d{2})(?:\\2(\\d{2}))?)?";
	var rxpWeekDate = "(\\d{4})(-?)[Ww](\\d{2})(?:\\2(\\d))?";
	var rxpOrdinalDate = "(\\d{4})-?(\\d{3})";
	var rxpTime = "(\\d{2})(?:(:?)(\\d{2})(?:\\2(\\d{2}))?)?(?:[.,](\\d+))?";
	var rxpOffset = "(?:([Zz])|([+-])(\\d{2})(?::?(\\d{2}))?)";
	var rxpFullDate = "^(?:(\\d{4})(?:(-?)(\\d{2})(?:\\2(\\d{2}))?)?|(\\d{4})(-?)[Ww](\\d{2})(?:\\6(\\d))?|(\\d{4})-?(\\d{3}))(?=[Tt]|$)"
	var rxpFullTime = rxpTime+rxpOffset+"?$";
	
	if(!( RegExp(rxpFullDate+"$").test(isoStr) || 
		  RegExp("^"+rxpFullTime).test(isoStr) || 
		  (RegExp(rxpFullDate+"[Tt].*").test(isoStr) && RegExp("^[^Tt]+[Tt]"+rxpFullTime).test(isoStr)) )) return null;	//not a supported ISO 8601 string
	
	var now = new Date();
	var parts = {year:now.getFullYear(), month:now.getMonth(), day:now.getDate(), hour:0, minute:0, second:0, fraction:0};	//default is midnight this morning, local time
	var rxp;
	
	var isoDate = RegExp(rxpFullDate).exec(isoStr);
	if(isoDate)	//string includes a date
	{
		try{
			isoDate = isoDate[0];
			if((rxp = RegExp("^"+rxpCalendarDate+"$")).exec(isoDate))	//calendar date
			{
				isoDate.replace(rxp, function(match, y, dash, m, d){
						if(m!="" && (m<1 || m>12)) throw new RangeError("Month is out of range");
						if(d!="" && (d<1 || d>31)) throw new RangeError("Day is out of range");
						
						parts.year = y;
						parts.month = m=="" ? 0 : m-1;
						parts.day = d=="" ? 1 : d;
						
						if(parts.day > Date.daysInMonth(parts.year, parts.month)) throw new RangeError("Day is out of range");
					});
			}
			else if((rxp = RegExp("^"+rxpWeekDate+"$")).exec(isoDate))	//week date
			{
				isoDate.replace(rxp, function(match, y, dash, w, d){
						if(w<1 || w>53) throw new RangeError("Week number is out of range");
						if(d!="" && (d<1 || d>7)) throw new RangeError("Weekday number is out of range");
						
						var jan4th = new Date(y, 0, 4);
						var dayOfJan4th = (jan4th.getDay()+6)%7;	//0-6 == Mon-Sun
						var firstMonday = new Date(jan4th.valueOf() - dayOfJan4th*86400000);
						if(w == 53)
						{
							var mondayAfter52Weeks = (new Date(firstMonday.valueOf() + 52*7*86400000)).getDate();
							//if the 53rd week is actually the first week of next year (i.e., it includes Jan 4th), throw range error
							if(!(mondayAfter52Weeks > 4 && mondayAfter52Weeks < 29)) throw new RangeError("Week number is out of range");
						}
						var weekday = d=="" ? 0 : d-1;	//0-6 == Mon-Sun
						var date = new Date(firstMonday.valueOf() + weekday*86400000 + (w-1)*7*86400000);
						parts.year = date.getFullYear();
						parts.month = date.getMonth();
						parts.day = date.getDate();
					});
			}
			else	//ordinal date
			{
				isoDate.replace(RegExp("^"+rxpOrdinalDate+"$"), function(match, y, d){
						if(d > (Date.isLeapYear(y)?366:365)) throw new RangeError("Day number is out of range");
						
						parts.year = y;
						var date = new Date((new Date(y, 0, 1)).valueOf() + (d-1)*86400000);
						parts.month = date.getMonth();
						parts.day = date.getDate();
					});
			}
		}catch(err){
			return null;	//date string is invalid
		}
	}
	
	var isoTime = RegExp("(?:^|[Tt])"+rxpFullTime).exec(isoStr);
	if(isoTime)	//string includes a time
	{
		try{
			isoTime = isoTime[0];
			if(isoTime[0].toUpperCase() == "T") isoTime = isoTime.slice(1);
			
			if(RegExp("^"+rxpTime+"$").exec(isoTime))	//if there is no time zone designator
			{
				//use local time
				var date = new Date(parts.year, parts.month, parts.day);
				//add hours, minutes, and seconds to date
				isoTime.replace(RegExp("^"+rxpTime+"$"), function(match, h, colon, m, s, f){
						if(h>24) throw new RangeError("Hour is out of range");
						if(m!="" && m>59) throw new RangeError("Minute is out of range");
						if(s!="" && s>60) throw new RangeError("Second is out of range");
						
						var val = date.valueOf();
						val += h*3600000;
						if(m) val += m*60000;
						if(s) val += s*1000;
						if(f) val += 1000*("."+f.substr(0,3));
						date = new Date(val);
					});
			}
			else	//there is a time zone designator
			{
				//use UTC time as base
				var date = new Date(Date.UTC(parts.year, parts.month, parts.day));
				//add hours, minutes, seconds, and offset to date
				isoTime.replace(RegExp("^"+rxpTime+rxpOffset+"$"), function(match, h, colon, m, s, f, z, sign, oh, om){
						if(h>24) throw new RangeError("Hour is out of range");
						if(m!="" && m>59) throw new RangeError("Minute is out of range");
						if(s!="" && s>60) throw new RangeError("Second is out of range");
						if(oh!="" && oh>23) throw new RangeError("Offset hour is out of range");
						if(om!="" && om>59) throw new RangeError("Offset minute is out of range");
						
						var val = date.valueOf();
						val += h*3600000;
						if(m) val += m*60000;
						if(s) val += s*1000;
						if(f) val += 1000*("."+f.substr(0,3));
						
						if(!z)	//if there is an offset from UTC time
						{
							//adjust time according to offset (i.e., convert to UTC time)
							sign = sign=="-" ? 1 : -1;
							val += sign*oh*3600000;
							if(om != "") val += sign*om*60000;
						}
						date = new Date(val);
					});
			}
			
			parts.year = date.getFullYear();
			parts.month = date.getMonth();
			parts.day = date.getDate();
			parts.hour = date.getHours();
			parts.minute = date.getMinutes();
			parts.second = date.getSeconds();
			parts.fraction = date.getMilliseconds();
		}catch(err){
			return null;	//time string is invalid
		}
	}
	
	return new Date(parts.year, parts.month, parts.day, parts.hour, parts.minute, parts.second, parts.fraction);
}

Revision: 50766
at September 2, 2011 00:28 by wizard04


Initial Code
//formats the Date into a string according to the Internet profile of ISO 8601 described in RFC 3339
//see http://tools.ietf.org/html/rfc3339
Date.prototype.toISO = function(useZulu, useFraction)
{
	if(!useZulu) return this.format("%Y-%M-%DT%I:%N:%S"+(useFraction?".%Z":"")+"%e%F:%G");
	
	//if the time is in UTC, uses "Z" for the zone designator instead of "+00:00" (both are correct)
	//however, this format does not allow times to be sorted alphabetically
	return this.getTimezoneOffset()==0 ? this.format("%Y-%M-%DT%I:%N:%S"+(useFraction?".%Z":"")+"Z") : this.format("%Y-%M-%DT%I:%N:%S"+(useFraction?".%Z":"")+"%e%F:%G");
}

//creates a Date object from a basic ISO 8601 formatted string
//expanded years, durations, and intervals are not supported
//see http://tools.ietf.org/html/rfc3339#section-5.6
//    http://en.wikipedia.org/wiki/Iso8601
Date.fromISO = function(isoStr)
{
	var rxpCalendarDate = "(\\d{4})(?:(-?)(\\d{2})(?:\\2(\\d{2}))?)?";
	var rxpWeekDate = "(\\d{4})(-?)[Ww](\\d{2})(?:\\2(\\d))?";
	var rxpOrdinalDate = "(\\d{4})-?(\\d{3})";
	var rxpTime = "(\\d{2})(?:(:?)(\\d{2})(?:\\2(\\d{2}))?)?(?:[.,](\\d+))?";
	var rxpOffset = "(?:([Zz])|([+-])(\\d{2})(?::?(\\d{2}))?)";
	var rxpFullDate = "^(?:(\\d{4})(?:(-?)(\\d{2})(?:\\2(\\d{2}))?)?|(\\d{4})(-?)[Ww](\\d{2})(?:\\6(\\d))?|(\\d{4})-?(\\d{3}))(?=[Tt]|$)"
	var rxpFullTime = rxpTime+rxpOffset+"?$";
	
	if(!( RegExp(rxpFullDate+"$").test(isoStr) || 
		  RegExp("^"+rxpFullTime).test(isoStr) || 
		  (RegExp(rxpFullDate+"[Tt].*").test(isoStr) && RegExp("^[^Tt]+[Tt]"+rxpFullTime).test(isoStr)) )) return null;	//not a supported ISO 8601 string
	
	var now = new Date();
	var parts = {year:now.getFullYear(), month:now.getMonth(), day:now.getDate(), hour:0, minute:0, second:0, fraction:0};	//default is midnight this morning, local time
	var rxp;
	
	var isoDate = RegExp(rxpFullDate).exec(isoStr);
	if(isoDate)	//string includes a date
	{
		try{
			isoDate = isoDate[0];
			if((rxp = RegExp("^"+rxpCalendarDate+"$")).exec(isoDate))	//calendar date
			{
				isoDate.replace(rxp, function(match, y, dash, m, d){
						if(m!="" && (m<1 || m>12)) throw new RangeError("Month is out of range");
						if(d!="" && (d<1 || d>31)) throw new RangeError("Day is out of range");
						
						parts.year = y;
						parts.month = m=="" ? 0 : m-1;
						parts.day = d=="" ? 1 : d;
						
						if(parts.day > Date.daysInMonth(parts.year, parts.month)) throw new RangeError("Day is out of range");
					});
			}
			else if((rxp = RegExp("^"+rxpWeekDate+"$")).exec(isoDate))	//week date
			{
				isoDate.replace(rxp, function(match, y, dash, w, d){
						if(w<1 || w>53) throw new RangeError("Week number is out of range");
						if(d!="" && (d<1 || d>7)) throw new RangeError("Weekday number is out of range");
						
						var jan4th = new Date(y, 0, 4);
						var dayOfJan4th = (jan4th.getDay()+6)%7;	//0-6 == Mon-Sun
						var firstMonday = new Date(jan4th.valueOf() - dayOfJan4th*86400000);
						if(w == 53)
						{
							var mondayAfter52Weeks = (new Date(firstMonday.valueOf() + 52*7*86400000)).getDate();
							//if the 53rd week is actually the first week of next year (i.e., it includes Jan 4th), throw range error
							if(!(mondayAfter52Weeks > 4 && mondayAfter52Weeks < 29)) throw new RangeError("Week number is out of range");
						}
						var weekday = d=="" ? 0 : d-1;	//0-6 == Mon-Sun
						var date = new Date(firstMonday.valueOf() + weekday*86400000 + (w-1)*7*86400000);
						parts.year = date.getFullYear();
						parts.month = date.getMonth();
						parts.day = date.getDate();
					});
			}
			else	//ordinal date
			{
				isoDate.replace(RegExp("^"+rxpOrdinalDate+"$"), function(match, y, d){
						if(d > (Date.isLeapYear(y)?366:365)) throw new RangeError("Day number is out of range");
						
						parts.year = y;
						var date = new Date((new Date(y, 0, 1)).valueOf() + (d-1)*86400000);
						parts.month = date.getMonth();
						parts.day = date.getDate();
					});
			}
		}catch(err){
			return null;	//date string is invalid
		}
	}
	
	var isoTime = RegExp("(?:^|[Tt])"+rxpFullTime).exec(isoStr);
	if(isoTime)	//string includes a time
	{
		try{
			isoTime = isoTime[0];
			if(isoTime[0].toUpperCase() == "T") isoTime = isoTime.slice(1);
			
			if(RegExp("^"+rxpTime+"$").exec(isoTime))	//if there is no time zone designator
			{
				//use local time
				var date = new Date(parts.year, parts.month, parts.day);
				//add hours, minutes, and seconds to date
				isoTime.replace(RegExp("^"+rxpTime+"$"), function(match, h, colon, m, s, f){
						if(h>24) throw new RangeError("Hour is out of range");
						if(m!="" && m>59) throw new RangeError("Minute is out of range");
						if(s!="" && s>60) throw new RangeError("Second is out of range");
						
						var val = date.valueOf();
						val += h*3600000;
						if(m) val += m*60000;
						if(s) val += s*1000;
						if(f) val += 1000*("."+f.substr(0,3));
						date = new Date(val);
					});
			}
			else	//there is a time zone designator
			{
				//use UTC time as base
				var date = new Date(Date.UTC(parts.year, parts.month, parts.day));
				//add hours, minutes, seconds, and offset to date
				isoTime.replace(RegExp("^"+rxpTime+rxpOffset+"$"), function(match, h, colon, m, s, f, z, sign, oh, om){
						if(h>24) throw new RangeError("Hour is out of range");
						if(m!="" && m>59) throw new RangeError("Minute is out of range");
						if(s!="" && s>60) throw new RangeError("Second is out of range");
						if(oh!="" && oh>23) throw new RangeError("Offset hour is out of range");
						if(om!="" && om>59) throw new RangeError("Offset minute is out of range");
						
						var val = date.valueOf();
						val += h*3600000;
						if(m) val += m*60000;
						if(s) val += s*1000;
						if(f) val += 1000*("."+f.substr(0,3));
						
						if(!z)	//if there is an offset from UTC time
						{
							//adjust time according to offset (i.e., convert to UTC time)
							sign = sign=="-" ? 1 : -1;
							val += sign*oh*3600000;
							if(om != "") val += sign*om*60000;
						}
						date = new Date(val);
					});
			}
			
			parts.year = date.getFullYear();
			parts.month = date.getMonth();
			parts.day = date.getDate();
			parts.hour = date.getHours();
			parts.minute = date.getMinutes();
			parts.second = date.getSeconds();
			parts.fraction = date.getMilliseconds();
		}catch(err){
			return null;	//time string is invalid
		}
	}
	
	return new Date(parts.year, parts.month, parts.day, parts.hour, parts.minute, parts.second, parts.fraction);
}

Initial URL


Initial Description
This requires my [date formatting script](http://snipplr.com/view/54806/javascript-date-formatting/).

(date).toISO() converts the Date object to an ISO 8601 string

Date.fromISO() converts an ISO 8601 string to a Date object

Initial Title
ISO 8601 date conversion

Initial Tags
javascript, date

Initial Language
JavaScript