Lasso Soft Inc. > Home

[lp_date_stringToDate]

Linklp_date_stringToDate
AuthorBil Corry
CategoryDate
Version8.x
LicensePublic Domain
Posted18 Jan 2006
Updated30 Oct 2006
More by this author...

Description

Converts a date string to a date type.  Valid for dates after 1900.

Requires [lp_error_clearerror] [lp_string_extendedToPlain] [lp_date_leapyear] [lp_logical_between] [lp_date_getLocalTimeZone] [lp_date_offsetToLocal] [lp_date_stringToOffset]

Sample Usage

	'
';
	var:'test_vector' = (array:
		'january 13, 2005',
		'13-JAN-05',
		'10/4',
		'3/2005/16',
		'16/2005/3',
		'11/12/8',
		'11/12/9',
		'11/12/10',
		'11/12/48',
		'11/12/49',
		'11/12/50',
		'2/29',
		'2/15/11',
		'2/15/32',
		'2/15/41',
		'15 März 2005',
		'lùglio 15 6',
		'2/10/2006 18:30:45',
		'6/10/2006 18:30:45',
		'2/10/2006 18:30:45 GMT',
		'6/10/2006 18:30:45 UTC',
		'2/10/2006 18:30:45 -0800',
		'6/10/2006 18:30:45 -0800',
		'2/10/2006 18:30:45 +0800',
		'6/10/2006 18:30:45 +0800',
		'4/2001/2002',
		'4/7-10/2005'
		);
		
	'American\r\n--------\r\n';
	iterate: $test_vector, local:'t';
		#t ' = \r\n\t\t' (lp_date_stringToDate: #t, -error='message', -autoYear);
		'\r\n\r\n';
	/iterate;
	
	'\r\nEuro\r\n----\r\n';
	iterate: $test_vector, local:'t';
		#t ' = \r\n\t\t' (lp_date_stringToDate: #t, -euro, -error='message', -autoYear);
		'\r\n';
	/iterate;
	
	'
';

Source Code

Click the "Download" button below to retrieve a copy of this tag, including the complete documentation and sample usage shown on this page. Place the downloaded ".inc" file in your LassoStartup folder, restart Lasso, and you can begin using this tag immediately.

[

define_tag:'lp_date_stringToDate',
	-description='Converts a date string to a date type in local server time.',
	-priority='replace',
	-required='date',-copy,
	-optional='error',
	-optional='map'; // returns a map instead of a date

	// TODO - detect AM/PM, milliseconds, tz timezone

	// Valid for dates after 1900.

	// save error so we won't overwrite it with our protect below
	local:'_ec' = error_code;
	local:'_em' = error_msg;
	lp_error_clearError;


	// we convert the string to plain ascii, so don't need extended chars like
	// f?vrier, M?rz, m?ggio, l?glio, ao?t, sett?mbre, ott?gono, nov?mbre, dic?mbre, d?cembre
	local:'month_names' = (array:
			'jan' = (array:'January','Januarius','janvier','enero','gennaio','Januar','januari','Jan','ene','gen'),
			'feb' = (array:'February','Februarius','febrero','febbraio','Februar','fevrier','februari','Feb','fev'),
			'mar' = (array: 'March','Martius','mars','marzo','Marz','maart','Mar'),
			'apr' = (array: 'April','Aprilis','avril','abril','aprile','Apr','avr','abr'),
			'may' = (array: 'May','Maius','mai','mayo','maj','maggio','mag','mei'),
			'jun' = (array: 'June','Junius','juin','junio','giugno','Juni','Jun'),
			'jul' = (array: 'July','Julius','juillet','julio','Juli','luglio','Jul','lug'),
			'aug' = (array: 'August','Augustus','agosto','aout','augusti','Aug','ago','aou'),
			'sep' = (array: 'September','septembre','septiembre','settembre','Sep','set'),
			'oct' = (array: 'October','octobre','octubre','Oktober','ottagono','Oct','Okt','ott'),
			'nov' = (array: 'November','novembre','noviembre','Nov'),
			'dec' = (array: 'December','diciembre','Dezember','decembre','dicembre','Dec','dic','dez')
			);


	// euro format?  aka dd/mm/yyyy
	local:'euroDate' = false;
	if: params->(find:'-euro')->size > 0;
		#euroDate = true;
	/if;

	// autoyear?  i.e. if no year is passed, we guessimate
	local:'autoYear' = false;
	if: params->(find:'-autoYear')->size > 0;
		#autoYear = true;
	/if;


	// error handling
	if: !(local_defined:'error');
		local:'error' = 'null';	
	/if;


	// what we want to find
	local:'dateday' = string;
	local:'datemonth' = string;
	local:'dateyear' = string;
	local:'datetime' = '00:00:00'; // default =  midnight
	local:'dateoffset' = null; // default = local server time, set below

	protect;
		handle_error;
			select: #error;
				case: 'fail';
					fail: error_code, error_msg;
				case: 'message';
					local:'error_msg' = 'Error: ' error_msg;
					#error_msg = #error_msg->(split:'\r\n')->(get:1);

					// restore the error, if any
					error_code = #_ec;
					error_msg  = #_em;

					if: #eurodate;
						return: #error_msg  ' (string="' #date '", date="' #dateday '/' #datemonth '/' #dateyear '")';
					else;
						return: #error_msg  ' (string="' #date '", date="' #datemonth '/' #dateday '/' #dateyear '")';
					/if;
				case: 'null';
					// restore the error, if any
					error_code = #_ec;
					error_msg  = #_em;
					return: null;
				case: 'zerodate';
					// restore the error, if any
					error_code = #_ec;
					error_msg  = #_em;
					return: (date: '1/1/0000');
				case; // custom error
					// restore the error, if any
					error_code = #_ec;
					error_msg  = #_em;
					return: #error;
			/select;
		/handle_error;

		// massage the passed-in date string into pieces
	
			// make sure it's a string, convert to plain ASCII
			#date = (lp_string_ExtendedtoPlain:(string: #date));


			// find time and remove from date string (assumes 24 hour time, either hh:mm or hh:mm:ss)
			local:'time' = (string_findregexp:#date, -find='\\b\\d{1,2}:\\d{1,2}:\\d{1,2}\\b|\\b\\d{1,2}:\\d{1,2}\\b', -ignorecase);
			if: #time->size;
				#time = #time->(get:1);
				#date->(replace: #time, '');
				#datetime = #time;
				if: #datetime->size == 5; // hh:mm
					#datetime += ':00'; // to hh:mm:ss
				/if;
			/if;
			
			
			// find offset and remove from date string (assumes +hhmm or -hhmm formats)
			local:'offset' = (string_findregexp:#date, -find='\\s\\+\\d{4}\\b|\\s\\-\\d{4}\\b', -ignorecase);
			if: #offset->size;
				#offset = #offset->(get:1);
				#date->(replace: #offset, '');
				#dateoffset = (lp_date_stringToOffset: #offset);
			else; // check for GMT/UTC, the only named timezones we understand
				if: #date->(contains:'GMT');
					#date->(replace: 'GMT', '');
					#dateoffset = '+0000';
				else: #date->(contains:'UTC');
					#date->(replace: 'UTC', '');
					#dateoffset = '+0000';
				/if;
			/if;


			// find month names and handle them
			iterate: #month_names, local:'mon';
				local:'month_number' = loop_count;
				iterate: #mon->value, local:'m';
					if: #date->(contains: #m);
						#datemonth = #month_number;
						loop_abort; 
					/if;
				/iterate;
				if: #datemonth != '';
					loop_abort;
				/if;
			/iterate;
			

			// replace the delimiters with ours
			#date = (string_replaceregexp: #date, -find='[^0-9]',-replace='x');
			#date = (string_replaceregexp: #date, -find='x+',-replace='/');
			#date->(removeleading:'/');
			#date->(removetrailing:'/');
			
			// split up the date into pieces
			local:'dateitems'=#date->(split:'/');

		
		// process the date pieces
		iterate: #dateitems, local:'dateitem';
	
			// check that the length of the number is reasonable, e.g. we disallow 3-digit years like 987 A.D.
			if: #dateitem->size != 1 && #dateitem->size != 2 && #dateitem->size != 4;
				fail:'-1','Could not convert date.  Improper number of digits.';
			/if;
	
	
			// check if it's the year and that we don't already have a year
			if: #dateitem->size == 4 || (lp_logical_between: (integer: #dateitem), 32, 99);
				if: (integer:#dateitem) >= 1000;
					if: #dateyear == '';
						#dateyear = #dateitem;
					else;
						fail:'-1','Could not convert date.  Year passed twice.';
					/if;
				else: (lp_logical_between: (integer: #dateitem), 32, 99);
					if: #dateyear == '';
						// two-digit year century assignment same as Lasso
						if: (integer:#dateitem) >= 40;
							#dateyear = 1900 + (integer:#dateitem);
						else;
							#dateyear = 2000 + (integer:#dateitem);
						/if;
					else;
						fail:'-1','Could not convert date.  Year passed twice.';
					/if;
				else;
					fail:'-1','Could not convert date.  Year is before 1000 B.C.';
				/if;
			else;
				if: #euroDate && (#dateyear == '' || loop_count > 1); // make sure didn't pass the year first, in which case, euro and american dates are the same
				
					// check if it's the day and that we don't already have a day
					if: #dateday == '' && (integer:#dateitem <= 31) && (integer:#dateitem) >=1;
						#dateday = #dateitem;
					else;
						// check if it's a month
						if: #datemonth == '' && (integer:#dateitem) <= 12 && (integer:#dateitem) >=1;
							#datemonth = #dateitem;
						else;
							// last ditch effort to identify two digit year
							if: #datemonth != '' && #dateday !='';
								// two-digit year century assignment same as Lasso
								if: (integer:#dateitem) >= 40;
									#dateyear = 1900 + (integer:#dateitem);
								else;
									#dateyear = 2000 + (integer:#dateitem);
								/if;
							else: #dateday != '' && #datemonth == '' && (integer:#dateitem <= 31) && (integer:#dateitem) >=1;
								// reverse day and month
								#datemonth = #dateday;
								#dateday = #dateitem;
							else: #dateday == '';
								fail:'-1','Could not convert date. Not a valid day.';
							else: #datemonth == '';
								fail:'-1','Could not convert date. Not a valid month.';
							else;
								fail:'-1','Could not convert date. Unknown problem.';
							/if;
						/if;
					/if;
	
				else; // not euroDate
	
					// check if it's the month and that we don't already have a month
					if: #datemonth == '' && (integer:#dateitem) <= 12 && (integer:#dateitem) >=1;
						#datemonth = #dateitem;
					else;
						// check if it's a day
						if: #dateday == '' && (integer:#dateitem) <= 31 && (integer:#dateitem) >=1;
							#dateday = #dateitem;
						else;
							// last ditch effort to identify two digit year
							if: #datemonth != '' && #dateday !='';
								// two-digit year century assignment same as Lasso
								if: (integer:#dateitem) >= 40;
									#dateyear = 1900 + (integer:#dateitem);
								else;
									#dateyear = 2000 + (integer:#dateitem);
								/if;
							else: #dateday == '';
								fail:'-1','Could not convert date. Not a valid day.';
							else: #datemonth == '';
								fail:'-1','Could not convert date. Not a valid month.';
							else;
								fail:'-1','Could not convert date. Unknown problem.';
							/if;
						/if;
					/if;
	
				/if; // if euroDate
	
			/if; // #dateitem->length == 4		
		
		/iterate;
	
		// did we get all the pieces we wanted?
		if: #datemonth == '';
			fail:'-1','Could not convert date.  Missing month value.';
		else: #dateday == '';
			fail:'-1','Could not convert date.  Missing day value.';
		else: #dateyear == '' && #autoYear;
			// auto year
			//
			// algorithm is if date selected falls within last month or greater, than current year, otherwise next year
			// 2/29 looks for this year first, then future year
			if: date->month == 1; // jan must look back to dec of last year
				if: (integer:#datemonth) == 12;
					#dateyear = (date->year) - 1;
				else;
					#dateyear = (date->year);
				/if;
			else: #datemonth == 2 && #dateday == 29; // handle leap year date
				local:'findleap' = (integer: date->year);
				while: !(lp_date_leapyear: #findleap);
					#findleap += 1;
				/while;
				#dateyear = #findleap;
			else;
				if: (date->month - 1) <= (integer:#datemonth);
					#dateyear = date->year;
				else;
					#dateyear = (date->year) + 1;
				/if;
			/if;
		else: #dateyear == '';
			fail:'-1','Could not convert date.  Missing year value.';	
		/if;
	
	
		// validate the date range is correct
		if: (integer:#datemonth) == 1 || (integer:#datemonth) == 3 || (integer:#datemonth) == 5 ||
			(integer:#datemonth) == 7 || (integer:#datemonth) == 8 || (integer:#datemonth) == 10 ||
			(integer:#datemonth) == 12;
			if: (integer: #dateday) < 1 || (integer: #dateday) > 31;
				// invalid date, day doesn't fall in range of 1 to 31
				fail:'-1','Could not convert date.  Day falls outside allowed range for given month.';
			/if;
		else: (integer:#datemonth) == 4 || (integer:#datemonth) == 6 || (integer:#datemonth) == 9 || (integer:#datemonth) == 11;
			if: (integer: #dateday) < 1 || (integer: #dateday) > 30;
				// invalid date, day doesn't fall in range of 1 to 30
				fail:'-1','Could not convert date.';
			/if;
		else: (integer:#datemonth) == 2;
			if: (lp_date_leapyear: #dateyear);
				// leap year
				if: (integer: #dateday) < 1 || (integer: #dateday) > 29;
					// invalid date, day doesn't fall in range of 1 to 29
					fail:'-1','Could not convert date.  Day falls outside allowed range for given month.';
				/if;
			else;
				if: (integer: #dateday) < 1 || (integer: #dateday) > 28;
					// invalid date, day doesn't fall in range of 1 to 28
					fail:'-1','Could not convert date.  Day falls outside allowed range for given month.';
				/if;
			/if;
		else;
			// invalid date, month doesn't fall in range of 1 to 12
			fail:'-1','Could not convert date.  Month falls outside allowed range.';
		/if;
		

		// output into Lasso format
		protect;
		local:'lassodate' = (date: #datemonth '/' #dateday '/' #dateyear ' ' #datetime);
		/protect;
		
		if: #lassodate->type == 'date' && #lassodate != null;
		
			if: #dateoffset == null;
				#dateoffset = lp_date_getLocalTimeZone: #lassodate;
			/if;
			
			// return date in local server time
			#lassodate = (lp_date_offsetToLocal: #lassodate, #dateoffset);
			
			if: (local_defined:'map'); // return as a map
				return: (map:
							'month'			= #lassodate->month,
							'day'			= #lassodate->day,
							'year'			= #lassodate->year,
							'hour'			= #lassodate->hour,
							'minute'		= #lassodate->minute,
							'second'		= #lassodate->second,
							'millisecond'	= 0, // we don't detect milliseconds currently, so just a default value
							'offset'		= (lp_date_getLocalTimeZone: #lassodate)
						);
			else;
				return: #lassodate;
			/if;
		/if;
		
		// invalid date for something we didn't catch sooner
		fail:'-1','Could not convert date.  Lasso could not cast as date type.';
	
	/protect;

/define_tag;						

						
]

Related Tags

Comments

09 Jul 2007, Jussi Hirvi

More dependencies

The tag also requires the following tags to execute the test code included on this page:
lp_string_CP1252toUTF8.lasso, lp_logical_in, lp_string_zap, lp_date_serverDST, lp_date_offsetToUTC, lp_date_UTCtoLocal, lp_string_getnumeric, lp_string_pad

28 Mar 2007, Nikolaj de Fine Licht

Weird behaviour

I'm seeing something weird in a solution I have which uses this tag and it's dependencies: it adds a day to the input date and sets the time to 23:00:00 instead of 00:00:00. I suspect something DST-related, maybe having to do with the server where the solution runs and not the tag itself...

02 Sep 2006, Bil Corry

zip file

The entire Lasso.Pro library is available as a .zip here:

http://tagSwap.net/lp__library

HTH,

- Bil

29 May 2006, James Powell

Can't we?

create a zip package or something so that we download all the dependencies required/needed for this tag?

would be easier that way, me thinks.

thanks.

24 Jan 2006, Bil Corry

Updated

Thanks for the feedback. Updated for Dutch/Belgian.

24 Jan 2006, Bram Van Damme

Dutch/Belgian Support

nl-nl/be-nl:
januari, februari, maart, april, mei, juni, juli, augustus, september, oktober, november, december

23 Jan 2006, Bil Corry

Updated

I updated it with Swedish months and guessed at foreign abbreviations. Thanks for the feedback.

19 Jan 2006, Trevor Jacques

Oops

Sorry about the accent comment. I just noticed the remark about accents, but I'd still recommend the extra abbreviations.

19 Jan 2006, Trevor Jacques

French month support

Please note that there are acents on some French months: février, fév, août, décembre, déc, so I suspect that these should be included in the appropriate arrays. Likewise, perhaps abbreviations from languages other than English (such as okt from Johan's list above) would also help the tag.

19 Jan 2006, Johan Solve

Support for Swedish

If you want to add support for Swedish, these are the month names:
januari, februari, mars, april, maj, juni, juli, augusti, september, oktober, november, december

Please log in to comment

Subscribe to the LassoTalk mail list

LassoSoft Inc. > Home

 

 

©LassoSoft Inc 2015 | Web Development by Treefrog Inc | PrivacyLegal terms and Shipping | Contact LassoSoft